diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..f787047b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: haraldk diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..715c3e25 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,53 @@ +--- +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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..6526528b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +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. diff --git a/.github/ISSUE_TEMPLATE/trouble_shooting_and_programming_help.md b/.github/ISSUE_TEMPLATE/trouble_shooting_and_programming_help.md new file mode 100644 index 00000000..c16210b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/trouble_shooting_and_programming_help.md @@ -0,0 +1,13 @@ +--- +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. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..71958325 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,17 @@ +**What is fixed** + +Add link to the issue this PR fixes. + +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 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 7058f82d..764c1ee9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ +dist: trusty language: java jdk: - - oraclejdk8 -# Oracle JDK 7 no longer supported, we use env matrix to test various CMM providers -# - oraclejdk7 -# Some JPEGImageReader tests fail on OpenJDK, need to investigate/fix before enabling -# - openjdk7 -env: - - MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider - - MAVEN_OPTS="" + - oraclejdk8 # Legacy + - oraclejdk11 # LTS + - oraclejdk15 # Latest +jobs: + include: + # Extra job, testing legacy CMM option + - jdk: oraclejdk8 + env: MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider cache: directories: - $HOME/.m2 diff --git a/LICENSE.txt b/LICENSE.txt index 5c0e09d1..bdff7265 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2017, Harald Kuhr +Copyright (c) 2008-2020, Harald Kuhr All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 4b693402..98c58abb 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,13 @@ -## Latest - -Master branch build status: [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys) - -Latest release is TwelveMonkeys ImageIO [3.3.2](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.3.2%22) (Feb. 2nd, 2017). -[Release notes](https://github.com/haraldk/TwelveMonkeys/releases/latest). +[![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio/badge.svg?color=slateblue)](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio) +[![StackOverflow](https://img.shields.io/badge/stack_overflow-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/twelvemonkeys) +[![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://paypal.me/haraldk76/100) ## About TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO. -These plugins extends the number of image file formats supported in Java, using the javax.imageio.* package. +These plugins extend the number of image file formats supported in Java, using the `javax.imageio.*` package. The main purpose of this project is to provide support for formats not covered by the JRE itself. Support for formats is important, both to be able to read data found @@ -19,243 +17,64 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b ---- -## Features +## File formats supported -Mainstream format support +| Plugin | Format | Description | Read | Write | Metadata | Notes | +| ------ | -------- | ----------- |:----:|:-----:| -------- | ----- | +| Batik | **SVG** | Scalable Vector Graphics | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) +| | WMF | MS Windows Metafile | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) +| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/bmp_metadata.html) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) | +| | CUR | MS Windows Cursor Format | ✔ | - | - | +| | ICO | MS Windows Icon Format | ✔ | ✔ | - | +| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) | +| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | ✔ | ✔ | - | +| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) | +| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) | +| | JPEG Lossless | | ✔ | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) | +| [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) | +| [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) | +| | 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) | +| [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) | -#### BMP - MS Windows/IBM OS/2 Device Independent Bitmap - -* Read support for all known versions of the DIB/BMP format - * Indexed color, 1, 4 and 8 bit, including 4 and 8 bit RLE - * RGB, 16, 24 and 32 bit - * Embedded PNG and JPEG data - * Windows and OS/2 versions -* Native and standard metadata format -#### JPEG - -* Read support for the following JPEG "flavors": - * All JFIF compliant JPEGs - * All Exif compliant JPEGs - * YCbCr JPEGs without JFIF segment (converted to RGB, using embedded ICC profile) - * CMYK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile) - * Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile) - * JPEGs containing ICC profiles with interpretation other than 'Perceptual' or class other than 'Display' - * JPEGs containing ICC profiles that are incompatible with stream data, corrupted ICC profiles or corrupted `ICC_PROFILE` segments - * JPEGs using non-standard color spaces, unsupported by Java 2D - * JPEGs with APP14/Adobe segments with length other than 14 bytes - * 8 bit JPEGs with 16 bit DQT segments - * Issues warnings instead of throwing exceptions in cases of corrupted or non-conformant data where ever the image - data can still be read in a reasonable way -* Thumbnail support: - * JFIF thumbnails (even if stream contains "inconsistent metadata") - * JFXX thumbnails (JPEG, Indexed and RGB) - * EXIF thumbnails (JPEG, RGB and YCbCr) -* Metadata support: - * JPEG metadata in both standard and native formats (even if stream contains "inconsistent metadata") - * `javax_imageio_jpeg_image_1.0` format (currently as native format, may change in the future) - * Non-conforming combinations of JFIF, Exif and Adobe markers, using "unknown" segments in the - "MarkerSequence" tag for the unsupported segments (for `javax_imageio_jpeg_image_1.0` format) -* Extended write support in progress: - * CMYK JPEGs - * YCCK JPEGs - -#### JPEG-2000 - -* Possibly coming in the future, pending some license issues. - -If you are one of the authors, or know one of the authors and/or the current license holders of either the original -jj2000 package or the JAI ImageIO project, please contact me (I've tried to get in touch in various ways, -without success so far). - -Alternatively, if you have or know of a JPEG-2000 implementation in Java with a suitable license, get in touch. :-) - -#### PNM - NetPBM Portable Any Map - -* Read support for the following file types: - * PBM in 'P1' (ASCII) and 'P4' (binary) formats, 1 bit per pixel - * PGM in 'P2' (ASCII) and 'P5' (binary) formats, up to 16/32 bits per pixel - * PPM in 'P3' (ASCII) and 'P6' (binary) formats, up to 16/32 bits per pixel component - * PAM in 'P7' (binary) format up to 32 bits per pixel component - * Limited support for PFM in 'Pf' (gray) and 'PF' (RGB) formats, 32 bits floating point -* Write support for the following formats: - * PPM in 'P6' (binary) format - * PAM in 'P7' (binary) format -* Standard metadata support - -#### PSD - Adobe Photoshop Document - -* Read support for the following file types: - * Monochrome, 1 channel, 1 bit - * Indexed, 1 channel, 8 bit - * Gray, 1 channel, 8, 16 and 32 bit - * Duotone, 1 channel, 8, 16 and 32 bit - * RGB, 3-4 channels, 8, 16 and 32 bit - * CMYK, 4-5 channels, 8, 16 and 32 bit -* Read support for the following compression types: - * Uncompressed - * RLE (PackBits) -* Layer support - * Image layers only, in all of the above types -* Thumbnail support - * JPEG - * RAW (RGB) -* Support for "Large Document Format" (PSB) -* Native and Standard metadata support - -#### TIFF - Aldus/Adobe Tagged Image File Format - -* Read support for the following "Baseline" TIFF file types: - * Class B (Bi-level), all relevant compression types, 1 bit per sample - * Class G (Gray), all relevant compression types, 2, 4, 8, 16 or 32 bits per sample, unsigned integer - * Class P (Palette/indexed color), all relevant compression types, 1, 2, 4, 8 or 16 bits per sample, unsigned integer - * Class R (RGB), all relevant compression types, 8 or 16 bits per sample, unsigned integer -* Read support for the following TIFF extensions: - * Tiling - * Class F (Facsimile), CCITT Modified Huffman RLE, T4 and T6 (type 2, 3 and 4) compressions. - * LZW Compression (type 5) - * "Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined - * JPEG Compression (type 7) - * ZLib (aka Adobe-style Deflate) Compression (type 8) - * Deflate Compression (type 32946) - * Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression - * Alpha channel (ExtraSamples type 1/Associated Alpha and type 2/Unassociated Alpha) - * CMYK data (PhotometricInterpretation type 5/Separated) - * YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG - * CIELab data in TIFF, ITU and ICC variants (PhotometricInterpretation type 9, 10 and 11) - * Planar data (PlanarConfiguration type 2/Planar) - * ICC profiles (ICCProfile) - * BitsPerSample values up to 16 for most PhotometricInterpretations - * Multiple images (pages) in one file -* Write support for most "Baseline" TIFF options - * Uncompressed, PackBits, ZLib and Deflate - * Additional support for CCITT T4 and and T6 compressions. - * Additional support for LZW and JPEG (type 7) compressions - * Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate -* Native and Standard metadata support - -Legacy formats - -#### HDR - Radiance High Dynamic Range RGBE Format - -* Read support for the most common RGBE (.hdr) format -* Samples are converted to 32 bit floating point (`float`) and normalized using a global tone mapper by default. - * Support for custom global tone mappers - * Alternatively, use a "null-tone mapper", for unnormalized data (allows local tone mapping) -* Unconverted RGBE samples accessible using `readRaster` -* Standard metadata support - -#### IFF - Commodore Amiga/Electronic Arts Interchange File Format - -* Legacy format, allows reading popular image format from the Commodore Amiga computer. -* Read support for the following file types: - * ILBM Indexed color, 1-8 interleaved bit planes, including 6 bit EHB - * ILBM Gray, 8 bit interleaved bit planes - * ILBM RGB, 24 and 32 bit interleaved bit planes - * ILBM HAM6 and HAM8 - * PBM Indexed color, 1-8 bit, - * PBM Gray, 8 bit - * PBM RGB, 24 and 32 bit - * PBM HAM6 and HAM8 -* Write support - * ILBM Indexed color, 1-8 bits per sample, 8 bit gray, 24 and 32 bit true color. -* Support for the following compression types (read/write): - * Uncompressed - * RLE (PackBits) - -#### PCX - ZSoft Paintbrush Format - -* Read support for the following file types: - * Indexed color, 1, 2, 4 or 8 bits per pixel, bit planes or interleaved - * Grayscale, 8 bits per pixel - * Color (RGB), 8 bits per pixel component -* Read support for DCX (multi-page) fax format, containing any of the above types -* Support for the following compression types: - * Uncompressed (experimental) - * RLE compressed -* Standard metadata support - -#### PICT - Apple Mac Paint Picture Format - -* Legacy format, especially useful for reading OS X clipboard data. -* Read support for the following file types: - * QuickDraw (format support is not complete, but supports most OS X clipboard data as well as RGB pixel data) - * QuickDraw bitmap - * QuickDraw pixmap - * QuickTime stills -* Write support for RGB pixel data: - * QuickDraw pixmap - -#### SGI - Silicon Graphics Image Format - -* Read support for the following file types: - * 1, 2, 3 or 4 channel image data - * 8 or 16 bits per pixel component -* Support for the following compression types: - * Uncompressed - * RLE compressed -* Standard metadata support - -#### TGA - Truevision TGA Image Format - -* Read support for the following file types: - * ColorMapped - * Monochrome - * TrueColor -* Support for the following compression types: - * Uncompressed - * RLE compressed -* Standard metadata support - -Icon/other formats - -#### ICNS - Apple Icon Image - -* Read support for the following icon types: - * All known "native" icon types - * Large PNG encoded icons - * Large JPEG 2000 encoded icons (requires JPEG 2000 ImageIO plugin or fallback to `sips` command line tool) - -#### ICO & CUR - MS Windows Icon and Cursor Formats - -* Read support for the following file types: - * ICO Indexed color, 1, 4 and 8 bit - * ICO RGB, 16, 24 and 32 bit - * CUR Indexed color, 1, 4 and 8 bit - * CUR RGB, 16, 24 and 32 bit -* *3.1* Note: These formats are now part of the BMP plugin - -#### Thumbs.db - MS Windows Thumbs DB - -* Read support - -Other formats, using 3rd party libraries - -#### SVG - Scalable Vector Graphics - -* Read-only support using Batik - -#### WMF - MS Windows MetaFile - -* Limited read-only support using Batik - -**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](http://xmlgraphics.apache.org/security.html), and make sure you use -either version 1.6.1, 1.7.1 or 1.8+.* +**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html), +and make sure you use version 1.14 or later.* +Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the +[JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html). +For BMP, JPEG, and TIFF formats the TwelveMonkeys plugins provides extended format support and additional features. ## Basic usage Most of the time, all you need to do is simply include the plugins in your project and write: - BufferedImage image = ImageIO.read(file); +```java +BufferedImage image = ImageIO.read(file); +``` This will load the first image of the file, entirely into memory. The basic and simplest form of writing is: - if (!ImageIO.write(image, format, file)) { - // Handle image not written case - } +```java +if (!ImageIO.write(image, format, file)) { + // Handle image not written case +} +``` This will write the entire image into a single file, using the default settings for the given format. @@ -265,50 +84,46 @@ The plugins are discovered automatically at run time. See the [FAQ](#faq) for mo If you need more control of read parameters and the reading process, the common idiom for reading is something like: - // Create input stream - ImageInputStream input = ImageIO.createImageInputStream(file); +```java +// Create input stream (in try-with-resource block to avoid leaks) +try (ImageInputStream input = ImageIO.createImageInputStream(file)) { + // Get the reader + Iterator readers = ImageIO.getImageReaders(input); + + if (!readers.hasNext()) { + throw new IllegalArgumentException("No reader for: " + file); + } + + ImageReader reader = readers.next(); try { - // Get the reader - Iterator readers = ImageIO.getImageReaders(input); + reader.setInput(input); - if (!readers.hasNext()) { - throw new IllegalArgumentException("No reader for: " + file); - } + // Optionally, listen for read warnings, progress, etc. + reader.addIIOReadWarningListener(...); + reader.addIIOReadProgressListener(...); - ImageReader reader = readers.next(); + ImageReadParam param = reader.getDefaultReadParam(); - try { - reader.setInput(input); + // Optionally, control read settings like sub sampling, source region or destination etc. + param.setSourceSubsampling(...); + param.setSourceRegion(...); + param.setDestination(...); + // ... - // Optionally, listen for read warnings, progress, etc. - reader.addIIOReadWarningListener(...); - reader.addIIOReadProgressListener(...); + // Finally read the image, using settings from param + BufferedImage image = reader.read(0, param); - ImageReadParam param = reader.getDefaultReadParam(); - - // Optionally, control read settings like sub sampling, source region or destination etc. - param.setSourceSubsampling(...); - param.setSourceRegion(...); - param.setDestination(...); - // ... - - // Finally read the image, using settings from param - BufferedImage image = reader.read(0, param); - - // Optionally, read thumbnails, meta data, etc... - int numThumbs = reader.getNumThumbnails(0); - // ... - } - finally { - // Dispose reader in finally block to avoid memory leaks - reader.dispose(); - } + // Optionally, read thumbnails, meta data, etc... + int numThumbs = reader.getNumThumbnails(0); + // ... } finally { - // Close stream in finally block to avoid resource leaks - input.close(); + // Dispose reader in finally block to avoid memory leaks + reader.dispose(); } +} +``` Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the entire image into memory first. @@ -318,129 +133,105 @@ It's also possible to read multiple images from the same file in a loop, using ` If you need more control of write parameters and the writing process, the common idiom for writing is something like: - // Get the writer - Iterator writers = ImageIO.getImageWritersByFormatName(format); +```java +// Get the writer +Iterator writers = ImageIO.getImageWritersByFormatName(format); - if (!writers.hasNext()) { - throw new IllegalArgumentException("No writer for: " + format); - } - - ImageWriter writer = writers.next(); - - try { - // Create output stream - ImageOutputStream output = ImageIO.createImageOutputStream(file); - - try { - writer.setOutput(output); - - // Optionally, listen to progress, warnings, etc. - - ImageWriteParam param = writer.getDefaultWriteParam(); - - // Optionally, control format specific settings of param (requires casting), or - // control generic write settings like sub sampling, source region, output type etc. - - // Optionally, provide thumbnails and image/stream metadata - writer.write(..., new IIOImage(..., image, ...), param); - } - finally { - // Close stream in finally block to avoid resource leaks - output.close(); - } - } - finally { - // Dispose writer in finally block to avoid memory leaks - writer.dispose(); +if (!writers.hasNext()) { + throw new IllegalArgumentException("No writer for: " + format); +} + +ImageWriter writer = writers.next(); + +try { + // Create output stream (in try-with-resource block to avoid leaks) + try (ImageOutputStream output = ImageIO.createImageOutputStream(file)) { + writer.setOutput(output); + + // Optionally, listen to progress, warnings, etc. + + ImageWriteParam param = writer.getDefaultWriteParam(); + + // Optionally, control format specific settings of param (requires casting), or + // control generic write settings like sub sampling, source region, output type etc. + + // Optionally, provide thumbnails and image/stream metadata + writer.write(..., new IIOImage(..., image, ...), param); } +} +finally { + // Dispose writer in finally block to avoid memory leaks + writer.dispose(); +} +``` For more advanced usage, and information on how to use the ImageIO API, I suggest you read the -[Java Image I/O API Guide](http://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html) +[Java Image I/O API Guide](https://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html) from Oracle. +#### Adobe Clipping Path support -#### Deploying the plugins in a web app +```java +import com.twelvemonkeys.imageio.path.Paths; -Because the `ImageIO` plugin registry (the `IIORegistry`) is "VM global", it doesn't by default work well with -servlet contexts. This is especially evident if you load plugins from the `WEB-INF/lib` or `classes` folder. -Unless you add `ImageIO.scanForPlugins()` somewhere in your code, the plugins might never be available at all. +... -In addition, servlet contexts dynamically loads and unloads classes (using a new class loader per context). -If you restart your application, old classes will by default remain in memory forever (because the next time -`scanForPlugins` is called, it's another `ClassLoader` that scans/loads classes, and thus they will be new instances -in the registry). If a read is attempted using one of the remaining "old" readers, weird exceptions -(like `NullPointerException`s when accessing `static final` initialized fields or `NoClassDefFoundError`s -for uninitialized inner classes) may occur. +try (ImageInputStream stream = ImageIO.createImageInputStream(new File("image_with_path.jpg")) { + BufferedImage image = Paths.readClipped(stream); -To work around both the discovery problem and the resource leak, -it is *strongly recommended* to use the `IIOProviderContextListener` that implements -dynamic loading and unloading of ImageIO plugins for web applications. + // Do something with the clipped image... +} +``` +See [Adobe Clipping Path support on the Wiki](https://github.com/haraldk/TwelveMonkeys/wiki/Photoshop-Clipping-Path-support) for more details and example code. - - - ... - - - ImageIO service provider loader/unloader - com.twelvemonkeys.servlet.image.IIOProviderContextListener - - - ... - - - -Loading plugins from `WEB-INF/lib` without the context listener installed is unsupported and will not work correctly. - -The context listener has no dependencies to the TwelveMonkeys ImageIO plugins, and may be used with JAI ImageIO -or other ImageIO plugins as well. - -Another safe option, is to place the JAR files in the application server's shared or common lib folder. #### Using the ResampleOp The library comes with a resampling (image resizing) operation, that contains many different algorithms to provide excellent results at reasonable speed. - import com.twelvemonkeys.image.ResampleOp; +```java +import com.twelvemonkeys.image.ResampleOp; - ... +... - BufferedImage input = ...; // Image to resample - int width, height = ...; // new width/height - - BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info - BufferedImage output = resampler.filter(input, null); +BufferedImage input = ...; // Image to resample +int width, height = ...; // new width/height +BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info +BufferedImage output = resampler.filter(input, null); +``` #### Using the DiffusionDither The library comes with a dithering operation, that can be used to convert `BufferedImage`s to `IndexColorModel` using Floyd-Steinberg error-diffusion dither. - import com.twelvemonkeys.image.DiffusionDither; +```java +import com.twelvemonkeys.image.DiffusionDither; - ... +... - BufferedImage input = ...; // Image to dither - - BufferedImageOp ditherer = new DiffusionDither(); - BufferedImage output = ditherer.filter(input, null); +BufferedImage input = ...; // Image to dither +BufferedImageOp ditherer = new DiffusionDither(); +BufferedImage output = ditherer.filter(input, null); +``` ## Building -Download the project (using [Git](http://git-scm.com/downloads)): +Download the project (using [Git](https://git-scm.com/downloads)): $ git clone git@github.com:haraldk/TwelveMonkeys.git This should create a folder named `TwelveMonkeys` in your current directory. Change directory to the `TwelveMonkeys` folder, and issue the command below to build. -Build the project (using [Maven](http://maven.apache.org/download.cgi)): +Build the project (using [Maven](https://maven.apache.org/download.cgi)): $ mvn package -Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x. +Currently, the recommended JDK for making a build is Oracle JDK 8.x. It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether. @@ -461,10 +252,12 @@ The ImageIO registry and service lookup mechanism will make sure the plugins are To verify that the JPEG plugin is installed and used at run-time, you could use the following code: - Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); - while (readers.hasNext()) { - System.out.println("reader: " + readers.next()); - } +```java +Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); +while (readers.hasNext()) { + System.out.println("reader: " + readers.next()); +} +``` The first line should print: @@ -474,136 +267,210 @@ The first line should print: To depend on the JPEG and TIFF plugin using Maven, add the following to your POM: +```xml +... + ... - - ... - - com.twelvemonkeys.imageio - imageio-jpeg - 3.3.2 - - - com.twelvemonkeys.imageio - imageio-tiff - 3.3.2 - - + + com.twelvemonkeys.imageio + imageio-jpeg + 3.7.0 + + + com.twelvemonkeys.imageio + imageio-tiff + 3.7.0 + + + + + com.twelvemonkeys.servlet + servlet + 3.7.0 + + +``` #### Manual dependency example 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.3.2.jar - twelvemonkeys-common-io-3.3.2.jar - twelvemonkeys-common-image-3.3.2.jar - twelvemonkeys-imageio-core-3.3.2.jar - twelvemonkeys-imageio-metadata-3.3.2.jar - twelvemonkeys-imageio-jpeg-3.3.2.jar - twelvemonkeys-imageio-tiff-3.3.2.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 + +Because the `ImageIO` plugin registry (the `IIORegistry`) is "VM global", it doesn't by default work well with +servlet contexts. This is especially evident if you load plugins from the `WEB-INF/lib` or `classes` folder. +Unless you add `ImageIO.scanForPlugins()` somewhere in your code, the plugins might never be available at all. + +In addition, servlet contexts dynamically loads and unloads classes (using a new class loader per context). +If you restart your application, old classes will by default remain in memory forever (because the next time +`scanForPlugins` is called, it's another `ClassLoader` that scans/loads classes, and thus they will be new instances +in the registry). If a read is attempted using one of the remaining "old" readers, weird exceptions +(like `NullPointerException`s when accessing `static final` initialized fields or `NoClassDefFoundError`s +for uninitialized inner classes) may occur. + +To work around both the discovery problem and the resource leak, +it is *strongly recommended* to use the `IIOProviderContextListener` that implements +dynamic loading and unloading of ImageIO plugins for web applications. + +```xml + + +... + + + ImageIO service provider loader/unloader + com.twelvemonkeys.servlet.image.IIOProviderContextListener + + +... + + +``` + +Loading plugins from `WEB-INF/lib` without the context listener installed is unsupported and will not work correctly. + +The context listener has no dependencies to the TwelveMonkeys ImageIO plugins, and may be used with JAI ImageIO +or other ImageIO plugins as well. + +Another safe option, is to place the JAR files in the application server's shared or common lib folder. + +#### Including the plugins in a "fat" JAR + +The recommended way to use the plugins, is just to include the JARs as-is in your project, through a Maven dependency or similar. +Re-packaging is not necessary to use the library, and not recommended. + +However, if you like to create a "fat" +JAR, or otherwise like to re-package the JARs for some reason, it's important to remember that automatic discovery of +the plugins by ImageIO depends on the +[Service Provider Interface (SPI)](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html) mechanism. +In short, each JAR contains a special folder, named `META-INF/services` containing one or more files, +typically `javax.imageio.spi.ImageReaderSpi` and `javax.imageio.spi.ImageWriterSpi`. +These files exist *with the same name in every JAR*, +so if you simply unpack everything to a single folder or create a JAR, files will be overwritten and behavior be +unspecified (most likely you will end up with a single plugin being installed). + +The solution is to make sure all files with the same name, are merged to a single file, +containing all the SPI information of each type. If using the Maven Shade plugin, you should use the +[ServicesResourceTransformer](https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html#ServicesResourceTransformer) +to properly merge these files. You may also want to use the +[ManifestResourceTransforme](https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html#ManifestResourceTransformer) +to get the correct vendor name, version info etc. +Other "fat" JAR bundlers will probably have similar mechanisms to merge entries with the same name. ### Links to prebuilt binaries -##### Latest version (3.2.x) +##### Latest version (3.7.0) Requires Java 7 or later. Common dependencies -* [common-lang-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.3.2/common-lang-3.3.2.jar) -* [common-io-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.3.2/common-io-3.3.2.jar) -* [common-image-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.3.2/common-image-3.3.2.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.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.3.2/imageio-core-3.3.2.jar) -* [imageio-metadata-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.3.2/imageio-metadata-3.3.2.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.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.3.2/imageio-bmp-3.3.2.jar) -* [imageio-jpeg-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.3.2/imageio-jpeg-3.3.2.jar) -* [imageio-tiff-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.3.2/imageio-tiff-3.3.2.jar) -* [imageio-pnm-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.3.2/imageio-pnm-3.3.2.jar) -* [imageio-psd-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.3.2/imageio-psd-3.3.2.jar) -* [imageio-hdr-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.3.2/imageio-hdr-3.3.2.jar) -* [imageio-iff-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.3.2/imageio-iff-3.3.2.jar) -* [imageio-pcx-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.3.2/imageio-pcx-3.3.2.jar) -* [imageio-pict-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.3.2/imageio-pict-3.3.2.jar) -* [imageio-sgi-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.3.2/imageio-sgi-3.3.2.jar) -* [imageio-tga-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.3.2/imageio-tga-3.3.2.jar) -* [imageio-icns-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.3.2/imageio-icns-3.3.2.jar) -* [imageio-thumbsdb-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.3.2/imageio-thumbsdb-3.3.2.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.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.3.2/imageio-batik-3.3.2.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.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.3.2/imageio-clippath-3.3.2.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.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.3.2/servlet-3.3.2.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) Use this version for projects that requires Java 6 or need the JMagick support. *Does not support Java 8 or later*. Common dependencies -* [common-lang-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0.2/common-lang-3.0.2.jar) -* [common-io-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0.2/common-io-3.0.2.jar) -* [common-image-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0.2/common-image-3.0.2.jar) +* [common-lang-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0.2/common-lang-3.0.2.jar) +* [common-io-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0.2/common-io-3.0.2.jar) +* [common-image-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0.2/common-image-3.0.2.jar) ImageIO dependencies -* [imageio-core-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0.2/imageio-core-3.0.2.jar) -* [imageio-metadata-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0.2/imageio-metadata-3.0.2.jar) +* [imageio-core-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0.2/imageio-core-3.0.2.jar) +* [imageio-metadata-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0.2/imageio-metadata-3.0.2.jar) ImageIO plugins -* [imageio-jpeg-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0.2/imageio-jpeg-3.0.2.jar) -* [imageio-tiff-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0.2/imageio-tiff-3.0.2.jar) -* [imageio-psd-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.0.2/imageio-psd-3.0.2.jar) -* [imageio-pict-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.0.2/imageio-pict-3.0.2.jar) -* [imageio-iff-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.0.2/imageio-iff-3.0.2.jar) -* [imageio-icns-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0.2/imageio-icns-3.0.2.jar) -* [imageio-ico-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0.2/imageio-ico-3.0.2.jar) -* [imageio-thumbsdb-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0.2/imageio-thumbsdb-3.0.2.jar) +* [imageio-jpeg-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0.2/imageio-jpeg-3.0.2.jar) +* [imageio-tiff-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0.2/imageio-tiff-3.0.2.jar) +* [imageio-psd-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.0.2/imageio-psd-3.0.2.jar) +* [imageio-pict-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.0.2/imageio-pict-3.0.2.jar) +* [imageio-iff-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.0.2/imageio-iff-3.0.2.jar) +* [imageio-icns-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0.2/imageio-icns-3.0.2.jar) +* [imageio-ico-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0.2/imageio-ico-3.0.2.jar) +* [imageio-thumbsdb-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0.2/imageio-thumbsdb-3.0.2.jar) ImageIO plugins requiring 3rd party libs -* [imageio-batik-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0.2/imageio-batik-3.0.2.jar) -* [imageio-jmagick-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0.2/imageio-jmagick-3.0.2.jar) +* [imageio-batik-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0.2/imageio-batik-3.0.2.jar) +* [imageio-jmagick-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0.2/imageio-jmagick-3.0.2.jar) Servlet support -* [servlet-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar) +* [servlet-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar) ## License -The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause): +This project is provided under the OSI approved [BSD license](https://opensource.org/licenses/BSD-3-Clause): - Copyright (c) 2008-2015, Harald Kuhr + Copyright (c) 2008-2020, 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: - o Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + o Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. - o 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. + o 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. - o Neither the name "TwelveMonkeys" 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 OWNER 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. + o 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. ## FAQ @@ -630,7 +497,7 @@ a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO. ImageIO uses All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath. -You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](http://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html). +You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html). The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before @@ -638,10 +505,15 @@ the Sun/Oracle provided `JPEGImageReader` and `BMPImageReader`, and the Apple pr respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most cases you'll end up using the TwelveMonkeys plug-ins instead. +q: Why is there no support for common formats like GIF or PNG? + +a: The short answer is simply that the built-in support in ImageIO for these formats are good enough as-is. +If you are looking for better PNG write performance on Java 7 and 8, see [JDK9 PNG Writer Backport](https://github.com/gredler/jdk9-png-writer-backport). + q: What about JAI? Several of the formats are already supported by JAI. -a: While JAI (and jai-imageio in particular) have support for some of the formats, JAI has some major issues. +a: While JAI (and jai-imageio in particular) have support for some of the same formats, JAI has some major issues. The most obvious being: - It's not actively developed. No issues has been fixed for years. - To get full format support, you need native libs. @@ -649,7 +521,7 @@ Native libs does not exist for several popular platforms/architectures, and furt Some environments may also prevent deployment of native libs, which brings us back to square one. -q: What about JMagick or IM4Java? Can't you just use what´s already available? +q: What about JMagick or IM4Java? Can't you just use what's already available? a: While great libraries with a wide range of formats support, the ImageMagick-based libraries has some disadvantages compared to ImageIO. diff --git a/bom/pom.xml b/bom/pom.xml index 57f07c35..ab8689cc 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -5,7 +5,7 @@ com.twelvemonkeys twelvemonkeys - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT com.twelvemonkeys.bom @@ -138,6 +138,11 @@ imageio-tiff ${project.version} + + com.twelvemonkeys.imageio + imageio-xwd + ${project.version} + diff --git a/common/common-image/pom.xml b/common/common-image/pom.xml index cdef6ab4..d5d69885 100644 --- a/common/common-image/pom.xml +++ b/common/common-image/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.common common - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT common-image jar @@ -13,6 +13,10 @@ The TwelveMonkeys Common Image support + + com.twelvemonkeys.common.image + + ${project.groupId} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java b/common/common-image/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java index ae3b8e9e..37a4d3bb 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java @@ -1,108 +1,109 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import java.awt.image.ImageProducer; -import java.awt.image.ImageConsumer; -import java.util.List; -import java.util.ArrayList; - -/** - * AbstractImageSource - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java#1 $ - */ -public abstract class AbstractImageSource implements ImageProducer { - private List consumers = new ArrayList(); - protected int width; - protected int height; - protected int xOff; - protected int yOff; - - // ImageProducer interface - public void addConsumer(final ImageConsumer pConsumer) { - if (consumers.contains(pConsumer)) { - return; - } - - consumers.add(pConsumer); - - try { - initConsumer(pConsumer); - sendPixels(pConsumer); - - if (isConsumer(pConsumer)) { - pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE); - - // Get rid of "sticky" consumers... - if (isConsumer(pConsumer)) { - pConsumer.imageComplete(ImageConsumer.IMAGEERROR); - removeConsumer(pConsumer); - } - } - } - catch (Exception e) { - e.printStackTrace(); - - if (isConsumer(pConsumer)) { - pConsumer.imageComplete(ImageConsumer.IMAGEERROR); - } - } - } - - public void removeConsumer(final ImageConsumer pConsumer) { - consumers.remove(pConsumer); - } - - /** - * This implementation silently ignores this instruction. If pixel data is - * not in TDLR order by default, subclasses must override this method. - * - * @param pConsumer the consumer that requested the resend - * - * @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer) - */ - public void requestTopDownLeftRightResend(final ImageConsumer pConsumer) { - // ignore - } - - public void startProduction(final ImageConsumer pConsumer) { - addConsumer(pConsumer); - } - - public boolean isConsumer(final ImageConsumer pConsumer) { - return consumers.contains(pConsumer); - } - - protected abstract void initConsumer(ImageConsumer pConsumer); - - protected abstract void sendPixels(ImageConsumer pConsumer); -} +/* + * 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.image; + +import java.awt.image.ImageConsumer; +import java.awt.image.ImageProducer; +import java.util.ArrayList; +import java.util.List; + +/** + * AbstractImageSource + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java#1 $ + */ +public abstract class AbstractImageSource implements ImageProducer { + private List consumers = new ArrayList(); + protected int width; + protected int height; + protected int xOff; + protected int yOff; + + // ImageProducer interface + public void addConsumer(final ImageConsumer pConsumer) { + if (consumers.contains(pConsumer)) { + return; + } + + consumers.add(pConsumer); + + try { + initConsumer(pConsumer); + sendPixels(pConsumer); + + if (isConsumer(pConsumer)) { + pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE); + + // Get rid of "sticky" consumers... + if (isConsumer(pConsumer)) { + pConsumer.imageComplete(ImageConsumer.IMAGEERROR); + removeConsumer(pConsumer); + } + } + } + catch (Exception e) { + e.printStackTrace(); + + if (isConsumer(pConsumer)) { + pConsumer.imageComplete(ImageConsumer.IMAGEERROR); + } + } + } + + public void removeConsumer(final ImageConsumer pConsumer) { + consumers.remove(pConsumer); + } + + /** + * This implementation silently ignores this instruction. If pixel data is + * not in TDLR order by default, subclasses must override this method. + * + * @param pConsumer the consumer that requested the resend + * + * @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer) + */ + public void requestTopDownLeftRightResend(final ImageConsumer pConsumer) { + // ignore + } + + public void startProduction(final ImageConsumer pConsumer) { + addConsumer(pConsumer); + } + + public boolean isConsumer(final ImageConsumer pConsumer) { + return consumers.contains(pConsumer); + } + + protected abstract void initConsumer(ImageConsumer pConsumer); + + protected abstract void sendPixels(ImageConsumer pConsumer); +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/AffineTransformOp.java b/common/common-image/src/main/java/com/twelvemonkeys/image/AffineTransformOp.java index f362c0d4..c9621c51 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/AffineTransformOp.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/AffineTransformOp.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/AreaAverageOp.java b/common/common-image/src/main/java/com/twelvemonkeys/image/AreaAverageOp.java index 73e52172..96635d2d 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/AreaAverageOp.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/AreaAverageOp.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java index 0dbb7598..e01f0713 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java @@ -1,168 +1,173 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import java.awt.image.RGBImageFilter; - - -/** - * Adjusts the contrast and brightness of an image. - *

- * For brightness, the valid range is {@code -2.0,..,0.0,..,2.0}. - * A value of {@code 0.0} means no change. - * Negative values will make the pixels darker. - * Maximum negative value ({@code -2}) will make all filtered pixels black. - * Positive values will make the pixels brighter. - * Maximum positive value ({@code 2}) will make all filtered pixels white. - *

- * For contrast, the valid range is {@code -1.0,..,0.0,..,1.0}. - * A value of {@code 0.0} means no change. - * Negative values will reduce contrast. - * Maximum negative value ({@code -1}) will make all filtered pixels grey - * (no contrast). - * Positive values will increase contrast. - * Maximum positive value ({@code 1}) will make all filtered pixels primary - * colors (either black, white, cyan, magenta, yellow, red, blue or green). - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java#1 $ - * - * @todo consider doing something similar to http://archives.java.sun.com/cgi-bin/wa?A2=ind0302&L=jai-interest&F=&S=&P=15947 - */ - -public class BrightnessContrastFilter extends RGBImageFilter { - - // TODO: Replace with RescaleOp? - - // This filter can filter IndexColorModel, as it is does not depend on - // the pixels' location - { - canFilterIndexColorModel = true; - } - - // Use a pre-calculated lookup table for performance - private final int[] LUT; - - /** - * Creates a BrightnessContrastFilter with default values - * ({@code brightness=0.3, contrast=0.3}). - *

- * This will slightly increase both brightness and contrast. - */ - public BrightnessContrastFilter() { - this(0.3f, 0.3f); - } - - /** - * Creates a BrightnessContrastFilter with the given values for brightness - * and contrast. - *

- * For brightness, the valid range is {@code -2.0,..,0.0,..,2.0}. - * A value of {@code 0.0} means no change. - * Negative values will make the pixels darker. - * Maximum negative value ({@code -2}) will make all filtered pixels black. - * Positive values will make the pixels brighter. - * Maximum positive value ({@code 2}) will make all filtered pixels white. - *

- * For contrast, the valid range is {@code -1.0,..,0.0,..,1.0}. - * A value of {@code 0.0} means no change. - * Negative values will reduce contrast. - * Maximum negative value ({@code -1}) will make all filtered pixels grey - * (no contrast). - * Positive values will increase contrast. - * Maximum positive value ({@code 1}) will make all filtered pixels primary - * colors (either black, white, cyan, magenta, yellow, red, blue or green). - * - * @param pBrightness adjust the brightness of the image, in the range - * {@code -2.0,..,0.0,..,2.0}. - * @param pContrast adjust the contrast of the image, in the range - * {@code -1.0,..,0.0,..,1.0}. - */ - public BrightnessContrastFilter(float pBrightness, float pContrast) { - LUT = createLUT(pBrightness, pContrast); - } - - private static int[] createLUT(float pBrightness, float pContrast) { - int[] lut = new int[256]; - - // Hmmm.. This approximates Photoshop values.. Not good though.. - double contrast = pContrast > 0 ? Math.pow(pContrast, 7.0) * 127.0 : pContrast; - - // Convert range [-1,..,0,..,1] -> [0,..,1,..,2] - double brightness = pBrightness + 1.0; - - for (int i = 0; i < 256; i++) { - lut[i] = clamp((int) (127.5 * brightness + (i - 127) * (contrast + 1.0))); - } - - // Special case, to ensure only primary colors for max contrast - if (pContrast == 1f) { - lut[127] = lut[126]; - } - - return lut; - } - - private static int clamp(int i) { - if (i < 0) { - return 0; - } - if (i > 255) { - return 255; - } - return i; - } - - /** - * Filters one pixel, adjusting brightness and contrast according to this - * filter. - * - * @param pX x - * @param pY y - * @param pARGB pixel value in default color space - * - * @return the filtered pixel value in the default color space - */ - public int filterRGB(int pX, int pY, int pARGB) { - // Get color components - int r = pARGB >> 16 & 0xFF; - int g = pARGB >> 8 & 0xFF; - int b = pARGB & 0xFF; - - // Scale to new contrast - r = LUT[r]; - g = LUT[g]; - b = LUT[b]; - - // Return ARGB pixel, leave transparency as is - return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b; - } -} +/* + * 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.image; + +import java.awt.image.RGBImageFilter; + + +/** + * Adjusts the contrast and brightness of an image. + *

+ * For brightness, the valid range is {@code -2.0,..,0.0,..,2.0}. + * A value of {@code 0.0} means no change. + * Negative values will make the pixels darker. + * Maximum negative value ({@code -2}) will make all filtered pixels black. + * Positive values will make the pixels brighter. + * Maximum positive value ({@code 2}) will make all filtered pixels white. + *

+ *

+ * For contrast, the valid range is {@code -1.0,..,0.0,..,1.0}. + * A value of {@code 0.0} means no change. + * Negative values will reduce contrast. + * Maximum negative value ({@code -1}) will make all filtered pixels grey + * (no contrast). + * Positive values will increase contrast. + * Maximum positive value ({@code 1}) will make all filtered pixels primary + * colors (either black, white, cyan, magenta, yellow, red, blue or green). + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java#1 $ + */ +// TODO: consider doing something similar to http://archives.java.sun.com/cgi-bin/wa?A2=ind0302&L=jai-interest&F=&S=&P=15947 +public class BrightnessContrastFilter extends RGBImageFilter { + + // TODO: Replace with RescaleOp? + + // This filter can filter IndexColorModel, as it is does not depend on + // the pixels' location + { + canFilterIndexColorModel = true; + } + + // Use a pre-calculated lookup table for performance + private final int[] LUT; + + /** + * Creates a BrightnessContrastFilter with default values + * ({@code brightness=0.3, contrast=0.3}). + *

+ * This will slightly increase both brightness and contrast. + *

+ */ + public BrightnessContrastFilter() { + this(0.3f, 0.3f); + } + + /** + * Creates a BrightnessContrastFilter with the given values for brightness + * and contrast. + *

+ * For brightness, the valid range is {@code -2.0,..,0.0,..,2.0}. + * A value of {@code 0.0} means no change. + * Negative values will make the pixels darker. + * Maximum negative value ({@code -2}) will make all filtered pixels black. + * Positive values will make the pixels brighter. + * Maximum positive value ({@code 2}) will make all filtered pixels white. + *

+ *

+ * For contrast, the valid range is {@code -1.0,..,0.0,..,1.0}. + * A value of {@code 0.0} means no change. + * Negative values will reduce contrast. + * Maximum negative value ({@code -1}) will make all filtered pixels grey + * (no contrast). + * Positive values will increase contrast. + * Maximum positive value ({@code 1}) will make all filtered pixels primary + * colors (either black, white, cyan, magenta, yellow, red, blue or green). + *

+ * + * @param pBrightness adjust the brightness of the image, in the range + * {@code -2.0,..,0.0,..,2.0}. + * @param pContrast adjust the contrast of the image, in the range + * {@code -1.0,..,0.0,..,1.0}. + */ + public BrightnessContrastFilter(float pBrightness, float pContrast) { + LUT = createLUT(pBrightness, pContrast); + } + + private static int[] createLUT(float pBrightness, float pContrast) { + int[] lut = new int[256]; + + // Hmmm.. This approximates Photoshop values.. Not good though.. + double contrast = pContrast > 0 ? Math.pow(pContrast, 7.0) * 127.0 : pContrast; + + // Convert range [-1,..,0,..,1] -> [0,..,1,..,2] + double brightness = pBrightness + 1.0; + + for (int i = 0; i < 256; i++) { + lut[i] = clamp((int) (127.5 * brightness + (i - 127) * (contrast + 1.0))); + } + + // Special case, to ensure only primary colors for max contrast + if (pContrast == 1f) { + lut[127] = lut[126]; + } + + return lut; + } + + private static int clamp(int i) { + if (i < 0) { + return 0; + } + if (i > 255) { + return 255; + } + return i; + } + + /** + * Filters one pixel, adjusting brightness and contrast according to this + * filter. + * + * @param pX x + * @param pY y + * @param pARGB pixel value in default color space + * + * @return the filtered pixel value in the default color space + */ + public int filterRGB(int pX, int pY, int pARGB) { + // Get color components + int r = pARGB >> 16 & 0xFF; + int g = pARGB >> 8 & 0xFF; + int b = pARGB & 0xFF; + + // Scale to new contrast + r = LUT[r]; + g = LUT[g]; + b = LUT[b]; + + // Return ARGB pixel, leave transparency as is + return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b; + } +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java index d6b097ff..a8d16cb7 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; @@ -42,14 +44,16 @@ import java.util.concurrent.CopyOnWriteArrayList; * A faster, lighter and easier way to convert an {@code Image} to a * {@code BufferedImage} than using a {@code PixelGrabber}. * Clients may provide progress listeners to monitor conversion progress. - *

+ *

* Supports source image subsampling and source region extraction. * Supports source images with 16 bit {@link ColorModel} and * {@link DataBuffer#TYPE_USHORT} transfer type, without converting to * 32 bit/TYPE_INT. - *

+ *

+ *

* NOTE: Does not support images with more than one {@code ColorModel} or * different types of pixel data. This is not very common. + *

* * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java#1 $ diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java index 649666ba..27104fea 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java @@ -1,90 +1,91 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import com.twelvemonkeys.lang.Validate; - -import javax.swing.Icon; -import java.awt.image.BufferedImage; -import java.awt.*; -import java.awt.geom.AffineTransform; - -/** - * An {@code Icon} implementation backed by a {@code BufferedImage}. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java#2 $ - */ -public class BufferedImageIcon implements Icon { - private final BufferedImage image; - private int width; - private int height; - private final boolean fast; - - public BufferedImageIcon(BufferedImage pImage) { - this(pImage, pImage != null ? pImage.getWidth() : 0, pImage != null ? pImage.getHeight() : 0); - } - - public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) { - this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight); - } - - public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) { - image = Validate.notNull(pImage, "image"); - width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d"); - height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d"); - - fast = useFastRendering; - } - - public int getIconHeight() { - return height; - } - - public int getIconWidth() { - return width; - } - - public void paintIcon(Component c, Graphics g, int x, int y) { - if (fast || !(g instanceof Graphics2D)) { - //System.out.println("Scaling fast"); - g.drawImage(image, x, y, width, height, null); - } - else { - //System.out.println("Scaling using interpolation"); - Graphics2D g2 = (Graphics2D) g; - AffineTransform xform = AffineTransform.getTranslateInstance(x, y); - xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight()); - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2.drawImage(image, xform, null); - } - } -} +/* + * 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.image; + +import com.twelvemonkeys.lang.Validate; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; + +/** + * An {@code Icon} implementation backed by a {@code BufferedImage}. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java#2 $ + */ +public class BufferedImageIcon implements Icon { + private final BufferedImage image; + private int width; + private int height; + private final boolean fast; + + public BufferedImageIcon(BufferedImage pImage) { + this(pImage, pImage != null ? pImage.getWidth() : 0, pImage != null ? pImage.getHeight() : 0); + } + + public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) { + this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight); + } + + public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) { + image = Validate.notNull(pImage, "image"); + width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d"); + height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d"); + + fast = useFastRendering; + } + + public int getIconHeight() { + return height; + } + + public int getIconWidth() { + return width; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + if (fast || !(g instanceof Graphics2D)) { + //System.out.println("Scaling fast"); + g.drawImage(image, x, y, width, height, null); + } + else { + //System.out.println("Scaling using interpolation"); + Graphics2D g2 = (Graphics2D) g; + AffineTransform xform = AffineTransform.getTranslateInstance(x, y); + xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight()); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.drawImage(image, xform, null); + } + } +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ConvolveWithEdgeOp.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ConvolveWithEdgeOp.java index 8a6ac22c..9b34249d 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ConvolveWithEdgeOp.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ConvolveWithEdgeOp.java @@ -4,34 +4,36 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; -import java.awt.image.*; import java.awt.*; -import java.awt.geom.Rectangle2D; import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.*; /** * This class implements a convolution from the source diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/CopyDither.java b/common/common-image/src/main/java/com/twelvemonkeys/image/CopyDither.java index 041bb8a6..768fcaba 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/CopyDither.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/CopyDither.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; @@ -31,13 +33,7 @@ package com.twelvemonkeys.image; import java.awt.*; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.Raster; -import java.awt.image.RasterOp; -import java.awt.image.WritableRaster; +import java.awt.image.*; /** * This BufferedImageOp simply copies pixels, converting to a diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java b/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java index d530d077..50c21390 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java @@ -1,460 +1,485 @@ -package com.twelvemonkeys.image; - - -import java.awt.*; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.Raster; -import java.awt.image.RasterOp; -import java.awt.image.WritableRaster; -import java.util.Random; - -/** - * This {@code BufferedImageOp/RasterOp} implements basic - * Floyd-Steinberg error-diffusion algorithm for dithering. - *

- * The weights used are 7/16, 3/16, 5/16 and 1/16, distributed like this: - * - *

- * - * - * - *
 X7/16
3/165/161/16
- *

- * See Computer Graphics (Foley et al.) - * for more information. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: DiffusionDither.java#1 $ - */ -public class DiffusionDither implements BufferedImageOp, RasterOp { - - private static final int FS_SCALE = 1 << 8; - private static final Random RANDOM = new Random(); - - protected final IndexColorModel indexColorModel; - private boolean alternateScans = true; - - /** - * Creates a {@code DiffusionDither}, using the given - * {@code IndexColorModel} for dithering into. - * - * @param pICM an IndexColorModel. - */ - public DiffusionDither(final IndexColorModel pICM) { - // Store color model - indexColorModel = pICM; - } - - /** - * Creates a {@code DiffusionDither}, with no fixed - * {@code IndexColorModel}. The color model will be generated for each - * filtering, unless the destination image already has an - * {@code IndexColorModel}. - */ - public DiffusionDither() { - this(null); - } - - /** - * Sets the scan mode. If the parameter is true, error distribution for - * every even line will be left-to-right, while odd lines will be - * right-to-left. - * The default is {@code true}. - * - * @param pUse {@code true} if scan mode should be alternating left/right - */ - public void setAlternateScans(boolean pUse) { - alternateScans = pUse; - } - - /** - * Creates a compatible {@code BufferedImage} to dither into. - * Only {@code IndexColorModel} allowed. - * - * @return a compatible {@code BufferedImage} - * - * @throws ImageFilterException if {@code pDestCM} is not {@code null} or - * an instance of {@code IndexColorModel}. - */ - public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) { - if (pDestCM == null) { - return new BufferedImage(pSource.getWidth(), pSource.getHeight(), - BufferedImage.TYPE_BYTE_INDEXED, - getICM(pSource)); - } - else if (pDestCM instanceof IndexColorModel) { - return new BufferedImage(pSource.getWidth(), pSource.getHeight(), - BufferedImage.TYPE_BYTE_INDEXED, - (IndexColorModel) pDestCM); - } - else { - throw new ImageFilterException("Only IndexColorModel allowed."); - } - } - - /** - * Creates a compatible {@code Raster} to dither into. - * Only {@code IndexColorModel} allowed. - * - * @param pSrc the source raster - * - * @return a {@code WritableRaster} - */ - public final WritableRaster createCompatibleDestRaster(Raster pSrc) { - return createCompatibleDestRaster(pSrc, getICM(pSrc)); - } - - /** - * Creates a compatible {@code Raster} to dither into. - * - * @param pSrc the source raster. - * @param pIndexColorModel the index color model used to create a {@code Raster}. - * - * @return a {@code WritableRaster} - */ - public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) { - return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight()); - } - - - /** - * Returns the bounding box of the filtered destination image. Since - * this is not a geometric operation, the bounding box does not - * change. - * @param pSrc the {@code BufferedImage} to be filtered - * @return the bounds of the filtered definition image. - */ - public final Rectangle2D getBounds2D(BufferedImage pSrc) { - return getBounds2D(pSrc.getRaster()); - } - - /** - * Returns the bounding box of the filtered destination Raster. Since - * this is not a geometric operation, the bounding box does not - * change. - * @param pSrc the {@code Raster} to be filtered - * @return the bounds of the filtered definition {@code Raster}. - */ - public final Rectangle2D getBounds2D(Raster pSrc) { - return pSrc.getBounds(); - } - - /** - * Returns the location of the destination point given a - * point in the source. If {@code dstPt} is not - * {@code null}, it will be used to hold the return value. - * Since this is not a geometric operation, the {@code srcPt} - * will equal the {@code dstPt}. - * @param pSrcPt a {@code Point2D} that represents a point - * in the source image - * @param pDstPt a {@code Point2D}that represents the location - * in the destination - * @return the {@code Point2D} in the destination that - * corresponds to the specified point in the source. - */ - public final Point2D getPoint2D(Point2D pSrcPt, Point2D pDstPt) { - // Create new Point, if needed - if (pDstPt == null) { - pDstPt = new Point2D.Float(); - } - - // Copy location - pDstPt.setLocation(pSrcPt.getX(), pSrcPt.getY()); - - // Return dest - return pDstPt; - } - - /** - * Returns the rendering mHints for this op. - * @return the {@code RenderingHints} object associated - * with this op. - */ - public final RenderingHints getRenderingHints() { - return null; - } - - /** - * Converts an int ARGB to int triplet. - */ - private static int[] toRGBArray(int pARGB, int[] pBuffer) { - pBuffer[0] = ((pARGB & 0x00ff0000) >> 16); - pBuffer[1] = ((pARGB & 0x0000ff00) >> 8); - pBuffer[2] = ((pARGB & 0x000000ff)); - //pBuffer[3] = ((pARGB & 0xff000000) >> 24); // alpha - - return pBuffer; - } - - /** - * Converts a int triplet to int ARGB. - */ - private static int toIntARGB(int[] pRGB) { - return 0xff000000 // All opaque - | (pRGB[0] << 16) - | (pRGB[1] << 8) - | (pRGB[2]); - /* - | ((int) (pRGB[0] << 16) & 0x00ff0000) - | ((int) (pRGB[1] << 8) & 0x0000ff00) - | ((int) (pRGB[2] ) & 0x000000ff); - */ - } - - - /** - * Performs a single-input/single-output dither operation, applying basic - * Floyd-Steinberg error-diffusion to the image. - * - * @param pSource the source image - * @param pDest the destination image - * - * @return the destination image, or a new image, if {@code pDest} was - * {@code null}. - */ - public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) { - // Create destination image, if none provided - if (pDest == null) { - pDest = createCompatibleDestImage(pSource, getICM(pSource)); - } - else if (!(pDest.getColorModel() instanceof IndexColorModel)) { - throw new ImageFilterException("Only IndexColorModel allowed."); - } - - // Filter rasters - filter(pSource.getRaster(), pDest.getRaster(), (IndexColorModel) pDest.getColorModel()); - - return pDest; - } - - /** - * Performs a single-input/single-output dither operation, applying basic - * Floyd-Steinberg error-diffusion to the image. - * - * @param pSource the source raster, assumed to be in sRGB - * @param pDest the destination raster, may be {@code null} - * - * @return the destination raster, or a new raster, if {@code pDest} was - * {@code null}. - */ - public final WritableRaster filter(final Raster pSource, WritableRaster pDest) { - return filter(pSource, pDest, getICM(pSource)); - } - - private IndexColorModel getICM(BufferedImage pSource) { - return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK)); - } - private IndexColorModel getICM(Raster pSource) { - return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource)); - } - - private IndexColorModel createIndexColorModel(Raster pSource) { - BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), - BufferedImage.TYPE_INT_ARGB); - image.setData(pSource); - return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK); - } - - /** - * Performs a single-input/single-output dither operation, applying basic - * Floyd-Steinberg error-diffusion to the image. - * - * @param pSource the source raster, assumed to be in sRGB - * @param pDest the destination raster, may be {@code null} - * @param pColorModel the indexed color model to use - * - * @return the destination raster, or a new raster, if {@code pDest} was - * {@code null}. - */ - public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) { - int width = pSource.getWidth(); - int height = pSource.getHeight(); - - // Create destination raster if needed - if (pDest == null) { - pDest = createCompatibleDestRaster(pSource, pColorModel); - } - - // Initialize Floyd-Steinberg error vectors. - // +2 to handle the previous pixel and next pixel case minimally - // When reference for column, add 1 to reference as this buffer is - // offset from actual column position by one to allow FS to not check - // left/right edge conditions - int[][] currErr = new int[width + 2][3]; - int[][] nextErr = new int[width + 2][3]; - - // Random errors in [-1 .. 1] - for first row - for (int i = 0; i < width + 2; i++) { - // Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE - currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; - currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; - currErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; - } - - // Temp buffers - final int[] diff = new int[3]; // No alpha - final int[] inRGB = new int[4]; - final int[] outRGB = new int[4]; - Object pixel = null; - boolean forward = true; - - // Loop through image data - for (int y = 0; y < height; y++) { - // Clear out next error rows for colour errors - for (int i = nextErr.length; --i >= 0;) { - nextErr[i][0] = 0; - nextErr[i][1] = 0; - nextErr[i][2] = 0; - } - - // Set up start column and limit - int x; - int limit; - if (forward) { - x = 0; - limit = width; - } - else { - x = width - 1; - limit = -1; - } - - // TODO: Use getPixels instead of getPixel for better performance? - - // Loop over row - while (true) { - // Get RGB from original raster - // DON'T KNOW IF THIS WILL WORK FOR ALL TYPES. - pSource.getPixel(x, y, inRGB); - - // Get error for this pixel & add error to rgb - for (int i = 0; i < 3; i++) { - // Make a 28.4 FP number, add Error (with fraction), - // rounding and truncate to int - inRGB[i] = ((inRGB[i] << 4) + currErr[x + 1][i] + 0x08) >> 4; - - // Clamp - if (inRGB[i] > 255) { - inRGB[i] = 255; - } - else if (inRGB[i] < 0) { - inRGB[i] = 0; - } - } - - // Get pixel value... - // It is VERY important that we are using a IndexColorModel that - // support reverse color lookup for speed. - pixel = pColorModel.getDataElements(toIntARGB(inRGB), pixel); - - // ...set it... - pDest.setDataElements(x, y, pixel); - - // ..and get back the closet match - pDest.getPixel(x, y, outRGB); - - // Convert the value to default sRGB - // Should work for all transfertypes supported by IndexColorModel - toRGBArray(pColorModel.getRGB(outRGB[0]), outRGB); - - // Find diff - diff[0] = inRGB[0] - outRGB[0]; - diff[1] = inRGB[1] - outRGB[1]; - diff[2] = inRGB[2] - outRGB[2]; - - // Apply F-S error diffusion - // Serpentine scan: left-right - if (forward) { - // Row 1 (y) - // Update error in this pixel (x + 1) - currErr[x + 2][0] += diff[0] * 7; - currErr[x + 2][1] += diff[1] * 7; - currErr[x + 2][2] += diff[2] * 7; - - // Row 2 (y + 1) - // Update error in this pixel (x - 1) - nextErr[x][0] += diff[0] * 3; - nextErr[x][1] += diff[1] * 3; - nextErr[x][2] += diff[2] * 3; - // Update error in this pixel (x) - nextErr[x + 1][0] += diff[0] * 5; - nextErr[x + 1][1] += diff[1] * 5; - nextErr[x + 1][2] += diff[2] * 5; - // Update error in this pixel (x + 1) - // TODO: Consider calculating this using - // error term = error - sum(error terms 1, 2 and 3) - // See Computer Graphics (Foley et al.), p. 573 - nextErr[x + 2][0] += diff[0]; // * 1; - nextErr[x + 2][1] += diff[1]; // * 1; - nextErr[x + 2][2] += diff[2]; // * 1; - - // Next - x++; - - // Done? - if (x >= limit) { - break; - } - - } - else { - // Row 1 (y) - // Update error in this pixel (x - 1) - currErr[x][0] += diff[0] * 7; - currErr[x][1] += diff[1] * 7; - currErr[x][2] += diff[2] * 7; - - // Row 2 (y + 1) - // Update error in this pixel (x + 1) - nextErr[x + 2][0] += diff[0] * 3; - nextErr[x + 2][1] += diff[1] * 3; - nextErr[x + 2][2] += diff[2] * 3; - // Update error in this pixel (x) - nextErr[x + 1][0] += diff[0] * 5; - nextErr[x + 1][1] += diff[1] * 5; - nextErr[x + 1][2] += diff[2] * 5; - // Update error in this pixel (x - 1) - // TODO: Consider calculating this using - // error term = error - sum(error terms 1, 2 and 3) - // See Computer Graphics (Foley et al.), p. 573 - nextErr[x][0] += diff[0]; // * 1; - nextErr[x][1] += diff[1]; // * 1; - nextErr[x][2] += diff[2]; // * 1; - - // Previous - x--; - - // Done? - if (x <= limit) { - break; - } - } - } - - // Make next error info current for next iteration - int[][] temperr; - temperr = currErr; - currErr = nextErr; - nextErr = temperr; - - // Toggle direction - if (alternateScans) { - forward = !forward; - } - } - - return pDest; - } +/* + * 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.image; + +import java.awt.*; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.*; +import java.util.Random; + +/** + * This {@code BufferedImageOp/RasterOp} implements basic + * Floyd-Steinberg error-diffusion algorithm for dithering. + *

+ * The weights used are 7/16, 3/16, 5/16 and 1/16, distributed like this: + * + *

+ * + * + * + * + *
Floyd-Steinberg error-diffusion weights
 x7/16
3/165/161/16
+ *

+ * See Computer Graphics (Foley et al.) + * for more information. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: DiffusionDither.java#1 $ + */ +public class DiffusionDither implements BufferedImageOp, RasterOp { + + private static final int FS_SCALE = 1 << 8; + private static final Random RANDOM = new Random(); + + protected final IndexColorModel indexColorModel; + private boolean alternateScans = true; + + /** + * Creates a {@code DiffusionDither}, using the given + * {@code IndexColorModel} for dithering into. + * + * @param pICM an IndexColorModel. + */ + public DiffusionDither(final IndexColorModel pICM) { + // Store color model + indexColorModel = pICM; + } + + /** + * Creates a {@code DiffusionDither}, with no fixed + * {@code IndexColorModel}. The color model will be generated for each + * filtering, unless the destination image already has an + * {@code IndexColorModel}. + */ + public DiffusionDither() { + this(null); + } + + /** + * Sets the scan mode. If the parameter is true, error distribution for + * every even line will be left-to-right, while odd lines will be + * right-to-left. + * The default is {@code true}. + * + * @param pUse {@code true} if scan mode should be alternating left/right + */ + public void setAlternateScans(boolean pUse) { + alternateScans = pUse; + } + + /** + * Creates a compatible {@code BufferedImage} to dither into. + * Only {@code IndexColorModel} allowed. + * + * @return a compatible {@code BufferedImage} + * + * @throws ImageFilterException if {@code pDestCM} is not {@code null} or + * an instance of {@code IndexColorModel}. + */ + public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) { + if (pDestCM == null) { + return new BufferedImage(pSource.getWidth(), pSource.getHeight(), + BufferedImage.TYPE_BYTE_INDEXED, + getICM(pSource)); + } + else if (pDestCM instanceof IndexColorModel) { + return new BufferedImage(pSource.getWidth(), pSource.getHeight(), + BufferedImage.TYPE_BYTE_INDEXED, + (IndexColorModel) pDestCM); + } + else { + throw new ImageFilterException("Only IndexColorModel allowed."); + } + } + + /** + * Creates a compatible {@code Raster} to dither into. + * Only {@code IndexColorModel} allowed. + * + * @param pSrc the source raster + * + * @return a {@code WritableRaster} + */ + public final WritableRaster createCompatibleDestRaster(Raster pSrc) { + return createCompatibleDestRaster(pSrc, getICM(pSrc)); + } + + /** + * Creates a compatible {@code Raster} to dither into. + * + * @param pSrc the source raster. + * @param pIndexColorModel the index color model used to create a {@code Raster}. + * + * @return a {@code WritableRaster} + */ + public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) { + return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight()); + } + + + /** + * Returns the bounding box of the filtered destination image. Since + * this is not a geometric operation, the bounding box does not + * change. + * @param pSrc the {@code BufferedImage} to be filtered + * @return the bounds of the filtered definition image. + */ + public final Rectangle2D getBounds2D(BufferedImage pSrc) { + return getBounds2D(pSrc.getRaster()); + } + + /** + * Returns the bounding box of the filtered destination Raster. Since + * this is not a geometric operation, the bounding box does not + * change. + * @param pSrc the {@code Raster} to be filtered + * @return the bounds of the filtered definition {@code Raster}. + */ + public final Rectangle2D getBounds2D(Raster pSrc) { + return pSrc.getBounds(); + } + + /** + * Returns the location of the destination point given a + * point in the source. If {@code dstPt} is not + * {@code null}, it will be used to hold the return value. + * Since this is not a geometric operation, the {@code srcPt} + * will equal the {@code dstPt}. + * @param pSrcPt a {@code Point2D} that represents a point + * in the source image + * @param pDstPt a {@code Point2D}that represents the location + * in the destination + * @return the {@code Point2D} in the destination that + * corresponds to the specified point in the source. + */ + public final Point2D getPoint2D(Point2D pSrcPt, Point2D pDstPt) { + // Create new Point, if needed + if (pDstPt == null) { + pDstPt = new Point2D.Float(); + } + + // Copy location + pDstPt.setLocation(pSrcPt.getX(), pSrcPt.getY()); + + // Return dest + return pDstPt; + } + + /** + * Returns the rendering mHints for this op. + * @return the {@code RenderingHints} object associated + * with this op. + */ + public final RenderingHints getRenderingHints() { + return null; + } + + /** + * Converts an int ARGB to int triplet. + */ + private static int[] toRGBArray(int pARGB, int[] pBuffer) { + pBuffer[0] = ((pARGB & 0x00ff0000) >> 16); + pBuffer[1] = ((pARGB & 0x0000ff00) >> 8); + pBuffer[2] = ((pARGB & 0x000000ff)); + //pBuffer[3] = ((pARGB & 0xff000000) >> 24); // alpha + + return pBuffer; + } + + /** + * Converts a int triplet to int ARGB. + */ + private static int toIntARGB(int[] pRGB) { + return 0xff000000 // All opaque + | (pRGB[0] << 16) + | (pRGB[1] << 8) + | (pRGB[2]); + /* + | ((int) (pRGB[0] << 16) & 0x00ff0000) + | ((int) (pRGB[1] << 8) & 0x0000ff00) + | ((int) (pRGB[2] ) & 0x000000ff); + */ + } + + + /** + * Performs a single-input/single-output dither operation, applying basic + * Floyd-Steinberg error-diffusion to the image. + * + * @param pSource the source image + * @param pDest the destination image + * + * @return the destination image, or a new image, if {@code pDest} was + * {@code null}. + */ + public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) { + // Create destination image, if none provided + if (pDest == null) { + pDest = createCompatibleDestImage(pSource, getICM(pSource)); + } + else if (!(pDest.getColorModel() instanceof IndexColorModel)) { + throw new ImageFilterException("Only IndexColorModel allowed."); + } + + // Filter rasters + filter(pSource.getRaster(), pDest.getRaster(), (IndexColorModel) pDest.getColorModel()); + + return pDest; + } + + /** + * Performs a single-input/single-output dither operation, applying basic + * Floyd-Steinberg error-diffusion to the image. + * + * @param pSource the source raster, assumed to be in sRGB + * @param pDest the destination raster, may be {@code null} + * + * @return the destination raster, or a new raster, if {@code pDest} was + * {@code null}. + */ + public final WritableRaster filter(final Raster pSource, WritableRaster pDest) { + return filter(pSource, pDest, getICM(pSource)); + } + + private IndexColorModel getICM(BufferedImage pSource) { + return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK)); + } + private IndexColorModel getICM(Raster pSource) { + return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource)); + } + + private IndexColorModel createIndexColorModel(Raster pSource) { + BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), + BufferedImage.TYPE_INT_ARGB); + image.setData(pSource); + return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK); + } + + /** + * Performs a single-input/single-output dither operation, applying basic + * Floyd-Steinberg error-diffusion to the image. + * + * @param pSource the source raster, assumed to be in sRGB + * @param pDest the destination raster, may be {@code null} + * @param pColorModel the indexed color model to use + * + * @return the destination raster, or a new raster, if {@code pDest} was + * {@code null}. + */ + public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) { + int width = pSource.getWidth(); + int height = pSource.getHeight(); + + // Create destination raster if needed + if (pDest == null) { + pDest = createCompatibleDestRaster(pSource, pColorModel); + } + + // Initialize Floyd-Steinberg error vectors. + // +2 to handle the previous pixel and next pixel case minimally + // When reference for column, add 1 to reference as this buffer is + // offset from actual column position by one to allow FS to not check + // left/right edge conditions + int[][] currErr = new int[width + 2][3]; + int[][] nextErr = new int[width + 2][3]; + + // Random errors in [-1 .. 1] - for first row + for (int i = 0; i < width + 2; i++) { + // Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE + currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; + currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; + currErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; + } + + // Temp buffers + final int[] diff = new int[3]; // No alpha + final int[] inRGB = new int[4]; + final int[] outRGB = new int[4]; + Object pixel = null; + boolean forward = true; + + // Loop through image data + for (int y = 0; y < height; y++) { + // Clear out next error rows for colour errors + for (int i = nextErr.length; --i >= 0;) { + nextErr[i][0] = 0; + nextErr[i][1] = 0; + nextErr[i][2] = 0; + } + + // Set up start column and limit + int x; + int limit; + if (forward) { + x = 0; + limit = width; + } + else { + x = width - 1; + limit = -1; + } + + // TODO: Use getPixels instead of getPixel for better performance? + + // Loop over row + while (true) { + // Get RGB from original raster + // DON'T KNOW IF THIS WILL WORK FOR ALL TYPES. + pSource.getPixel(x, y, inRGB); + + // Get error for this pixel & add error to rgb + for (int i = 0; i < 3; i++) { + // Make a 28.4 FP number, add Error (with fraction), + // rounding and truncate to int + inRGB[i] = ((inRGB[i] << 4) + currErr[x + 1][i] + 0x08) >> 4; + + // Clamp + if (inRGB[i] > 255) { + inRGB[i] = 255; + } + else if (inRGB[i] < 0) { + inRGB[i] = 0; + } + } + + // Get pixel value... + // It is VERY important that we are using a IndexColorModel that + // support reverse color lookup for speed. + pixel = pColorModel.getDataElements(toIntARGB(inRGB), pixel); + + // ...set it... + pDest.setDataElements(x, y, pixel); + + // ..and get back the closet match + pDest.getPixel(x, y, outRGB); + + // Convert the value to default sRGB + // Should work for all transfertypes supported by IndexColorModel + toRGBArray(pColorModel.getRGB(outRGB[0]), outRGB); + + // Find diff + diff[0] = inRGB[0] - outRGB[0]; + diff[1] = inRGB[1] - outRGB[1]; + diff[2] = inRGB[2] - outRGB[2]; + + // Apply F-S error diffusion + // Serpentine scan: left-right + if (forward) { + // Row 1 (y) + // Update error in this pixel (x + 1) + currErr[x + 2][0] += diff[0] * 7; + currErr[x + 2][1] += diff[1] * 7; + currErr[x + 2][2] += diff[2] * 7; + + // Row 2 (y + 1) + // Update error in this pixel (x - 1) + nextErr[x][0] += diff[0] * 3; + nextErr[x][1] += diff[1] * 3; + nextErr[x][2] += diff[2] * 3; + // Update error in this pixel (x) + nextErr[x + 1][0] += diff[0] * 5; + nextErr[x + 1][1] += diff[1] * 5; + nextErr[x + 1][2] += diff[2] * 5; + // Update error in this pixel (x + 1) + // TODO: Consider calculating this using + // error term = error - sum(error terms 1, 2 and 3) + // See Computer Graphics (Foley et al.), p. 573 + nextErr[x + 2][0] += diff[0]; // * 1; + nextErr[x + 2][1] += diff[1]; // * 1; + nextErr[x + 2][2] += diff[2]; // * 1; + + // Next + x++; + + // Done? + if (x >= limit) { + break; + } + + } + else { + // Row 1 (y) + // Update error in this pixel (x - 1) + currErr[x][0] += diff[0] * 7; + currErr[x][1] += diff[1] * 7; + currErr[x][2] += diff[2] * 7; + + // Row 2 (y + 1) + // Update error in this pixel (x + 1) + nextErr[x + 2][0] += diff[0] * 3; + nextErr[x + 2][1] += diff[1] * 3; + nextErr[x + 2][2] += diff[2] * 3; + // Update error in this pixel (x) + nextErr[x + 1][0] += diff[0] * 5; + nextErr[x + 1][1] += diff[1] * 5; + nextErr[x + 1][2] += diff[2] * 5; + // Update error in this pixel (x - 1) + // TODO: Consider calculating this using + // error term = error - sum(error terms 1, 2 and 3) + // See Computer Graphics (Foley et al.), p. 573 + nextErr[x][0] += diff[0]; // * 1; + nextErr[x][1] += diff[1]; // * 1; + nextErr[x][2] += diff[2]; // * 1; + + // Previous + x--; + + // Done? + if (x <= limit) { + break; + } + } + } + + // Make next error info current for next iteration + int[][] temperr; + temperr = currErr; + currErr = nextErr; + nextErr = temperr; + + // Toggle direction + if (alternateScans) { + forward = !forward; + } + } + + return pDest; + } } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/GraphicsUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/GraphicsUtil.java index f221bdb5..94be2459 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/GraphicsUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/GraphicsUtil.java @@ -1,82 +1,86 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import java.awt.*; - -/** - * GraphicsUtil - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/GraphicsUtil.java#1 $ - */ -public final class GraphicsUtil { - - /** - * Enables anti-aliasing in the {@code Graphics} object. - *

- * Anti-aliasing is enabled by casting to {@code Graphics2D} and setting - * the rendering hint {@code RenderingHints.KEY_ANTIALIASING} to - * {@code RenderingHints.VALUE_ANTIALIAS_ON}. - * - * @param pGraphics the graphics object - * @throws ClassCastException if {@code pGraphics} is not an instance of - * {@code Graphics2D}. - * - * @see java.awt.RenderingHints#KEY_ANTIALIASING - */ - public static void enableAA(final Graphics pGraphics) { - ((Graphics2D) pGraphics).setRenderingHint( - RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON - ); - } - - /** - * Sets the alpha in the {@code Graphics} object. - *

- * Alpha is set by casting to {@code Graphics2D} and setting the composite - * to the rule {@code AlphaComposite.SRC_OVER} multiplied by the given - * alpha. - * - * @param pGraphics the graphics object - * @param pAlpha the alpha level, {@code alpha} must be a floating point - * number in the inclusive range [0.0, 1.0]. - * @throws ClassCastException if {@code pGraphics} is not an instance of - * {@code Graphics2D}. - * - * @see java.awt.AlphaComposite#SRC_OVER - * @see java.awt.AlphaComposite#getInstance(int, float) - */ - public static void setAlpha(final Graphics pGraphics, final float pAlpha) { - ((Graphics2D) pGraphics).setComposite( - AlphaComposite.getInstance(AlphaComposite.SRC_OVER, pAlpha) - ); - } -} +/* + * 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.image; + +import java.awt.*; + +/** + * GraphicsUtil + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/GraphicsUtil.java#1 $ + */ +public final class GraphicsUtil { + + /** + * Enables anti-aliasing in the {@code Graphics} object. + *

+ * Anti-aliasing is enabled by casting to {@code Graphics2D} and setting + * the rendering hint {@code RenderingHints.KEY_ANTIALIASING} to + * {@code RenderingHints.VALUE_ANTIALIAS_ON}. + *

+ * + * @param pGraphics the graphics object + * @throws ClassCastException if {@code pGraphics} is not an instance of + * {@code Graphics2D}. + * + * @see java.awt.RenderingHints#KEY_ANTIALIASING + */ + public static void enableAA(final Graphics pGraphics) { + ((Graphics2D) pGraphics).setRenderingHint( + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON + ); + } + + /** + * Sets the alpha in the {@code Graphics} object. + *

+ * Alpha is set by casting to {@code Graphics2D} and setting the composite + * to the rule {@code AlphaComposite.SRC_OVER} multiplied by the given + * alpha. + *

+ * + * @param pGraphics the graphics object + * @param pAlpha the alpha level, {@code alpha} must be a floating point + * number in the inclusive range [0.0, 1.0]. + * @throws ClassCastException if {@code pGraphics} is not an instance of + * {@code Graphics2D}. + * + * @see java.awt.AlphaComposite#SRC_OVER + * @see java.awt.AlphaComposite#getInstance(int, float) + */ + public static void setAlpha(final Graphics pGraphics, final float pAlpha) { + ((Graphics2D) pGraphics).setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, pAlpha) + ); + } +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/GrayColorModel.java b/common/common-image/src/main/java/com/twelvemonkeys/image/GrayColorModel.java index c636e9fc..9f02beaa 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/GrayColorModel.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/GrayColorModel.java @@ -4,31 +4,33 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; -import java.awt.image.*; +import java.awt.image.IndexColorModel; /** * This class represents a 256 color fixed grayscale IndexColorModel. diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/GrayFilter.java b/common/common-image/src/main/java/com/twelvemonkeys/image/GrayFilter.java index 03e2dee1..27332422 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/GrayFilter.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/GrayFilter.java @@ -1,129 +1,132 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import java.awt.image.*; - -/** - * This class can convert a color image to grayscale. - *

- * Uses ITU standard conversion: (222 * Red + 707 * Green + 71 * Blue) / 1000. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/GrayFilter.java#1 $ - * - */ -public class GrayFilter extends RGBImageFilter { - - // This filter can filter IndexColorModel - { - canFilterIndexColorModel = true; - } - - private int low = 0; - private float range = 1.0f; - - /** - * Constructs a GrayFilter using ITU color-conversion. - */ - public GrayFilter() { - } - - /** - * Constructs a GrayFilter using ITU color-conversion, and a dynamic range between - * pLow and pHigh. - * - * @param pLow float in the range 0..1 - * @param pHigh float in the range 0..1 and >= pLow - */ - public GrayFilter(float pLow, float pHigh) { - if (pLow > pHigh) { - pLow = 0f; - } - // Make sure high and low are inside range - if (pLow < 0f) { - pLow = 0f; - } - else if (pLow > 1f) { - pLow = 1f; - } - if (pHigh < 0f) { - pHigh = 0f; - } - else if (pHigh > 1f) { - pHigh = 1f; - } - - low = (int) (pLow * 255f); - range = pHigh - pLow; - - } - - /** - * Constructs a GrayFilter using ITU color-conversion, and a dynamic - * range between pLow and pHigh. - * - * @param pLow integer in the range 0..255 - * @param pHigh inteeger in the range 0..255 and >= pLow - */ - public GrayFilter(int pLow, int pHigh) { - this(pLow / 255f, pHigh / 255f); - } - - /** - * Filters one pixel using ITU color-conversion. - * - * @param pX x - * @param pY y - * @param pARGB pixel value in default color space - * - * @return the filtered pixel value in the default color space - */ - public int filterRGB(int pX, int pY, int pARGB) { - // Get color components - int r = pARGB >> 16 & 0xFF; - int g = pARGB >> 8 & 0xFF; - int b = pARGB & 0xFF; - - // ITU standard: Gray scale=(222*Red+707*Green+71*Blue)/1000 - int gray = (222 * r + 707 * g + 71 * b) / 1000; - - //int gray = (int) ((float) (r + g + b) / 3.0f); - - if (range != 1.0f) { - // Apply range - gray = low + (int) (gray * range); - } - - // Return ARGB pixel - return (pARGB & 0xFF000000) | (gray << 16) | (gray << 8) | gray; - } -} +/* + * 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.image; + +import java.awt.image.RGBImageFilter; + +/** + * This class can convert a color image to grayscale. + *

+ * Uses ITU standard conversion: (222 * Red + 707 * Green + 71 * Blue) / 1000. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/GrayFilter.java#1 $ + * + */ +public class GrayFilter extends RGBImageFilter { + + // This filter can filter IndexColorModel + { + canFilterIndexColorModel = true; + } + + private int low = 0; + private float range = 1.0f; + + /** + * Constructs a GrayFilter using ITU color-conversion. + */ + public GrayFilter() { + } + + /** + * Constructs a GrayFilter using ITU color-conversion, and a dynamic range between + * pLow and pHigh. + * + * @param pLow float in the range 0..1 + * @param pHigh float in the range 0..1 and >= pLow + */ + public GrayFilter(float pLow, float pHigh) { + if (pLow > pHigh) { + pLow = 0f; + } + // Make sure high and low are inside range + if (pLow < 0f) { + pLow = 0f; + } + else if (pLow > 1f) { + pLow = 1f; + } + if (pHigh < 0f) { + pHigh = 0f; + } + else if (pHigh > 1f) { + pHigh = 1f; + } + + low = (int) (pLow * 255f); + range = pHigh - pLow; + + } + + /** + * Constructs a GrayFilter using ITU color-conversion, and a dynamic + * range between pLow and pHigh. + * + * @param pLow integer in the range 0..255 + * @param pHigh integer in the range 0..255 and >= pLow + */ + public GrayFilter(int pLow, int pHigh) { + this(pLow / 255f, pHigh / 255f); + } + + /** + * Filters one pixel using ITU color-conversion. + * + * @param pX x + * @param pY y + * @param pARGB pixel value in default color space + * + * @return the filtered pixel value in the default color space + */ + public int filterRGB(int pX, int pY, int pARGB) { + // Get color components + int r = pARGB >> 16 & 0xFF; + int g = pARGB >> 8 & 0xFF; + int b = pARGB & 0xFF; + + // ITU standard: Gray scale=(222*Red+707*Green+71*Blue)/1000 + int gray = (222 * r + 707 * g + 71 * b) / 1000; + + //int gray = (int) ((float) (r + g + b) / 3.0f); + + if (range != 1.0f) { + // Apply range + gray = low + (int) (gray * range); + } + + // Return ARGB pixel + return (pARGB & 0xFF000000) | (gray << 16) | (gray << 8) | gray; + } +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageConversionException.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageConversionException.java index aefa2f2c..6e0a6c2e 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageConversionException.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageConversionException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageFilterException.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageFilterException.java index d05cb5c4..c10e84c9 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageFilterException.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageFilterException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java index 88fd7f1d..cef0b7e2 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java @@ -1,1940 +1,1956 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.awt.image.*; -import java.util.Hashtable; - -/** - * This class contains methods for basic image manipulation and conversion. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $ - */ -public final class ImageUtil { - // TODO: Split palette generation out, into ColorModel classes (?) - - public final static int ROTATE_90_CCW = -90; - public final static int ROTATE_90_CW = 90; - public final static int ROTATE_180 = 180; - - public final static int FLIP_VERTICAL = -1; - public final static int FLIP_HORIZONTAL = 1; - - /** - * Alias for {@link ConvolveOp#EDGE_ZERO_FILL}. - * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) - * @see #EDGE_REFLECT - */ - public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL; - - /** - * Alias for {@link ConvolveOp#EDGE_NO_OP}. - * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) - * @see #EDGE_REFLECT - */ - public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP; - - /** - * Adds a border to the image while convolving. The border will reflect the - * edges of the original image. This is usually a good default. - * Note that while this mode typically provides better quality than the - * standard modes {@code EDGE_ZERO_FILL} and {@code EDGE_NO_OP}, it does so - * at the expense of higher memory consumption and considerable more computation. - * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) - */ - public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT - - /** - * Adds a border to the image while convolving. The border will wrap the - * edges of the original image. This is usually the best choice for tiles. - * Note that while this mode typically provides better quality than the - * standard modes {@code EDGE_ZERO_FILL} and {@code EDGE_NO_OP}, it does so - * at the expense of higher memory consumption and considerable more computation. - * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) - * @see #EDGE_REFLECT - */ - public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP - - /** - * Java default dither - */ - public final static int DITHER_DEFAULT = IndexImage.DITHER_DEFAULT; - - /** - * No dither - */ - public final static int DITHER_NONE = IndexImage.DITHER_NONE; - - /** - * Error diffusion dither - */ - public final static int DITHER_DIFFUSION = IndexImage.DITHER_DIFFUSION; - - /** - * Error diffusion dither with alternating scans - */ - public final static int DITHER_DIFFUSION_ALTSCANS = IndexImage.DITHER_DIFFUSION_ALTSCANS; - - /** - * Default color selection - */ - public final static int COLOR_SELECTION_DEFAULT = IndexImage.COLOR_SELECTION_DEFAULT; - - /** - * Prioritize speed - */ - public final static int COLOR_SELECTION_FAST = IndexImage.COLOR_SELECTION_FAST; - - /** - * Prioritize quality - */ - public final static int COLOR_SELECTION_QUALITY = IndexImage.COLOR_SELECTION_QUALITY; - - /** - * Default transparency (none) - */ - public final static int TRANSPARENCY_DEFAULT = IndexImage.TRANSPARENCY_DEFAULT; - - /** - * Discard any alpha information - */ - public final static int TRANSPARENCY_OPAQUE = IndexImage.TRANSPARENCY_OPAQUE; - - /** - * Convert alpha to bitmask - */ - public final static int TRANSPARENCY_BITMASK = IndexImage.TRANSPARENCY_BITMASK; - - /** - * Keep original alpha (not supported yet) - */ - protected final static int TRANSPARENCY_TRANSLUCENT = IndexImage.TRANSPARENCY_TRANSLUCENT; - - /** Passed to the createXxx methods, to indicate that the type does not matter */ - private final static int BI_TYPE_ANY = -1; - /* - public final static int BI_TYPE_ANY_TRANSLUCENT = -1; - public final static int BI_TYPE_ANY_BITMASK = -2; - public final static int BI_TYPE_ANY_OPAQUE = -3;*/ - - /** Tells wether this WM may support acceleration of some images */ - private static boolean VM_SUPPORTS_ACCELERATION = true; - - /** The sharpen matrix */ - private static final float[] SHARPEN_MATRIX = new float[] { - 0.0f, -0.3f, 0.0f, - -0.3f, 2.2f, -0.3f, - 0.0f, -0.3f, 0.0f - }; - - /** - * The sharpen kernel. Uses the following 3 by 3 matrix: - * - * - * - * - *
0.0-0.30.0
-0.32.2-0.3
0.0-0.30.0
- */ - private static final Kernel SHARPEN_KERNEL = new Kernel(3, 3, SHARPEN_MATRIX); - - /** - * Component that can be used with the MediaTracker etc. - */ - private static final Component NULL_COMPONENT = new Component() {}; - - /** Our static image tracker */ - private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT); - - /** */ - protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); - /** */ - protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0); - - /** */ - private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration(); - - private static GraphicsConfiguration getDefaultGraphicsConfiguration() { - try { - GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); - if (!env.isHeadlessInstance()) { - return env.getDefaultScreenDevice().getDefaultConfiguration(); - } - } - catch (LinkageError e) { - // Means we are not in a 1.4+ VM, so skip testing for headless again - VM_SUPPORTS_ACCELERATION = false; - } - return null; - } - - /** Creates an ImageUtil. Private constructor. */ - private ImageUtil() { - } - - /** - * Converts the {@code RenderedImage} to a {@code BufferedImage}. - * The new image will have the same {@code ColorModel}, - * {@code Raster} and properties as the original image, if possible. - *

- * If the image is already a {@code BufferedImage}, it is simply returned - * and no conversion takes place. - * - * @param pOriginal the image to convert. - * - * @return a {@code BufferedImage} - */ - public static BufferedImage toBuffered(RenderedImage pOriginal) { - // Don't convert if it already is a BufferedImage - if (pOriginal instanceof BufferedImage) { - return (BufferedImage) pOriginal; - } - if (pOriginal == null) { - throw new IllegalArgumentException("original == null"); - } - - // Copy properties - Hashtable properties; - String[] names = pOriginal.getPropertyNames(); - if (names != null && names.length > 0) { - properties = new Hashtable(names.length); - - for (String name : names) { - properties.put(name, pOriginal.getProperty(name)); - } - } - else { - properties = null; - } - - // NOTE: This is a workaround for the broken Batik '*Red' classes, that - // throw NPE if copyData(null) is used. This may actually be faster too. - // See RenderedImage#copyData / RenderedImage#getData - Raster data = pOriginal.getData(); - WritableRaster raster; - if (data instanceof WritableRaster) { - raster = (WritableRaster) data; - } - else { - raster = data.createCompatibleWritableRaster(); - raster = pOriginal.copyData(raster); - } - - // Create buffered image - ColorModel colorModel = pOriginal.getColorModel(); - return new BufferedImage(colorModel, raster, - colorModel.isAlphaPremultiplied(), - properties); - } - - /** - * Converts the {@code RenderedImage} to a {@code BufferedImage} of the - * given type. - *

- * If the image is already a {@code BufferedImage} of the given type, it - * is simply returned and no conversion takes place. - * - * @param pOriginal the image to convert. - * @param pType the type of buffered image - * - * @return a {@code BufferedImage} - * - * @throws IllegalArgumentException if {@code pOriginal == null} - * or {@code pType} is not a valid type for {@code BufferedImage} - * - * @see java.awt.image.BufferedImage#getType() - */ - public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) { - // Don't convert if it already is BufferedImage and correct type - if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) { - return (BufferedImage) pOriginal; - } - if (pOriginal == null) { - throw new IllegalArgumentException("original == null"); - } - - // Create a buffered image - BufferedImage image = createBuffered(pOriginal.getWidth(), - pOriginal.getHeight(), - pType, Transparency.TRANSLUCENT); - - // Draw the image onto the buffer - // NOTE: This is faster than doing a raster conversion in most cases - Graphics2D g = image.createGraphics(); - try { - g.setComposite(AlphaComposite.Src); - g.drawRenderedImage(pOriginal, IDENTITY_TRANSFORM); - } - finally { - g.dispose(); - } - - return image; - } - - /** - * Converts the {@code BufferedImage} to a {@code BufferedImage} of the - * given type. The new image will have the same {@code ColorModel}, - * {@code Raster} and properties as the original image, if possible. - *

- * If the image is already a {@code BufferedImage} of the given type, it - * is simply returned and no conversion takes place. - *

- * This method simply invokes - * {@link #toBuffered(RenderedImage,int) toBuffered((RenderedImage) pOriginal, pType)}. - * - * @param pOriginal the image to convert. - * @param pType the type of buffered image - * - * @return a {@code BufferedImage} - * - * @throws IllegalArgumentException if {@code pOriginal == null} - * or if {@code pType} is not a valid type for {@code BufferedImage} - * - * @see java.awt.image.BufferedImage#getType() - */ - public static BufferedImage toBuffered(BufferedImage pOriginal, int pType) { - return toBuffered((RenderedImage) pOriginal, pType); - } - - /** - * Converts the {@code Image} to a {@code BufferedImage}. - * The new image will have the same {@code ColorModel}, {@code Raster} and - * properties as the original image, if possible. - *

- * If the image is already a {@code BufferedImage}, it is simply returned - * and no conversion takes place. - * - * @param pOriginal the image to convert. - * - * @return a {@code BufferedImage} - * - * @throws IllegalArgumentException if {@code pOriginal == null} - * @throws ImageConversionException if the image cannot be converted - */ - public static BufferedImage toBuffered(Image pOriginal) { - // Don't convert if it already is BufferedImage - if (pOriginal instanceof BufferedImage) { - return (BufferedImage) pOriginal; - } - if (pOriginal == null) { - throw new IllegalArgumentException("original == null"); - } - - //System.out.println("--> Doing full BufferedImage conversion..."); - - BufferedImageFactory factory = new BufferedImageFactory(pOriginal); - return factory.getBufferedImage(); - } - - /** - * Creates a deep copy of the given image. The image will have the same - * color model and raster type, but will not share image (pixel) data - * with the input image. - * - * @param pImage the image to clone. - * - * @return a new {@code BufferedImage} - * - * @throws IllegalArgumentException if {@code pImage} is {@code null} - */ - public static BufferedImage createCopy(final BufferedImage pImage) { - if (pImage == null) { - throw new IllegalArgumentException("image == null"); - } - - ColorModel cm = pImage.getColorModel(); - - BufferedImage img = new BufferedImage(cm, - cm.createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()), - cm.isAlphaPremultiplied(), null); - - drawOnto(img, pImage); - - return img; - } - - /** - * Creates a {@code WritableRaster} for the given {@code ColorModel} and - * pixel data. - *

- * This method is optimized for the most common cases of {@code ColorModel} - * and pixel data combinations. The raster's backing {@code DataBuffer} is - * created directly from the pixel data, as this is faster and more - * resource-friendly than using - * {@code ColorModel.createCompatibleWritableRaster(w, h)}. - *

- * For uncommon combinations, the method will fallback to using - * {@code ColorModel.createCompatibleWritableRaster(w, h)} and - * {@code WritableRaster.setDataElements(w, h, pixels)} - *

- * Note that the {@code ColorModel} and pixel data are not cloned - * (in most cases). - * - * @param pWidth the requested raster width - * @param pHeight the requested raster height - * @param pPixels the pixels, as an array, of a type supported by the - * different {@link DataBuffer} - * @param pColorModel the color model to use - * @return a new {@code WritableRaster} - * - * @throws NullPointerException if either {@code pColorModel} or - * {@code pPixels} are {@code null}. - * @throws RuntimeException if {@code pWidth} and {@code pHeight} does not - * match the pixel data in {@code pPixels}. - * - * @see ColorModel#createCompatibleWritableRaster(int, int) - * @see ColorModel#createCompatibleSampleModel(int, int) - * @see WritableRaster#setDataElements(int, int, Object) - * @see DataBuffer - */ - static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) { - // NOTE: This is optimized code for most common cases. - // We create a DataBuffer from the pixel array directly, - // and creating a raster based on the DataBuffer and ColorModel. - // Creating rasters this way is faster and more resource-friendly, as - // cm.createCompatibleWritableRaster allocates an - // "empty" DataBuffer with a storage array of w*h. This array is - // later discarded, and replaced in the raster.setDataElements() call. - // The "old" way is kept as a more compatible fall-back mode. - - DataBuffer buffer = null; - WritableRaster raster = null; - - int bands; - if (pPixels instanceof int[]) { - int[] data = (int[]) pPixels; - buffer = new DataBufferInt(data, data.length); - bands = pColorModel.getNumComponents(); - } - else if (pPixels instanceof short[]) { - short[] data = (short[]) pPixels; - buffer = new DataBufferUShort(data, data.length); - bands = data.length / (pWidth * pHeight); - } - else if (pPixels instanceof byte[]) { - byte[] data = (byte[]) pPixels; - buffer = new DataBufferByte(data, data.length); - - // NOTE: This only holds for gray and indexed with one byte per pixel... - if (pColorModel instanceof IndexColorModel) { - bands = 1; - } - else { - bands = data.length / (pWidth * pHeight); - } - } - else { - // Fallback mode, slower & requires more memory, but compatible - bands = -1; - - // Create raster from color model, w and h - raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight); - raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions.. - } - - if (raster == null) { - if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) { - raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT); - } - else if (pColorModel instanceof PackedColorModel) { - PackedColorModel pcm = (PackedColorModel) pColorModel; - raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT); - } - else { - // (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE. - int[] bandsOffsets = new int[bands]; - for (int i = 0; i < bands;) { - bandsOffsets[i] = bands - (++i); - } - - raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT); - } - } - - return raster; - } - - private static boolean isIndexedPacked(IndexColorModel pColorModel) { - return (pColorModel.getPixelSize() == 1 || pColorModel.getPixelSize() == 2 || pColorModel.getPixelSize() == 4); - } - - /** - * Workaround for bug: TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR and - * TYPE_4BYTE_ABGR_PRE are all converted to TYPE_CUSTOM when using the - * default createCompatibleWritableRaster from ComponentColorModel. - * - * @param pOriginal the orignal image - * @param pModel the original color model - * @param width the requested width of the raster - * @param height the requested height of the raster - * - * @return a new WritableRaster - */ - static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int width, int height) { - if (pModel == null || equals(pOriginal.getColorModel(), pModel)) { - int[] bOffs; - switch (pOriginal.getType()) { - case BufferedImage.TYPE_3BYTE_BGR: - bOffs = new int[]{2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return - return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, - width, height, - width * 3, 3, - bOffs, null); - case BufferedImage.TYPE_4BYTE_ABGR: - case BufferedImage.TYPE_4BYTE_ABGR_PRE: - bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return - return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, - width, height, - width * 4, 4, - bOffs, null); - case BufferedImage.TYPE_CUSTOM: - // Peek into the sample model to see if we have a sample model that will be incompatible with the default case - SampleModel sm = pOriginal.getRaster().getSampleModel(); - if (sm instanceof ComponentSampleModel) { - bOffs = ((ComponentSampleModel) sm).getBandOffsets(); - return Raster.createInterleavedRaster(sm.getDataType(), - width, height, - width * bOffs.length, bOffs.length, - bOffs, null); - } - // Else fall through - default: - return pOriginal.getColorModel().createCompatibleWritableRaster(width, height); - } - } - - return pModel.createCompatibleWritableRaster(width, height); - } - - /** - * Converts the {@code Image} to a {@code BufferedImage} of the given type. - * The new image will have the same {@code ColorModel}, {@code Raster} and - * properties as the original image, if possible. - *

- * If the image is already a {@code BufferedImage} of the given type, it - * is simply returned and no conversion takes place. - * - * @param pOriginal the image to convert. - * @param pType the type of buffered image - * - * @return a {@code BufferedImage} - * - * @throws IllegalArgumentException if {@code pOriginal == null} - * or if {@code pType} is not a valid type for {@code BufferedImage} - * - * @see java.awt.image.BufferedImage#getType() - */ - public static BufferedImage toBuffered(Image pOriginal, int pType) { - return toBuffered(pOriginal, pType, null); - } - - /** - * - * @param pOriginal the original image - * @param pType the type of {@code BufferedImage} to create - * @param pICM the optional {@code IndexColorModel} to use. If not - * {@code null} the {@code pType} must be compatible with the color model - * @return a {@code BufferedImage} - * @throws IllegalArgumentException if {@code pType} is not compatible with - * the color model - */ - private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) { - // Don't convert if it already is BufferedImage and correct type - if ((pOriginal instanceof BufferedImage) - && ((BufferedImage) pOriginal).getType() == pType - && (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) { - return (BufferedImage) pOriginal; - } - if (pOriginal == null) { - throw new IllegalArgumentException("original == null"); - } - - //System.out.println("--> Doing full BufferedImage conversion, using Graphics.drawImage()."); - - // Create a buffered image - // NOTE: The getWidth and getHeight methods, will wait for the image - BufferedImage image; - if (pICM == null) { - image = createBuffered(getWidth(pOriginal), getHeight(pOriginal), pType, Transparency.TRANSLUCENT);//new BufferedImage(getWidth(pOriginal), getHeight(pOriginal), pType); - } - else { - image = new BufferedImage(getWidth(pOriginal), getHeight(pOriginal), pType, pICM); - } - - // Draw the image onto the buffer - drawOnto(image, pOriginal); - - return image; - } - - /** - * Draws the source image onto the buffered image, using - * {@code AlphaComposite.Src} and coordinates {@code 0, 0}. - * - * @param pDestination the image to draw on - * @param pSource the source image to draw - * - * @throws NullPointerException if {@code pDestination} or {@code pSource} is {@code null} - */ - static void drawOnto(final BufferedImage pDestination, final Image pSource) { - Graphics2D g = pDestination.createGraphics(); - try { - g.setComposite(AlphaComposite.Src); - g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); - g.drawImage(pSource, 0, 0, null); - } - finally { - g.dispose(); - } - } - - /** - * Creates a flipped version of the given image. - * - * @param pImage the image to flip - * @param pAxis the axis to flip around - * @return a new {@code BufferedImage} - */ - public static BufferedImage createFlipped(final Image pImage, final int pAxis) { - switch (pAxis) { - case FLIP_HORIZONTAL: - case FLIP_VERTICAL: - // TODO case FLIP_BOTH:?? same as rotate 180? - break; - default: - throw new IllegalArgumentException("Illegal direction: " + pAxis); - } - BufferedImage source = toBuffered(pImage); - AffineTransform transform; - if (pAxis == FLIP_HORIZONTAL) { - transform = AffineTransform.getTranslateInstance(0, source.getHeight()); - transform.scale(1, -1); - } - else { - transform = AffineTransform.getTranslateInstance(source.getWidth(), 0); - transform.scale(-1, 1); - } - AffineTransformOp transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); - return transformOp.filter(source, null); - } - - - /** - * Rotates the image 90 degrees, clockwise (aka "rotate right"), - * counter-clockwise (aka "rotate left") or 180 degrees, depending on the - * {@code pDirection} argument. - *

- * The new image will be completely covered with pixels from the source - * image. - * - * @param pImage the source image. - * @param pDirection the direction, must be either {@link #ROTATE_90_CW}, - * {@link #ROTATE_90_CCW} or {@link #ROTATE_180} - * - * @return a new {@code BufferedImage} - * - */ - public static BufferedImage createRotated(final Image pImage, final int pDirection) { - switch (pDirection) { - case ROTATE_90_CW: - case ROTATE_90_CCW: - case ROTATE_180: - return createRotated(pImage, Math.toRadians(pDirection)); - default: - throw new IllegalArgumentException("Illegal direction: " + pDirection); - } - } - - /** - * Rotates the image to the given angle. Areas not covered with pixels from - * the source image will be left transparent, if possible. - * - * @param pImage the source image - * @param pAngle the angle of rotation, in radians - * - * @return a new {@code BufferedImage}, unless {@code pAngle == 0.0} - */ - public static BufferedImage createRotated(final Image pImage, final double pAngle) { - return createRotated0(toBuffered(pImage), pAngle); - } - - private static BufferedImage createRotated0(final BufferedImage pSource, final double pAngle) { - if ((Math.abs(Math.toDegrees(pAngle)) % 360) == 0) { - return pSource; - } - - final boolean fast = ((Math.abs(Math.toDegrees(pAngle)) % 90) == 0.0); - final int w = pSource.getWidth(); - final int h = pSource.getHeight(); - - // Compute new width and height - double sin = Math.abs(Math.sin(pAngle)); - double cos = Math.abs(Math.cos(pAngle)); - - int newW = (int) Math.floor(w * cos + h * sin); - int newH = (int) Math.floor(h * cos + w * sin); - - AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0); - transform.rotate(pAngle, w / 2.0, h / 2.0); - - // TODO: Figure out if this is correct - BufferedImage dest = createTransparent(newW, newH); - - // See: http://weblogs.java.net/blog/campbell/archive/2007/03/java_2d_tricker_1.html - Graphics2D g = dest.createGraphics(); - try { - g.transform(transform); - if (!fast) { - // Max quality - g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, - RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - g.setPaint(new TexturePaint(pSource, - new Rectangle2D.Float(0, 0, pSource.getWidth(), pSource.getHeight()))); - g.fillRect(0, 0, pSource.getWidth(), pSource.getHeight()); - } - else { - g.drawImage(pSource, 0, 0, null); - } - } - finally { - g.dispose(); - } - - return dest; - } - - /** - * Creates a scaled instance of the given {@code Image}, and converts it to - * a {@code BufferedImage} if needed. - * If the original image is a {@code BufferedImage} the result will have - * same type and color model. Note that this implies overhead, and is - * probably not useful for anything but {@code IndexColorModel} images. - * - * @param pImage the {@code Image} to scale - * @param pWidth width in pixels - * @param pHeight height in pixels - * @param pHints scaling ints - * - * @return a {@code BufferedImage} - * - * @throws NullPointerException if {@code pImage} is {@code null}. - * - * @see #createResampled(java.awt.Image, int, int, int) - * @see Image#getScaledInstance(int,int,int) - * @see Image#SCALE_AREA_AVERAGING - * @see Image#SCALE_DEFAULT - * @see Image#SCALE_FAST - * @see Image#SCALE_REPLICATE - * @see Image#SCALE_SMOOTH - */ - public static BufferedImage createScaled(Image pImage, int pWidth, int pHeight, int pHints) { - ColorModel cm; - int type = BI_TYPE_ANY; - if (pImage instanceof RenderedImage) { - cm = ((RenderedImage) pImage).getColorModel(); - if (pImage instanceof BufferedImage) { - type = ((BufferedImage) pImage).getType(); - } - } - else { - BufferedImageFactory factory = new BufferedImageFactory(pImage); - cm = factory.getColorModel(); - } - - BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints); - - // Convert if color models or type differ, to behave as documented - if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) { - //System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... "); - //long start = System.currentTimeMillis(); - WritableRaster raster; - if (pImage instanceof BufferedImage) { - raster = createCompatibleWritableRaster((BufferedImage) pImage, cm, pWidth, pHeight); - } - else { - raster = cm.createCompatibleWritableRaster(pWidth, pHeight); - } - - BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); - - if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) { - // TODO: DiffusionDither does not support transparency at the moment, this will create bad results - new DiffusionDither((IndexColorModel) cm).filter(scaled, temp); - } - else { - drawOnto(temp, scaled); - } - - scaled = temp; - //long end = System.currentTimeMillis(); - //System.out.println("Time: " + (end - start) + " ms"); - } - - return scaled; - } - - private static boolean equals(ColorModel pLeft, ColorModel pRight) { - if (pLeft == pRight) { - return true; - } - - if (!pLeft.equals(pRight)) { - return false; - } - - // Now, the models are equal, according to the equals method - // Test indexcolormodels for equality, the maps must be equal - if (pLeft instanceof IndexColorModel) { - IndexColorModel icm1 = (IndexColorModel) pLeft; - IndexColorModel icm2 = (IndexColorModel) pRight; // NOTE: Safe, they're equal - - - final int mapSize1 = icm1.getMapSize(); - final int mapSize2 = icm2.getMapSize(); - - if (mapSize1 != mapSize2) { - return false; - } - - for (int i = 0; i > mapSize1; i++) { - if (icm1.getRGB(i) != icm2.getRGB(i)) { - return false; - } - } - - return true; - - } - - return true; - } - - /** - * Creates a scaled instance of the given {@code Image}, and converts it to - * a {@code BufferedImage} if needed. - * - * @param pImage the {@code Image} to scale - * @param pWidth width in pixels - * @param pHeight height in pixels - * @param pHints scaling mHints - * - * @return a {@code BufferedImage} - * - * @throws NullPointerException if {@code pImage} is {@code null}. - * - * @see Image#SCALE_AREA_AVERAGING - * @see Image#SCALE_DEFAULT - * @see Image#SCALE_FAST - * @see Image#SCALE_REPLICATE - * @see Image#SCALE_SMOOTH - * @see ResampleOp - */ - public static BufferedImage createResampled(Image pImage, int pWidth, int pHeight, int pHints) { - // NOTE: TYPE_4BYTE_ABGR or TYPE_3BYTE_BGR is more efficient when accelerated... - BufferedImage image = pImage instanceof BufferedImage - ? (BufferedImage) pImage - : toBuffered(pImage, BufferedImage.TYPE_4BYTE_ABGR); - return createResampled(image, pWidth, pHeight, pHints); - } - - /** - * Creates a scaled instance of the given {@code RenderedImage}, and - * converts it to a {@code BufferedImage} if needed. - * - * @param pImage the {@code RenderedImage} to scale - * @param pWidth width in pixels - * @param pHeight height in pixels - * @param pHints scaling mHints - * - * @return a {@code BufferedImage} - * - * @throws NullPointerException if {@code pImage} is {@code null}. - * - * @see Image#SCALE_AREA_AVERAGING - * @see Image#SCALE_DEFAULT - * @see Image#SCALE_FAST - * @see Image#SCALE_REPLICATE - * @see Image#SCALE_SMOOTH - * @see ResampleOp - */ - public static BufferedImage createResampled(RenderedImage pImage, int pWidth, int pHeight, int pHints) { - // NOTE: TYPE_4BYTE_ABGR or TYPE_3BYTE_BGR is more efficient when accelerated... - BufferedImage image = pImage instanceof BufferedImage - ? (BufferedImage) pImage - : toBuffered(pImage, pImage.getColorModel().hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); - return createResampled(image, pWidth, pHeight, pHints); - } - - /** - * Creates a scaled instance of the given {@code BufferedImage}. - * - * @param pImage the {@code BufferedImage} to scale - * @param pWidth width in pixels - * @param pHeight height in pixels - * @param pHints scaling mHints - * - * @return a {@code BufferedImage} - * - * @throws NullPointerException if {@code pImage} is {@code null}. - * - * @see Image#SCALE_AREA_AVERAGING - * @see Image#SCALE_DEFAULT - * @see Image#SCALE_FAST - * @see Image#SCALE_REPLICATE - * @see Image#SCALE_SMOOTH - * @see ResampleOp - */ - public static BufferedImage createResampled(BufferedImage pImage, int pWidth, int pHeight, int pHints) { - // Hints are converted between java.awt.Image hints and filter types - return new ResampleOp(pWidth, pHeight, convertAWTHints(pHints)).filter(pImage, null); - } - - private static int convertAWTHints(int pHints) { - switch (pHints) { - case Image.SCALE_FAST: - case Image.SCALE_REPLICATE: - return ResampleOp.FILTER_POINT; - case Image.SCALE_AREA_AVERAGING: - return ResampleOp.FILTER_BOX; - //return ResampleOp.FILTER_CUBIC; - case Image.SCALE_SMOOTH: - return ResampleOp.FILTER_LANCZOS; - default: - //return ResampleOp.FILTER_TRIANGLE; - return ResampleOp.FILTER_QUADRATIC; - } - } - - /** - * Extracts an {@code IndexColorModel} from the given image. - * - * @param pImage the image to get the color model from - * @param pColors the maximum number of colors in the resulting color model - * @param pHints hints controlling transparency and color selection - * - * @return the extracted {@code IndexColorModel} - * - * @see #COLOR_SELECTION_DEFAULT - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_DEFAULT - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see #TRANSPARENCY_TRANSLUCENT - */ - public static IndexColorModel getIndexColorModel(Image pImage, int pColors, int pHints) { - return IndexImage.getIndexColorModel(pImage, pColors, pHints); - } - - /** - * Creates an indexed version of the given image (a {@code BufferedImage} - * with an {@code IndexColorModel}. - * The resulting image will have a maximum of 256 different colors. - * Transparent parts of the original will be replaced with solid black. - * Default (possibly HW accelerated) dither will be used. - * - * @param pImage the image to convert - * - * @return an indexed version of the given image - */ - public static BufferedImage createIndexed(Image pImage) { - return IndexImage.getIndexedImage(toBuffered(pImage), 256, Color.black, IndexImage.DITHER_DEFAULT); - } - - /** - * Creates an indexed version of the given image (a {@code BufferedImage} - * with an {@code IndexColorModel}. - * - * @param pImage the image to convert - * @param pColors number of colors in the resulting image - * @param pMatte color to replace transparent parts of the original. - * @param pHints hints controlling dither, transparency and color selection - * - * @return an indexed version of the given image - * - * @see #COLOR_SELECTION_DEFAULT - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #DITHER_NONE - * @see #DITHER_DEFAULT - * @see #DITHER_DIFFUSION - * @see #DITHER_DIFFUSION_ALTSCANS - * @see #TRANSPARENCY_DEFAULT - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see #TRANSPARENCY_TRANSLUCENT - */ - public static BufferedImage createIndexed(Image pImage, int pColors, Color pMatte, int pHints) { - return IndexImage.getIndexedImage(toBuffered(pImage), pColors, pMatte, pHints); - } - - /** - * Creates an indexed version of the given image (a {@code BufferedImage} - * with an {@code IndexColorModel}. - * - * @param pImage the image to convert - * @param pColors the {@code IndexColorModel} to be used in the resulting - * image. - * @param pMatte color to replace transparent parts of the original. - * @param pHints hints controlling dither, transparency and color selection - * - * @return an indexed version of the given image - * - * @see #COLOR_SELECTION_DEFAULT - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #DITHER_NONE - * @see #DITHER_DEFAULT - * @see #DITHER_DIFFUSION - * @see #DITHER_DIFFUSION_ALTSCANS - * @see #TRANSPARENCY_DEFAULT - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see #TRANSPARENCY_TRANSLUCENT - */ - public static BufferedImage createIndexed(Image pImage, IndexColorModel pColors, Color pMatte, int pHints) { - return IndexImage.getIndexedImage(toBuffered(pImage), pColors, pMatte, pHints); - } - - /** - * Creates an indexed version of the given image (a {@code BufferedImage} - * with an {@code IndexColorModel}. - * - * @param pImage the image to convert - * @param pColors an {@code Image} used to get colors from. If the image is - * has an {@code IndexColorModel}, it will be uesd, otherwise an - * {@code IndexColorModel} is created from the image. - * @param pMatte color to replace transparent parts of the original. - * @param pHints hints controlling dither, transparency and color selection - * - * @return an indexed version of the given image - * - * @see #COLOR_SELECTION_DEFAULT - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #DITHER_NONE - * @see #DITHER_DEFAULT - * @see #DITHER_DIFFUSION - * @see #DITHER_DIFFUSION_ALTSCANS - * @see #TRANSPARENCY_DEFAULT - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see #TRANSPARENCY_TRANSLUCENT - */ - public static BufferedImage createIndexed(Image pImage, Image pColors, Color pMatte, int pHints) { - return IndexImage.getIndexedImage(toBuffered(pImage), - IndexImage.getIndexColorModel(pColors, 255, pHints), - pMatte, pHints); - } - - /** - * Sharpens an image using a convolution matrix. - * The sharpen kernel used, is defined by the following 3 by 3 matrix: - * - * - * - * - *
0.0-0.30.0
-0.32.2-0.3
0.0-0.30.0
- *

- * This is the same result returned as - * {@code sharpen(pOriginal, 0.3f)}. - * - * @param pOriginal the BufferedImage to sharpen - * - * @return a new BufferedImage, containing the sharpened image. - */ - public static BufferedImage sharpen(BufferedImage pOriginal) { - return convolve(pOriginal, SHARPEN_KERNEL, EDGE_REFLECT); - } - - /** - * Sharpens an image using a convolution matrix. - * The sharpen kernel used, is defined by the following 3 by 3 matrix: - * - * - * - * - * - * - *
0.0-{@code pAmount}0.0
-{@code pAmount}4.0 * {@code pAmount} + 1.0-{@code pAmount}
0.0-{@code pAmount}0.0
- * - * @param pOriginal the BufferedImage to sharpen - * @param pAmount the amount of sharpening - * - * @return a BufferedImage, containing the sharpened image. - */ - public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) { - if (pAmount == 0f) { - return pOriginal; - } - - // Create the convolution matrix - float[] data = new float[] { - 0.0f, -pAmount, 0.0f, -pAmount, 4f * pAmount + 1f, -pAmount, 0.0f, -pAmount, 0.0f - }; - - // Do the filtering - return convolve(pOriginal, new Kernel(3, 3, data), EDGE_REFLECT); - } - - /** - * Creates a blurred version of the given image. - * - * @param pOriginal the original image - * - * @return a new {@code BufferedImage} with a blurred version of the given image - */ - public static BufferedImage blur(BufferedImage pOriginal) { - return blur(pOriginal, 1.5f); - } - - // Some work to do... Is okay now, for range 0...1, anything above creates - // artifacts. - // The idea here is that the sum of all terms in the matrix must be 1. - - /** - * Creates a blurred version of the given image. - * - * @param pOriginal the original image - * @param pRadius the amount to blur - * - * @return a new {@code BufferedImage} with a blurred version of the given image - */ - public static BufferedImage blur(BufferedImage pOriginal, float pRadius) { - if (pRadius <= 1f) { - return pOriginal; - } - - // TODO: Re-implement using two-pass one-dimensional gaussion blur - // See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation - // Also see http://www.jhlabs.com/ip/blurring.html - - // TODO: Rethink... Fixed amount and scale matrix instead? -// pAmount = 1f - pAmount; -// float pAmount = 1f - pRadius; -// -// // Normalize amount -// float normAmt = (1f - pAmount) / 24; -// -// // Create the convolution matrix -// float[] data = new float[] { -// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2, -// normAmt, normAmt, normAmt * 2, normAmt, normAmt, -// normAmt, normAmt * 2, pAmount, normAmt * 2, normAmt, -// normAmt, normAmt, normAmt * 2, normAmt, normAmt, -// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2 -// }; -// -// // Do the filtering -// return convolve(pOriginal, new Kernel(5, 5, data), EDGE_REFLECT); - - Kernel horizontal = makeKernel(pRadius); - Kernel vertical = new Kernel(horizontal.getHeight(), horizontal.getWidth(), horizontal.getKernelData(null)); - - BufferedImage temp = addBorder(pOriginal, horizontal.getWidth() / 2, vertical.getHeight() / 2, EDGE_REFLECT); - - temp = convolve(temp, horizontal, EDGE_NO_OP); - temp = convolve(temp, vertical, EDGE_NO_OP); - - return temp.getSubimage( - horizontal.getWidth() / 2, vertical.getHeight() / 2, pOriginal.getWidth(), pOriginal.getHeight() - ); - } - - /** - * Make a Gaussian blur {@link Kernel}. - * - * @param radius the blur radius - * @return a new blur {@code Kernel} - */ - private static Kernel makeKernel(float radius) { - int r = (int) Math.ceil(radius); - int rows = r * 2 + 1; - float[] matrix = new float[rows]; - float sigma = radius / 3; - float sigma22 = 2 * sigma * sigma; - float sigmaPi2 = (float) (2 * Math.PI * sigma); - float sqrtSigmaPi2 = (float) Math.sqrt(sigmaPi2); - float radius2 = radius * radius; - float total = 0; - int index = 0; - for (int row = -r; row <= r; row++) { - float distance = row * row; - if (distance > radius2) { - matrix[index] = 0; - } - else { - matrix[index] = (float) Math.exp(-(distance) / sigma22) / sqrtSigmaPi2; - } - total += matrix[index]; - index++; - } - for (int i = 0; i < rows; i++) { - matrix[i] /= total; - } - - return new Kernel(rows, 1, matrix); - } - - - /** - * Convolves an image, using a convolution matrix. - * - * @param pOriginal the BufferedImage to sharpen - * @param pKernel the kernel - * @param pEdgeOperation the edge operation. Must be one of {@link #EDGE_NO_OP}, - * {@link #EDGE_ZERO_FILL}, {@link #EDGE_REFLECT} or {@link #EDGE_WRAP} - * - * @return a new BufferedImage, containing the sharpened image. - */ - public static BufferedImage convolve(BufferedImage pOriginal, Kernel pKernel, int pEdgeOperation) { - // Allow for 2 more edge operations - BufferedImage original; - switch (pEdgeOperation) { - case EDGE_REFLECT: - case EDGE_WRAP: - original = addBorder(pOriginal, pKernel.getWidth() / 2, pKernel.getHeight() / 2, pEdgeOperation); - break; - default: - original = pOriginal; - break; - } - - // Create convolution operation - ConvolveOp convolve = new ConvolveOp(pKernel, pEdgeOperation, null); - - // Workaround for what seems to be a Java2D bug: - // ConvolveOp needs explicit destination image type for some "uncommon" - // image types. However, TYPE_3BYTE_BGR is what javax.imageio.ImageIO - // normally returns for color JPEGs... :-/ - BufferedImage result = null; - if (original.getType() == BufferedImage.TYPE_3BYTE_BGR) { - result = createBuffered( - pOriginal.getWidth(), pOriginal.getHeight(), - pOriginal.getType(), pOriginal.getColorModel().getTransparency() - ); - } - - // Do the filtering (if result is null, a new image will be created) - BufferedImage image = convolve.filter(original, result); - - if (pOriginal != original) { - // Remove the border - image = image.getSubimage( - pKernel.getWidth() / 2, pKernel.getHeight() / 2, pOriginal.getWidth(), pOriginal.getHeight() - ); - } - - return image; - } - - private static BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY, final int pEdgeOperation) { - // TODO: Might be faster if we could clone raster and strech it... - int w = pOriginal.getWidth(); - int h = pOriginal.getHeight(); - - ColorModel cm = pOriginal.getColorModel(); - WritableRaster raster = cm.createCompatibleWritableRaster(w + 2 * pBorderX, h + 2 * pBorderY); - BufferedImage bordered = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); - - Graphics2D g = bordered.createGraphics(); - try { - g.setComposite(AlphaComposite.Src); - g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); - - // Draw original in center - g.drawImage(pOriginal, pBorderX, pBorderY, null); - - // TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel - switch (pEdgeOperation) { - case EDGE_REFLECT: - // Top/left (empty) - g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center - // Top/right (empty) - - g.drawImage(pOriginal, -w + pBorderX, pBorderY, pBorderX, h + pBorderY, 0, 0, 1, h, null); // Center/left - // Center/center (already drawn) - g.drawImage(pOriginal, w + pBorderX, pBorderY, 2 * pBorderX + w, h + pBorderY, w - 1, 0, w, h, null); // Center/right - - // Bottom/left (empty) - g.drawImage(pOriginal, pBorderX, pBorderY + h, pBorderX + w, 2 * pBorderY + h, 0, h - 1, w, h, null); // Bottom/center - // Bottom/right (empty) - break; - case EDGE_WRAP: - g.drawImage(pOriginal, -w + pBorderX, -h + pBorderY, null); // Top/left - g.drawImage(pOriginal, pBorderX, -h + pBorderY, null); // Top/center - g.drawImage(pOriginal, w + pBorderX, -h + pBorderY, null); // Top/right - - g.drawImage(pOriginal, -w + pBorderX, pBorderY, null); // Center/left - // Center/center (already drawn) - g.drawImage(pOriginal, w + pBorderX, pBorderY, null); // Center/right - - g.drawImage(pOriginal, -w + pBorderX, h + pBorderY, null); // Bottom/left - g.drawImage(pOriginal, pBorderX, h + pBorderY, null); // Bottom/center - g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right - break; - default: - throw new IllegalArgumentException("Illegal edge operation " + pEdgeOperation); - } - - } - finally { - g.dispose(); - } - - //ConvolveTester.showIt(bordered, "jaffe"); - - return bordered; - } - - /** - * Adds contrast - * - * @param pOriginal the BufferedImage to add contrast to - * - * @return an {@code Image}, containing the contrasted image. - */ - public static Image contrast(Image pOriginal) { - return contrast(pOriginal, 0.3f); - } - - /** - * Changes the contrast of the image - * - * @param pOriginal the {@code Image} to change - * @param pAmount the amount of contrast in the range [-1.0..1.0]. - * - * @return an {@code Image}, containing the contrasted image. - */ - public static Image contrast(Image pOriginal, float pAmount) { - // No change, return original - if (pAmount == 0f) { - return pOriginal; - } - - // Create filter - RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount); - - // Return contrast adjusted image - return filter(pOriginal, filter); - } - - - /** - * Changes the brightness of the original image. - * - * @param pOriginal the {@code Image} to change - * @param pAmount the amount of brightness in the range [-2.0..2.0]. - * - * @return an {@code Image} - */ - public static Image brightness(Image pOriginal, float pAmount) { - // No change, return original - if (pAmount == 0f) { - return pOriginal; - } - - // Create filter - RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f); - - // Return brightness adjusted image - return filter(pOriginal, filter); - } - - - /** - * Converts an image to grayscale. - * - * @see GrayFilter - * @see RGBImageFilter - * - * @param pOriginal the image to convert. - * @return a new Image, containing the gray image data. - */ - public static Image grayscale(Image pOriginal) { - // Create filter - RGBImageFilter filter = new GrayFilter(); - - // Convert to gray - return filter(pOriginal, filter); - } - - /** - * Filters an image, using the given {@code ImageFilter}. - * - * @param pOriginal the original image - * @param pFilter the filter to apply - * - * @return the new {@code Image} - */ - public static Image filter(Image pOriginal, ImageFilter pFilter) { - // Create a filtered source - ImageProducer source = new FilteredImageSource(pOriginal.getSource(), pFilter); - - // Create new image - return Toolkit.getDefaultToolkit().createImage(source); - } - - /** - * Tries to use H/W-accelerated code for an image for display purposes. - * Note that transparent parts of the image might be replaced by solid - * color. Additional image information not used by the current diplay - * hardware may be discarded, like extra bith depth etc. - * - * @param pImage any {@code Image} - * @return a {@code BufferedImage} - */ - public static BufferedImage accelerate(Image pImage) { - return accelerate(pImage, null, DEFAULT_CONFIGURATION); - } - - /** - * Tries to use H/W-accelerated code for an image for display purposes. - * Note that transparent parts of the image might be replaced by solid - * color. Additional image information not used by the current diplay - * hardware may be discarded, like extra bith depth etc. - * - * @param pImage any {@code Image} - * @param pConfiguration the {@code GraphicsConfiguration} to accelerate - * for - * - * @return a {@code BufferedImage} - */ - public static BufferedImage accelerate(Image pImage, GraphicsConfiguration pConfiguration) { - return accelerate(pImage, null, pConfiguration); - } - - /** - * Tries to use H/W-accelerated code for an image for display purposes. - * Note that transparent parts of the image will be replaced by solid - * color. Additional image information not used by the current diplay - * hardware may be discarded, like extra bith depth etc. - * - * @param pImage any {@code Image} - * @param pBackgroundColor the background color to replace any transparent - * parts of the image. - * May be {@code null}, in such case the color is undefined. - * @param pConfiguration the graphics configuration - * May be {@code null}, in such case the color is undefined. - * - * @return a {@code BufferedImage} - */ - static BufferedImage accelerate(Image pImage, Color pBackgroundColor, GraphicsConfiguration pConfiguration) { - // Skip acceleration if the layout of the image and color model is already ok - if (pImage instanceof BufferedImage) { - BufferedImage buffered = (BufferedImage) pImage; - // TODO: What if the createCompatibleImage insist on TYPE_CUSTOM...? :-P - if (buffered.getType() != BufferedImage.TYPE_CUSTOM && equals(buffered.getColorModel(), pConfiguration.getColorModel(buffered.getTransparency()))) { - return buffered; - } - } - if (pImage == null) { - throw new IllegalArgumentException("image == null"); - } - - int w = ImageUtil.getWidth(pImage); - int h = ImageUtil.getHeight(pImage); - - // Create accelerated version - BufferedImage temp = createClear(w, h, BI_TYPE_ANY, getTransparency(pImage), pBackgroundColor, pConfiguration); - drawOnto(temp, pImage); - - return temp; - } - - private static int getTransparency(Image pImage) { - if (pImage instanceof BufferedImage) { - BufferedImage bi = (BufferedImage) pImage; - return bi.getTransparency(); - } - return Transparency.OPAQUE; - } - - /** - * Creates a transparent image. - * - * @param pWidth the requested width of the image - * @param pHeight the requested height of the image - * - * @throws IllegalArgumentException if {@code pType} is not a valid type - * for {@code BufferedImage} - * - * @return the new image - */ - public static BufferedImage createTransparent(int pWidth, int pHeight) { - return createTransparent(pWidth, pHeight, BI_TYPE_ANY); - } - - /** - * Creates a transparent image. - * - * @see BufferedImage#BufferedImage(int,int,int) - * - * @param pWidth the requested width of the image - * @param pHeight the requested height of the image - * @param pType the type of {@code BufferedImage} to create - * - * @throws IllegalArgumentException if {@code pType} is not a valid type - * for {@code BufferedImage} - * - * @return the new image - */ - public static BufferedImage createTransparent(int pWidth, int pHeight, int pType) { - // Create - BufferedImage image = createBuffered(pWidth, pHeight, pType, Transparency.TRANSLUCENT); - - // Clear image with transparent alpha by drawing a rectangle - Graphics2D g = image.createGraphics(); - try { - g.setComposite(AlphaComposite.Clear); - g.fillRect(0, 0, pWidth, pHeight); - } - finally { - g.dispose(); - } - - return image; - } - - /** - * Creates a clear image with the given background color. - * - * @see BufferedImage#BufferedImage(int,int,int) - * - * @param pWidth the requested width of the image - * @param pHeight the requested height of the image - * @param pBackground the background color. The color may be translucent. - * May be {@code null}, in such case the color is undefined. - * - * @throws IllegalArgumentException if {@code pType} is not a valid type - * for {@code BufferedImage} - * - * @return the new image - */ - public static BufferedImage createClear(int pWidth, int pHeight, Color pBackground) { - return createClear(pWidth, pHeight, BI_TYPE_ANY, pBackground); - } - - /** - * Creates a clear image with the given background color. - * - * @see BufferedImage#BufferedImage(int,int,int) - * - * @param pWidth the width of the image to create - * @param pHeight the height of the image to create - * @param pType the type of image to create (one of the constants from - * {@link BufferedImage} or {@link #BI_TYPE_ANY}) - * @param pBackground the background color. The color may be translucent. - * May be {@code null}, in such case the color is undefined. - * - * @throws IllegalArgumentException if {@code pType} is not a valid type - * for {@code BufferedImage} - * - * @return the new image - */ - public static BufferedImage createClear(int pWidth, int pHeight, int pType, Color pBackground) { - return createClear(pWidth, pHeight, pType, Transparency.OPAQUE, pBackground, DEFAULT_CONFIGURATION); - } - - static BufferedImage createClear(int pWidth, int pHeight, int pType, int pTransparency, Color pBackground, GraphicsConfiguration pConfiguration) { - // Create - int transparency = (pBackground != null) ? pBackground.getTransparency() : pTransparency; - BufferedImage image = createBuffered(pWidth, pHeight, pType, transparency, pConfiguration); - - if (pBackground != null) { - // Clear image with clear color, by drawing a rectangle - Graphics2D g = image.createGraphics(); - try { - g.setComposite(AlphaComposite.Src); // Allow color to be translucent - g.setColor(pBackground); - g.fillRect(0, 0, pWidth, pHeight); - } - finally { - g.dispose(); - } - } - - return image; - } - - /** - * Creates a {@code BufferedImage} of the given size and type. If possible, - * uses accelerated versions of BufferedImage from GraphicsConfiguration. - * - * @param pWidth the width of the image to create - * @param pHeight the height of the image to create - * @param pType the type of image to create (one of the constants from - * {@link BufferedImage} or {@link #BI_TYPE_ANY}) - * @param pTransparency the transparency type (from {@link Transparency}) - * - * @return a {@code BufferedImage} - */ - private static BufferedImage createBuffered(int pWidth, int pHeight, int pType, int pTransparency) { - return createBuffered(pWidth, pHeight, pType, pTransparency, DEFAULT_CONFIGURATION); - } - - static BufferedImage createBuffered(int pWidth, int pHeight, int pType, int pTransparency, - GraphicsConfiguration pConfiguration) { - if (VM_SUPPORTS_ACCELERATION && pType == BI_TYPE_ANY) { - GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); - if (supportsAcceleration(env)) { - return getConfiguration(pConfiguration).createCompatibleImage(pWidth, pHeight, pTransparency); - } - } - - return new BufferedImage(pWidth, pHeight, getImageType(pType, pTransparency)); - } - - private static GraphicsConfiguration getConfiguration(final GraphicsConfiguration pConfiguration) { - return pConfiguration != null ? pConfiguration : DEFAULT_CONFIGURATION; - } - - private static int getImageType(int pType, int pTransparency) { - // TODO: Handle TYPE_CUSTOM? - if (pType != BI_TYPE_ANY) { - return pType; - } - else { - switch (pTransparency) { - case Transparency.OPAQUE: - return BufferedImage.TYPE_INT_RGB; - case Transparency.BITMASK: - case Transparency.TRANSLUCENT: - return BufferedImage.TYPE_INT_ARGB; - default: - throw new IllegalArgumentException("Unknown transparency type: " + pTransparency); - } - } - } - - /** - * Tests if the given {@code GraphicsEnvironment} supports accelleration - * - * @param pEnv the environment - * @return {@code true} if the {@code GraphicsEnvironment} supports - * acceleration - */ - private static boolean supportsAcceleration(GraphicsEnvironment pEnv) { - try { - // Acceleration only supported in non-headless environments, on 1.4+ VMs - return /*VM_SUPPORTS_ACCELERATION &&*/ !pEnv.isHeadlessInstance(); - } - catch (LinkageError ignore) { - // Means we are not in a 1.4+ VM, so skip testing for headless again - VM_SUPPORTS_ACCELERATION = false; - } - - // If the invocation fails, assume no accelleration is possible - return false; - } - - /** - * Gets the width of an Image. - * This method has the side-effect of completely loading the image. - * - * @param pImage an image. - * - * @return the width of the image, or -1 if the width could not be - * determined (i.e. an error occured while waiting for the - * image to load). - */ - public static int getWidth(Image pImage) { - int width = pImage.getWidth(NULL_COMPONENT); - if (width < 0) { - if (!waitForImage(pImage)) { - return -1; // Error while waiting - } - width = pImage.getWidth(NULL_COMPONENT); - } - - return width; - } - - /** - * Gets the height of an Image. - * This method has the side-effect of completely loading the image. - * - * @param pImage an image. - * - * @return the height of the image, or -1 if the height could not be - * determined (i.e. an error occured while waiting for the - * image to load). - */ - public static int getHeight(Image pImage) { - int height = pImage.getHeight(NULL_COMPONENT); - if (height < 0) { - if (!waitForImage(pImage)) { - return -1; // Error while waiting - } - height = pImage.getHeight(NULL_COMPONENT); - } - - return height; - } - - /** - * Waits for an image to load completely. - * Will wait forever. - * - * @param pImage an Image object to wait for. - * - * @return true if the image was loaded successfully, false if an error - * occured, or the wait was interrupted. - * - * @see #waitForImage(Image,long) - */ - public static boolean waitForImage(Image pImage) { - return waitForImages(new Image[]{pImage}, -1L); - } - - /** - * Waits for an image to load completely. - * Will wait the specified time. - * - * @param pImage an Image object to wait for. - * @param pTimeOut the time to wait, in milliseconds. - * - * @return true if the image was loaded successfully, false if an error - * occurred, or the wait was interrupted. - * - * @see #waitForImages(Image[],long) - */ - public static boolean waitForImage(Image pImage, long pTimeOut) { - return waitForImages(new Image[]{pImage}, pTimeOut); - } - - /** - * Waits for a number of images to load completely. - * Will wait forever. - * - * @param pImages an array of Image objects to wait for. - * - * @return true if the images was loaded successfully, false if an error - * occurred, or the wait was interrupted. - * - * @see #waitForImages(Image[],long) - */ - public static boolean waitForImages(Image[] pImages) { - return waitForImages(pImages, -1L); - } - - /** - * Waits for a number of images to load completely. - * Will wait the specified time. - * - * @param pImages an array of Image objects to wait for - * @param pTimeOut the time to wait, in milliseconds - * - * @return true if the images was loaded successfully, false if an error - * occurred, or the wait was interrupted. - */ - public static boolean waitForImages(Image[] pImages, long pTimeOut) { - // TODO: Need to make sure that we don't wait for the same image many times - // Use hashcode as id? Don't remove images from tracker? Hmmm... - boolean success = true; - - // Create a local id for use with the mediatracker - int imageId; - - // NOTE: This is very experimental... - imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages); - - // Add images to tracker - for (Image image : pImages) { - sTracker.addImage(image, imageId); - - // Start loading immediately - if (sTracker.checkID(imageId, false)) { - // Image is done, so remove again - sTracker.removeImage(image, imageId); - } - } - - try { - if (pTimeOut < 0L) { - // Just wait - sTracker.waitForID(imageId); - } - else { - // Wait until timeout - // NOTE: waitForID(int, long) return value is undocumented. - // I assume that it returns true, if the image(s) loaded - // successfully before the timeout, however, I always check - // isErrorID later on, just in case... - success = sTracker.waitForID(imageId, pTimeOut); - } - } - catch (InterruptedException ie) { - // Interrupted while waiting, image not loaded - success = false; - } - finally { - // Remove images from mediatracker - for (Image pImage : pImages) { - sTracker.removeImage(pImage, imageId); - } - } - - // If the wait was successfull, and no errors were reported for the - // images, return true - return success && !sTracker.isErrorID(imageId); - } - - /** - * Tests whether the image has any transparent or semi-transparent pixels. - * - * @param pImage the image - * @param pFast if {@code true}, the method tests maximum 10 x 10 pixels, - * evenly spaced out in the image. - * - * @return {@code true} if transparent pixels are found, otherwise - * {@code false}. - */ - public static boolean hasTransparentPixels(RenderedImage pImage, boolean pFast) { - if (pImage == null) { - return false; - } - - // First, test if the ColorModel supports alpha... - ColorModel cm = pImage.getColorModel(); - if (!cm.hasAlpha()) { - return false; - } - - if (cm.getTransparency() != Transparency.BITMASK - && cm.getTransparency() != Transparency.TRANSLUCENT) { - return false; - } - - // ... if so, test the pixels of the image hard way - Object data = null; - - // Loop over tiles (noramally, BufferedImages have only one) - for (int yT = pImage.getMinTileY(); yT < pImage.getNumYTiles(); yT++) { - for (int xT = pImage.getMinTileX(); xT < pImage.getNumXTiles(); xT++) { - // Test pixels of each tile - Raster raster = pImage.getTile(xT, yT); - int xIncrement = pFast ? Math.max(raster.getWidth() / 10, 1) : 1; - int yIncrement = pFast ? Math.max(raster.getHeight() / 10, 1) : 1; - - for (int y = 0; y < raster.getHeight(); y += yIncrement) { - for (int x = 0; x < raster.getWidth(); x += xIncrement) { - // Copy data for each pixel, without allocation array - data = raster.getDataElements(x, y, data); - - // Test alpha value - if (cm.getAlpha(data) != 0xff) { - return true; - } - } - } - } - } - - return false; - } - - /** - * Creates a translucent version of the given color. - * - * @param pColor the original color - * @param pTransparency the transparency level ({@code 0 - 255}) - * @return a translucent color - * - * @throws NullPointerException if {@code pColor} is {@code null} - */ - public static Color createTranslucent(Color pColor, int pTransparency) { - //return new Color(pColor.getRed(), pColor.getGreen(), pColor.getBlue(), pTransparency); - return new Color(((pTransparency & 0xff) << 24) | (pColor.getRGB() & 0x00ffffff), true); - } - - /** - * Blends two ARGB values half and half, to create a tone in between. - * - * @param pRGB1 color 1 - * @param pRGB2 color 2 - * @return the new rgb value - */ - static int blend(int pRGB1, int pRGB2) { - // Slightly modified from http://www.compuphase.com/graphic/scale3.htm - // to support alpha values - return (((pRGB1 ^ pRGB2) & 0xfefefefe) >> 1) + (pRGB1 & pRGB2); - } - - /** - * Blends two colors half and half, to create a tone in between. - * - * @param pColor color 1 - * @param pOther color 2 - * @return a new {@code Color} - */ - public static Color blend(Color pColor, Color pOther) { - return new Color(blend(pColor.getRGB(), pOther.getRGB()), true); - - /* - return new Color((pColor.getRed() + pOther.getRed()) / 2, - (pColor.getGreen() + pOther.getGreen()) / 2, - (pColor.getBlue() + pOther.getBlue()) / 2, - (pColor.getAlpha() + pOther.getAlpha()) / 2); - */ - } - - /** - * Blends two colors, controlled by the blending factor. - * A factor of {@code 0.0} will return the first color, - * a factor of {@code 1.0} will return the second. - * - * @param pColor color 1 - * @param pOther color 2 - * @param pBlendFactor {@code [0...1]} - * @return a new {@code Color} - */ - public static Color blend(Color pColor, Color pOther, float pBlendFactor) { - float inverseBlend = (1f - pBlendFactor); - return new Color( - clamp((pColor.getRed() * inverseBlend) + (pOther.getRed() * pBlendFactor)), - clamp((pColor.getGreen() * inverseBlend) + (pOther.getGreen() * pBlendFactor)), - clamp((pColor.getBlue() * inverseBlend) + (pOther.getBlue() * pBlendFactor)), - clamp((pColor.getAlpha() * inverseBlend) + (pOther.getAlpha() * pBlendFactor)) - ); - } - - private static int clamp(float f) { - return (int) f; - } +/* + * 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.image; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.*; +import java.util.Hashtable; + +/** + * This class contains methods for basic image manipulation and conversion. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $ + */ +public final class ImageUtil { + // TODO: Split palette generation out, into ColorModel classes (?) + + public final static int ROTATE_90_CCW = -90; + public final static int ROTATE_90_CW = 90; + public final static int ROTATE_180 = 180; + + public final static int FLIP_VERTICAL = -1; + public final static int FLIP_HORIZONTAL = 1; + + /** + * Alias for {@link ConvolveOp#EDGE_ZERO_FILL}. + * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) + * @see #EDGE_REFLECT + */ + public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL; + + /** + * Alias for {@link ConvolveOp#EDGE_NO_OP}. + * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) + * @see #EDGE_REFLECT + */ + public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP; + + /** + * Adds a border to the image while convolving. The border will reflect the + * edges of the original image. This is usually a good default. + * Note that while this mode typically provides better quality than the + * standard modes {@code EDGE_ZERO_FILL} and {@code EDGE_NO_OP}, it does so + * at the expense of higher memory consumption and considerable more computation. + * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) + */ + public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT + + /** + * Adds a border to the image while convolving. The border will wrap the + * edges of the original image. This is usually the best choice for tiles. + * Note that while this mode typically provides better quality than the + * standard modes {@code EDGE_ZERO_FILL} and {@code EDGE_NO_OP}, it does so + * at the expense of higher memory consumption and considerable more computation. + * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) + * @see #EDGE_REFLECT + */ + public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP + + /** + * Java default dither + */ + public final static int DITHER_DEFAULT = IndexImage.DITHER_DEFAULT; + + /** + * No dither + */ + public final static int DITHER_NONE = IndexImage.DITHER_NONE; + + /** + * Error diffusion dither + */ + public final static int DITHER_DIFFUSION = IndexImage.DITHER_DIFFUSION; + + /** + * Error diffusion dither with alternating scans + */ + public final static int DITHER_DIFFUSION_ALTSCANS = IndexImage.DITHER_DIFFUSION_ALTSCANS; + + /** + * Default color selection + */ + public final static int COLOR_SELECTION_DEFAULT = IndexImage.COLOR_SELECTION_DEFAULT; + + /** + * Prioritize speed + */ + public final static int COLOR_SELECTION_FAST = IndexImage.COLOR_SELECTION_FAST; + + /** + * Prioritize quality + */ + public final static int COLOR_SELECTION_QUALITY = IndexImage.COLOR_SELECTION_QUALITY; + + /** + * Default transparency (none) + */ + public final static int TRANSPARENCY_DEFAULT = IndexImage.TRANSPARENCY_DEFAULT; + + /** + * Discard any alpha information + */ + public final static int TRANSPARENCY_OPAQUE = IndexImage.TRANSPARENCY_OPAQUE; + + /** + * Convert alpha to bitmask + */ + public final static int TRANSPARENCY_BITMASK = IndexImage.TRANSPARENCY_BITMASK; + + /** + * Keep original alpha (not supported yet) + */ + protected final static int TRANSPARENCY_TRANSLUCENT = IndexImage.TRANSPARENCY_TRANSLUCENT; + + /** Passed to the createXxx methods, to indicate that the type does not matter */ + private final static int BI_TYPE_ANY = -1; + /* + public final static int BI_TYPE_ANY_TRANSLUCENT = -1; + public final static int BI_TYPE_ANY_BITMASK = -2; + public final static int BI_TYPE_ANY_OPAQUE = -3;*/ + + /** Tells wether this WM may support acceleration of some images */ + private static boolean VM_SUPPORTS_ACCELERATION = true; + + /** The sharpen matrix */ + private static final float[] SHARPEN_MATRIX = new float[] { + 0.0f, -0.3f, 0.0f, + -0.3f, 2.2f, -0.3f, + 0.0f, -0.3f, 0.0f + }; + + /** + * The sharpen kernel. Uses the following 3 by 3 matrix: + * + * + * + * + * + *
Sharpen Kernel Matrix
0.0-0.30.0
-0.32.2-0.3
0.0-0.30.0
+ */ + private static final Kernel SHARPEN_KERNEL = new Kernel(3, 3, SHARPEN_MATRIX); + + /** + * Component that can be used with the MediaTracker etc. + */ + private static final Component NULL_COMPONENT = new Component() {}; + + /** Our static image tracker */ + private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT); + + /** */ + protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); + /** */ + protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0); + + /** */ + private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration(); + + private static GraphicsConfiguration getDefaultGraphicsConfiguration() { + try { + GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (!env.isHeadlessInstance()) { + return env.getDefaultScreenDevice().getDefaultConfiguration(); + } + } + catch (LinkageError e) { + // Means we are not in a 1.4+ VM, so skip testing for headless again + VM_SUPPORTS_ACCELERATION = false; + } + return null; + } + + /** Creates an ImageUtil. Private constructor. */ + private ImageUtil() { + } + + /** + * Converts the {@code RenderedImage} to a {@code BufferedImage}. + * The new image will have the same {@code ColorModel}, + * {@code Raster} and properties as the original image, if possible. + *

+ * If the image is already a {@code BufferedImage}, it is simply returned + * and no conversion takes place. + *

+ * + * @param pOriginal the image to convert. + * + * @return a {@code BufferedImage} + */ + public static BufferedImage toBuffered(RenderedImage pOriginal) { + // Don't convert if it already is a BufferedImage + if (pOriginal instanceof BufferedImage) { + return (BufferedImage) pOriginal; + } + if (pOriginal == null) { + throw new IllegalArgumentException("original == null"); + } + + // Copy properties + Hashtable properties; + String[] names = pOriginal.getPropertyNames(); + if (names != null && names.length > 0) { + properties = new Hashtable(names.length); + + for (String name : names) { + properties.put(name, pOriginal.getProperty(name)); + } + } + else { + properties = null; + } + + // NOTE: This is a workaround for the broken Batik '*Red' classes, that + // throw NPE if copyData(null) is used. This may actually be faster too. + // See RenderedImage#copyData / RenderedImage#getData + Raster data = pOriginal.getData(); + WritableRaster raster; + if (data instanceof WritableRaster) { + raster = (WritableRaster) data; + } + else { + raster = data.createCompatibleWritableRaster(); + raster = pOriginal.copyData(raster); + } + + // Create buffered image + ColorModel colorModel = pOriginal.getColorModel(); + return new BufferedImage(colorModel, raster, + colorModel.isAlphaPremultiplied(), + properties); + } + + /** + * Converts the {@code RenderedImage} to a {@code BufferedImage} of the + * given type. + *

+ * If the image is already a {@code BufferedImage} of the given type, it + * is simply returned and no conversion takes place. + *

+ * + * @param pOriginal the image to convert. + * @param pType the type of buffered image + * + * @return a {@code BufferedImage} + * + * @throws IllegalArgumentException if {@code pOriginal == null} + * or {@code pType} is not a valid type for {@code BufferedImage} + * + * @see java.awt.image.BufferedImage#getType() + */ + public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) { + // Don't convert if it already is BufferedImage and correct type + if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) { + return (BufferedImage) pOriginal; + } + if (pOriginal == null) { + throw new IllegalArgumentException("original == null"); + } + + // Create a buffered image + BufferedImage image = createBuffered(pOriginal.getWidth(), + pOriginal.getHeight(), + pType, Transparency.TRANSLUCENT); + + // Draw the image onto the buffer + // NOTE: This is faster than doing a raster conversion in most cases + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.drawRenderedImage(pOriginal, IDENTITY_TRANSFORM); + } + finally { + g.dispose(); + } + + return image; + } + + /** + * Converts the {@code BufferedImage} to a {@code BufferedImage} of the + * given type. The new image will have the same {@code ColorModel}, + * {@code Raster} and properties as the original image, if possible. + *

+ * If the image is already a {@code BufferedImage} of the given type, it + * is simply returned and no conversion takes place. + *

+ *

+ * This method simply invokes + * {@link #toBuffered(RenderedImage,int) toBuffered((RenderedImage) pOriginal, pType)}. + *

+ * + * @param pOriginal the image to convert. + * @param pType the type of buffered image + * + * @return a {@code BufferedImage} + * + * @throws IllegalArgumentException if {@code pOriginal == null} + * or if {@code pType} is not a valid type for {@code BufferedImage} + * + * @see java.awt.image.BufferedImage#getType() + */ + public static BufferedImage toBuffered(BufferedImage pOriginal, int pType) { + return toBuffered((RenderedImage) pOriginal, pType); + } + + /** + * Converts the {@code Image} to a {@code BufferedImage}. + * The new image will have the same {@code ColorModel}, {@code Raster} and + * properties as the original image, if possible. + *

+ * If the image is already a {@code BufferedImage}, it is simply returned + * and no conversion takes place. + *

+ * + * @param pOriginal the image to convert. + * + * @return a {@code BufferedImage} + * + * @throws IllegalArgumentException if {@code pOriginal == null} + * @throws ImageConversionException if the image cannot be converted + */ + public static BufferedImage toBuffered(Image pOriginal) { + // Don't convert if it already is BufferedImage + if (pOriginal instanceof BufferedImage) { + return (BufferedImage) pOriginal; + } + if (pOriginal == null) { + throw new IllegalArgumentException("original == null"); + } + + //System.out.println("--> Doing full BufferedImage conversion..."); + + BufferedImageFactory factory = new BufferedImageFactory(pOriginal); + return factory.getBufferedImage(); + } + + /** + * Creates a deep copy of the given image. The image will have the same + * color model and raster type, but will not share image (pixel) data + * with the input image. + * + * @param pImage the image to clone. + * + * @return a new {@code BufferedImage} + * + * @throws IllegalArgumentException if {@code pImage} is {@code null} + */ + public static BufferedImage createCopy(final BufferedImage pImage) { + if (pImage == null) { + throw new IllegalArgumentException("image == null"); + } + + ColorModel cm = pImage.getColorModel(); + + BufferedImage img = new BufferedImage(cm, + cm.createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()), + cm.isAlphaPremultiplied(), null); + + drawOnto(img, pImage); + + return img; + } + + /** + * Creates a {@code WritableRaster} for the given {@code ColorModel} and + * pixel data. + *

+ * This method is optimized for the most common cases of {@code ColorModel} + * and pixel data combinations. The raster's backing {@code DataBuffer} is + * created directly from the pixel data, as this is faster and more + * resource-friendly than using + * {@code ColorModel.createCompatibleWritableRaster(w, h)}. + *

+ *

+ * For uncommon combinations, the method will fallback to using + * {@code ColorModel.createCompatibleWritableRaster(w, h)} and + * {@code WritableRaster.setDataElements(w, h, pixels)} + *

+ *

+ * Note that the {@code ColorModel} and pixel data are not cloned + * (in most cases). + *

+ * + * @param pWidth the requested raster width + * @param pHeight the requested raster height + * @param pPixels the pixels, as an array, of a type supported by the + * different {@link DataBuffer} + * @param pColorModel the color model to use + * @return a new {@code WritableRaster} + * + * @throws NullPointerException if either {@code pColorModel} or + * {@code pPixels} are {@code null}. + * @throws RuntimeException if {@code pWidth} and {@code pHeight} does not + * match the pixel data in {@code pPixels}. + * + * @see ColorModel#createCompatibleWritableRaster(int, int) + * @see ColorModel#createCompatibleSampleModel(int, int) + * @see WritableRaster#setDataElements(int, int, Object) + * @see DataBuffer + */ + static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) { + // NOTE: This is optimized code for most common cases. + // We create a DataBuffer from the pixel array directly, + // and creating a raster based on the DataBuffer and ColorModel. + // Creating rasters this way is faster and more resource-friendly, as + // cm.createCompatibleWritableRaster allocates an + // "empty" DataBuffer with a storage array of w*h. This array is + // later discarded, and replaced in the raster.setDataElements() call. + // The "old" way is kept as a more compatible fall-back mode. + + DataBuffer buffer = null; + WritableRaster raster = null; + + int bands; + if (pPixels instanceof int[]) { + int[] data = (int[]) pPixels; + buffer = new DataBufferInt(data, data.length); + bands = pColorModel.getNumComponents(); + } + else if (pPixels instanceof short[]) { + short[] data = (short[]) pPixels; + buffer = new DataBufferUShort(data, data.length); + bands = data.length / (pWidth * pHeight); + } + else if (pPixels instanceof byte[]) { + byte[] data = (byte[]) pPixels; + buffer = new DataBufferByte(data, data.length); + + // NOTE: This only holds for gray and indexed with one byte per pixel... + if (pColorModel instanceof IndexColorModel) { + bands = 1; + } + else { + bands = data.length / (pWidth * pHeight); + } + } + else { + // Fallback mode, slower & requires more memory, but compatible + bands = -1; + + // Create raster from color model, w and h + raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight); + raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions.. + } + + if (raster == null) { + if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) { + raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT); + } + else if (pColorModel instanceof PackedColorModel) { + PackedColorModel pcm = (PackedColorModel) pColorModel; + raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT); + } + else { + // (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE. + int[] bandsOffsets = new int[bands]; + for (int i = 0; i < bands;) { + bandsOffsets[i] = bands - (++i); + } + + raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT); + } + } + + return raster; + } + + private static boolean isIndexedPacked(IndexColorModel pColorModel) { + return (pColorModel.getPixelSize() == 1 || pColorModel.getPixelSize() == 2 || pColorModel.getPixelSize() == 4); + } + + /** + * Workaround for bug: TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR and + * TYPE_4BYTE_ABGR_PRE are all converted to TYPE_CUSTOM when using the + * default createCompatibleWritableRaster from ComponentColorModel. + * + * @param pOriginal the orignal image + * @param pModel the original color model + * @param width the requested width of the raster + * @param height the requested height of the raster + * + * @return a new WritableRaster + */ + static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int width, int height) { + if (pModel == null || equals(pOriginal.getColorModel(), pModel)) { + int[] bOffs; + switch (pOriginal.getType()) { + case BufferedImage.TYPE_3BYTE_BGR: + bOffs = new int[]{2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return + return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, + width, height, + width * 3, 3, + bOffs, null); + case BufferedImage.TYPE_4BYTE_ABGR: + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return + return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, + width, height, + width * 4, 4, + bOffs, null); + case BufferedImage.TYPE_CUSTOM: + // Peek into the sample model to see if we have a sample model that will be incompatible with the default case + SampleModel sm = pOriginal.getRaster().getSampleModel(); + if (sm instanceof ComponentSampleModel) { + bOffs = ((ComponentSampleModel) sm).getBandOffsets(); + return Raster.createInterleavedRaster(sm.getDataType(), + width, height, + width * bOffs.length, bOffs.length, + bOffs, null); + } + // Else fall through + default: + return pOriginal.getColorModel().createCompatibleWritableRaster(width, height); + } + } + + return pModel.createCompatibleWritableRaster(width, height); + } + + /** + * Converts the {@code Image} to a {@code BufferedImage} of the given type. + * The new image will have the same {@code ColorModel}, {@code Raster} and + * properties as the original image, if possible. + *

+ * If the image is already a {@code BufferedImage} of the given type, it + * is simply returned and no conversion takes place. + *

+ * + * @param pOriginal the image to convert. + * @param pType the type of buffered image + * + * @return a {@code BufferedImage} + * + * @throws IllegalArgumentException if {@code pOriginal == null} + * or if {@code pType} is not a valid type for {@code BufferedImage} + * + * @see java.awt.image.BufferedImage#getType() + */ + public static BufferedImage toBuffered(Image pOriginal, int pType) { + return toBuffered(pOriginal, pType, null); + } + + /** + * + * @param pOriginal the original image + * @param pType the type of {@code BufferedImage} to create + * @param pICM the optional {@code IndexColorModel} to use. If not + * {@code null} the {@code pType} must be compatible with the color model + * @return a {@code BufferedImage} + * @throws IllegalArgumentException if {@code pType} is not compatible with + * the color model + */ + private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) { + // Don't convert if it already is BufferedImage and correct type + if ((pOriginal instanceof BufferedImage) + && ((BufferedImage) pOriginal).getType() == pType + && (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) { + return (BufferedImage) pOriginal; + } + if (pOriginal == null) { + throw new IllegalArgumentException("original == null"); + } + + //System.out.println("--> Doing full BufferedImage conversion, using Graphics.drawImage()."); + + // Create a buffered image + // NOTE: The getWidth and getHeight methods, will wait for the image + BufferedImage image; + if (pICM == null) { + image = createBuffered(getWidth(pOriginal), getHeight(pOriginal), pType, Transparency.TRANSLUCENT);//new BufferedImage(getWidth(pOriginal), getHeight(pOriginal), pType); + } + else { + image = new BufferedImage(getWidth(pOriginal), getHeight(pOriginal), pType, pICM); + } + + // Draw the image onto the buffer + drawOnto(image, pOriginal); + + return image; + } + + /** + * Draws the source image onto the buffered image, using + * {@code AlphaComposite.Src} and coordinates {@code 0, 0}. + * + * @param pDestination the image to draw on + * @param pSource the source image to draw + * + * @throws NullPointerException if {@code pDestination} or {@code pSource} is {@code null} + */ + static void drawOnto(final BufferedImage pDestination, final Image pSource) { + Graphics2D g = pDestination.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); + g.drawImage(pSource, 0, 0, null); + } + finally { + g.dispose(); + } + } + + /** + * Creates a flipped version of the given image. + * + * @param pImage the image to flip + * @param pAxis the axis to flip around + * @return a new {@code BufferedImage} + */ + public static BufferedImage createFlipped(final Image pImage, final int pAxis) { + switch (pAxis) { + case FLIP_HORIZONTAL: + case FLIP_VERTICAL: + // TODO case FLIP_BOTH:?? same as rotate 180? + break; + default: + throw new IllegalArgumentException("Illegal direction: " + pAxis); + } + BufferedImage source = toBuffered(pImage); + AffineTransform transform; + if (pAxis == FLIP_HORIZONTAL) { + transform = AffineTransform.getTranslateInstance(0, source.getHeight()); + transform.scale(1, -1); + } + else { + transform = AffineTransform.getTranslateInstance(source.getWidth(), 0); + transform.scale(-1, 1); + } + AffineTransformOp transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + return transformOp.filter(source, null); + } + + + /** + * Rotates the image 90 degrees, clockwise (aka "rotate right"), + * counter-clockwise (aka "rotate left") or 180 degrees, depending on the + * {@code pDirection} argument. + *

+ * The new image will be completely covered with pixels from the source + * image. + *

+ * + * @param pImage the source image. + * @param pDirection the direction, must be either {@link #ROTATE_90_CW}, + * {@link #ROTATE_90_CCW} or {@link #ROTATE_180} + * + * @return a new {@code BufferedImage} + * + */ + public static BufferedImage createRotated(final Image pImage, final int pDirection) { + switch (pDirection) { + case ROTATE_90_CW: + case ROTATE_90_CCW: + case ROTATE_180: + return createRotated(pImage, Math.toRadians(pDirection)); + default: + throw new IllegalArgumentException("Illegal direction: " + pDirection); + } + } + + /** + * Rotates the image to the given angle. Areas not covered with pixels from + * the source image will be left transparent, if possible. + * + * @param pImage the source image + * @param pAngle the angle of rotation, in radians + * + * @return a new {@code BufferedImage}, unless {@code pAngle == 0.0} + */ + public static BufferedImage createRotated(final Image pImage, final double pAngle) { + return createRotated0(toBuffered(pImage), pAngle); + } + + private static BufferedImage createRotated0(final BufferedImage pSource, final double pAngle) { + if ((Math.abs(Math.toDegrees(pAngle)) % 360) == 0) { + return pSource; + } + + final boolean fast = ((Math.abs(Math.toDegrees(pAngle)) % 90) == 0.0); + final int w = pSource.getWidth(); + final int h = pSource.getHeight(); + + // Compute new width and height + double sin = Math.abs(Math.sin(pAngle)); + double cos = Math.abs(Math.cos(pAngle)); + + int newW = (int) Math.floor(w * cos + h * sin); + int newH = (int) Math.floor(h * cos + w * sin); + + AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0); + transform.rotate(pAngle, w / 2.0, h / 2.0); + + // TODO: Figure out if this is correct + BufferedImage dest = createTransparent(newW, newH); + + // See: http://weblogs.java.net/blog/campbell/archive/2007/03/java_2d_tricker_1.html + Graphics2D g = dest.createGraphics(); + try { + g.transform(transform); + if (!fast) { + // Max quality + g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.setPaint(new TexturePaint(pSource, + new Rectangle2D.Float(0, 0, pSource.getWidth(), pSource.getHeight()))); + g.fillRect(0, 0, pSource.getWidth(), pSource.getHeight()); + } + else { + g.drawImage(pSource, 0, 0, null); + } + } + finally { + g.dispose(); + } + + return dest; + } + + /** + * Creates a scaled instance of the given {@code Image}, and converts it to + * a {@code BufferedImage} if needed. + * If the original image is a {@code BufferedImage} the result will have + * same type and color model. Note that this implies overhead, and is + * probably not useful for anything but {@code IndexColorModel} images. + * + * @param pImage the {@code Image} to scale + * @param pWidth width in pixels + * @param pHeight height in pixels + * @param pHints scaling ints + * + * @return a {@code BufferedImage} + * + * @throws NullPointerException if {@code pImage} is {@code null}. + * + * @see #createResampled(java.awt.Image, int, int, int) + * @see Image#getScaledInstance(int,int,int) + * @see Image#SCALE_AREA_AVERAGING + * @see Image#SCALE_DEFAULT + * @see Image#SCALE_FAST + * @see Image#SCALE_REPLICATE + * @see Image#SCALE_SMOOTH + */ + public static BufferedImage createScaled(Image pImage, int pWidth, int pHeight, int pHints) { + ColorModel cm; + int type = BI_TYPE_ANY; + if (pImage instanceof RenderedImage) { + cm = ((RenderedImage) pImage).getColorModel(); + if (pImage instanceof BufferedImage) { + type = ((BufferedImage) pImage).getType(); + } + } + else { + BufferedImageFactory factory = new BufferedImageFactory(pImage); + cm = factory.getColorModel(); + } + + BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints); + + // Convert if color models or type differ, to behave as documented + if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) { + //System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... "); + //long start = System.currentTimeMillis(); + WritableRaster raster; + if (pImage instanceof BufferedImage) { + raster = createCompatibleWritableRaster((BufferedImage) pImage, cm, pWidth, pHeight); + } + else { + raster = cm.createCompatibleWritableRaster(pWidth, pHeight); + } + + BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + + if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) { + // TODO: DiffusionDither does not support transparency at the moment, this will create bad results + new DiffusionDither((IndexColorModel) cm).filter(scaled, temp); + } + else { + drawOnto(temp, scaled); + } + + scaled = temp; + //long end = System.currentTimeMillis(); + //System.out.println("Time: " + (end - start) + " ms"); + } + + return scaled; + } + + private static boolean equals(ColorModel pLeft, ColorModel pRight) { + if (pLeft == pRight) { + return true; + } + + if (!pLeft.equals(pRight)) { + return false; + } + + // Now, the models are equal, according to the equals method + // Test indexcolormodels for equality, the maps must be equal + if (pLeft instanceof IndexColorModel) { + IndexColorModel icm1 = (IndexColorModel) pLeft; + IndexColorModel icm2 = (IndexColorModel) pRight; // NOTE: Safe, they're equal + + + final int mapSize1 = icm1.getMapSize(); + final int mapSize2 = icm2.getMapSize(); + + if (mapSize1 != mapSize2) { + return false; + } + + for (int i = 0; i > mapSize1; i++) { + if (icm1.getRGB(i) != icm2.getRGB(i)) { + return false; + } + } + + return true; + + } + + return true; + } + + /** + * Creates a scaled instance of the given {@code Image}, and converts it to + * a {@code BufferedImage} if needed. + * + * @param pImage the {@code Image} to scale + * @param pWidth width in pixels + * @param pHeight height in pixels + * @param pHints scaling mHints + * + * @return a {@code BufferedImage} + * + * @throws NullPointerException if {@code pImage} is {@code null}. + * + * @see Image#SCALE_AREA_AVERAGING + * @see Image#SCALE_DEFAULT + * @see Image#SCALE_FAST + * @see Image#SCALE_REPLICATE + * @see Image#SCALE_SMOOTH + * @see ResampleOp + */ + public static BufferedImage createResampled(Image pImage, int pWidth, int pHeight, int pHints) { + // NOTE: TYPE_4BYTE_ABGR or TYPE_3BYTE_BGR is more efficient when accelerated... + BufferedImage image = pImage instanceof BufferedImage + ? (BufferedImage) pImage + : toBuffered(pImage, BufferedImage.TYPE_4BYTE_ABGR); + return createResampled(image, pWidth, pHeight, pHints); + } + + /** + * Creates a scaled instance of the given {@code RenderedImage}, and + * converts it to a {@code BufferedImage} if needed. + * + * @param pImage the {@code RenderedImage} to scale + * @param pWidth width in pixels + * @param pHeight height in pixels + * @param pHints scaling mHints + * + * @return a {@code BufferedImage} + * + * @throws NullPointerException if {@code pImage} is {@code null}. + * + * @see Image#SCALE_AREA_AVERAGING + * @see Image#SCALE_DEFAULT + * @see Image#SCALE_FAST + * @see Image#SCALE_REPLICATE + * @see Image#SCALE_SMOOTH + * @see ResampleOp + */ + public static BufferedImage createResampled(RenderedImage pImage, int pWidth, int pHeight, int pHints) { + // NOTE: TYPE_4BYTE_ABGR or TYPE_3BYTE_BGR is more efficient when accelerated... + BufferedImage image = pImage instanceof BufferedImage + ? (BufferedImage) pImage + : toBuffered(pImage, pImage.getColorModel().hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); + return createResampled(image, pWidth, pHeight, pHints); + } + + /** + * Creates a scaled instance of the given {@code BufferedImage}. + * + * @param pImage the {@code BufferedImage} to scale + * @param pWidth width in pixels + * @param pHeight height in pixels + * @param pHints scaling mHints + * + * @return a {@code BufferedImage} + * + * @throws NullPointerException if {@code pImage} is {@code null}. + * + * @see Image#SCALE_AREA_AVERAGING + * @see Image#SCALE_DEFAULT + * @see Image#SCALE_FAST + * @see Image#SCALE_REPLICATE + * @see Image#SCALE_SMOOTH + * @see ResampleOp + */ + public static BufferedImage createResampled(BufferedImage pImage, int pWidth, int pHeight, int pHints) { + // Hints are converted between java.awt.Image hints and filter types + return new ResampleOp(pWidth, pHeight, convertAWTHints(pHints)).filter(pImage, null); + } + + private static int convertAWTHints(int pHints) { + switch (pHints) { + case Image.SCALE_FAST: + case Image.SCALE_REPLICATE: + return ResampleOp.FILTER_POINT; + case Image.SCALE_AREA_AVERAGING: + return ResampleOp.FILTER_BOX; + //return ResampleOp.FILTER_CUBIC; + case Image.SCALE_SMOOTH: + return ResampleOp.FILTER_LANCZOS; + default: + //return ResampleOp.FILTER_TRIANGLE; + return ResampleOp.FILTER_QUADRATIC; + } + } + + /** + * Extracts an {@code IndexColorModel} from the given image. + * + * @param pImage the image to get the color model from + * @param pColors the maximum number of colors in the resulting color model + * @param pHints hints controlling transparency and color selection + * + * @return the extracted {@code IndexColorModel} + * + * @see #COLOR_SELECTION_DEFAULT + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_DEFAULT + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see #TRANSPARENCY_TRANSLUCENT + */ + public static IndexColorModel getIndexColorModel(Image pImage, int pColors, int pHints) { + return IndexImage.getIndexColorModel(pImage, pColors, pHints); + } + + /** + * Creates an indexed version of the given image (a {@code BufferedImage} + * with an {@code IndexColorModel}. + * The resulting image will have a maximum of 256 different colors. + * Transparent parts of the original will be replaced with solid black. + * Default (possibly HW accelerated) dither will be used. + * + * @param pImage the image to convert + * + * @return an indexed version of the given image + */ + public static BufferedImage createIndexed(Image pImage) { + return IndexImage.getIndexedImage(toBuffered(pImage), 256, Color.black, IndexImage.DITHER_DEFAULT); + } + + /** + * Creates an indexed version of the given image (a {@code BufferedImage} + * with an {@code IndexColorModel}. + * + * @param pImage the image to convert + * @param pColors number of colors in the resulting image + * @param pMatte color to replace transparent parts of the original. + * @param pHints hints controlling dither, transparency and color selection + * + * @return an indexed version of the given image + * + * @see #COLOR_SELECTION_DEFAULT + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #DITHER_NONE + * @see #DITHER_DEFAULT + * @see #DITHER_DIFFUSION + * @see #DITHER_DIFFUSION_ALTSCANS + * @see #TRANSPARENCY_DEFAULT + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see #TRANSPARENCY_TRANSLUCENT + */ + public static BufferedImage createIndexed(Image pImage, int pColors, Color pMatte, int pHints) { + return IndexImage.getIndexedImage(toBuffered(pImage), pColors, pMatte, pHints); + } + + /** + * Creates an indexed version of the given image (a {@code BufferedImage} + * with an {@code IndexColorModel}. + * + * @param pImage the image to convert + * @param pColors the {@code IndexColorModel} to be used in the resulting + * image. + * @param pMatte color to replace transparent parts of the original. + * @param pHints hints controlling dither, transparency and color selection + * + * @return an indexed version of the given image + * + * @see #COLOR_SELECTION_DEFAULT + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #DITHER_NONE + * @see #DITHER_DEFAULT + * @see #DITHER_DIFFUSION + * @see #DITHER_DIFFUSION_ALTSCANS + * @see #TRANSPARENCY_DEFAULT + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see #TRANSPARENCY_TRANSLUCENT + */ + public static BufferedImage createIndexed(Image pImage, IndexColorModel pColors, Color pMatte, int pHints) { + return IndexImage.getIndexedImage(toBuffered(pImage), pColors, pMatte, pHints); + } + + /** + * Creates an indexed version of the given image (a {@code BufferedImage} + * with an {@code IndexColorModel}. + * + * @param pImage the image to convert + * @param pColors an {@code Image} used to get colors from. If the image is + * has an {@code IndexColorModel}, it will be uesd, otherwise an + * {@code IndexColorModel} is created from the image. + * @param pMatte color to replace transparent parts of the original. + * @param pHints hints controlling dither, transparency and color selection + * + * @return an indexed version of the given image + * + * @see #COLOR_SELECTION_DEFAULT + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #DITHER_NONE + * @see #DITHER_DEFAULT + * @see #DITHER_DIFFUSION + * @see #DITHER_DIFFUSION_ALTSCANS + * @see #TRANSPARENCY_DEFAULT + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see #TRANSPARENCY_TRANSLUCENT + */ + public static BufferedImage createIndexed(Image pImage, Image pColors, Color pMatte, int pHints) { + return IndexImage.getIndexedImage(toBuffered(pImage), + IndexImage.getIndexColorModel(pColors, 255, pHints), + pMatte, pHints); + } + + /** + * Sharpens an image using a convolution matrix. + * The sharpen kernel used, is defined by the following 3 by 3 matrix: + * + * + * + * + * + *
Sharpen Kernel Matrix
0.0-0.30.0
-0.32.2-0.3
0.0-0.30.0
+ *

+ * This is the same result returned as + * {@code sharpen(pOriginal, 0.3f)}. + *

+ * + * @param pOriginal the BufferedImage to sharpen + * + * @return a new BufferedImage, containing the sharpened image. + */ + public static BufferedImage sharpen(BufferedImage pOriginal) { + return convolve(pOriginal, SHARPEN_KERNEL, EDGE_REFLECT); + } + + /** + * Sharpens an image using a convolution matrix. + * The sharpen kernel used, is defined by the following 3 by 3 matrix: + * + * + * + * + * + * + * + *
Sharpen Kernel Matrix
0.0-{@code pAmount}0.0
-{@code pAmount}4.0 * {@code pAmount} + 1.0-{@code pAmount}
0.0-{@code pAmount}0.0
+ * + * @param pOriginal the BufferedImage to sharpen + * @param pAmount the amount of sharpening + * + * @return a BufferedImage, containing the sharpened image. + */ + public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) { + if (pAmount == 0f) { + return pOriginal; + } + + // Create the convolution matrix + float[] data = new float[] { + 0.0f, -pAmount, 0.0f, -pAmount, 4f * pAmount + 1f, -pAmount, 0.0f, -pAmount, 0.0f + }; + + // Do the filtering + return convolve(pOriginal, new Kernel(3, 3, data), EDGE_REFLECT); + } + + /** + * Creates a blurred version of the given image. + * + * @param pOriginal the original image + * + * @return a new {@code BufferedImage} with a blurred version of the given image + */ + public static BufferedImage blur(BufferedImage pOriginal) { + return blur(pOriginal, 1.5f); + } + + // Some work to do... Is okay now, for range 0...1, anything above creates + // artifacts. + // The idea here is that the sum of all terms in the matrix must be 1. + + /** + * Creates a blurred version of the given image. + * + * @param pOriginal the original image + * @param pRadius the amount to blur + * + * @return a new {@code BufferedImage} with a blurred version of the given image + */ + public static BufferedImage blur(BufferedImage pOriginal, float pRadius) { + if (pRadius <= 1f) { + return pOriginal; + } + + // TODO: Re-implement using two-pass one-dimensional gaussion blur + // See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation + // Also see http://www.jhlabs.com/ip/blurring.html + + // TODO: Rethink... Fixed amount and scale matrix instead? +// pAmount = 1f - pAmount; +// float pAmount = 1f - pRadius; +// +// // Normalize amount +// float normAmt = (1f - pAmount) / 24; +// +// // Create the convolution matrix +// float[] data = new float[] { +// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2, +// normAmt, normAmt, normAmt * 2, normAmt, normAmt, +// normAmt, normAmt * 2, pAmount, normAmt * 2, normAmt, +// normAmt, normAmt, normAmt * 2, normAmt, normAmt, +// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2 +// }; +// +// // Do the filtering +// return convolve(pOriginal, new Kernel(5, 5, data), EDGE_REFLECT); + + Kernel horizontal = makeKernel(pRadius); + Kernel vertical = new Kernel(horizontal.getHeight(), horizontal.getWidth(), horizontal.getKernelData(null)); + + BufferedImage temp = addBorder(pOriginal, horizontal.getWidth() / 2, vertical.getHeight() / 2, EDGE_REFLECT); + + temp = convolve(temp, horizontal, EDGE_NO_OP); + temp = convolve(temp, vertical, EDGE_NO_OP); + + return temp.getSubimage( + horizontal.getWidth() / 2, vertical.getHeight() / 2, pOriginal.getWidth(), pOriginal.getHeight() + ); + } + + /** + * Make a Gaussian blur {@link Kernel}. + * + * @param radius the blur radius + * @return a new blur {@code Kernel} + */ + private static Kernel makeKernel(float radius) { + int r = (int) Math.ceil(radius); + int rows = r * 2 + 1; + float[] matrix = new float[rows]; + float sigma = radius / 3; + float sigma22 = 2 * sigma * sigma; + float sigmaPi2 = (float) (2 * Math.PI * sigma); + float sqrtSigmaPi2 = (float) Math.sqrt(sigmaPi2); + float radius2 = radius * radius; + float total = 0; + int index = 0; + for (int row = -r; row <= r; row++) { + float distance = row * row; + if (distance > radius2) { + matrix[index] = 0; + } + else { + matrix[index] = (float) Math.exp(-(distance) / sigma22) / sqrtSigmaPi2; + } + total += matrix[index]; + index++; + } + for (int i = 0; i < rows; i++) { + matrix[i] /= total; + } + + return new Kernel(rows, 1, matrix); + } + + + /** + * Convolves an image, using a convolution matrix. + * + * @param pOriginal the BufferedImage to sharpen + * @param pKernel the kernel + * @param pEdgeOperation the edge operation. Must be one of {@link #EDGE_NO_OP}, + * {@link #EDGE_ZERO_FILL}, {@link #EDGE_REFLECT} or {@link #EDGE_WRAP} + * + * @return a new BufferedImage, containing the sharpened image. + */ + public static BufferedImage convolve(BufferedImage pOriginal, Kernel pKernel, int pEdgeOperation) { + // Allow for 2 more edge operations + BufferedImage original; + switch (pEdgeOperation) { + case EDGE_REFLECT: + case EDGE_WRAP: + original = addBorder(pOriginal, pKernel.getWidth() / 2, pKernel.getHeight() / 2, pEdgeOperation); + break; + default: + original = pOriginal; + break; + } + + // Create convolution operation + ConvolveOp convolve = new ConvolveOp(pKernel, pEdgeOperation, null); + + // Workaround for what seems to be a Java2D bug: + // ConvolveOp needs explicit destination image type for some "uncommon" + // image types. However, TYPE_3BYTE_BGR is what javax.imageio.ImageIO + // normally returns for color JPEGs... :-/ + BufferedImage result = null; + if (original.getType() == BufferedImage.TYPE_3BYTE_BGR) { + result = createBuffered( + pOriginal.getWidth(), pOriginal.getHeight(), + pOriginal.getType(), pOriginal.getColorModel().getTransparency() + ); + } + + // Do the filtering (if result is null, a new image will be created) + BufferedImage image = convolve.filter(original, result); + + if (pOriginal != original) { + // Remove the border + image = image.getSubimage( + pKernel.getWidth() / 2, pKernel.getHeight() / 2, pOriginal.getWidth(), pOriginal.getHeight() + ); + } + + return image; + } + + private static BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY, final int pEdgeOperation) { + // TODO: Might be faster if we could clone raster and strech it... + int w = pOriginal.getWidth(); + int h = pOriginal.getHeight(); + + ColorModel cm = pOriginal.getColorModel(); + WritableRaster raster = cm.createCompatibleWritableRaster(w + 2 * pBorderX, h + 2 * pBorderY); + BufferedImage bordered = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + + Graphics2D g = bordered.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); + + // Draw original in center + g.drawImage(pOriginal, pBorderX, pBorderY, null); + + // TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel + switch (pEdgeOperation) { + case EDGE_REFLECT: + // Top/left (empty) + g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center + // Top/right (empty) + + g.drawImage(pOriginal, -w + pBorderX, pBorderY, pBorderX, h + pBorderY, 0, 0, 1, h, null); // Center/left + // Center/center (already drawn) + g.drawImage(pOriginal, w + pBorderX, pBorderY, 2 * pBorderX + w, h + pBorderY, w - 1, 0, w, h, null); // Center/right + + // Bottom/left (empty) + g.drawImage(pOriginal, pBorderX, pBorderY + h, pBorderX + w, 2 * pBorderY + h, 0, h - 1, w, h, null); // Bottom/center + // Bottom/right (empty) + break; + case EDGE_WRAP: + g.drawImage(pOriginal, -w + pBorderX, -h + pBorderY, null); // Top/left + g.drawImage(pOriginal, pBorderX, -h + pBorderY, null); // Top/center + g.drawImage(pOriginal, w + pBorderX, -h + pBorderY, null); // Top/right + + g.drawImage(pOriginal, -w + pBorderX, pBorderY, null); // Center/left + // Center/center (already drawn) + g.drawImage(pOriginal, w + pBorderX, pBorderY, null); // Center/right + + g.drawImage(pOriginal, -w + pBorderX, h + pBorderY, null); // Bottom/left + g.drawImage(pOriginal, pBorderX, h + pBorderY, null); // Bottom/center + g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right + break; + default: + throw new IllegalArgumentException("Illegal edge operation " + pEdgeOperation); + } + + } + finally { + g.dispose(); + } + + //ConvolveTester.showIt(bordered, "jaffe"); + + return bordered; + } + + /** + * Adds contrast + * + * @param pOriginal the BufferedImage to add contrast to + * + * @return an {@code Image}, containing the contrasted image. + */ + public static Image contrast(Image pOriginal) { + return contrast(pOriginal, 0.3f); + } + + /** + * Changes the contrast of the image + * + * @param pOriginal the {@code Image} to change + * @param pAmount the amount of contrast in the range [-1.0..1.0]. + * + * @return an {@code Image}, containing the contrasted image. + */ + public static Image contrast(Image pOriginal, float pAmount) { + // No change, return original + if (pAmount == 0f) { + return pOriginal; + } + + // Create filter + RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount); + + // Return contrast adjusted image + return filter(pOriginal, filter); + } + + + /** + * Changes the brightness of the original image. + * + * @param pOriginal the {@code Image} to change + * @param pAmount the amount of brightness in the range [-2.0..2.0]. + * + * @return an {@code Image} + */ + public static Image brightness(Image pOriginal, float pAmount) { + // No change, return original + if (pAmount == 0f) { + return pOriginal; + } + + // Create filter + RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f); + + // Return brightness adjusted image + return filter(pOriginal, filter); + } + + + /** + * Converts an image to grayscale. + * + * @see GrayFilter + * @see RGBImageFilter + * + * @param pOriginal the image to convert. + * @return a new Image, containing the gray image data. + */ + public static Image grayscale(Image pOriginal) { + // Create filter + RGBImageFilter filter = new GrayFilter(); + + // Convert to gray + return filter(pOriginal, filter); + } + + /** + * Filters an image, using the given {@code ImageFilter}. + * + * @param pOriginal the original image + * @param pFilter the filter to apply + * + * @return the new {@code Image} + */ + public static Image filter(Image pOriginal, ImageFilter pFilter) { + // Create a filtered source + ImageProducer source = new FilteredImageSource(pOriginal.getSource(), pFilter); + + // Create new image + return Toolkit.getDefaultToolkit().createImage(source); + } + + /** + * Tries to use H/W-accelerated code for an image for display purposes. + * Note that transparent parts of the image might be replaced by solid + * color. Additional image information not used by the current diplay + * hardware may be discarded, like extra bith depth etc. + * + * @param pImage any {@code Image} + * @return a {@code BufferedImage} + */ + public static BufferedImage accelerate(Image pImage) { + return accelerate(pImage, null, DEFAULT_CONFIGURATION); + } + + /** + * Tries to use H/W-accelerated code for an image for display purposes. + * Note that transparent parts of the image might be replaced by solid + * color. Additional image information not used by the current diplay + * hardware may be discarded, like extra bith depth etc. + * + * @param pImage any {@code Image} + * @param pConfiguration the {@code GraphicsConfiguration} to accelerate + * for + * + * @return a {@code BufferedImage} + */ + public static BufferedImage accelerate(Image pImage, GraphicsConfiguration pConfiguration) { + return accelerate(pImage, null, pConfiguration); + } + + /** + * Tries to use H/W-accelerated code for an image for display purposes. + * Note that transparent parts of the image will be replaced by solid + * color. Additional image information not used by the current diplay + * hardware may be discarded, like extra bith depth etc. + * + * @param pImage any {@code Image} + * @param pBackgroundColor the background color to replace any transparent + * parts of the image. + * May be {@code null}, in such case the color is undefined. + * @param pConfiguration the graphics configuration + * May be {@code null}, in such case the color is undefined. + * + * @return a {@code BufferedImage} + */ + static BufferedImage accelerate(Image pImage, Color pBackgroundColor, GraphicsConfiguration pConfiguration) { + // Skip acceleration if the layout of the image and color model is already ok + if (pImage instanceof BufferedImage) { + BufferedImage buffered = (BufferedImage) pImage; + // TODO: What if the createCompatibleImage insist on TYPE_CUSTOM...? :-P + if (buffered.getType() != BufferedImage.TYPE_CUSTOM && equals(buffered.getColorModel(), pConfiguration.getColorModel(buffered.getTransparency()))) { + return buffered; + } + } + if (pImage == null) { + throw new IllegalArgumentException("image == null"); + } + + int w = ImageUtil.getWidth(pImage); + int h = ImageUtil.getHeight(pImage); + + // Create accelerated version + BufferedImage temp = createClear(w, h, BI_TYPE_ANY, getTransparency(pImage), pBackgroundColor, pConfiguration); + drawOnto(temp, pImage); + + return temp; + } + + private static int getTransparency(Image pImage) { + if (pImage instanceof BufferedImage) { + BufferedImage bi = (BufferedImage) pImage; + return bi.getTransparency(); + } + return Transparency.OPAQUE; + } + + /** + * Creates a transparent image. + * + * @param pWidth the requested width of the image + * @param pHeight the requested height of the image + * + * @throws IllegalArgumentException if {@code pType} is not a valid type + * for {@code BufferedImage} + * + * @return the new image + */ + public static BufferedImage createTransparent(int pWidth, int pHeight) { + return createTransparent(pWidth, pHeight, BI_TYPE_ANY); + } + + /** + * Creates a transparent image. + * + * @see BufferedImage#BufferedImage(int,int,int) + * + * @param pWidth the requested width of the image + * @param pHeight the requested height of the image + * @param pType the type of {@code BufferedImage} to create + * + * @throws IllegalArgumentException if {@code pType} is not a valid type + * for {@code BufferedImage} + * + * @return the new image + */ + public static BufferedImage createTransparent(int pWidth, int pHeight, int pType) { + // Create + BufferedImage image = createBuffered(pWidth, pHeight, pType, Transparency.TRANSLUCENT); + + // Clear image with transparent alpha by drawing a rectangle + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.Clear); + g.fillRect(0, 0, pWidth, pHeight); + } + finally { + g.dispose(); + } + + return image; + } + + /** + * Creates a clear image with the given background color. + * + * @see BufferedImage#BufferedImage(int,int,int) + * + * @param pWidth the requested width of the image + * @param pHeight the requested height of the image + * @param pBackground the background color. The color may be translucent. + * May be {@code null}, in such case the color is undefined. + * + * @throws IllegalArgumentException if {@code pType} is not a valid type + * for {@code BufferedImage} + * + * @return the new image + */ + public static BufferedImage createClear(int pWidth, int pHeight, Color pBackground) { + return createClear(pWidth, pHeight, BI_TYPE_ANY, pBackground); + } + + /** + * Creates a clear image with the given background color. + * + * @see BufferedImage#BufferedImage(int,int,int) + * + * @param pWidth the width of the image to create + * @param pHeight the height of the image to create + * @param pType the type of image to create (one of the constants from + * {@link BufferedImage} or {@link #BI_TYPE_ANY}) + * @param pBackground the background color. The color may be translucent. + * May be {@code null}, in such case the color is undefined. + * + * @throws IllegalArgumentException if {@code pType} is not a valid type + * for {@code BufferedImage} + * + * @return the new image + */ + public static BufferedImage createClear(int pWidth, int pHeight, int pType, Color pBackground) { + return createClear(pWidth, pHeight, pType, Transparency.OPAQUE, pBackground, DEFAULT_CONFIGURATION); + } + + static BufferedImage createClear(int pWidth, int pHeight, int pType, int pTransparency, Color pBackground, GraphicsConfiguration pConfiguration) { + // Create + int transparency = (pBackground != null) ? pBackground.getTransparency() : pTransparency; + BufferedImage image = createBuffered(pWidth, pHeight, pType, transparency, pConfiguration); + + if (pBackground != null) { + // Clear image with clear color, by drawing a rectangle + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); // Allow color to be translucent + g.setColor(pBackground); + g.fillRect(0, 0, pWidth, pHeight); + } + finally { + g.dispose(); + } + } + + return image; + } + + /** + * Creates a {@code BufferedImage} of the given size and type. If possible, + * uses accelerated versions of BufferedImage from GraphicsConfiguration. + * + * @param pWidth the width of the image to create + * @param pHeight the height of the image to create + * @param pType the type of image to create (one of the constants from + * {@link BufferedImage} or {@link #BI_TYPE_ANY}) + * @param pTransparency the transparency type (from {@link Transparency}) + * + * @return a {@code BufferedImage} + */ + private static BufferedImage createBuffered(int pWidth, int pHeight, int pType, int pTransparency) { + return createBuffered(pWidth, pHeight, pType, pTransparency, DEFAULT_CONFIGURATION); + } + + static BufferedImage createBuffered(int pWidth, int pHeight, int pType, int pTransparency, + GraphicsConfiguration pConfiguration) { + if (VM_SUPPORTS_ACCELERATION && pType == BI_TYPE_ANY) { + GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (supportsAcceleration(env)) { + return getConfiguration(pConfiguration).createCompatibleImage(pWidth, pHeight, pTransparency); + } + } + + return new BufferedImage(pWidth, pHeight, getImageType(pType, pTransparency)); + } + + private static GraphicsConfiguration getConfiguration(final GraphicsConfiguration pConfiguration) { + return pConfiguration != null ? pConfiguration : DEFAULT_CONFIGURATION; + } + + private static int getImageType(int pType, int pTransparency) { + // TODO: Handle TYPE_CUSTOM? + if (pType != BI_TYPE_ANY) { + return pType; + } + else { + switch (pTransparency) { + case Transparency.OPAQUE: + return BufferedImage.TYPE_INT_RGB; + case Transparency.BITMASK: + case Transparency.TRANSLUCENT: + return BufferedImage.TYPE_INT_ARGB; + default: + throw new IllegalArgumentException("Unknown transparency type: " + pTransparency); + } + } + } + + /** + * Tests if the given {@code GraphicsEnvironment} supports accelleration + * + * @param pEnv the environment + * @return {@code true} if the {@code GraphicsEnvironment} supports + * acceleration + */ + private static boolean supportsAcceleration(GraphicsEnvironment pEnv) { + try { + // Acceleration only supported in non-headless environments, on 1.4+ VMs + return /*VM_SUPPORTS_ACCELERATION &&*/ !pEnv.isHeadlessInstance(); + } + catch (LinkageError ignore) { + // Means we are not in a 1.4+ VM, so skip testing for headless again + VM_SUPPORTS_ACCELERATION = false; + } + + // If the invocation fails, assume no accelleration is possible + return false; + } + + /** + * Gets the width of an Image. + * This method has the side-effect of completely loading the image. + * + * @param pImage an image. + * + * @return the width of the image, or -1 if the width could not be + * determined (i.e. an error occured while waiting for the + * image to load). + */ + public static int getWidth(Image pImage) { + int width = pImage.getWidth(NULL_COMPONENT); + if (width < 0) { + if (!waitForImage(pImage)) { + return -1; // Error while waiting + } + width = pImage.getWidth(NULL_COMPONENT); + } + + return width; + } + + /** + * Gets the height of an Image. + * This method has the side-effect of completely loading the image. + * + * @param pImage an image. + * + * @return the height of the image, or -1 if the height could not be + * determined (i.e. an error occured while waiting for the + * image to load). + */ + public static int getHeight(Image pImage) { + int height = pImage.getHeight(NULL_COMPONENT); + if (height < 0) { + if (!waitForImage(pImage)) { + return -1; // Error while waiting + } + height = pImage.getHeight(NULL_COMPONENT); + } + + return height; + } + + /** + * Waits for an image to load completely. + * Will wait forever. + * + * @param pImage an Image object to wait for. + * + * @return true if the image was loaded successfully, false if an error + * occured, or the wait was interrupted. + * + * @see #waitForImage(Image,long) + */ + public static boolean waitForImage(Image pImage) { + return waitForImages(new Image[]{pImage}, -1L); + } + + /** + * Waits for an image to load completely. + * Will wait the specified time. + * + * @param pImage an Image object to wait for. + * @param pTimeOut the time to wait, in milliseconds. + * + * @return true if the image was loaded successfully, false if an error + * occurred, or the wait was interrupted. + * + * @see #waitForImages(Image[],long) + */ + public static boolean waitForImage(Image pImage, long pTimeOut) { + return waitForImages(new Image[]{pImage}, pTimeOut); + } + + /** + * Waits for a number of images to load completely. + * Will wait forever. + * + * @param pImages an array of Image objects to wait for. + * + * @return true if the images was loaded successfully, false if an error + * occurred, or the wait was interrupted. + * + * @see #waitForImages(Image[],long) + */ + public static boolean waitForImages(Image[] pImages) { + return waitForImages(pImages, -1L); + } + + /** + * Waits for a number of images to load completely. + * Will wait the specified time. + * + * @param pImages an array of Image objects to wait for + * @param pTimeOut the time to wait, in milliseconds + * + * @return true if the images was loaded successfully, false if an error + * occurred, or the wait was interrupted. + */ + public static boolean waitForImages(Image[] pImages, long pTimeOut) { + // TODO: Need to make sure that we don't wait for the same image many times + // Use hashcode as id? Don't remove images from tracker? Hmmm... + boolean success = true; + + // Create a local id for use with the mediatracker + int imageId; + + // NOTE: This is very experimental... + imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages); + + // Add images to tracker + for (Image image : pImages) { + sTracker.addImage(image, imageId); + + // Start loading immediately + if (sTracker.checkID(imageId, false)) { + // Image is done, so remove again + sTracker.removeImage(image, imageId); + } + } + + try { + if (pTimeOut < 0L) { + // Just wait + sTracker.waitForID(imageId); + } + else { + // Wait until timeout + // NOTE: waitForID(int, long) return value is undocumented. + // I assume that it returns true, if the image(s) loaded + // successfully before the timeout, however, I always check + // isErrorID later on, just in case... + success = sTracker.waitForID(imageId, pTimeOut); + } + } + catch (InterruptedException ie) { + // Interrupted while waiting, image not loaded + success = false; + } + finally { + // Remove images from mediatracker + for (Image pImage : pImages) { + sTracker.removeImage(pImage, imageId); + } + } + + // If the wait was successfull, and no errors were reported for the + // images, return true + return success && !sTracker.isErrorID(imageId); + } + + /** + * Tests whether the image has any transparent or semi-transparent pixels. + * + * @param pImage the image + * @param pFast if {@code true}, the method tests maximum 10 x 10 pixels, + * evenly spaced out in the image. + * + * @return {@code true} if transparent pixels are found, otherwise + * {@code false}. + */ + public static boolean hasTransparentPixels(RenderedImage pImage, boolean pFast) { + if (pImage == null) { + return false; + } + + // First, test if the ColorModel supports alpha... + ColorModel cm = pImage.getColorModel(); + if (!cm.hasAlpha()) { + return false; + } + + if (cm.getTransparency() != Transparency.BITMASK + && cm.getTransparency() != Transparency.TRANSLUCENT) { + return false; + } + + // ... if so, test the pixels of the image hard way + Object data = null; + + // Loop over tiles (noramally, BufferedImages have only one) + for (int yT = pImage.getMinTileY(); yT < pImage.getNumYTiles(); yT++) { + for (int xT = pImage.getMinTileX(); xT < pImage.getNumXTiles(); xT++) { + // Test pixels of each tile + Raster raster = pImage.getTile(xT, yT); + int xIncrement = pFast ? Math.max(raster.getWidth() / 10, 1) : 1; + int yIncrement = pFast ? Math.max(raster.getHeight() / 10, 1) : 1; + + for (int y = 0; y < raster.getHeight(); y += yIncrement) { + for (int x = 0; x < raster.getWidth(); x += xIncrement) { + // Copy data for each pixel, without allocation array + data = raster.getDataElements(x, y, data); + + // Test alpha value + if (cm.getAlpha(data) != 0xff) { + return true; + } + } + } + } + } + + return false; + } + + /** + * Creates a translucent version of the given color. + * + * @param pColor the original color + * @param pTransparency the transparency level ({@code 0 - 255}) + * @return a translucent color + * + * @throws NullPointerException if {@code pColor} is {@code null} + */ + public static Color createTranslucent(Color pColor, int pTransparency) { + //return new Color(pColor.getRed(), pColor.getGreen(), pColor.getBlue(), pTransparency); + return new Color(((pTransparency & 0xff) << 24) | (pColor.getRGB() & 0x00ffffff), true); + } + + /** + * Blends two ARGB values half and half, to create a tone in between. + * + * @param pRGB1 color 1 + * @param pRGB2 color 2 + * @return the new rgb value + */ + static int blend(int pRGB1, int pRGB2) { + // Slightly modified from http://www.compuphase.com/graphic/scale3.htm + // to support alpha values + return (((pRGB1 ^ pRGB2) & 0xfefefefe) >> 1) + (pRGB1 & pRGB2); + } + + /** + * Blends two colors half and half, to create a tone in between. + * + * @param pColor color 1 + * @param pOther color 2 + * @return a new {@code Color} + */ + public static Color blend(Color pColor, Color pOther) { + return new Color(blend(pColor.getRGB(), pOther.getRGB()), true); + + /* + return new Color((pColor.getRed() + pOther.getRed()) / 2, + (pColor.getGreen() + pOther.getGreen()) / 2, + (pColor.getBlue() + pOther.getBlue()) / 2, + (pColor.getAlpha() + pOther.getAlpha()) / 2); + */ + } + + /** + * Blends two colors, controlled by the blending factor. + * A factor of {@code 0.0} will return the first color, + * a factor of {@code 1.0} will return the second. + * + * @param pColor color 1 + * @param pOther color 2 + * @param pBlendFactor {@code [0...1]} + * @return a new {@code Color} + */ + public static Color blend(Color pColor, Color pOther, float pBlendFactor) { + float inverseBlend = (1f - pBlendFactor); + return new Color( + clamp((pColor.getRed() * inverseBlend) + (pOther.getRed() * pBlendFactor)), + clamp((pColor.getGreen() * inverseBlend) + (pOther.getGreen() * pBlendFactor)), + clamp((pColor.getBlue() * inverseBlend) + (pOther.getBlue() * pBlendFactor)), + clamp((pColor.getAlpha() * inverseBlend) + (pOther.getAlpha() * pBlendFactor)) + ); + } + + private static int clamp(float f) { + return (int) f; + } } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java b/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java index f7419204..2a359ca3 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java @@ -1,1515 +1,1527 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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. - */ -/* -****************************************************************************** -* -* ============================================================================ -* The Apache Software License, Version 1.1 -* ============================================================================ -* -* Copyright (C) 2000 The Apache Software Foundation. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modifica- -* tion, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. 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. -* -* 3. The end-user documentation included with the redistribution, if any, must -* include the following acknowledgment: "This product includes software -* developed by the Apache Software Foundation (http://www.apache.org/)." -* Alternately, this acknowledgment may appear in the software itself, if -* and wherever such third-party acknowledgments normally appear. -* -* 4. The names "Batik" and "Apache Software Foundation" must not be used to -* endorse or promote products derived from this software without prior -* written permission. For written permission, please contact -* apache@apache.org. -* -* 5. Products derived from this software may not be called "Apache", nor may -* "Apache" appear in their name, without prior written permission of the -* Apache Software Foundation. -* -* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 -* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- -* DING, 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. -* -* This software consists of voluntary contributions made by many individuals -* on behalf of the Apache Software Foundation. For more information on the -* Apache Software Foundation, please see . -* -****************************************************************************** -* -*/ - -package com.twelvemonkeys.image; - -import com.twelvemonkeys.io.FileUtil; -import com.twelvemonkeys.lang.StringUtil; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.RenderedImage; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * This class implements an adaptive palette generator to reduce images - * to a variable number of colors. - * It can also render images into fixed color pallettes. - *

- * Support for the default JVM (ordered/pattern) dither, Floyd-Steinberg like - * error-diffusion and no dither, controlled by the hints - * {@link #DITHER_DIFFUSION}, - * {@link #DITHER_NONE} and - * {@link #DITHER_DEFAULT}. - *

- * Color selection speed/accuracy can be controlled using the hints - * {@link #COLOR_SELECTION_FAST}, - * {@link #COLOR_SELECTION_QUALITY} and - * {@link #COLOR_SELECTION_DEFAULT}. - *

- * Transparency support can be controlled using the hints - * {@link #TRANSPARENCY_OPAQUE}, - * {@link #TRANSPARENCY_BITMASK} and - * {@link #TRANSPARENCY_TRANSLUCENT}. - *

- *


- *

- *

- * This product includes software developed by the Apache Software Foundation.
- * 

- * This software consists of voluntary contributions made by many individuals - * on behalf of the Apache Software Foundation. For more information on the - * Apache Software Foundation, please see http://www.apache.org/ - *

- * - * @author Thomas DeWeese - * @author Jun Inamori - * @author Harald Kuhr - * @version $Id: IndexImage.java#1 $ - * @see DiffusionDither - */ -class IndexImage { - - /** - * Dither mask - */ - protected final static int DITHER_MASK = 0xFF; - - /** - * Java default dither - */ - public final static int DITHER_DEFAULT = 0x00; - - /** - * No dither - */ - public final static int DITHER_NONE = 0x01; - - /** - * Error diffusion dither - */ - public final static int DITHER_DIFFUSION = 0x02; - - /** - * Error diffusion dither with alternating scans - */ - public final static int DITHER_DIFFUSION_ALTSCANS = 0x03; - - /** - * Color Selection mask - */ - protected final static int COLOR_SELECTION_MASK = 0xFF00; - - /** - * Default color selection - */ - public final static int COLOR_SELECTION_DEFAULT = 0x0000; - - /** - * Prioritize speed - */ - public final static int COLOR_SELECTION_FAST = 0x0100; - - /** - * Prioritize quality - */ - public final static int COLOR_SELECTION_QUALITY = 0x0200; - - /** - * Transparency mask - */ - protected final static int TRANSPARENCY_MASK = 0xFF0000; - - /** - * Default transparency (none) - */ - public final static int TRANSPARENCY_DEFAULT = 0x000000; - - /** - * Discard any alpha information - */ - public final static int TRANSPARENCY_OPAQUE = 0x010000; - - /** - * Convert alpha to bitmask - */ - public final static int TRANSPARENCY_BITMASK = 0x020000; - - /** - * Keep original alpha (not supported yet) - */ - protected final static int TRANSPARENCY_TRANSLUCENT = 0x030000; - - /** - * Used to track a color and the number of pixels of that colors - */ - private static class Counter { - - /** - * Field val - */ - public int val; - - /** - * Field count - */ - public int count = 1; - - /** - * Constructor Counter - * - * @param val the initial value - */ - public Counter(int val) { - this.val = val; - } - - /** - * Method add - * - * @param val the new value - * @return {@code true} if the value was added, otherwise {@code false} - */ - public boolean add(int val) { - // See if the value matches us... - if (this.val != val) { - return false; - } - - count++; - - return true; - } - } - - /** - * Used to define a cube of the color space. The cube can be split - * approximately in half to generate two cubes. - */ - private static class Cube { - int[] min = {0, 0, 0}; - int[] max = {255, 255, 255}; - boolean done = false; - List[] colors = null; - int count = 0; - static final int RED = 0; - static final int GRN = 1; - static final int BLU = 2; - - /** - * Define a new cube. - * - * @param colors contains the 3D color histogram to be subdivided - * @param count the total number of pixels in the 3D histogram. - */ - public Cube(List[] colors, int count) { - this.colors = colors; - this.count = count; - } - - /** - * If this returns true then the cube can not be subdivided any - * further - * - * @return true if cube can not be subdivided any further - */ - public boolean isDone() { - return done; - } - - /** - * Splits the cube into two parts. This cube is - * changed to be one half and the returned cube is the other half. - * This tries to pick the right channel to split on. - * - * @return the {@code Cube} containing the other half - */ - public Cube split() { - int dr = max[0] - min[0] + 1; - int dg = max[1] - min[1] + 1; - int db = max[2] - min[2] + 1; - int c0, c1, splitChannel; - - // Figure out which axis is the longest and split along - // that axis (this tries to keep cubes square-ish). - if (dr >= dg) { - c0 = GRN; - if (dr >= db) { - splitChannel = RED; - c1 = BLU; - } - else { - splitChannel = BLU; - c1 = RED; - } - } - else if (dg >= db) { - splitChannel = GRN; - c0 = RED; - c1 = BLU; - } - else { - splitChannel = BLU; - c0 = RED; - c1 = GRN; - } - - Cube ret; - - ret = splitChannel(splitChannel, c0, c1); - - if (ret != null) { - return ret; - } - - ret = splitChannel(c0, splitChannel, c1); - - if (ret != null) { - return ret; - } - - ret = splitChannel(c1, splitChannel, c0); - - if (ret != null) { - return ret; - } - - done = true; - - return null; - } - - /** - * Splits the image according to the parameters. It tries - * to find a location where half the pixels are on one side - * and half the pixels are on the other. - * - * @param splitChannel split channel - * @param c0 channel 0 - * @param c1 channel 1 - * @return the {@code Cube} containing the other half - */ - public Cube splitChannel(int splitChannel, int c0, int c1) { - if (min[splitChannel] == max[splitChannel]) { - return null; - } - int splitSh4 = (2 - splitChannel) * 4; - int c0Sh4 = (2 - c0) * 4; - int c1Sh4 = (2 - c1) * 4; - - // int splitSh8 = (2-splitChannel)*8; - // int c0Sh8 = (2-c0)*8; - // int c1Sh8 = (2-c1)*8; - // - int half = count / 2; - - // Each entry is the number of pixels that have that value - // in the split channel within the cube (so pixels - // that have that value in the split channel aren't counted - // if they are outside the cube in the other color channels. - int counts[] = new int[256]; - int tcount = 0; - - // System.out.println("Cube: [" + - // min[0] + "-" + max[0] + "] [" + - // min[1] + "-" + max[1] + "] [" + - // min[2] + "-" + max[2] + "]"); - int[] minIdx = {min[0] >> 4, min[1] >> 4, min[2] >> 4}; - int[] maxIdx = {max[0] >> 4, max[1] >> 4, max[2] >> 4}; - int minR = min[0], minG = min[1], minB = min[2]; - int maxR = max[0], maxG = max[1], maxB = max[2]; - int val; - int[] vals = {0, 0, 0}; - - for (int i = minIdx[splitChannel]; i <= maxIdx[splitChannel]; i++) { - int idx1 = i << splitSh4; - - for (int j = minIdx[c0]; j <= maxIdx[c0]; j++) { - int idx2 = idx1 | (j << c0Sh4); - - for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) { - int idx = idx2 | (k << c1Sh4); - List v = colors[idx]; - - if (v == null) { - continue; - } - - for (Counter c : v) { - val = c.val; - vals[0] = (val & 0xFF0000) >> 16; - vals[1] = (val & 0xFF00) >> 8; - vals[2] = (val & 0xFF); - if (((vals[0] >= minR) && (vals[0] <= maxR)) && ((vals[1] >= minG) && (vals[1] <= maxG)) - && ((vals[2] >= minB) && (vals[2] <= maxB))) { - - // The val lies within this cube so count it. - counts[vals[splitChannel]] += c.count; - tcount += c.count; - } - } - } - } - - // We've found the half way point. Note that the - // rest of counts is not filled out. - if (tcount >= half) { - break; - } - } - tcount = 0; - int lastAdd = -1; - - // These indicate what the top value for the low cube and - // the low value of the high cube should be in the split channel - // (they may not be one off if there are 'dead' spots in the - // counts array.) - int splitLo = min[splitChannel], splitHi = max[splitChannel]; - - for (int i = min[splitChannel]; i <= max[splitChannel]; i++) { - int c = counts[i]; - - if (c == 0) { - // No counts below this so move up bottom of cube. - if ((tcount == 0) && (i < max[splitChannel])) { - this.min[splitChannel] = i + 1; - } - continue; - } - if (tcount + c < half) { - lastAdd = i; - tcount += c; - continue; - } - if ((half - tcount) <= ((tcount + c) - half)) { - // Then lastAdd is a better top idx for this then i. - if (lastAdd == -1) { - // No lower place to break. - if (c == this.count) { - - // All pixels are at this value so make min/max - // reflect that. - this.max[splitChannel] = i; - return null;// no split to make. - } - else { - - // There are values about this one so - // split above. - splitLo = i; - splitHi = i + 1; - break; - } - } - splitLo = lastAdd; - splitHi = i; - } - else { - if (i == this.max[splitChannel]) { - if (c == this.count) { - // would move min up but that should - // have happened already. - return null;// no split to make. - } - else { - // Would like to break between i and i+1 - // but no i+1 so use lastAdd and i; - splitLo = lastAdd; - splitHi = i; - break; - } - } - - // Include c in counts - tcount += c; - splitLo = i; - splitHi = i + 1; - } - break; - } - - // System.out.println("Split: " + splitChannel + "@" - // + splitLo + "-"+splitHi + - // " Count: " + tcount + " of " + count + - // " LA: " + lastAdd); - // Create the new cube and update everyone's bounds & counts. - Cube ret = new Cube(colors, tcount); - - this.count = this.count - tcount; - ret.min[splitChannel] = this.min[splitChannel]; - ret.max[splitChannel] = splitLo; - this.min[splitChannel] = splitHi; - ret.min[c0] = this.min[c0]; - ret.max[c0] = this.max[c0]; - ret.min[c1] = this.min[c1]; - ret.max[c1] = this.max[c1]; - - return ret; - } - - /** - * Returns the average color for this cube - * - * @return the average - */ - public int averageColor() { - if (this.count == 0) { - return 0; - } - - float red = 0, grn = 0, blu = 0; - int minR = min[0], minG = min[1], minB = min[2]; - int maxR = max[0], maxG = max[1], maxB = max[2]; - int[] minIdx = {minR >> 4, minG >> 4, minB >> 4}; - int[] maxIdx = {maxR >> 4, maxG >> 4, maxB >> 4}; - int val, ired, igrn, iblu; - float weight; - - for (int i = minIdx[0]; i <= maxIdx[0]; i++) { - int idx1 = i << 8; - - for (int j = minIdx[1]; j <= maxIdx[1]; j++) { - int idx2 = idx1 | (j << 4); - - for (int k = minIdx[2]; k <= maxIdx[2]; k++) { - int idx = idx2 | k; - List v = colors[idx]; - - if (v == null) { - continue; - } - - for (Counter c : v) { - val = c.val; - ired = (val & 0xFF0000) >> 16; - igrn = (val & 0x00FF00) >> 8; - iblu = (val & 0x0000FF); - - if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) { - weight = (c.count / (float) this.count); - red += ((float) ired) * weight; - grn += ((float) igrn) * weight; - blu += ((float) iblu) * weight; - } - } - } - } - } - - // System.out.println("RGB: [" + red + ", " + - // grn + ", " + blu + "]"); - return (((int) (red + 0.5f)) << 16 | ((int) (grn + 0.5f)) << 8 | ((int) (blu + 0.5f))); - } - }// end Cube - - /** - * You cannot create this - */ - private IndexImage() { - } - - /** - * @param pImage the image to get {@code IndexColorModel} from - * @param pNumberOfColors the number of colors for the {@code IndexColorModel} - * @param pFast {@code true} if fast - * @return an {@code IndexColorModel} - * @see #getIndexColorModel(Image,int,int) - * - * @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead! - * This version will be removed in a later version of the API. - */ - public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) { - return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY); - } - - /** - * Gets an {@code IndexColorModel} from the given image. If the image has an - * {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel} - * is created, using an adaptive palette. - * - * @param pImage the image to get {@code IndexColorModel} from - * @param pNumberOfColors the number of colors for the {@code IndexColorModel} - * @param pHints one of {@link #COLOR_SELECTION_FAST}, - * {@link #COLOR_SELECTION_QUALITY} or - * {@link #COLOR_SELECTION_DEFAULT}. - * @return The {@code IndexColorModel} from the given image, or a newly created - * {@code IndexColorModel} using an adaptive palette. - * @throws ImageConversionException if an exception occurred during color - * model extraction. - */ - public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, int pHints) throws ImageConversionException { - IndexColorModel icm = null; - RenderedImage image = null; - - if (pImage instanceof RenderedImage) { - image = (RenderedImage) pImage; - ColorModel cm = image.getColorModel(); - - if (cm instanceof IndexColorModel) { - // Test if we have right number of colors - if (((IndexColorModel) cm).getMapSize() <= pNumberOfColors) { - //System.out.println("IndexColorModel from BufferedImage"); - icm = (IndexColorModel) cm;// Done - } - } - - // Else create from buffered image, hard way, see below - } - else { - // Create from image using BufferedImageFactory - BufferedImageFactory factory = new BufferedImageFactory(pImage); - ColorModel cm = factory.getColorModel(); - - if ((cm instanceof IndexColorModel) && ((IndexColorModel) cm).getMapSize() <= pNumberOfColors) { - //System.out.println("IndexColorModel from Image"); - icm = (IndexColorModel) cm;// Done - } - else { - // Else create from (buffered) image, hard way - image = factory.getBufferedImage(); - } - } - - // We now have at least a buffered image, create model from it - if (icm == null) { - icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints); - } - else if (!(icm instanceof InverseColorMapIndexColorModel)) { - // If possible, use faster code - icm = new InverseColorMapIndexColorModel(icm); - } - - return icm; - } - - /** - * Creates an {@code IndexColorModel} from the given image, using an adaptive - * palette. - * - * @param pImage the image to get {@code IndexColorModel} from - * @param pNumberOfColors the number of colors for the {@code IndexColorModel} - * @param pHints use fast mode if possible (might give slightly lower - * quality) - * @return a new {@code IndexColorModel} created from the given image - */ - private static IndexColorModel createIndexColorModel(BufferedImage pImage, int pNumberOfColors, int pHints) { - // TODO: Use ImageUtil.hasTransparentPixels(pImage, true) || - // -- haraldK, 20021024, experimental, try to use one transparent pixel - boolean useTransparency = isTransparent(pHints); - - if (useTransparency) { - pNumberOfColors--; - } - - //System.out.println("Transp: " + useTransparency + " colors: " + pNumberOfColors); - int width = pImage.getWidth(); - int height = pImage.getHeight(); - - // Using 4 bits from R, G & B. - @SuppressWarnings("unchecked") - List[] colors = new List[1 << 12];// [4096] - - // Speedup, doesn't decrease image quality much - int step = 1; - - if (isFast(pHints)) { - step += (width * height / 16384);// 128x128px - } - int sampleCount = 0; - int rgb; - - //for (int x = 0; x < width; x++) { - //for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - for (int y = x % step; y < height; y += step) { - // Count the number of color samples - sampleCount++; - - // Get ARGB pixel from image - rgb = (pImage.getRGB(x, y) & 0xFFFFFF); - - // Get index from high four bits of each component. - int index = (((rgb & 0xF00000) >>> 12) | ((rgb & 0x00F000) >>> 8) | ((rgb & 0x0000F0) >>> 4)); - - // Get the 'hash vector' for that key. - List v = colors[index]; - - if (v == null) { - // No colors in this bin yet so create vector and - // add color. - v = new ArrayList(); - v.add(new Counter(rgb)); - colors[index] = v; - } - else { - // Find our color in the bin or create a counter for it. - Iterator i = v.iterator(); - - while (true) { - if (i.hasNext()) { - // try adding our color to each counter... - if (((Counter) i.next()).add(rgb)) { - break; - } - } - else { - v.add(new Counter(rgb)); - break; - } - } - } - } - } - - // All colours found, reduce to pNumberOfColors - int numberOfCubes = 1; - int fCube = 0; - Cube[] cubes = new Cube[pNumberOfColors]; - - cubes[0] = new Cube(colors, sampleCount); - - //cubes[0] = new Cube(colors, width * height); - while (numberOfCubes < pNumberOfColors) { - while (cubes[fCube].isDone()) { - fCube++; - - if (fCube == numberOfCubes) { - break; - } - } - - if (fCube == numberOfCubes) { - break; - } - - Cube cube = cubes[fCube]; - Cube newCube = cube.split(); - - if (newCube != null) { - if (newCube.count > cube.count) { - Cube tmp = cube; - - cube = newCube; - newCube = tmp; - } - - int j = fCube; - int count = cube.count; - - for (int i = fCube + 1; i < numberOfCubes; i++) { - if (cubes[i].count < count) { - break; - } - cubes[j++] = cubes[i]; - } - - cubes[j++] = cube; - count = newCube.count; - - while (j < numberOfCubes) { - if (cubes[j].count < count) { - break; - } - j++; - } - - System.arraycopy(cubes, j, cubes, j + 1, numberOfCubes - j); - - cubes[j/*++*/] = newCube; - numberOfCubes++; - } - } - - // Create RGB arrays with correct number of colors - // If we have transparency, the last color will be the transparent one - byte[] r = new byte[useTransparency ? numberOfCubes + 1 : numberOfCubes]; - byte[] g = new byte[useTransparency ? numberOfCubes + 1 : numberOfCubes]; - byte[] b = new byte[useTransparency ? numberOfCubes + 1 : numberOfCubes]; - - for (int i = 0; i < numberOfCubes; i++) { - int val = cubes[i].averageColor(); - - r[i] = (byte) ((val >> 16) & 0xFF); - g[i] = (byte) ((val >> 8) & 0xFF); - b[i] = (byte) ((val) & 0xFF); - - //System.out.println("Color [" + i + "]: #" + - // (((val>>16)<16)?"0":"") + - // Integer.toHexString(val)); - } - - // For some reason using less than 8 bits causes a bug in the dither - // - transparency added to all totally black colors? - int numOfBits = 8; - - // -- haraldK, 20021024, as suggested by Thomas E. Deweese - // plus adding a transparent pixel - IndexColorModel icm; - if (useTransparency) { - icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1); - } - else { - icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b); - } - return icm; - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive - * palette (8 bit) from the color data in the image, and uses default - * dither. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index and get color information from. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED}, and use an - * {@code IndexColorModel}. - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage) { - return getIndexedImage(pImage, 256, DITHER_DEFAULT); - } - - /** - * Tests if the hint {@code COLOR_SELECTION_QUALITY} is not - * set. - * - * @param pHints hints - * @return true if the hint {@code COLOR_SELECTION_QUALITY} - * is not set. - */ - private static boolean isFast(int pHints) { - return (pHints & COLOR_SELECTION_MASK) != COLOR_SELECTION_QUALITY; - } - - /** - * Tests if the hint {@code TRANSPARENCY_BITMASK} or - * {@code TRANSPARENCY_TRANSLUCENT} is set. - * - * @param pHints hints - * @return true if the hint {@code TRANSPARENCY_BITMASK} or - * {@code TRANSPARENCY_TRANSLUCENT} is set. - */ - static boolean isTransparent(int pHints) { - return (pHints & TRANSPARENCY_BITMASK) != 0 || (pHints & TRANSPARENCY_TRANSLUCENT) != 0; - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. If the palette image - * uses an {@code IndexColorModel}, this will be used. Otherwise, generating an - * adaptive palette (8 bit) from the given palette image. - * Dithering, transparency and color selection is controlled with the - * {@code pHints}parameter. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index - * @param pPalette the Image to read color information from - * @param pMatte the background color, used where the original image was - * transparent - * @param pHints hints that control output quality and speed. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED} or - * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an - * {@code IndexColorModel}. - * @throws ImageConversionException if an exception occurred during color - * model extraction. - * @see #DITHER_DIFFUSION - * @see #DITHER_NONE - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see BufferedImage#TYPE_BYTE_BINARY - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage, Image pPalette, Color pMatte, int pHints) - throws ImageConversionException { - return getIndexedImage(pImage, getIndexColorModel(pPalette, 256, pHints), pMatte, pHints); - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive - * palette with the given number of colors. - * Dithering, transparency and color selection is controlled with the - * {@code pHints}parameter. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index - * @param pNumberOfColors the number of colors for the image - * @param pMatte the background color, used where the original image was - * transparent - * @param pHints hints that control output quality and speed. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED} or - * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an - * {@code IndexColorModel}. - * @see #DITHER_DIFFUSION - * @see #DITHER_NONE - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see BufferedImage#TYPE_BYTE_BINARY - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) { - // NOTE: We need to apply matte before creating color model, otherwise we - // won't have colors for potential faded transitions - IndexColorModel icm; - - if (pMatte != null) { - icm = getIndexColorModel(createSolid(pImage, pMatte), pNumberOfColors, pHints); - } - else { - icm = getIndexColorModel(pImage, pNumberOfColors, pHints); - } - - // If we found less colors, then no need to dither - if ((pHints & DITHER_MASK) != DITHER_NONE && (icm.getMapSize() < pNumberOfColors)) { - pHints = (pHints & ~DITHER_MASK) | DITHER_NONE; - } - return getIndexedImage(pImage, icm, pMatte, pHints); - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied - * {@code IndexColorModel}'s palette. - * Dithering, transparency and color selection is controlled with the - * {@code pHints} parameter. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index - * @param pColors an {@code IndexColorModel} containing the color information - * @param pMatte the background color, used where the original image was - * transparent. Also note that any transparent antialias will be - * rendered against this color. - * @param pHints RenderingHints that control output quality and speed. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED} or - * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an - * {@code IndexColorModel}. - * @see #DITHER_DIFFUSION - * @see #DITHER_NONE - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see BufferedImage#TYPE_BYTE_BINARY - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage, IndexColorModel pColors, Color pMatte, int pHints) { - // TODO: Consider: - /* - if (pImage.getType() == BufferedImage.TYPE_BYTE_INDEXED - || pImage.getType() == BufferedImage.TYPE_BYTE_BINARY) { - pImage = ImageUtil.toBufferedImage(pImage, BufferedImage.TYPE_INT_ARGB); - } - */ - - // Get dimensions - final int width = pImage.getWidth(); - final int height = pImage.getHeight(); - - // Support transparency? - boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE); - - // Create image with solid background - BufferedImage solid = pImage; - - if (pMatte != null) { // transparency doesn't really matter - solid = createSolid(pImage, pMatte); - } - - BufferedImage indexed; - - // Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default - // dither does not work with TYPE_BYTE_BINARY it seems... - if (pColors.getMapSize() > 2) { - indexed = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, pColors); - } - else { - indexed = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY, pColors); - } - - // Apply dither if requested - switch (pHints & DITHER_MASK) { - case DITHER_DIFFUSION: - case DITHER_DIFFUSION_ALTSCANS: - // Create a DiffusionDither to apply dither to indexed - DiffusionDither dither = new DiffusionDither(pColors); - - if ((pHints & DITHER_MASK) == DITHER_DIFFUSION_ALTSCANS) { - dither.setAlternateScans(true); - } - - dither.filter(solid, indexed); - - break; - case DITHER_NONE: - // Just copy pixels, without dither - // NOTE: This seems to be slower than the method below, using - // Graphics2D.drawImage, and VALUE_DITHER_DISABLE, - // however you possibly end up getting a dithered image anyway, - // therefore, do it slower and produce correct result. :-) - CopyDither copy = new CopyDither(pColors); - copy.filter(solid, indexed); - - break; - case DITHER_DEFAULT: - // This is the default - default: - // Render image data onto indexed image, using default - // (probably we get dither, but it depends on the GFX engine). - Graphics2D g2d = indexed.createGraphics(); - try { - RenderingHints hints = new RenderingHints(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); - - g2d.setRenderingHints(hints); - g2d.drawImage(solid, 0, 0, null); - } - finally { - g2d.dispose(); - } - - break; - } - - // Transparency support, this approach seems lame, but it's the only - // solution I've found until now (that actually works). - if (transparency) { - // Re-apply the alpha-channel of the original image - applyAlpha(indexed, pImage); - } - - // Return the indexed BufferedImage - return indexed; - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive - * palette with the given number of colors. - * Dithering, transparency and color selection is controlled with the - * {@code pHints}parameter. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index - * @param pNumberOfColors the number of colors for the image - * @param pHints hints that control output quality and speed. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED} or - * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an - * {@code IndexColorModel}. - * @see #DITHER_DIFFUSION - * @see #DITHER_NONE - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see BufferedImage#TYPE_BYTE_BINARY - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, int pHints) { - return getIndexedImage(pImage, pNumberOfColors, null, pHints); - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied - * {@code IndexColorModel}'s palette. - * Dithering, transparency and color selection is controlled with the - * {@code pHints}parameter. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index - * @param pColors an {@code IndexColorModel} containing the color information - * @param pHints RenderingHints that control output quality and speed. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED} or - * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an - * {@code IndexColorModel}. - * @see #DITHER_DIFFUSION - * @see #DITHER_NONE - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see BufferedImage#TYPE_BYTE_BINARY - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage, IndexColorModel pColors, int pHints) { - return getIndexedImage(pImage, pColors, null, pHints); - } - - /** - * Converts the input image (must be {@code TYPE_INT_RGB} or - * {@code TYPE_INT_ARGB}) to an indexed image. If the palette image - * uses an {@code IndexColorModel}, this will be used. Otherwise, generating an - * adaptive palette (8 bit) from the given palette image. - * Dithering, transparency and color selection is controlled with the - * {@code pHints}parameter. - *

- * The image returned is a new image, the input image is not modified. - * - * @param pImage the BufferedImage to index - * @param pPalette the Image to read color information from - * @param pHints hints that control output quality and speed. - * @return the indexed BufferedImage. The image will be of type - * {@code BufferedImage.TYPE_BYTE_INDEXED} or - * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an - * {@code IndexColorModel}. - * @see #DITHER_DIFFUSION - * @see #DITHER_NONE - * @see #COLOR_SELECTION_FAST - * @see #COLOR_SELECTION_QUALITY - * @see #TRANSPARENCY_OPAQUE - * @see #TRANSPARENCY_BITMASK - * @see BufferedImage#TYPE_BYTE_INDEXED - * @see BufferedImage#TYPE_BYTE_BINARY - * @see IndexColorModel - */ - public static BufferedImage getIndexedImage(BufferedImage pImage, Image pPalette, int pHints) { - return getIndexedImage(pImage, pPalette, null, pHints); - } - - /** - * Creates a copy of the given image, with a solid background - * - * @param pOriginal the original image - * @param pBackground the background color - * @return a new {@code BufferedImage} - */ - private static BufferedImage createSolid(BufferedImage pOriginal, Color pBackground) { - // Create a temporary image of same dimension and type - BufferedImage solid = new BufferedImage(pOriginal.getColorModel(), pOriginal.copyData(null), pOriginal.isAlphaPremultiplied(), null); - Graphics2D g = solid.createGraphics(); - - try { - // Clear in background color - g.setColor(pBackground); - g.setComposite(AlphaComposite.DstOver);// Paint "underneath" - g.fillRect(0, 0, pOriginal.getWidth(), pOriginal.getHeight()); - } - finally { - g.dispose(); - } - - return solid; - } - - /** - * Applies the alpha-component of the alpha image to the given image. - * The given image is modified in place. - * - * @param pImage the image to apply alpha to - * @param pAlpha the image containing the alpha - */ - private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) { - // Apply alpha as transparency, using threshold of 25% - for (int y = 0; y < pAlpha.getHeight(); y++) { - for (int x = 0; x < pAlpha.getWidth(); x++) { - - // Get alpha component of pixel, if less than 25% opaque - // (0x40 = 64 => 25% of 256), the pixel will be transparent - if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) { - pImage.setRGB(x, y, 0x00FFFFFF); // 100% transparent - } - } - } - } - - /* - * This class is also a command-line utility. - */ - public static void main(String pArgs[]) { - // Defaults - int argIdx = 0; - int speedTest = -1; - boolean overWrite = false; - boolean monochrome = false; - boolean gray = false; - int numColors = 256; - String dither = null; - String quality = null; - String format = null; - Color background = null; - boolean transparency = false; - String paletteFileName = null; - boolean errArgs = false; - - // Parse args - while ((argIdx < pArgs.length) && (pArgs[argIdx].charAt(0) == '-') && (pArgs[argIdx].length() >= 2)) { - if ((pArgs[argIdx].charAt(1) == 's') || pArgs[argIdx].equals("--speedtest")) { - argIdx++; - - // Get number of iterations - if ((pArgs.length > argIdx) && (pArgs[argIdx].charAt(0) != '-')) { - try { - speedTest = Integer.parseInt(pArgs[argIdx++]); - } - catch (NumberFormatException nfe) { - errArgs = true; - break; - } - } - else { - - // Default to 10 iterations - speedTest = 10; - } - } - else if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) { - overWrite = true; - argIdx++; - } - else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) { - argIdx++; - - try { - numColors = Integer.parseInt(pArgs[argIdx++]); - } - catch (NumberFormatException nfe) { - errArgs = true; - break; - } - } - else if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) { - argIdx++; - gray = true; - } - else if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) { - argIdx++; - numColors = 2; - monochrome = true; - } - else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) { - argIdx++; - dither = pArgs[argIdx++]; - } - else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) { - argIdx++; - paletteFileName = pArgs[argIdx++]; - } - else if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) { - argIdx++; - quality = pArgs[argIdx++]; - } - else if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) { - argIdx++; - try { - background = StringUtil.toColor(pArgs[argIdx++]); - } - catch (Exception e) { - errArgs = true; - break; - } - } - else if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) { - argIdx++; - transparency = true; - } - else if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) { - argIdx++; - format = StringUtil.toLowerCase(pArgs[argIdx++]); - } - else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) { - argIdx++; - - // Setting errArgs to true, to print usage - errArgs = true; - } - else { - System.err.println("Unknown option \"" + pArgs[argIdx++] + "\""); - } - } - if (errArgs || (pArgs.length < (argIdx + 1))) { - System.err.println("Usage: IndexImage [--help|-h] [--speedtest|-s ] [--bgcolor|-b ] [--colors|-c | --grayscale|g | --monochrome|-m | --palette|-p ] [--dither|-d (default|diffusion|none)] [--quality|-q (default|high|low)] [--transparency|-t] [--outputformat|-f (gif|jpeg|png|wbmp|...)] [--overwrite|-w] []"); - System.err.print("Input format names: "); - String[] readers = ImageIO.getReaderFormatNames(); - - for (int i = 0; i < readers.length; i++) { - System.err.print(readers[i] + ((i + 1 < readers.length) - ? ", " - : "\n")); - } - - System.err.print("Output format names: "); - String[] writers = ImageIO.getWriterFormatNames(); - - for (int i = 0; i < writers.length; i++) { - System.err.print(writers[i] + ((i + 1 < writers.length) - ? ", " - : "\n")); - } - System.exit(5); - } - - // Read in image - File in = new File(pArgs[argIdx++]); - - if (!in.exists()) { - System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!"); - System.exit(5); - } - - // Read palette if needed - File paletteFile = null; - - if (paletteFileName != null) { - paletteFile = new File(paletteFileName); - if (!paletteFile.exists()) { - System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!"); - System.exit(5); - } - } - - // Make sure we can write - File out; - - if (argIdx < pArgs.length) { - out = new File(pArgs[argIdx/*++*/]); - - // Get format from file extension - if (format == null) { - format = FileUtil.getExtension(out); - } - } - else { - // Create new file in current dir, same name + format extension - String baseName = FileUtil.getBasename(in); - - // Use png as default format - if (format == null) { - format = "png"; - } - out = new File(baseName + '.' + format); - } - - if (!overWrite && out.exists()) { - System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!"); - System.exit(5); - } - - // Do the image processing - BufferedImage image = null; - BufferedImage paletteImg = null; - - try { - image = ImageIO.read(in); - if (image == null) { - System.err.println("No reader for image: \"" + in.getAbsolutePath() + "\"!"); - System.exit(5); - } - if (paletteFile != null) { - paletteImg = ImageIO.read(paletteFile); - if (paletteImg == null) { - System.err.println("No reader for image: \"" + paletteFile.getAbsolutePath() + "\"!"); - System.exit(5); - } - } - } - catch (IOException ioe) { - ioe.printStackTrace(System.err); - System.exit(5); - } - - // Create hints - int hints = DITHER_DEFAULT; - - if ("DIFFUSION".equalsIgnoreCase(dither)) { - hints |= DITHER_DIFFUSION; - } - else if ("DIFFUSION_ALTSCANS".equalsIgnoreCase(dither)) { - hints |= DITHER_DIFFUSION_ALTSCANS; - } - else if ("NONE".equalsIgnoreCase(dither)) { - hints |= DITHER_NONE; - } - else { - - // Don't care, use default - } - if ("HIGH".equalsIgnoreCase(quality)) { - hints |= COLOR_SELECTION_QUALITY; - } - else if ("LOW".equalsIgnoreCase(quality)) { - hints |= COLOR_SELECTION_FAST; - } - else { - - // Don't care, use default - } - if (transparency) { - hints |= TRANSPARENCY_BITMASK; - } - - ////////////////////////////// - // Apply bg-color WORKAROUND! - // This needs to be done BEFORE palette creation to have desired effect.. - if ((background != null) && (paletteImg == null)) { - paletteImg = createSolid(image, background); - } - - /////////////////////////////// - // Index - long start = 0; - - if (speedTest > 0) { - // SPEED TESTING - System.out.println("Measuring speed!"); - start = System.currentTimeMillis(); - // END SPEED TESTING - } - - BufferedImage indexed; - IndexColorModel colors; - - if (monochrome) { - indexed = getIndexedImage(image, MonochromeColorModel.getInstance(), background, hints); - colors = MonochromeColorModel.getInstance(); - } - else if (gray) { - //indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY); - image = ImageUtil.toBuffered(ImageUtil.grayscale(image)); - indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints); - - // In casse of speedtest, this makes sense... - if (speedTest > 0) { - colors = getIndexColorModel(indexed, numColors, hints); - } - } - else if (paletteImg != null) { - // Get palette from image - indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB), - colors = getIndexColorModel(paletteImg, numColors, hints), background, hints); - } - else { - image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB); - indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints); - } - - if (speedTest > 0) { - // SPEED TESTING - System.out.println("Color selection + dither: " + (System.currentTimeMillis() - start) + " ms"); - // END SPEED TESTING - } - - // Write output (in given format) - try { - if (!ImageIO.write(indexed, format, out)) { - System.err.println("No writer for format: \"" + format + "\"!"); - } - } - catch (IOException ioe) { - ioe.printStackTrace(System.err); - } - - if (speedTest > 0) { - // SPEED TESTING - System.out.println("Measuring speed!"); - - // Warmup! - for (int i = 0; i < 10; i++) { - getIndexedImage(image, colors, background, hints); - } - - // Measure - long time = 0; - - for (int i = 0; i < speedTest; i++) { - start = System.currentTimeMillis(); - getIndexedImage(image, colors, background, hints); - time += (System.currentTimeMillis() - start); - System.out.print('.'); - if ((i + 1) % 10 == 0) { - System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms"); - } - } - - System.out.println("\nDither only:"); - System.out.println("Total time (" + speedTest + " invocations): " + time + "ms"); - System.out.println("Average: " + time / speedTest + "ms"); - // END SPEED TESTING - } - } +/* + * 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. + */ +/* +****************************************************************************** +* +* ============================================================================ +* The Apache Software License, Version 1.1 +* ============================================================================ +* +* Copyright (C) 2000 The Apache Software Foundation. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modifica- +* tion, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. 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. +* +* 3. The end-user documentation included with the redistribution, if any, must +* include the following acknowledgment: "This product includes software +* developed by the Apache Software Foundation (http://www.apache.org/)." +* Alternately, this acknowledgment may appear in the software itself, if +* and wherever such third-party acknowledgments normally appear. +* +* 4. The names "Batik" and "Apache Software Foundation" must not be used to +* endorse or promote products derived from this software without prior +* written permission. For written permission, please contact +* apache@apache.org. +* +* 5. Products derived from this software may not be called "Apache", nor may +* "Apache" appear in their name, without prior written permission of the +* Apache Software Foundation. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 +* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- +* DING, 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. +* +* This software consists of voluntary contributions made by many individuals +* on behalf of the Apache Software Foundation. For more information on the +* Apache Software Foundation, please see . +* +****************************************************************************** +* +*/ + +package com.twelvemonkeys.image; + +import com.twelvemonkeys.io.FileUtil; +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * This class implements an adaptive palette generator to reduce images + * to a variable number of colors. + * It can also render images into fixed color pallettes. + *

+ * Support for the default JVM (ordered/pattern) dither, Floyd-Steinberg like + * error-diffusion and no dither, controlled by the hints + * {@link #DITHER_DIFFUSION}, + * {@link #DITHER_NONE} and + * {@link #DITHER_DEFAULT}. + *

+ *

+ * Color selection speed/accuracy can be controlled using the hints + * {@link #COLOR_SELECTION_FAST}, + * {@link #COLOR_SELECTION_QUALITY} and + * {@link #COLOR_SELECTION_DEFAULT}. + *

+ *

+ * Transparency support can be controlled using the hints + * {@link #TRANSPARENCY_OPAQUE}, + * {@link #TRANSPARENCY_BITMASK} and + * {@link #TRANSPARENCY_TRANSLUCENT}. + *

+ *
+ *

+ *

+ * This product includes software developed by the Apache Software Foundation.
+ *
+ * This software  consists of voluntary contributions made  by many individuals
+ * on  behalf  of the Apache Software  Foundation. For more  information on the
+ * Apache Software Foundation, please see http://www.apache.org/
+ * 
+ *

+ * + * @author Thomas DeWeese + * @author Jun Inamori + * @author Harald Kuhr + * @version $Id: IndexImage.java#1 $ + * @see DiffusionDither + */ +class IndexImage { + + /** + * Dither mask + */ + protected final static int DITHER_MASK = 0xFF; + + /** + * Java default dither + */ + public final static int DITHER_DEFAULT = 0x00; + + /** + * No dither + */ + public final static int DITHER_NONE = 0x01; + + /** + * Error diffusion dither + */ + public final static int DITHER_DIFFUSION = 0x02; + + /** + * Error diffusion dither with alternating scans + */ + public final static int DITHER_DIFFUSION_ALTSCANS = 0x03; + + /** + * Color Selection mask + */ + protected final static int COLOR_SELECTION_MASK = 0xFF00; + + /** + * Default color selection + */ + public final static int COLOR_SELECTION_DEFAULT = 0x0000; + + /** + * Prioritize speed + */ + public final static int COLOR_SELECTION_FAST = 0x0100; + + /** + * Prioritize quality + */ + public final static int COLOR_SELECTION_QUALITY = 0x0200; + + /** + * Transparency mask + */ + protected final static int TRANSPARENCY_MASK = 0xFF0000; + + /** + * Default transparency (none) + */ + public final static int TRANSPARENCY_DEFAULT = 0x000000; + + /** + * Discard any alpha information + */ + public final static int TRANSPARENCY_OPAQUE = 0x010000; + + /** + * Convert alpha to bitmask + */ + public final static int TRANSPARENCY_BITMASK = 0x020000; + + /** + * Keep original alpha (not supported yet) + */ + protected final static int TRANSPARENCY_TRANSLUCENT = 0x030000; + + /** + * Used to track a color and the number of pixels of that colors + */ + private static class Counter { + + /** + * Field val + */ + public int val; + + /** + * Field count + */ + public int count = 1; + + /** + * Constructor Counter + * + * @param val the initial value + */ + public Counter(int val) { + this.val = val; + } + + /** + * Method add + * + * @param val the new value + * @return {@code true} if the value was added, otherwise {@code false} + */ + public boolean add(int val) { + // See if the value matches us... + if (this.val != val) { + return false; + } + + count++; + + return true; + } + } + + /** + * Used to define a cube of the color space. The cube can be split + * approximately in half to generate two cubes. + */ + private static class Cube { + int[] min = {0, 0, 0}; + int[] max = {255, 255, 255}; + boolean done = false; + List[] colors = null; + int count = 0; + static final int RED = 0; + static final int GRN = 1; + static final int BLU = 2; + + /** + * Define a new cube. + * + * @param colors contains the 3D color histogram to be subdivided + * @param count the total number of pixels in the 3D histogram. + */ + public Cube(List[] colors, int count) { + this.colors = colors; + this.count = count; + } + + /** + * If this returns true then the cube can not be subdivided any + * further + * + * @return true if cube can not be subdivided any further + */ + public boolean isDone() { + return done; + } + + /** + * Splits the cube into two parts. This cube is + * changed to be one half and the returned cube is the other half. + * This tries to pick the right channel to split on. + * + * @return the {@code Cube} containing the other half + */ + public Cube split() { + int dr = max[0] - min[0] + 1; + int dg = max[1] - min[1] + 1; + int db = max[2] - min[2] + 1; + int c0, c1, splitChannel; + + // Figure out which axis is the longest and split along + // that axis (this tries to keep cubes square-ish). + if (dr >= dg) { + c0 = GRN; + if (dr >= db) { + splitChannel = RED; + c1 = BLU; + } + else { + splitChannel = BLU; + c1 = RED; + } + } + else if (dg >= db) { + splitChannel = GRN; + c0 = RED; + c1 = BLU; + } + else { + splitChannel = BLU; + c0 = RED; + c1 = GRN; + } + + Cube ret; + + ret = splitChannel(splitChannel, c0, c1); + + if (ret != null) { + return ret; + } + + ret = splitChannel(c0, splitChannel, c1); + + if (ret != null) { + return ret; + } + + ret = splitChannel(c1, splitChannel, c0); + + if (ret != null) { + return ret; + } + + done = true; + + return null; + } + + /** + * Splits the image according to the parameters. It tries + * to find a location where half the pixels are on one side + * and half the pixels are on the other. + * + * @param splitChannel split channel + * @param c0 channel 0 + * @param c1 channel 1 + * @return the {@code Cube} containing the other half + */ + public Cube splitChannel(int splitChannel, int c0, int c1) { + if (min[splitChannel] == max[splitChannel]) { + return null; + } + int splitSh4 = (2 - splitChannel) * 4; + int c0Sh4 = (2 - c0) * 4; + int c1Sh4 = (2 - c1) * 4; + + // int splitSh8 = (2-splitChannel)*8; + // int c0Sh8 = (2-c0)*8; + // int c1Sh8 = (2-c1)*8; + // + int half = count / 2; + + // Each entry is the number of pixels that have that value + // in the split channel within the cube (so pixels + // that have that value in the split channel aren't counted + // if they are outside the cube in the other color channels. + int counts[] = new int[256]; + int tcount = 0; + + // System.out.println("Cube: [" + + // min[0] + "-" + max[0] + "] [" + + // min[1] + "-" + max[1] + "] [" + + // min[2] + "-" + max[2] + "]"); + int[] minIdx = {min[0] >> 4, min[1] >> 4, min[2] >> 4}; + int[] maxIdx = {max[0] >> 4, max[1] >> 4, max[2] >> 4}; + int minR = min[0], minG = min[1], minB = min[2]; + int maxR = max[0], maxG = max[1], maxB = max[2]; + int val; + int[] vals = {0, 0, 0}; + + for (int i = minIdx[splitChannel]; i <= maxIdx[splitChannel]; i++) { + int idx1 = i << splitSh4; + + for (int j = minIdx[c0]; j <= maxIdx[c0]; j++) { + int idx2 = idx1 | (j << c0Sh4); + + for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) { + int idx = idx2 | (k << c1Sh4); + List v = colors[idx]; + + if (v == null) { + continue; + } + + for (Counter c : v) { + val = c.val; + vals[0] = (val & 0xFF0000) >> 16; + vals[1] = (val & 0xFF00) >> 8; + vals[2] = (val & 0xFF); + if (((vals[0] >= minR) && (vals[0] <= maxR)) && ((vals[1] >= minG) && (vals[1] <= maxG)) + && ((vals[2] >= minB) && (vals[2] <= maxB))) { + + // The val lies within this cube so count it. + counts[vals[splitChannel]] += c.count; + tcount += c.count; + } + } + } + } + + // We've found the half way point. Note that the + // rest of counts is not filled out. + if (tcount >= half) { + break; + } + } + tcount = 0; + int lastAdd = -1; + + // These indicate what the top value for the low cube and + // the low value of the high cube should be in the split channel + // (they may not be one off if there are 'dead' spots in the + // counts array.) + int splitLo = min[splitChannel], splitHi = max[splitChannel]; + + for (int i = min[splitChannel]; i <= max[splitChannel]; i++) { + int c = counts[i]; + + if (c == 0) { + // No counts below this so move up bottom of cube. + if ((tcount == 0) && (i < max[splitChannel])) { + this.min[splitChannel] = i + 1; + } + continue; + } + if (tcount + c < half) { + lastAdd = i; + tcount += c; + continue; + } + if ((half - tcount) <= ((tcount + c) - half)) { + // Then lastAdd is a better top idx for this then i. + if (lastAdd == -1) { + // No lower place to break. + if (c == this.count) { + + // All pixels are at this value so make min/max + // reflect that. + this.max[splitChannel] = i; + return null;// no split to make. + } + else { + + // There are values about this one so + // split above. + splitLo = i; + splitHi = i + 1; + break; + } + } + splitLo = lastAdd; + splitHi = i; + } + else { + if (i == this.max[splitChannel]) { + if (c == this.count) { + // would move min up but that should + // have happened already. + return null;// no split to make. + } + else { + // Would like to break between i and i+1 + // but no i+1 so use lastAdd and i; + splitLo = lastAdd; + splitHi = i; + break; + } + } + + // Include c in counts + tcount += c; + splitLo = i; + splitHi = i + 1; + } + break; + } + + // System.out.println("Split: " + splitChannel + "@" + // + splitLo + "-"+splitHi + + // " Count: " + tcount + " of " + count + + // " LA: " + lastAdd); + // Create the new cube and update everyone's bounds & counts. + Cube ret = new Cube(colors, tcount); + + this.count = this.count - tcount; + ret.min[splitChannel] = this.min[splitChannel]; + ret.max[splitChannel] = splitLo; + this.min[splitChannel] = splitHi; + ret.min[c0] = this.min[c0]; + ret.max[c0] = this.max[c0]; + ret.min[c1] = this.min[c1]; + ret.max[c1] = this.max[c1]; + + return ret; + } + + /** + * Returns the average color for this cube + * + * @return the average + */ + public int averageColor() { + if (this.count == 0) { + return 0; + } + + float red = 0, grn = 0, blu = 0; + int minR = min[0], minG = min[1], minB = min[2]; + int maxR = max[0], maxG = max[1], maxB = max[2]; + int[] minIdx = {minR >> 4, minG >> 4, minB >> 4}; + int[] maxIdx = {maxR >> 4, maxG >> 4, maxB >> 4}; + int val, ired, igrn, iblu; + float weight; + + for (int i = minIdx[0]; i <= maxIdx[0]; i++) { + int idx1 = i << 8; + + for (int j = minIdx[1]; j <= maxIdx[1]; j++) { + int idx2 = idx1 | (j << 4); + + for (int k = minIdx[2]; k <= maxIdx[2]; k++) { + int idx = idx2 | k; + List v = colors[idx]; + + if (v == null) { + continue; + } + + for (Counter c : v) { + val = c.val; + ired = (val & 0xFF0000) >> 16; + igrn = (val & 0x00FF00) >> 8; + iblu = (val & 0x0000FF); + + if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) { + weight = (c.count / (float) this.count); + red += ((float) ired) * weight; + grn += ((float) igrn) * weight; + blu += ((float) iblu) * weight; + } + } + } + } + } + + // System.out.println("RGB: [" + red + ", " + + // grn + ", " + blu + "]"); + return (((int) (red + 0.5f)) << 16 | ((int) (grn + 0.5f)) << 8 | ((int) (blu + 0.5f))); + } + }// end Cube + + /** + * You cannot create this + */ + private IndexImage() { + } + + /** + * @param pImage the image to get {@code IndexColorModel} from + * @param pNumberOfColors the number of colors for the {@code IndexColorModel} + * @param pFast {@code true} if fast + * @return an {@code IndexColorModel} + * @see #getIndexColorModel(Image,int,int) + * + * @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead! + * This version will be removed in a later version of the API. + */ + public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) { + return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY); + } + + /** + * Gets an {@code IndexColorModel} from the given image. If the image has an + * {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel} + * is created, using an adaptive palette. + * + * @param pImage the image to get {@code IndexColorModel} from + * @param pNumberOfColors the number of colors for the {@code IndexColorModel} + * @param pHints one of {@link #COLOR_SELECTION_FAST}, + * {@link #COLOR_SELECTION_QUALITY} or + * {@link #COLOR_SELECTION_DEFAULT}. + * @return The {@code IndexColorModel} from the given image, or a newly created + * {@code IndexColorModel} using an adaptive palette. + * @throws ImageConversionException if an exception occurred during color + * model extraction. + */ + public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, int pHints) throws ImageConversionException { + IndexColorModel icm = null; + RenderedImage image = null; + + if (pImage instanceof RenderedImage) { + image = (RenderedImage) pImage; + ColorModel cm = image.getColorModel(); + + if (cm instanceof IndexColorModel) { + // Test if we have right number of colors + if (((IndexColorModel) cm).getMapSize() <= pNumberOfColors) { + //System.out.println("IndexColorModel from BufferedImage"); + icm = (IndexColorModel) cm;// Done + } + } + + // Else create from buffered image, hard way, see below + } + else { + // Create from image using BufferedImageFactory + BufferedImageFactory factory = new BufferedImageFactory(pImage); + ColorModel cm = factory.getColorModel(); + + if ((cm instanceof IndexColorModel) && ((IndexColorModel) cm).getMapSize() <= pNumberOfColors) { + //System.out.println("IndexColorModel from Image"); + icm = (IndexColorModel) cm;// Done + } + else { + // Else create from (buffered) image, hard way + image = factory.getBufferedImage(); + } + } + + // We now have at least a buffered image, create model from it + if (icm == null) { + icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints); + } + else if (!(icm instanceof InverseColorMapIndexColorModel)) { + // If possible, use faster code + icm = new InverseColorMapIndexColorModel(icm); + } + + return icm; + } + + /** + * Creates an {@code IndexColorModel} from the given image, using an adaptive + * palette. + * + * @param pImage the image to get {@code IndexColorModel} from + * @param pNumberOfColors the number of colors for the {@code IndexColorModel} + * @param pHints use fast mode if possible (might give slightly lower + * quality) + * @return a new {@code IndexColorModel} created from the given image + */ + private static IndexColorModel createIndexColorModel(BufferedImage pImage, int pNumberOfColors, int pHints) { + // TODO: Use ImageUtil.hasTransparentPixels(pImage, true) || + // -- haraldK, 20021024, experimental, try to use one transparent pixel + boolean useTransparency = isTransparent(pHints); + + if (useTransparency) { + pNumberOfColors--; + } + + //System.out.println("Transp: " + useTransparency + " colors: " + pNumberOfColors); + int width = pImage.getWidth(); + int height = pImage.getHeight(); + + // Using 4 bits from R, G & B. + @SuppressWarnings("unchecked") + List[] colors = new List[1 << 12];// [4096] + + // Speedup, doesn't decrease image quality much + int step = 1; + + if (isFast(pHints)) { + step += (width * height / 16384);// 128x128px + } + int sampleCount = 0; + int rgb; + + //for (int x = 0; x < width; x++) { + //for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + for (int y = x % step; y < height; y += step) { + // Count the number of color samples + sampleCount++; + + // Get ARGB pixel from image + rgb = (pImage.getRGB(x, y) & 0xFFFFFF); + + // Get index from high four bits of each component. + int index = (((rgb & 0xF00000) >>> 12) | ((rgb & 0x00F000) >>> 8) | ((rgb & 0x0000F0) >>> 4)); + + // Get the 'hash vector' for that key. + List v = colors[index]; + + if (v == null) { + // No colors in this bin yet so create vector and + // add color. + v = new ArrayList(); + v.add(new Counter(rgb)); + colors[index] = v; + } + else { + // Find our color in the bin or create a counter for it. + Iterator i = v.iterator(); + + while (true) { + if (i.hasNext()) { + // try adding our color to each counter... + if (((Counter) i.next()).add(rgb)) { + break; + } + } + else { + v.add(new Counter(rgb)); + break; + } + } + } + } + } + + // All colours found, reduce to pNumberOfColors + int numberOfCubes = 1; + int fCube = 0; + Cube[] cubes = new Cube[pNumberOfColors]; + + cubes[0] = new Cube(colors, sampleCount); + + //cubes[0] = new Cube(colors, width * height); + while (numberOfCubes < pNumberOfColors) { + while (cubes[fCube].isDone()) { + fCube++; + + if (fCube == numberOfCubes) { + break; + } + } + + if (fCube == numberOfCubes) { + break; + } + + Cube cube = cubes[fCube]; + Cube newCube = cube.split(); + + if (newCube != null) { + if (newCube.count > cube.count) { + Cube tmp = cube; + + cube = newCube; + newCube = tmp; + } + + int j = fCube; + int count = cube.count; + + for (int i = fCube + 1; i < numberOfCubes; i++) { + if (cubes[i].count < count) { + break; + } + cubes[j++] = cubes[i]; + } + + cubes[j++] = cube; + count = newCube.count; + + while (j < numberOfCubes) { + if (cubes[j].count < count) { + break; + } + j++; + } + + System.arraycopy(cubes, j, cubes, j + 1, numberOfCubes - j); + + cubes[j/*++*/] = newCube; + numberOfCubes++; + } + } + + // Create RGB arrays with correct number of colors + // If we have transparency, the last color will be the transparent one + byte[] r = new byte[useTransparency ? numberOfCubes + 1 : numberOfCubes]; + byte[] g = new byte[useTransparency ? numberOfCubes + 1 : numberOfCubes]; + byte[] b = new byte[useTransparency ? numberOfCubes + 1 : numberOfCubes]; + + for (int i = 0; i < numberOfCubes; i++) { + int val = cubes[i].averageColor(); + + r[i] = (byte) ((val >> 16) & 0xFF); + g[i] = (byte) ((val >> 8) & 0xFF); + b[i] = (byte) ((val) & 0xFF); + + //System.out.println("Color [" + i + "]: #" + + // (((val>>16)<16)?"0":"") + + // Integer.toHexString(val)); + } + + // For some reason using less than 8 bits causes a bug in the dither + // - transparency added to all totally black colors? + int numOfBits = 8; + + // -- haraldK, 20021024, as suggested by Thomas E. Deweese + // plus adding a transparent pixel + IndexColorModel icm; + if (useTransparency) { + icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1); + } + else { + icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b); + } + return icm; + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive + * palette (8 bit) from the color data in the image, and uses default + * dither. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index and get color information from. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED}, and use an + * {@code IndexColorModel}. + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage) { + return getIndexedImage(pImage, 256, DITHER_DEFAULT); + } + + /** + * Tests if the hint {@code COLOR_SELECTION_QUALITY} is not + * set. + * + * @param pHints hints + * @return true if the hint {@code COLOR_SELECTION_QUALITY} + * is not set. + */ + private static boolean isFast(int pHints) { + return (pHints & COLOR_SELECTION_MASK) != COLOR_SELECTION_QUALITY; + } + + /** + * Tests if the hint {@code TRANSPARENCY_BITMASK} or + * {@code TRANSPARENCY_TRANSLUCENT} is set. + * + * @param pHints hints + * @return true if the hint {@code TRANSPARENCY_BITMASK} or + * {@code TRANSPARENCY_TRANSLUCENT} is set. + */ + static boolean isTransparent(int pHints) { + return (pHints & TRANSPARENCY_BITMASK) != 0 || (pHints & TRANSPARENCY_TRANSLUCENT) != 0; + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. If the palette image + * uses an {@code IndexColorModel}, this will be used. Otherwise, generating an + * adaptive palette (8 bit) from the given palette image. + * Dithering, transparency and color selection is controlled with the + * {@code pHints}parameter. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index + * @param pPalette the Image to read color information from + * @param pMatte the background color, used where the original image was + * transparent + * @param pHints hints that control output quality and speed. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED} or + * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an + * {@code IndexColorModel}. + * @throws ImageConversionException if an exception occurred during color + * model extraction. + * @see #DITHER_DIFFUSION + * @see #DITHER_NONE + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see BufferedImage#TYPE_BYTE_BINARY + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage, Image pPalette, Color pMatte, int pHints) + throws ImageConversionException { + return getIndexedImage(pImage, getIndexColorModel(pPalette, 256, pHints), pMatte, pHints); + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive + * palette with the given number of colors. + * Dithering, transparency and color selection is controlled with the + * {@code pHints} parameter. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index + * @param pNumberOfColors the number of colors for the image + * @param pMatte the background color, used where the original image was + * transparent + * @param pHints hints that control output quality and speed. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED} or + * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an + * {@code IndexColorModel}. + * @see #DITHER_DIFFUSION + * @see #DITHER_NONE + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see BufferedImage#TYPE_BYTE_BINARY + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) { + // NOTE: We need to apply matte before creating color model, otherwise we + // won't have colors for potential faded transitions + IndexColorModel icm; + + if (pMatte != null) { + icm = getIndexColorModel(createSolid(pImage, pMatte), pNumberOfColors, pHints); + } + else { + icm = getIndexColorModel(pImage, pNumberOfColors, pHints); + } + + // If we found less colors, then no need to dither + if ((pHints & DITHER_MASK) != DITHER_NONE && (icm.getMapSize() < pNumberOfColors)) { + pHints = (pHints & ~DITHER_MASK) | DITHER_NONE; + } + return getIndexedImage(pImage, icm, pMatte, pHints); + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied + * {@code IndexColorModel}'s palette. + * Dithering, transparency and color selection is controlled with the + * {@code pHints} parameter. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index + * @param pColors an {@code IndexColorModel} containing the color information + * @param pMatte the background color, used where the original image was + * transparent. Also note that any transparent antialias will be + * rendered against this color. + * @param pHints RenderingHints that control output quality and speed. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED} or + * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an + * {@code IndexColorModel}. + * @see #DITHER_DIFFUSION + * @see #DITHER_NONE + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see BufferedImage#TYPE_BYTE_BINARY + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage, IndexColorModel pColors, Color pMatte, int pHints) { + // TODO: Consider: + /* + if (pImage.getType() == BufferedImage.TYPE_BYTE_INDEXED + || pImage.getType() == BufferedImage.TYPE_BYTE_BINARY) { + pImage = ImageUtil.toBufferedImage(pImage, BufferedImage.TYPE_INT_ARGB); + } + */ + + // Get dimensions + final int width = pImage.getWidth(); + final int height = pImage.getHeight(); + + // Support transparency? + boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE); + + // Create image with solid background + BufferedImage solid = pImage; + + if (pMatte != null) { // transparency doesn't really matter + solid = createSolid(pImage, pMatte); + } + + BufferedImage indexed; + + // Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default + // dither does not work with TYPE_BYTE_BINARY it seems... + if (pColors.getMapSize() > 2) { + indexed = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, pColors); + } + else { + indexed = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY, pColors); + } + + // Apply dither if requested + switch (pHints & DITHER_MASK) { + case DITHER_DIFFUSION: + case DITHER_DIFFUSION_ALTSCANS: + // Create a DiffusionDither to apply dither to indexed + DiffusionDither dither = new DiffusionDither(pColors); + + if ((pHints & DITHER_MASK) == DITHER_DIFFUSION_ALTSCANS) { + dither.setAlternateScans(true); + } + + dither.filter(solid, indexed); + + break; + case DITHER_NONE: + // Just copy pixels, without dither + // NOTE: This seems to be slower than the method below, using + // Graphics2D.drawImage, and VALUE_DITHER_DISABLE, + // however you possibly end up getting a dithered image anyway, + // therefore, do it slower and produce correct result. :-) + CopyDither copy = new CopyDither(pColors); + copy.filter(solid, indexed); + + break; + case DITHER_DEFAULT: + // This is the default + default: + // Render image data onto indexed image, using default + // (probably we get dither, but it depends on the GFX engine). + Graphics2D g2d = indexed.createGraphics(); + try { + RenderingHints hints = new RenderingHints(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); + + g2d.setRenderingHints(hints); + g2d.drawImage(solid, 0, 0, null); + } + finally { + g2d.dispose(); + } + + break; + } + + // Transparency support, this approach seems lame, but it's the only + // solution I've found until now (that actually works). + if (transparency) { + // Re-apply the alpha-channel of the original image + applyAlpha(indexed, pImage); + } + + // Return the indexed BufferedImage + return indexed; + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive + * palette with the given number of colors. + * Dithering, transparency and color selection is controlled with the + * {@code pHints}parameter. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index + * @param pNumberOfColors the number of colors for the image + * @param pHints hints that control output quality and speed. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED} or + * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an + * {@code IndexColorModel}. + * @see #DITHER_DIFFUSION + * @see #DITHER_NONE + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see BufferedImage#TYPE_BYTE_BINARY + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, int pHints) { + return getIndexedImage(pImage, pNumberOfColors, null, pHints); + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied + * {@code IndexColorModel}'s palette. + * Dithering, transparency and color selection is controlled with the + * {@code pHints}parameter. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index + * @param pColors an {@code IndexColorModel} containing the color information + * @param pHints RenderingHints that control output quality and speed. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED} or + * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an + * {@code IndexColorModel}. + * @see #DITHER_DIFFUSION + * @see #DITHER_NONE + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see BufferedImage#TYPE_BYTE_BINARY + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage, IndexColorModel pColors, int pHints) { + return getIndexedImage(pImage, pColors, null, pHints); + } + + /** + * Converts the input image (must be {@code TYPE_INT_RGB} or + * {@code TYPE_INT_ARGB}) to an indexed image. If the palette image + * uses an {@code IndexColorModel}, this will be used. Otherwise, generating an + * adaptive palette (8 bit) from the given palette image. + * Dithering, transparency and color selection is controlled with the + * {@code pHints}parameter. + *

+ * The image returned is a new image, the input image is not modified. + *

+ * + * @param pImage the BufferedImage to index + * @param pPalette the Image to read color information from + * @param pHints hints that control output quality and speed. + * @return the indexed BufferedImage. The image will be of type + * {@code BufferedImage.TYPE_BYTE_INDEXED} or + * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an + * {@code IndexColorModel}. + * @see #DITHER_DIFFUSION + * @see #DITHER_NONE + * @see #COLOR_SELECTION_FAST + * @see #COLOR_SELECTION_QUALITY + * @see #TRANSPARENCY_OPAQUE + * @see #TRANSPARENCY_BITMASK + * @see BufferedImage#TYPE_BYTE_INDEXED + * @see BufferedImage#TYPE_BYTE_BINARY + * @see IndexColorModel + */ + public static BufferedImage getIndexedImage(BufferedImage pImage, Image pPalette, int pHints) { + return getIndexedImage(pImage, pPalette, null, pHints); + } + + /** + * Creates a copy of the given image, with a solid background + * + * @param pOriginal the original image + * @param pBackground the background color + * @return a new {@code BufferedImage} + */ + private static BufferedImage createSolid(BufferedImage pOriginal, Color pBackground) { + // Create a temporary image of same dimension and type + BufferedImage solid = new BufferedImage(pOriginal.getColorModel(), pOriginal.copyData(null), pOriginal.isAlphaPremultiplied(), null); + Graphics2D g = solid.createGraphics(); + + try { + // Clear in background color + g.setColor(pBackground); + g.setComposite(AlphaComposite.DstOver);// Paint "underneath" + g.fillRect(0, 0, pOriginal.getWidth(), pOriginal.getHeight()); + } + finally { + g.dispose(); + } + + return solid; + } + + /** + * Applies the alpha-component of the alpha image to the given image. + * The given image is modified in place. + * + * @param pImage the image to apply alpha to + * @param pAlpha the image containing the alpha + */ + private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) { + // Apply alpha as transparency, using threshold of 25% + for (int y = 0; y < pAlpha.getHeight(); y++) { + for (int x = 0; x < pAlpha.getWidth(); x++) { + + // Get alpha component of pixel, if less than 25% opaque + // (0x40 = 64 => 25% of 256), the pixel will be transparent + if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) { + pImage.setRGB(x, y, 0x00FFFFFF); // 100% transparent + } + } + } + } + + /* + * This class is also a command-line utility. + */ + public static void main(String pArgs[]) { + // Defaults + int argIdx = 0; + int speedTest = -1; + boolean overWrite = false; + boolean monochrome = false; + boolean gray = false; + int numColors = 256; + String dither = null; + String quality = null; + String format = null; + Color background = null; + boolean transparency = false; + String paletteFileName = null; + boolean errArgs = false; + + // Parse args + while ((argIdx < pArgs.length) && (pArgs[argIdx].charAt(0) == '-') && (pArgs[argIdx].length() >= 2)) { + if ((pArgs[argIdx].charAt(1) == 's') || pArgs[argIdx].equals("--speedtest")) { + argIdx++; + + // Get number of iterations + if ((pArgs.length > argIdx) && (pArgs[argIdx].charAt(0) != '-')) { + try { + speedTest = Integer.parseInt(pArgs[argIdx++]); + } + catch (NumberFormatException nfe) { + errArgs = true; + break; + } + } + else { + + // Default to 10 iterations + speedTest = 10; + } + } + else if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) { + overWrite = true; + argIdx++; + } + else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) { + argIdx++; + + try { + numColors = Integer.parseInt(pArgs[argIdx++]); + } + catch (NumberFormatException nfe) { + errArgs = true; + break; + } + } + else if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) { + argIdx++; + gray = true; + } + else if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) { + argIdx++; + numColors = 2; + monochrome = true; + } + else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) { + argIdx++; + dither = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) { + argIdx++; + paletteFileName = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) { + argIdx++; + quality = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) { + argIdx++; + try { + background = StringUtil.toColor(pArgs[argIdx++]); + } + catch (Exception e) { + errArgs = true; + break; + } + } + else if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) { + argIdx++; + transparency = true; + } + else if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) { + argIdx++; + format = StringUtil.toLowerCase(pArgs[argIdx++]); + } + else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) { + argIdx++; + + // Setting errArgs to true, to print usage + errArgs = true; + } + else { + System.err.println("Unknown option \"" + pArgs[argIdx++] + "\""); + } + } + if (errArgs || (pArgs.length < (argIdx + 1))) { + System.err.println("Usage: IndexImage [--help|-h] [--speedtest|-s ] [--bgcolor|-b ] [--colors|-c | --grayscale|g | --monochrome|-m | --palette|-p ] [--dither|-d (default|diffusion|none)] [--quality|-q (default|high|low)] [--transparency|-t] [--outputformat|-f (gif|jpeg|png|wbmp|...)] [--overwrite|-w] []"); + System.err.print("Input format names: "); + String[] readers = ImageIO.getReaderFormatNames(); + + for (int i = 0; i < readers.length; i++) { + System.err.print(readers[i] + ((i + 1 < readers.length) + ? ", " + : "\n")); + } + + System.err.print("Output format names: "); + String[] writers = ImageIO.getWriterFormatNames(); + + for (int i = 0; i < writers.length; i++) { + System.err.print(writers[i] + ((i + 1 < writers.length) + ? ", " + : "\n")); + } + System.exit(5); + } + + // Read in image + File in = new File(pArgs[argIdx++]); + + if (!in.exists()) { + System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!"); + System.exit(5); + } + + // Read palette if needed + File paletteFile = null; + + if (paletteFileName != null) { + paletteFile = new File(paletteFileName); + if (!paletteFile.exists()) { + System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!"); + System.exit(5); + } + } + + // Make sure we can write + File out; + + if (argIdx < pArgs.length) { + out = new File(pArgs[argIdx/*++*/]); + + // Get format from file extension + if (format == null) { + format = FileUtil.getExtension(out); + } + } + else { + // Create new file in current dir, same name + format extension + String baseName = FileUtil.getBasename(in); + + // Use png as default format + if (format == null) { + format = "png"; + } + out = new File(baseName + '.' + format); + } + + if (!overWrite && out.exists()) { + System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!"); + System.exit(5); + } + + // Do the image processing + BufferedImage image = null; + BufferedImage paletteImg = null; + + try { + image = ImageIO.read(in); + if (image == null) { + System.err.println("No reader for image: \"" + in.getAbsolutePath() + "\"!"); + System.exit(5); + } + if (paletteFile != null) { + paletteImg = ImageIO.read(paletteFile); + if (paletteImg == null) { + System.err.println("No reader for image: \"" + paletteFile.getAbsolutePath() + "\"!"); + System.exit(5); + } + } + } + catch (IOException ioe) { + ioe.printStackTrace(System.err); + System.exit(5); + } + + // Create hints + int hints = DITHER_DEFAULT; + + if ("DIFFUSION".equalsIgnoreCase(dither)) { + hints |= DITHER_DIFFUSION; + } + else if ("DIFFUSION_ALTSCANS".equalsIgnoreCase(dither)) { + hints |= DITHER_DIFFUSION_ALTSCANS; + } + else if ("NONE".equalsIgnoreCase(dither)) { + hints |= DITHER_NONE; + } + else { + + // Don't care, use default + } + if ("HIGH".equalsIgnoreCase(quality)) { + hints |= COLOR_SELECTION_QUALITY; + } + else if ("LOW".equalsIgnoreCase(quality)) { + hints |= COLOR_SELECTION_FAST; + } + else { + + // Don't care, use default + } + if (transparency) { + hints |= TRANSPARENCY_BITMASK; + } + + ////////////////////////////// + // Apply bg-color WORKAROUND! + // This needs to be done BEFORE palette creation to have desired effect.. + if ((background != null) && (paletteImg == null)) { + paletteImg = createSolid(image, background); + } + + /////////////////////////////// + // Index + long start = 0; + + if (speedTest > 0) { + // SPEED TESTING + System.out.println("Measuring speed!"); + start = System.currentTimeMillis(); + // END SPEED TESTING + } + + BufferedImage indexed; + IndexColorModel colors; + + if (monochrome) { + indexed = getIndexedImage(image, MonochromeColorModel.getInstance(), background, hints); + colors = MonochromeColorModel.getInstance(); + } + else if (gray) { + //indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY); + image = ImageUtil.toBuffered(ImageUtil.grayscale(image)); + indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints); + + // In casse of speedtest, this makes sense... + if (speedTest > 0) { + colors = getIndexColorModel(indexed, numColors, hints); + } + } + else if (paletteImg != null) { + // Get palette from image + indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB), + colors = getIndexColorModel(paletteImg, numColors, hints), background, hints); + } + else { + image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB); + indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints); + } + + if (speedTest > 0) { + // SPEED TESTING + System.out.println("Color selection + dither: " + (System.currentTimeMillis() - start) + " ms"); + // END SPEED TESTING + } + + // Write output (in given format) + try { + if (!ImageIO.write(indexed, format, out)) { + System.err.println("No writer for format: \"" + format + "\"!"); + } + } + catch (IOException ioe) { + ioe.printStackTrace(System.err); + } + + if (speedTest > 0) { + // SPEED TESTING + System.out.println("Measuring speed!"); + + // Warmup! + for (int i = 0; i < 10; i++) { + getIndexedImage(image, colors, background, hints); + } + + // Measure + long time = 0; + + for (int i = 0; i < speedTest; i++) { + start = System.currentTimeMillis(); + getIndexedImage(image, colors, background, hints); + time += (System.currentTimeMillis() - start); + System.out.print('.'); + if ((i + 1) % 10 == 0) { + System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms"); + } + } + + System.out.println("\nDither only:"); + System.out.println("Total time (" + speedTest + " invocations): " + time + "ms"); + System.out.println("Average: " + time / speedTest + "ms"); + // END SPEED TESTING + } + } } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMap.java b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMap.java index 838275b6..ec1fb1be 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMap.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMap.java @@ -1,211 +1,214 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -/** - * Inverse Colormap to provide efficient lookup of any given input color - * to the closest match to the given color map. - *

- * Based on "Efficient Inverse Color Map Computation" by Spencer W. Thomas - * in "Graphics Gems Volume II" - * - * @author Harald Kuhr - * @author Robin Luiten (Java port) - * @author Spencer W. Thomas (original c version). - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/InverseColorMap.java#1 $ - */ -class InverseColorMap { - /** - * Number of high bits of each color channel to use to lookup near match - */ - final static int QUANTBITS = 5; - - /** - * Truncated bits of each color channel - */ - final static int TRUNCBITS = 8 - QUANTBITS; - - /** - * BITMASK representing the bits for blue in the color lookup - */ - final static int QUANTMASK_BLUE = (1 << 5) - 1; - - /** - * BITMASK representing the bits for green in the color lookup - */ - final static int QUANTMASK_GREEN = (QUANTMASK_BLUE << QUANTBITS); - - /** - * BITMASK representing the bits for red in the color lookup - */ - final static int QUANTMASK_RED = (QUANTMASK_GREEN << QUANTBITS); - - /** - * Maximum value a quantised color channel can have - */ - final static int MAXQUANTVAL = 1 << 5; - - byte[] rgbMapByte; - int[] rgbMapInt; - int numColors; - int maxColor; - byte[] inverseRGB; // inverse rgb color map - int transparentIndex = -1; - - /** - * @param pRGBColorMap the rgb color map to create inverse color map for. - */ - InverseColorMap(byte[] pRGBColorMap) { - this(pRGBColorMap, -1); - } - - /** - * @param pRGBColorMap the rgb color map to create inverse color map for. - */ - // HaraldK 20040801: Added support for int[] - InverseColorMap(int[] pRGBColorMap) { - this(pRGBColorMap, -1); - } - - /** - * @param pRGBColorMap the rgb color map to create inverse color map for. - * @param pTransparent the index of the transparent pixel in the map - */ - InverseColorMap(byte[] pRGBColorMap, int pTransparent) { - rgbMapByte = pRGBColorMap; - numColors = rgbMapByte.length / 4; - transparentIndex = pTransparent; - - inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]; - initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]); - } - - /** - * @param pRGBColorMap the rgb color map to create inverse color map for. - * @param pTransparent the index of the transparent pixel in the map - */ - InverseColorMap(int[] pRGBColorMap, int pTransparent) { - rgbMapInt = pRGBColorMap; - numColors = rgbMapInt.length; - transparentIndex = pTransparent; - - inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]; - initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]); - } - - - /** - * Simple inverse color table creation method. - * @param pTemp temp array - */ - void initIRGB(int[] pTemp) { - final int x = (1 << TRUNCBITS); // 8 the size of 1 Dimension of each quantized cell - final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors - final int xsqr2 = xsqr + xsqr; - - for (int i = 0; i < numColors; ++i) { - if (i == transparentIndex) { - // Skip the transparent pixel - continue; - } - - int red, r, rdist, rinc, rxx; - int green, g, gdist, ginc, gxx; - int blue, b, bdist, binc, bxx; - - // HaraldK 20040801: Added support for int[] - if (rgbMapByte != null) { - red = rgbMapByte[i * 4] & 0xFF; - green = rgbMapByte[i * 4 + 1] & 0xFF; - blue = rgbMapByte[i * 4 + 2] & 0xFF; - } - else if (rgbMapInt != null) { - red = (rgbMapInt[i] >> 16) & 0xFF; - green = (rgbMapInt[i] >> 8) & 0xFF; - blue = rgbMapInt[i] & 0xFF; - } - else { - throw new IllegalStateException("colormap == null"); - } - - rdist = red - x / 2; // distance of red to center of current cell - gdist = green - x / 2; // green - bdist = blue - x / 2; // blue - rdist = rdist * rdist + gdist * gdist + bdist * bdist; - - rinc = 2 * (xsqr - (red << TRUNCBITS)); - ginc = 2 * (xsqr - (green << TRUNCBITS)); - binc = 2 * (xsqr - (blue << TRUNCBITS)); - - int rgbI = 0; - for (r = 0, rxx = rinc; r < MAXQUANTVAL; rdist += rxx, ++r, rxx += xsqr2) { - for (g = 0, gdist = rdist, gxx = ginc; g < MAXQUANTVAL; gdist += gxx, ++g, gxx += xsqr2) { - for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) { - if (i == 0 || pTemp[rgbI] > bdist) { - pTemp[rgbI] = bdist; - inverseRGB[rgbI] = (byte) i; - } - } - } - } - } - } - - /** - * Gets the index of the nearest color to from the color map. - * - * @param pColor the color to get the nearest color to from color map - * color must be of format {@code 0x00RRGGBB} - standard default RGB - * @return index of color which closest matches input color by using the - * created inverse color map. - */ - public final int getIndexNearest(int pColor) { - return inverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) + - ((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) + - ((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF; - } - - /** - * Gets the index of the nearest color to from the color map. - * - * @param pRed red component of the color to get the nearest color to from color map - * @param pGreen green component of the color to get the nearest color to from color map - * @param pBlue blue component of the color to get the nearest color to from color map - * @return index of color which closest matches input color by using the - * created inverse color map. - */ - public final int getIndexNearest(int pRed, int pGreen, int pBlue) { - // NOTE: the third line in expression for blue is shifting DOWN not UP. - return inverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) + - ((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) + - ((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF; - } -} - +/* + * 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.image; + +/** + * Inverse Colormap to provide efficient lookup of any given input color + * to the closest match to the given color map. + *

+ * Based on "Efficient Inverse Color Map Computation" by Spencer W. Thomas + * in "Graphics Gems Volume II". + *

+ * + * @author Harald Kuhr + * @author Robin Luiten (Java port) + * @author Spencer W. Thomas (original c version). + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/InverseColorMap.java#1 $ + */ +class InverseColorMap { + /** + * Number of high bits of each color channel to use to lookup near match + */ + final static int QUANTBITS = 5; + + /** + * Truncated bits of each color channel + */ + final static int TRUNCBITS = 8 - QUANTBITS; + + /** + * BITMASK representing the bits for blue in the color lookup + */ + final static int QUANTMASK_BLUE = (1 << 5) - 1; + + /** + * BITMASK representing the bits for green in the color lookup + */ + final static int QUANTMASK_GREEN = (QUANTMASK_BLUE << QUANTBITS); + + /** + * BITMASK representing the bits for red in the color lookup + */ + final static int QUANTMASK_RED = (QUANTMASK_GREEN << QUANTBITS); + + /** + * Maximum value a quantised color channel can have + */ + final static int MAXQUANTVAL = 1 << 5; + + byte[] rgbMapByte; + int[] rgbMapInt; + int numColors; + int maxColor; + byte[] inverseRGB; // inverse rgb color map + int transparentIndex = -1; + + /** + * @param pRGBColorMap the rgb color map to create inverse color map for. + */ + InverseColorMap(byte[] pRGBColorMap) { + this(pRGBColorMap, -1); + } + + /** + * @param pRGBColorMap the rgb color map to create inverse color map for. + */ + // HaraldK 20040801: Added support for int[] + InverseColorMap(int[] pRGBColorMap) { + this(pRGBColorMap, -1); + } + + /** + * @param pRGBColorMap the rgb color map to create inverse color map for. + * @param pTransparent the index of the transparent pixel in the map + */ + InverseColorMap(byte[] pRGBColorMap, int pTransparent) { + rgbMapByte = pRGBColorMap; + numColors = rgbMapByte.length / 4; + transparentIndex = pTransparent; + + inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]; + initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]); + } + + /** + * @param pRGBColorMap the rgb color map to create inverse color map for. + * @param pTransparent the index of the transparent pixel in the map + */ + InverseColorMap(int[] pRGBColorMap, int pTransparent) { + rgbMapInt = pRGBColorMap; + numColors = rgbMapInt.length; + transparentIndex = pTransparent; + + inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]; + initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]); + } + + + /** + * Simple inverse color table creation method. + * @param pTemp temp array + */ + void initIRGB(int[] pTemp) { + final int x = (1 << TRUNCBITS); // 8 the size of 1 Dimension of each quantized cell + final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors + final int xsqr2 = xsqr + xsqr; + + for (int i = 0; i < numColors; ++i) { + if (i == transparentIndex) { + // Skip the transparent pixel + continue; + } + + int red, r, rdist, rinc, rxx; + int green, g, gdist, ginc, gxx; + int blue, b, bdist, binc, bxx; + + // HaraldK 20040801: Added support for int[] + if (rgbMapByte != null) { + red = rgbMapByte[i * 4] & 0xFF; + green = rgbMapByte[i * 4 + 1] & 0xFF; + blue = rgbMapByte[i * 4 + 2] & 0xFF; + } + else if (rgbMapInt != null) { + red = (rgbMapInt[i] >> 16) & 0xFF; + green = (rgbMapInt[i] >> 8) & 0xFF; + blue = rgbMapInt[i] & 0xFF; + } + else { + throw new IllegalStateException("colormap == null"); + } + + rdist = red - x / 2; // distance of red to center of current cell + gdist = green - x / 2; // green + bdist = blue - x / 2; // blue + rdist = rdist * rdist + gdist * gdist + bdist * bdist; + + rinc = 2 * (xsqr - (red << TRUNCBITS)); + ginc = 2 * (xsqr - (green << TRUNCBITS)); + binc = 2 * (xsqr - (blue << TRUNCBITS)); + + int rgbI = 0; + for (r = 0, rxx = rinc; r < MAXQUANTVAL; rdist += rxx, ++r, rxx += xsqr2) { + for (g = 0, gdist = rdist, gxx = ginc; g < MAXQUANTVAL; gdist += gxx, ++g, gxx += xsqr2) { + for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) { + if (i == 0 || pTemp[rgbI] > bdist) { + pTemp[rgbI] = bdist; + inverseRGB[rgbI] = (byte) i; + } + } + } + } + } + } + + /** + * Gets the index of the nearest color to from the color map. + * + * @param pColor the color to get the nearest color to from color map + * color must be of format {@code 0x00RRGGBB} - standard default RGB + * @return index of color which closest matches input color by using the + * created inverse color map. + */ + public final int getIndexNearest(int pColor) { + return inverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) + + ((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) + + ((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF; + } + + /** + * Gets the index of the nearest color to from the color map. + * + * @param pRed red component of the color to get the nearest color to from color map + * @param pGreen green component of the color to get the nearest color to from color map + * @param pBlue blue component of the color to get the nearest color to from color map + * @return index of color which closest matches input color by using the + * created inverse color map. + */ + public final int getIndexNearest(int pRed, int pGreen, int pBlue) { + // NOTE: the third line in expression for blue is shifting DOWN not UP. + return inverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) + + ((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) + + ((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF; + } +} + diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java index 640fa002..46b976f5 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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. */ diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java b/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java index 48165b7e..b348e22e 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java @@ -1,3 +1,34 @@ +/* + * 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.image; /** diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java b/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java index 681f2854..ed0753e9 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java @@ -1,184 +1,187 @@ -package com.twelvemonkeys.image; - -/* - * 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 "TwelveMonkeys" 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 OWNER 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. - */ - -import com.twelvemonkeys.lang.SystemUtil; - -import magick.MagickImage; - -import java.awt.image.*; - -/** - * This class accelerates certain graphics operations, using - * JMagick and ImageMagick, if available. - * If those libraries are not installed, this class silently does nothing. - *

- * Set the system property {@code "com.twelvemonkeys.image.accel"} to - * {@code false}, to disable, even if JMagick is installed. - * Set the system property {@code "com.twelvemonkeys.image.magick.debug"} to - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java#3 $ - */ -final class MagickAccelerator { - - private static final boolean DEBUG = Magick.DEBUG; - private static final boolean USE_MAGICK = useMagick(); - - private static final int RESAMPLE_OP = 0; - - private static Class[] nativeOp = new Class[1]; - - static { - try { - nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp"); - } - catch (ClassNotFoundException e) { - System.err.println("Could not find class: " + e); - } - } - - private static boolean useMagick() { - try { - boolean available = SystemUtil.isClassAvailable("magick.MagickImage"); - - if (DEBUG && !available) { - System.err.print("ImageMagick bindings not available."); - } - - boolean useMagick = - available && !"FALSE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.accel")); - - if (DEBUG) { - System.err.println( - useMagick - ? "Will use ImageMagick bindings to accelerate image resampling operations." - : "Will not use ImageMagick to accelerate image resampling operations." - ); - } - - return useMagick; - } - catch (Throwable t) { - // Most probably in case of a SecurityManager - System.err.println("Could not enable ImageMagick bindings: " + t); - return false; - } - } - - private static int getNativeOpIndex(Class pOpClass) { - for (int i = 0; i < nativeOp.length; i++) { - if (pOpClass == nativeOp[i]) { - return i; - } - } - - return -1; - } - - public static BufferedImage filter(BufferedImageOp pOperation, BufferedImage pInput, BufferedImage pOutput) { - if (!USE_MAGICK) { - return null; - } - - BufferedImage result = null; - switch (getNativeOpIndex(pOperation.getClass())) { - case RESAMPLE_OP: - ResampleOp resample = (ResampleOp) pOperation; - result = resampleMagick(pInput, resample.width, resample.height, resample.filterType); - - // NOTE: If output parameter is non-null, we have to return that - // image, instead of result - if (pOutput != null) { - //pOutput.setData(result.getRaster()); // Fast, but less compatible - // NOTE: For some reason, this is sometimes super-slow...? - ImageUtil.drawOnto(pOutput, result); - result = pOutput; - } - - break; - - default: - // Simply fall through, allowing acceleration to be added later - break; - - } - - return result; - } - - private static BufferedImage resampleMagick(BufferedImage pSrc, int pWidth, int pHeight, int pFilterType) { - // Convert to Magick, scale and convert back - MagickImage image = null; - MagickImage scaled = null; - try { - image = MagickUtil.toMagick(pSrc); - - long start = 0; - if (DEBUG) { - start = System.currentTimeMillis(); - } - - // NOTE: setFilter affects zoomImage, NOT scaleImage - image.setFilter(pFilterType); - scaled = image.zoomImage(pWidth, pHeight); - //scaled = image.scaleImage(pWidth, pHeight); // AREA_AVERAGING - - if (DEBUG) { - long time = System.currentTimeMillis() - start; - System.out.println("Filtered: " + time + " ms"); - } - - return MagickUtil.toBuffered(scaled); - } - //catch (MagickException e) { - catch (Exception e) { - // NOTE: Stupid workaround: If MagickException is caught, a - // NoClassDefFoundError is thrown, when MagickException class is - // unavailable... - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - - throw new ImageConversionException(e.getMessage(), e); - } - finally { - // NOTE: ImageMagick might be unstable after a while, if image data - // is not deallocated. The GC/finalize method handles this, but in - // special circumstances, it's not triggered often enough. - if (image != null) { - image.destroyImages(); - } - if (scaled != null) { - scaled.destroyImages(); - } - } - } +/* + * 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.image; + +import com.twelvemonkeys.lang.SystemUtil; +import magick.MagickImage; + +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; + +/** + * This class accelerates certain graphics operations, using + * JMagick and ImageMagick, if available. + * If those libraries are not installed, this class silently does nothing. + *

+ * Set the system property {@code "com.twelvemonkeys.image.accel"} to + * {@code false}, to disable, even if JMagick is installed. + * Set the system property {@code "com.twelvemonkeys.image.magick.debug"} to + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java#3 $ + */ +final class MagickAccelerator { + + private static final boolean DEBUG = Magick.DEBUG; + private static final boolean USE_MAGICK = useMagick(); + + private static final int RESAMPLE_OP = 0; + + private static Class[] nativeOp = new Class[1]; + + static { + try { + nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp"); + } + catch (ClassNotFoundException e) { + System.err.println("Could not find class: " + e); + } + } + + private static boolean useMagick() { + try { + boolean available = SystemUtil.isClassAvailable("magick.MagickImage"); + + if (DEBUG && !available) { + System.err.print("ImageMagick bindings not available."); + } + + boolean useMagick = + available && !"FALSE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.accel")); + + if (DEBUG) { + System.err.println( + useMagick + ? "Will use ImageMagick bindings to accelerate image resampling operations." + : "Will not use ImageMagick to accelerate image resampling operations." + ); + } + + return useMagick; + } + catch (Throwable t) { + // Most probably in case of a SecurityManager + System.err.println("Could not enable ImageMagick bindings: " + t); + return false; + } + } + + private static int getNativeOpIndex(Class pOpClass) { + for (int i = 0; i < nativeOp.length; i++) { + if (pOpClass == nativeOp[i]) { + return i; + } + } + + return -1; + } + + public static BufferedImage filter(BufferedImageOp pOperation, BufferedImage pInput, BufferedImage pOutput) { + if (!USE_MAGICK) { + return null; + } + + BufferedImage result = null; + switch (getNativeOpIndex(pOperation.getClass())) { + case RESAMPLE_OP: + ResampleOp resample = (ResampleOp) pOperation; + result = resampleMagick(pInput, resample.width, resample.height, resample.filterType); + + // NOTE: If output parameter is non-null, we have to return that + // image, instead of result + if (pOutput != null) { + //pOutput.setData(result.getRaster()); // Fast, but less compatible + // NOTE: For some reason, this is sometimes super-slow...? + ImageUtil.drawOnto(pOutput, result); + result = pOutput; + } + + break; + + default: + // Simply fall through, allowing acceleration to be added later + break; + + } + + return result; + } + + private static BufferedImage resampleMagick(BufferedImage pSrc, int pWidth, int pHeight, int pFilterType) { + // Convert to Magick, scale and convert back + MagickImage image = null; + MagickImage scaled = null; + try { + image = MagickUtil.toMagick(pSrc); + + long start = 0; + if (DEBUG) { + start = System.currentTimeMillis(); + } + + // NOTE: setFilter affects zoomImage, NOT scaleImage + image.setFilter(pFilterType); + scaled = image.zoomImage(pWidth, pHeight); + //scaled = image.scaleImage(pWidth, pHeight); // AREA_AVERAGING + + if (DEBUG) { + long time = System.currentTimeMillis() - start; + System.out.println("Filtered: " + time + " ms"); + } + + return MagickUtil.toBuffered(scaled); + } + //catch (MagickException e) { + catch (Exception e) { + // NOTE: Stupid workaround: If MagickException is caught, a + // NoClassDefFoundError is thrown, when MagickException class is + // unavailable... + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + + throw new ImageConversionException(e.getMessage(), e); + } + finally { + // NOTE: ImageMagick might be unstable after a while, if image data + // is not deallocated. The GC/finalize method handles this, but in + // special circumstances, it's not triggered often enough. + if (image != null) { + image.destroyImages(); + } + if (scaled != null) { + scaled.destroyImages(); + } + } + } } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java index 8f648ded..a5a316ea 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java @@ -1,611 +1,621 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import magick.*; - -import java.awt.*; -import java.awt.color.ColorSpace; -import java.awt.color.ICC_ColorSpace; -import java.awt.color.ICC_Profile; -import java.awt.image.*; - -/** - * Utility for converting JMagick {@code MagickImage}s to standard Java - * {@code BufferedImage}s and back. - *

- * NOTE: This class is considered an implementation detail and not part of - * the public API. This class is subject to change without further notice. - * You have been warned. :-) - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickUtil.java#4 $ - */ -public final class MagickUtil { - // IMPORTANT NOTE: Disaster happens if any of these constants are used outside this class - // because you then have a dependency on MagickException (this is due to Java class loading - // and initialization magic). - // Do not use outside this class. If the constants need to be shared, move to Magick or ImageUtil. - - /** Color Model usesd for bilevel (B/W) */ - private static final IndexColorModel CM_MONOCHROME = MonochromeColorModel.getInstance(); - - /** Color Model usesd for raw ABGR */ - private static final ColorModel CM_COLOR_ALPHA = - new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8}, - true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); - - /** Color Model usesd for raw BGR */ - private static final ColorModel CM_COLOR_OPAQUE = - new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8}, - false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); - - /** Color Model usesd for raw RGB */ - //private static final ColorModel CM_COLOR_RGB = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0); - - /** Color Model usesd for raw GRAY + ALPHA */ - private static final ColorModel CM_GRAY_ALPHA = - new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), - true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); - - /** Color Model usesd for raw GRAY */ - private static final ColorModel CM_GRAY_OPAQUE = - new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), - false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); - - /** Band offsets for raw ABGR */ - private static final int[] BAND_OFF_TRANS = new int[] {3, 2, 1, 0}; - - /** Band offsets for raw BGR */ - private static final int[] BAND_OFF_OPAQUE = new int[] {2, 1, 0}; - - /** The point at {@code 0, 0} */ - private static final Point LOCATION_UPPER_LEFT = new Point(0, 0); - - private static final boolean DEBUG = Magick.DEBUG; - - // Only static members and methods - private MagickUtil() {} - - /** - * Converts a {@code MagickImage} to a {@code BufferedImage}. - *

- * The conversion depends on {@code pImage}'s {@code ImageType}: - *

- *
{@code ImageType.BilevelType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}
- * - *
{@code ImageType.GrayscaleType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}
- *
{@code ImageType.GrayscaleMatteType}
- *
{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}
- * - *
{@code ImageType.PaletteType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images - * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}
- *
{@code ImageType.PaletteMatteType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images - * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}
- * - *
{@code ImageType.TrueColorType}
- *
{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}
- *
{@code ImageType.TrueColorPaletteType}
- *
{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}
- * - * @param pImage the original {@code MagickImage} - * @return a new {@code BufferedImage} - * - * @throws IllegalArgumentException if {@code pImage} is {@code null} - * or if the {@code ImageType} is not one mentioned above. - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - public static BufferedImage toBuffered(MagickImage pImage) throws MagickException { - if (pImage == null) { - throw new IllegalArgumentException("image == null"); - } - - long start = 0L; - if (DEBUG) { - start = System.currentTimeMillis(); - } - - BufferedImage image = null; - try { - switch (pImage.getImageType()) { - case ImageType.BilevelType: - image = bilevelToBuffered(pImage); - break; - case ImageType.GrayscaleType: - image = grayToBuffered(pImage, false); - break; - case ImageType.GrayscaleMatteType: - image = grayToBuffered(pImage, true); - break; - case ImageType.PaletteType: - image = paletteToBuffered(pImage, false); - break; - case ImageType.PaletteMatteType: - image = paletteToBuffered(pImage, true); - break; - case ImageType.TrueColorType: - image = rgbToBuffered(pImage, false); - break; - case ImageType.TrueColorMatteType: - image = rgbToBuffered(pImage, true); - break; - case ImageType.ColorSeparationType: - image = cmykToBuffered(pImage, false); - break; - case ImageType.ColorSeparationMatteType: - image = cmykToBuffered(pImage, true); - break; - case ImageType.OptimizeType: - default: - throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType()); - } - - } - finally { - if (DEBUG) { - long time = System.currentTimeMillis() - start; - System.out.println("Converted JMagick image type: " + pImage.getImageType() + " to BufferedImage: " + image); - System.out.println("Conversion to BufferedImage: " + time + " ms"); - } - } - - return image; - } - - /** - * Converts a {@code BufferedImage} to a {@code MagickImage}. - *

- * The conversion depends on {@code pImage}'s {@code ColorModel}: - *

- *
{@code IndexColorModel} with 1 bit b/w
- *
{@code MagickImage} of type {@code ImageType.BilevelType}
- *
{@code IndexColorModel} > 1 bit,
- *
{@code MagickImage} of type {@code ImageType.PaletteType} - * or {@code MagickImage} of type {@code ImageType.PaletteMatteType} - * depending on ColorModel.getAlpha()
- * - *
{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}
- *
{@code MagickImage} of type {@code ImageType.GrayscaleType} - * or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType} - * depending on ColorModel.getAlpha()
- * - *
{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}
- *
{@code MagickImage} of type {@code ImageType.TrueColorType} - * or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}
- * - * @param pImage the original {@code BufferedImage} - * @return a new {@code MagickImage} - * - * @throws IllegalArgumentException if {@code pImage} is {@code null} - * or if the {@code ColorModel} is not one mentioned above. - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - public static MagickImage toMagick(BufferedImage pImage) throws MagickException { - if (pImage == null) { - throw new IllegalArgumentException("image == null"); - } - - long start = 0L; - if (DEBUG) { - start = System.currentTimeMillis(); - } - - try { - ColorModel cm = pImage.getColorModel(); - if (cm instanceof IndexColorModel) { - // Handles both BilevelType, PaletteType and PaletteMatteType - return indexedToMagick(pImage, (IndexColorModel) cm, cm.hasAlpha()); - } - - switch (cm.getColorSpace().getType()) { - case ColorSpace.TYPE_GRAY: - // Handles GrayType and GrayMatteType - return grayToMagick(pImage, cm.hasAlpha()); - case ColorSpace.TYPE_RGB: - // Handles TrueColorType and TrueColorMatteType - return rgbToMagic(pImage, cm.hasAlpha()); - case ColorSpace.TYPE_CMY: - case ColorSpace.TYPE_CMYK: - case ColorSpace.TYPE_HLS: - case ColorSpace.TYPE_HSV: - // Other types not supported yet - default: - throw new IllegalArgumentException("Unknown buffered image type: " + pImage); - } - } - finally { - if (DEBUG) { - long time = System.currentTimeMillis() - start; - System.out.println("Conversion to MagickImage: " + time + " ms"); - } - } - } - - private static MagickImage rgbToMagic(BufferedImage pImage, boolean pAlpha) throws MagickException { - MagickImage image = new MagickImage(); - - BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); - - // Need to get data of sub raster, not the full data array, this is - // just a convenient way - Raster raster; - if (buffered.getRaster().getParent() != null) { - raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight())); - } - else { - raster = buffered.getRaster(); - } - - image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "BGR", - ((DataBufferByte) raster.getDataBuffer()).getData()); - - return image; - } - - private static MagickImage grayToMagick(BufferedImage pImage, boolean pAlpha) throws MagickException { - MagickImage image = new MagickImage(); - - // TODO: Make a fix for TYPE_USHORT_GRAY - // The code below does not seem to work (JMagick issues?)... - /* - if (pImage.getType() == BufferedImage.TYPE_USHORT_GRAY) { - short[] data = ((DataBufferUShort) pImage.getRaster().getDataBuffer()).getData(); - int[] intData = new int[data.length]; - for (int i = 0; i < data.length; i++) { - intData[i] = (data[i] & 0xffff) * 0xffff; - } - image.constituteImage(pImage.getWidth(), pImage.getHeight(), "I", intData); - - System.out.println("storageClass: " + image.getStorageClass()); - System.out.println("depth: " + image.getDepth()); - System.out.println("imageType: " + image.getImageType()); - } - else { - */ - BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_BYTE_GRAY); - - // Need to get data of sub raster, not the full data array, this is - // just a convenient way - Raster raster; - if (buffered.getRaster().getParent() != null) { - raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight())); - } - else { - raster = buffered.getRaster(); - } - - image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "I", ((DataBufferByte) raster.getDataBuffer()).getData()); - //} - - return image; - } - - private static MagickImage indexedToMagick(BufferedImage pImage, IndexColorModel pColorModel, boolean pAlpha) throws MagickException { - MagickImage image = rgbToMagic(pImage, pAlpha); - - int mapSize = pColorModel.getMapSize(); - image.setNumberColors(mapSize); - - return image; - } - - /* - public static MagickImage toMagick(BufferedImage pImage) throws MagickException { - if (pImage == null) { - throw new IllegalArgumentException("image == null"); - } - - final int width = pImage.getWidth(); - final int height = pImage.getHeight(); - - // int ARGB -> byte RGBA conversion - // NOTE: This is ImageMagick Q16 compatible raw RGBA format with 16 bits/sample... - // For a Q8 build, we could probably go with half the space... - // NOTE: This is close to insanity, as it wastes extreme ammounts of memory - final int[] argb = new int[width]; - final byte[] raw16 = new byte[width * height * 8]; - for (int y = 0; y < height; y++) { - // Fetch one line of ARGB data - pImage.getRGB(0, y, width, 1, argb, 0, width); - - for (int x = 0; x < width; x++) { - int pixel = (x + (y * width)) * 8; - raw16[pixel ] = (byte) ((argb[x] >> 16) & 0xff); // R - raw16[pixel + 2] = (byte) ((argb[x] >> 8) & 0xff); // G - raw16[pixel + 4] = (byte) ((argb[x] ) & 0xff); // B - raw16[pixel + 6] = (byte) ((argb[x] >> 24) & 0xff); // A - } - } - - // Create magick image - ImageInfo info = new ImageInfo(); - info.setMagick("RGBA"); // Raw RGBA samples - info.setSize(width + "x" + height); // String?!? - - MagickImage image = new MagickImage(info); - image.setImageAttribute("depth", "8"); - - // Set pixel data in 16 bit raw RGBA format - image.blobToImage(info, raw16); - - return image; - } - */ - - /** - * Converts a bi-level {@code MagickImage} to a {@code BufferedImage}, of - * type {@code TYPE_BYTE_BINARY}. - * - * @param pImage the original {@code MagickImage} - * @return a new {@code BufferedImage} - * - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - private static BufferedImage bilevelToBuffered(MagickImage pImage) throws MagickException { - // As there is no way to get the binary representation of the image, - // convert to gray, and the create a binary image from it - BufferedImage temp = grayToBuffered(pImage, false); - - BufferedImage image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CM_MONOCHROME); - - ImageUtil.drawOnto(image, temp); - - return image; - } - - /** - * Converts a gray {@code MagickImage} to a {@code BufferedImage}, of - * type {@code TYPE_USHORT_GRAY} or {@code TYPE_BYTE_GRAY}. - * - * @param pImage the original {@code MagickImage} - * @param pAlpha keep alpha channel - * @return a new {@code BufferedImage} - * - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - private static BufferedImage grayToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { - Dimension size = pImage.getDimension(); - int length = size.width * size.height; - int bands = pAlpha ? 2 : 1; - byte[] pixels = new byte[length * bands]; - - // TODO: Make a fix for 16 bit TYPE_USHORT_GRAY?! - // Note: The ordering AI or I corresponds to BufferedImage - // TYPE_CUSTOM and TYPE_BYTE_GRAY respectively - pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "AI" : "I", pixels); - - // Init databuffer with array, to avoid allocation of empty array - DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - - int[] bandOffsets = pAlpha ? new int[] {1, 0} : new int[] {0}; - - WritableRaster raster = - Raster.createInterleavedRaster(buffer, size.width, size.height, - size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT); - - return new BufferedImage(pAlpha ? CM_GRAY_ALPHA : CM_GRAY_OPAQUE, raster, pAlpha, null); - } - - /** - * Converts a palette-based {@code MagickImage} to a - * {@code BufferedImage}, of type {@code TYPE_BYTE_BINARY} (for images - * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}. - * - * @param pImage the original {@code MagickImage} - * @param pAlpha keep alpha channel - * @return a new {@code BufferedImage} - * - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - private static BufferedImage paletteToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { - // Create indexcolormodel for the image - IndexColorModel cm; - - try { - cm = createIndexColorModel(pImage.getColormap(), pAlpha); - } - catch (MagickException e) { - // NOTE: Some MagickImages incorrecly (?) reports to be paletteType, - // but does not have a colormap, this is a workaround. - return rgbToBuffered(pImage, pAlpha); - } - - // As there is no way to get the indexes of an indexed image, convert to - // RGB, and the create an indexed image from it - BufferedImage temp = rgbToBuffered(pImage, pAlpha); - - BufferedImage image; - if (cm.getMapSize() <= 16) { - image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, cm); - } - else { - image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); - } - - // Create transparent background for images containing alpha - if (pAlpha) { - Graphics2D g = image.createGraphics(); - try { - g.setComposite(AlphaComposite.Clear); - g.fillRect(0, 0, temp.getWidth(), temp.getHeight()); - } - finally { - g.dispose(); - } - } - - // NOTE: This is (surprisingly) much faster than using g2d.drawImage().. - // (Tests shows 20-30ms, vs. 600-700ms on the same image) - BufferedImageOp op = new CopyDither(cm); - op.filter(temp, image); - - return image; - } - - /** - * Creates an {@code IndexColorModel} from an array of - * {@code PixelPacket}s. - * - * @param pColormap the original colormap as a {@code PixelPacket} array - * @param pAlpha keep alpha channel - * - * @return a new {@code IndexColorModel} - */ - public static IndexColorModel createIndexColorModel(PixelPacket[] pColormap, boolean pAlpha) { - int[] colors = new int[pColormap.length]; - - // TODO: Verify if this is correct for alpha...? - int trans = pAlpha ? colors.length - 1 : -1; - - //for (int i = 0; i < pColormap.length; i++) { - for (int i = pColormap.length - 1; i != 0; i--) { - PixelPacket color = pColormap[i]; - if (pAlpha) { - colors[i] = (0xff - (color.getOpacity() & 0xff)) << 24 | - (color.getRed() & 0xff) << 16 | - (color.getGreen() & 0xff) << 8 | - (color.getBlue() & 0xff); - } - else { - colors[i] = (color.getRed() & 0xff) << 16 | - (color.getGreen() & 0xff) << 8 | - (color.getBlue() & 0xff); - } - } - - return new InverseColorMapIndexColorModel(8, colors.length, colors, 0, pAlpha, trans, DataBuffer.TYPE_BYTE); - } - - /** - * Converts an (A)RGB {@code MagickImage} to a {@code BufferedImage}, of - * type {@code TYPE_4BYTE_ABGR} or {@code TYPE_3BYTE_BGR}. - * - * @param pImage the original {@code MagickImage} - * @param pAlpha keep alpha channel - * @return a new {@code BufferedImage} - * - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - private static BufferedImage rgbToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { - Dimension size = pImage.getDimension(); - int length = size.width * size.height; - int bands = pAlpha ? 4 : 3; - byte[] pixels = new byte[length * bands]; - - // TODO: If we do multiple dispatches (one per line, typically), we could provide listener - // feedback. But it's currently a lot slower than fetching all the pixels in one go. - - // Note: The ordering ABGR or BGR corresponds to BufferedImage - // TYPE_4BYTE_ABGR and TYPE_3BYTE_BGR respectively - pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ABGR" : "BGR", pixels); - - // Init databuffer with array, to avoid allocation of empty array - DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - - int[] bandOffsets = pAlpha ? BAND_OFF_TRANS : BAND_OFF_OPAQUE; - - WritableRaster raster = - Raster.createInterleavedRaster(buffer, size.width, size.height, - size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT); - - return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null); - } - - - - - /** - * Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile - * - * @param pImage the original {@code MagickImage} - * @param pAlpha keep alpha channel - * @return a new {@code BufferedImage} - * - * @throws MagickException if an exception occurs during conversion - * - * @see BufferedImage - */ - private static BufferedImage cmykToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { - Dimension size = pImage.getDimension(); - int length = size.width * size.height; - - // Retreive the ICC profile - ICC_Profile profile = ICC_Profile.getInstance(pImage.getColorProfile().getInfo()); - ColorSpace cs = new ICC_ColorSpace(profile); - - int bands = cs.getNumComponents() + (pAlpha ? 1 : 0); - - int[] bits = new int[bands]; - for (int i = 0; i < bands; i++) { - bits[i] = 8; - } - - ColorModel cm = pAlpha ? - new ComponentColorModel(cs, bits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE) : - new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); - - byte[] pixels = new byte[length * bands]; - - // TODO: If we do multiple dispatches (one per line, typically), we could provide listener - // feedback. But it's currently a lot slower than fetching all the pixels in one go. - // TODO: handle more generic cases if profile is not CMYK - // TODO: Test "ACMYK" - pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ACMYK" : "CMYK", pixels); - - // Init databuffer with array, to avoid allocation of empty array - DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - - // TODO: build array from bands variable, here it just works for CMYK - // The values has not been tested with an alpha picture actually... - int[] bandOffsets = pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3}; - - WritableRaster raster = - Raster.createInterleavedRaster(buffer, size.width, size.height, - size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT); - - return new BufferedImage(cm, raster, pAlpha, null); - - } -} +/* + * 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.image; + +import magick.ImageType; +import magick.MagickException; +import magick.MagickImage; +import magick.PixelPacket; + +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.*; + +/** + * Utility for converting JMagick {@code MagickImage}s to standard Java + * {@code BufferedImage}s and back. + *

+ * NOTE: This class is considered an implementation detail and not part of + * the public API. This class is subject to change without further notice. + * You have been warned. :-) + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickUtil.java#4 $ + */ +public final class MagickUtil { + // IMPORTANT NOTE: Disaster happens if any of these constants are used outside this class + // because you then have a dependency on MagickException (this is due to Java class loading + // and initialization magic). + // Do not use outside this class. If the constants need to be shared, move to Magick or ImageUtil. + + /** Color Model usesd for bilevel (B/W) */ + private static final IndexColorModel CM_MONOCHROME = MonochromeColorModel.getInstance(); + + /** Color Model usesd for raw ABGR */ + private static final ColorModel CM_COLOR_ALPHA = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8}, + true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); + + /** Color Model usesd for raw BGR */ + private static final ColorModel CM_COLOR_OPAQUE = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8}, + false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); + + /** Color Model usesd for raw RGB */ + //private static final ColorModel CM_COLOR_RGB = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0); + + /** Color Model usesd for raw GRAY + ALPHA */ + private static final ColorModel CM_GRAY_ALPHA = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); + + /** Color Model usesd for raw GRAY */ + private static final ColorModel CM_GRAY_OPAQUE = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); + + /** Band offsets for raw ABGR */ + private static final int[] BAND_OFF_TRANS = new int[] {3, 2, 1, 0}; + + /** Band offsets for raw BGR */ + private static final int[] BAND_OFF_OPAQUE = new int[] {2, 1, 0}; + + /** The point at {@code 0, 0} */ + private static final Point LOCATION_UPPER_LEFT = new Point(0, 0); + + private static final boolean DEBUG = Magick.DEBUG; + + // Only static members and methods + private MagickUtil() {} + + /** + * Converts a {@code MagickImage} to a {@code BufferedImage}. + *

+ * The conversion depends on {@code pImage}'s {@code ImageType}: + *

+ *
+ *
{@code ImageType.BilevelType}
+ *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}
+ * + *
{@code ImageType.GrayscaleType}
+ *
{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}
+ *
{@code ImageType.GrayscaleMatteType}
+ *
{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}
+ * + *
{@code ImageType.PaletteType}
+ *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images + * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}
+ *
{@code ImageType.PaletteMatteType}
+ *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images + * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}
+ * + *
{@code ImageType.TrueColorType}
+ *
{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}
+ *
{@code ImageType.TrueColorPaletteType}
+ *
{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}
+ *
+ * + * @param pImage the original {@code MagickImage} + * @return a new {@code BufferedImage} + * + * @throws IllegalArgumentException if {@code pImage} is {@code null} + * or if the {@code ImageType} is not one mentioned above. + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + public static BufferedImage toBuffered(MagickImage pImage) throws MagickException { + if (pImage == null) { + throw new IllegalArgumentException("image == null"); + } + + long start = 0L; + if (DEBUG) { + start = System.currentTimeMillis(); + } + + BufferedImage image = null; + try { + switch (pImage.getImageType()) { + case ImageType.BilevelType: + image = bilevelToBuffered(pImage); + break; + case ImageType.GrayscaleType: + image = grayToBuffered(pImage, false); + break; + case ImageType.GrayscaleMatteType: + image = grayToBuffered(pImage, true); + break; + case ImageType.PaletteType: + image = paletteToBuffered(pImage, false); + break; + case ImageType.PaletteMatteType: + image = paletteToBuffered(pImage, true); + break; + case ImageType.TrueColorType: + image = rgbToBuffered(pImage, false); + break; + case ImageType.TrueColorMatteType: + image = rgbToBuffered(pImage, true); + break; + case ImageType.ColorSeparationType: + image = cmykToBuffered(pImage, false); + break; + case ImageType.ColorSeparationMatteType: + image = cmykToBuffered(pImage, true); + break; + case ImageType.OptimizeType: + default: + throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType()); + } + + } + finally { + if (DEBUG) { + long time = System.currentTimeMillis() - start; + System.out.println("Converted JMagick image type: " + pImage.getImageType() + " to BufferedImage: " + image); + System.out.println("Conversion to BufferedImage: " + time + " ms"); + } + } + + return image; + } + + /** + * Converts a {@code BufferedImage} to a {@code MagickImage}. + *

+ * The conversion depends on {@code pImage}'s {@code ColorModel}: + *

+ *
+ *
{@code IndexColorModel} with 1 bit b/w
+ *
{@code MagickImage} of type {@code ImageType.BilevelType}
+ *
{@code IndexColorModel} > 1 bit,
+ *
{@code MagickImage} of type {@code ImageType.PaletteType} + * or {@code MagickImage} of type {@code ImageType.PaletteMatteType} + * depending on ColorModel.getAlpha()
+ * + *
{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}
+ *
{@code MagickImage} of type {@code ImageType.GrayscaleType} + * or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType} + * depending on ColorModel.getAlpha()
+ * + *
{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}
+ *
{@code MagickImage} of type {@code ImageType.TrueColorType} + * or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}
+ *
+ * + * @param pImage the original {@code BufferedImage} + * @return a new {@code MagickImage} + * + * @throws IllegalArgumentException if {@code pImage} is {@code null} + * or if the {@code ColorModel} is not one mentioned above. + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + public static MagickImage toMagick(BufferedImage pImage) throws MagickException { + if (pImage == null) { + throw new IllegalArgumentException("image == null"); + } + + long start = 0L; + if (DEBUG) { + start = System.currentTimeMillis(); + } + + try { + ColorModel cm = pImage.getColorModel(); + if (cm instanceof IndexColorModel) { + // Handles both BilevelType, PaletteType and PaletteMatteType + return indexedToMagick(pImage, (IndexColorModel) cm, cm.hasAlpha()); + } + + switch (cm.getColorSpace().getType()) { + case ColorSpace.TYPE_GRAY: + // Handles GrayType and GrayMatteType + return grayToMagick(pImage, cm.hasAlpha()); + case ColorSpace.TYPE_RGB: + // Handles TrueColorType and TrueColorMatteType + return rgbToMagic(pImage, cm.hasAlpha()); + case ColorSpace.TYPE_CMY: + case ColorSpace.TYPE_CMYK: + case ColorSpace.TYPE_HLS: + case ColorSpace.TYPE_HSV: + // Other types not supported yet + default: + throw new IllegalArgumentException("Unknown buffered image type: " + pImage); + } + } + finally { + if (DEBUG) { + long time = System.currentTimeMillis() - start; + System.out.println("Conversion to MagickImage: " + time + " ms"); + } + } + } + + private static MagickImage rgbToMagic(BufferedImage pImage, boolean pAlpha) throws MagickException { + MagickImage image = new MagickImage(); + + BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); + + // Need to get data of sub raster, not the full data array, this is + // just a convenient way + Raster raster; + if (buffered.getRaster().getParent() != null) { + raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight())); + } + else { + raster = buffered.getRaster(); + } + + image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "BGR", + ((DataBufferByte) raster.getDataBuffer()).getData()); + + return image; + } + + private static MagickImage grayToMagick(BufferedImage pImage, boolean pAlpha) throws MagickException { + MagickImage image = new MagickImage(); + + // TODO: Make a fix for TYPE_USHORT_GRAY + // The code below does not seem to work (JMagick issues?)... + /* + if (pImage.getType() == BufferedImage.TYPE_USHORT_GRAY) { + short[] data = ((DataBufferUShort) pImage.getRaster().getDataBuffer()).getData(); + int[] intData = new int[data.length]; + for (int i = 0; i < data.length; i++) { + intData[i] = (data[i] & 0xffff) * 0xffff; + } + image.constituteImage(pImage.getWidth(), pImage.getHeight(), "I", intData); + + System.out.println("storageClass: " + image.getStorageClass()); + System.out.println("depth: " + image.getDepth()); + System.out.println("imageType: " + image.getImageType()); + } + else { + */ + BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_BYTE_GRAY); + + // Need to get data of sub raster, not the full data array, this is + // just a convenient way + Raster raster; + if (buffered.getRaster().getParent() != null) { + raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight())); + } + else { + raster = buffered.getRaster(); + } + + image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "I", ((DataBufferByte) raster.getDataBuffer()).getData()); + //} + + return image; + } + + private static MagickImage indexedToMagick(BufferedImage pImage, IndexColorModel pColorModel, boolean pAlpha) throws MagickException { + MagickImage image = rgbToMagic(pImage, pAlpha); + + int mapSize = pColorModel.getMapSize(); + image.setNumberColors(mapSize); + + return image; + } + + /* + public static MagickImage toMagick(BufferedImage pImage) throws MagickException { + if (pImage == null) { + throw new IllegalArgumentException("image == null"); + } + + final int width = pImage.getWidth(); + final int height = pImage.getHeight(); + + // int ARGB -> byte RGBA conversion + // NOTE: This is ImageMagick Q16 compatible raw RGBA format with 16 bits/sample... + // For a Q8 build, we could probably go with half the space... + // NOTE: This is close to insanity, as it wastes extreme ammounts of memory + final int[] argb = new int[width]; + final byte[] raw16 = new byte[width * height * 8]; + for (int y = 0; y < height; y++) { + // Fetch one line of ARGB data + pImage.getRGB(0, y, width, 1, argb, 0, width); + + for (int x = 0; x < width; x++) { + int pixel = (x + (y * width)) * 8; + raw16[pixel ] = (byte) ((argb[x] >> 16) & 0xff); // R + raw16[pixel + 2] = (byte) ((argb[x] >> 8) & 0xff); // G + raw16[pixel + 4] = (byte) ((argb[x] ) & 0xff); // B + raw16[pixel + 6] = (byte) ((argb[x] >> 24) & 0xff); // A + } + } + + // Create magick image + ImageInfo info = new ImageInfo(); + info.setMagick("RGBA"); // Raw RGBA samples + info.setSize(width + "x" + height); // String?!? + + MagickImage image = new MagickImage(info); + image.setImageAttribute("depth", "8"); + + // Set pixel data in 16 bit raw RGBA format + image.blobToImage(info, raw16); + + return image; + } + */ + + /** + * Converts a bi-level {@code MagickImage} to a {@code BufferedImage}, of + * type {@code TYPE_BYTE_BINARY}. + * + * @param pImage the original {@code MagickImage} + * @return a new {@code BufferedImage} + * + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + private static BufferedImage bilevelToBuffered(MagickImage pImage) throws MagickException { + // As there is no way to get the binary representation of the image, + // convert to gray, and the create a binary image from it + BufferedImage temp = grayToBuffered(pImage, false); + + BufferedImage image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CM_MONOCHROME); + + ImageUtil.drawOnto(image, temp); + + return image; + } + + /** + * Converts a gray {@code MagickImage} to a {@code BufferedImage}, of + * type {@code TYPE_USHORT_GRAY} or {@code TYPE_BYTE_GRAY}. + * + * @param pImage the original {@code MagickImage} + * @param pAlpha keep alpha channel + * @return a new {@code BufferedImage} + * + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + private static BufferedImage grayToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { + Dimension size = pImage.getDimension(); + int length = size.width * size.height; + int bands = pAlpha ? 2 : 1; + byte[] pixels = new byte[length * bands]; + + // TODO: Make a fix for 16 bit TYPE_USHORT_GRAY?! + // Note: The ordering AI or I corresponds to BufferedImage + // TYPE_CUSTOM and TYPE_BYTE_GRAY respectively + pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "AI" : "I", pixels); + + // Init databuffer with array, to avoid allocation of empty array + DataBuffer buffer = new DataBufferByte(pixels, pixels.length); + + int[] bandOffsets = pAlpha ? new int[] {1, 0} : new int[] {0}; + + WritableRaster raster = + Raster.createInterleavedRaster(buffer, size.width, size.height, + size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT); + + return new BufferedImage(pAlpha ? CM_GRAY_ALPHA : CM_GRAY_OPAQUE, raster, pAlpha, null); + } + + /** + * Converts a palette-based {@code MagickImage} to a + * {@code BufferedImage}, of type {@code TYPE_BYTE_BINARY} (for images + * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}. + * + * @param pImage the original {@code MagickImage} + * @param pAlpha keep alpha channel + * @return a new {@code BufferedImage} + * + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + private static BufferedImage paletteToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { + // Create indexcolormodel for the image + IndexColorModel cm; + + try { + cm = createIndexColorModel(pImage.getColormap(), pAlpha); + } + catch (MagickException e) { + // NOTE: Some MagickImages incorrecly (?) reports to be paletteType, + // but does not have a colormap, this is a workaround. + return rgbToBuffered(pImage, pAlpha); + } + + // As there is no way to get the indexes of an indexed image, convert to + // RGB, and the create an indexed image from it + BufferedImage temp = rgbToBuffered(pImage, pAlpha); + + BufferedImage image; + if (cm.getMapSize() <= 16) { + image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, cm); + } + else { + image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); + } + + // Create transparent background for images containing alpha + if (pAlpha) { + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.Clear); + g.fillRect(0, 0, temp.getWidth(), temp.getHeight()); + } + finally { + g.dispose(); + } + } + + // NOTE: This is (surprisingly) much faster than using g2d.drawImage().. + // (Tests shows 20-30ms, vs. 600-700ms on the same image) + BufferedImageOp op = new CopyDither(cm); + op.filter(temp, image); + + return image; + } + + /** + * Creates an {@code IndexColorModel} from an array of + * {@code PixelPacket}s. + * + * @param pColormap the original colormap as a {@code PixelPacket} array + * @param pAlpha keep alpha channel + * + * @return a new {@code IndexColorModel} + */ + public static IndexColorModel createIndexColorModel(PixelPacket[] pColormap, boolean pAlpha) { + int[] colors = new int[pColormap.length]; + + // TODO: Verify if this is correct for alpha...? + int trans = pAlpha ? colors.length - 1 : -1; + + //for (int i = 0; i < pColormap.length; i++) { + for (int i = pColormap.length - 1; i != 0; i--) { + PixelPacket color = pColormap[i]; + if (pAlpha) { + colors[i] = (0xff - (color.getOpacity() & 0xff)) << 24 | + (color.getRed() & 0xff) << 16 | + (color.getGreen() & 0xff) << 8 | + (color.getBlue() & 0xff); + } + else { + colors[i] = (color.getRed() & 0xff) << 16 | + (color.getGreen() & 0xff) << 8 | + (color.getBlue() & 0xff); + } + } + + return new InverseColorMapIndexColorModel(8, colors.length, colors, 0, pAlpha, trans, DataBuffer.TYPE_BYTE); + } + + /** + * Converts an (A)RGB {@code MagickImage} to a {@code BufferedImage}, of + * type {@code TYPE_4BYTE_ABGR} or {@code TYPE_3BYTE_BGR}. + * + * @param pImage the original {@code MagickImage} + * @param pAlpha keep alpha channel + * @return a new {@code BufferedImage} + * + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + private static BufferedImage rgbToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { + Dimension size = pImage.getDimension(); + int length = size.width * size.height; + int bands = pAlpha ? 4 : 3; + byte[] pixels = new byte[length * bands]; + + // TODO: If we do multiple dispatches (one per line, typically), we could provide listener + // feedback. But it's currently a lot slower than fetching all the pixels in one go. + + // Note: The ordering ABGR or BGR corresponds to BufferedImage + // TYPE_4BYTE_ABGR and TYPE_3BYTE_BGR respectively + pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ABGR" : "BGR", pixels); + + // Init databuffer with array, to avoid allocation of empty array + DataBuffer buffer = new DataBufferByte(pixels, pixels.length); + + int[] bandOffsets = pAlpha ? BAND_OFF_TRANS : BAND_OFF_OPAQUE; + + WritableRaster raster = + Raster.createInterleavedRaster(buffer, size.width, size.height, + size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT); + + return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null); + } + + + + + /** + * Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile + * + * @param pImage the original {@code MagickImage} + * @param pAlpha keep alpha channel + * @return a new {@code BufferedImage} + * + * @throws MagickException if an exception occurs during conversion + * + * @see BufferedImage + */ + private static BufferedImage cmykToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException { + Dimension size = pImage.getDimension(); + int length = size.width * size.height; + + // Retreive the ICC profile + ICC_Profile profile = ICC_Profile.getInstance(pImage.getColorProfile().getInfo()); + ColorSpace cs = new ICC_ColorSpace(profile); + + int bands = cs.getNumComponents() + (pAlpha ? 1 : 0); + + int[] bits = new int[bands]; + for (int i = 0; i < bands; i++) { + bits[i] = 8; + } + + ColorModel cm = pAlpha ? + new ComponentColorModel(cs, bits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE) : + new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); + + byte[] pixels = new byte[length * bands]; + + // TODO: If we do multiple dispatches (one per line, typically), we could provide listener + // feedback. But it's currently a lot slower than fetching all the pixels in one go. + // TODO: handle more generic cases if profile is not CMYK + // TODO: Test "ACMYK" + pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ACMYK" : "CMYK", pixels); + + // Init databuffer with array, to avoid allocation of empty array + DataBuffer buffer = new DataBufferByte(pixels, pixels.length); + + // TODO: build array from bands variable, here it just works for CMYK + // The values has not been tested with an alpha picture actually... + int[] bandOffsets = pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3}; + + WritableRaster raster = + Raster.createInterleavedRaster(buffer, size.width, size.height, + size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT); + + return new BufferedImage(cm, raster, pAlpha, null); + + } +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/MonochromeColorModel.java b/common/common-image/src/main/java/com/twelvemonkeys/image/MonochromeColorModel.java index 5e92dd86..275f9b9c 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/MonochromeColorModel.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/MonochromeColorModel.java @@ -4,31 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; -import java.awt.image.*; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; /** * Monochrome B/W color model. diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/PixelizeOp.java b/common/common-image/src/main/java/com/twelvemonkeys/image/PixelizeOp.java index f71dc330..8d850ab8 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/PixelizeOp.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/PixelizeOp.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.image; diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java index 73cc9ccb..d61d220d 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java @@ -1,1629 +1,1639 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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. - */ -/* - ******************************************************************************* - * - * Based on example code found in Graphics Gems III, Filtered Image Rescaling - * (filter_rcg.c), available from http://www.acm.org/tog/GraphicsGems/. - * - * Public Domain 1991 by Dale Schumacher. Mods by Ray Gardener - * - * Original by Dale Schumacher (fzoom) - * - * Additional changes by Ray Gardener, Daylon Graphics Ltd. - * December 4, 1999 - * - ******************************************************************************* - * - * Aditional changes inspired by ImageMagick's resize.c. - * - ******************************************************************************* - * - * Java port and additional changes/bugfixes by Harald Kuhr, Twelvemonkeys. - * February 20, 2006 - * - ******************************************************************************* - */ - -package com.twelvemonkeys.image; - -import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.*; - -/** - * Resamples (scales) a {@code BufferedImage} to a new width and height, using - * high performance and high quality algorithms. - * Several different interpolation algorithms may be specifed in the - * constructor, either using the - * filter type constants, or one of the - * {@code RendereingHints}. - *

- * For fastest results, use {@link #FILTER_POINT} or {@link #FILTER_BOX}. - * In most cases, {@link #FILTER_TRIANGLE} will produce acceptable results, while - * being relatively fast. - * For higher quality output, use more sophisticated interpolation algorithms, - * like {@link #FILTER_MITCHELL} or {@link #FILTER_LANCZOS}. - *

- * Example: - *

- * BufferedImage image;
- * 

- * //... - *

- * ResampleOp resampler = new ResampleOp(100, 100, ResampleOp.FILTER_TRIANGLE); - * BufferedImage thumbnail = resampler.filter(image, null); - *

- *

- * If your imput image is very large, it's possible to first resample using the - * very fast {@code FILTER_POINT} algorithm, then resample to the wanted size, - * using a higher quality algorithm: - *

- * BufferedImage verylLarge;
- * 

- * //... - *

- * int w = 300; - * int h = 200; - *

- * BufferedImage temp = new ResampleOp(w * 2, h * 2, FILTER_POINT).filter(verylLarge, null); - *

- * BufferedImage scaled = new ResampleOp(w, h).filter(temp, null); - *

- *

- * For maximum performance, this class will use native code, through - * JMagick, when available. - * Otherwise, the class will silently fall back to pure Java mode. - * Native code may be disabled globally, by setting the system property - * {@code com.twelvemonkeys.image.accel} to {@code false}. - * To allow debug of the native code, set the system property - * {@code com.twelvemonkeys.image.magick.debug} to {@code true}. - *

- * This {@code BufferedImageOp} is based on C example code found in - * Graphics Gems III, - * Filtered Image Rescaling, by Dale Schumacher (with additional improvments by - * Ray Gardener). - * Additional changes are inspired by - * ImageMagick and - * Marco Schmidt's Java Imaging Utilities - * (which are also adaptions of the same original code from Graphics Gems III). - *

- * For a description of the various interpolation algorithms, see - * General Filtered Image Rescaling in Graphics Gems III, - * Academic Press, 1994. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ResampleOp.java#1 $ - * @see #ResampleOp(int,int,int) - * @see #ResampleOp(int,int,java.awt.RenderingHints) - * @see BufferedImage - * @see RenderingHints - * @see AffineTransformOp - */ -// TODO: Consider using AffineTransformOp for more operations!? -public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { - - // NOTE: These MUST correspond to ImageMagick filter types, for the - // MagickAccelerator to work consistently (see magick.FilterType). - - /** - * Undefined interpolation, filter method will use default filter. - */ - public final static int FILTER_UNDEFINED = 0; - /** - * Point interpolation (also known as "nearest neighbour"). - * Very fast, but low quality - * (similar to {@link RenderingHints#VALUE_INTERPOLATION_NEAREST_NEIGHBOR} - * and {@link Image#SCALE_REPLICATE}). - */ - public final static int FILTER_POINT = 1; - /** - * Box interpolation. Fast, but low quality. - */ - public final static int FILTER_BOX = 2; - /** - * Triangle interpolation (also known as "linear" or "bilinear"). - * Quite fast, with acceptable quality - * (similar to {@link RenderingHints#VALUE_INTERPOLATION_BILINEAR} and - * {@link Image#SCALE_AREA_AVERAGING}). - */ - public final static int FILTER_TRIANGLE = 3; - /** - * Hermite interpolation. - */ - public final static int FILTER_HERMITE = 4; - /** - * Hanning interpolation. - */ - public final static int FILTER_HANNING = 5; - /** - * Hamming interpolation. - */ - public final static int FILTER_HAMMING = 6; - /** - * Blackman interpolation.. - */ - public final static int FILTER_BLACKMAN = 7; - /** - * Gaussian interpolation. - */ - public final static int FILTER_GAUSSIAN = 8; - /** - * Quadratic interpolation. - */ - public final static int FILTER_QUADRATIC = 9; - /** - * Cubic interpolation. - */ - public final static int FILTER_CUBIC = 10; - /** - * Catrom interpolation. - */ - public final static int FILTER_CATROM = 11; - /** - * Mitchell interpolation. High quality. - */ - public final static int FILTER_MITCHELL = 12; // IM default scale with palette or alpha, or scale up - /** - * Lanczos interpolation. High quality. - */ - public final static int FILTER_LANCZOS = 13; // IM default - /** - * Blackman-Bessel interpolation. High quality. - */ - public final static int FILTER_BLACKMAN_BESSEL = 14; - /** - * Blackman-Sinc interpolation. High quality. - */ - public final static int FILTER_BLACKMAN_SINC = 15; - - /** - * RenderingHints.Key specifying resampling interpolation algorithm. - */ - public final static RenderingHints.Key KEY_RESAMPLE_INTERPOLATION = new Key("ResampleInterpolation"); - - /** - * @see #FILTER_POINT - */ - public final static Object VALUE_INTERPOLATION_POINT = - new Value(KEY_RESAMPLE_INTERPOLATION, "Point", FILTER_POINT); - /** - * @see #FILTER_BOX - */ - public final static Object VALUE_INTERPOLATION_BOX = - new Value(KEY_RESAMPLE_INTERPOLATION, "Box", FILTER_BOX); - /** - * @see #FILTER_TRIANGLE - */ - public final static Object VALUE_INTERPOLATION_TRIANGLE = - new Value(KEY_RESAMPLE_INTERPOLATION, "Triangle", FILTER_TRIANGLE); - /** - * @see #FILTER_HERMITE - */ - public final static Object VALUE_INTERPOLATION_HERMITE = - new Value(KEY_RESAMPLE_INTERPOLATION, "Hermite", FILTER_HERMITE); - /** - * @see #FILTER_HANNING - */ - public final static Object VALUE_INTERPOLATION_HANNING = - new Value(KEY_RESAMPLE_INTERPOLATION, "Hanning", FILTER_HANNING); - /** - * @see #FILTER_HAMMING - */ - public final static Object VALUE_INTERPOLATION_HAMMING = - new Value(KEY_RESAMPLE_INTERPOLATION, "Hamming", FILTER_HAMMING); - /** - * @see #FILTER_BLACKMAN - */ - public final static Object VALUE_INTERPOLATION_BLACKMAN = - new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman", FILTER_BLACKMAN); - /** - * @see #FILTER_GAUSSIAN - */ - public final static Object VALUE_INTERPOLATION_GAUSSIAN = - new Value(KEY_RESAMPLE_INTERPOLATION, "Gaussian", FILTER_GAUSSIAN); - /** - * @see #FILTER_QUADRATIC - */ - public final static Object VALUE_INTERPOLATION_QUADRATIC = - new Value(KEY_RESAMPLE_INTERPOLATION, "Quadratic", FILTER_QUADRATIC); - /** - * @see #FILTER_CUBIC - */ - public final static Object VALUE_INTERPOLATION_CUBIC = - new Value(KEY_RESAMPLE_INTERPOLATION, "Cubic", FILTER_CUBIC); - /** - * @see #FILTER_CATROM - */ - public final static Object VALUE_INTERPOLATION_CATROM = - new Value(KEY_RESAMPLE_INTERPOLATION, "Catrom", FILTER_CATROM); - /** - * @see #FILTER_MITCHELL - */ - public final static Object VALUE_INTERPOLATION_MITCHELL = - new Value(KEY_RESAMPLE_INTERPOLATION, "Mitchell", FILTER_MITCHELL); - /** - * @see #FILTER_LANCZOS - */ - public final static Object VALUE_INTERPOLATION_LANCZOS = - new Value(KEY_RESAMPLE_INTERPOLATION, "Lanczos", FILTER_LANCZOS); - /** - * @see #FILTER_BLACKMAN_BESSEL - */ - public final static Object VALUE_INTERPOLATION_BLACKMAN_BESSEL = - new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Bessel", FILTER_BLACKMAN_BESSEL); - /** - * @see #FILTER_BLACKMAN_SINC - */ - public final static Object VALUE_INTERPOLATION_BLACKMAN_SINC = - new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC); - - // Member variables - // Package access, to allow access from MagickAccelerator - int width; - int height; - - int filterType; - - /** - * RendereingHints.Key implementation, works only with Value values. - */ - // TODO: Move to abstract class AbstractBufferedImageOp? - static class Key extends RenderingHints.Key { - static int sIndex = 10000; - - private final String name; - - public Key(final String pName) { - super(sIndex++); - name = pName; - } - - public boolean isCompatibleValue(Object pValue) { - return pValue instanceof Value && ((Value) pValue).isCompatibleKey(this); - } - - public String toString() { - return name; - } - } - - /** - * RenderingHints value implementation, works with Key keys. - */ - // TODO: Extract abstract Value class, and move to AbstractBufferedImageOp - static final class Value { - final private RenderingHints.Key key; - final private String name; - final private int type; - - public Value(final RenderingHints.Key pKey, final String pName, final int pType) { - key = pKey; - name = pName; - type = validateFilterType(pType); - } - - public boolean isCompatibleKey(Key pKey) { - return pKey == key; - } - - public int getFilterType() { - return type; - } - - public String toString() { - return name; - } - } - - /** - * Creates a {@code ResampleOp} that will resample input images to the - * given width and height, using the default interpolation filter. - * - * @param width width of the re-sampled image - * @param height height of the re-sampled image - */ - public ResampleOp(int width, int height) { - this(width, height, FILTER_UNDEFINED); - } - - /** - * Creates a {@code ResampleOp} that will resample input images to the - * given width and height, using the interpolation filter specified by - * the given hints. - * If using {@code RenderingHints}, the hints are mapped as follows: - *

    - *
  • {@code KEY_RESAMPLE_INTERPOLATION} takes precedence over any - * standard {@code java.awt} hints, and dictates interpolation - * directly, see - * {@code RenderingHints} constants.
  • - *

    - *

  • {@code KEY_INTERPOLATION} takes precedence over other hints. - *
      - *
    • {@link RenderingHints#VALUE_INTERPOLATION_NEAREST_NEIGHBOR} specifies - * {@code FILTER_POINT}
    • - *
    • {@link RenderingHints#VALUE_INTERPOLATION_BILINEAR} specifies - * {@code FILTER_TRIANGLE}
    • - *
    • {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} specifies - * {@code FILTER_QUADRATIC}
    • - *
    - *
  • - *

    - *

  • {@code KEY_RENDERING} or {@code KEY_COLOR_RENDERING} - *
      - *
    • {@link RenderingHints#VALUE_RENDER_SPEED} specifies - * {@code FILTER_POINT}
    • - *
    • {@link RenderingHints#VALUE_RENDER_QUALITY} specifies - * {@code FILTER_MITCHELL}
    • - *
    - *
  • - *
- * Other hints have no effect on this filter. - * - * @param width width of the re-sampled image - * @param height height of the re-sampled image - * @param hints rendering hints, affecting interpolation algorithm - * @see #KEY_RESAMPLE_INTERPOLATION - * @see RenderingHints#KEY_INTERPOLATION - * @see RenderingHints#KEY_RENDERING - * @see RenderingHints#KEY_COLOR_RENDERING - */ - public ResampleOp(int width, int height, RenderingHints hints) { - this(width, height, getFilterType(hints)); - } - - /** - * Creates a {@code ResampleOp} that will resample input images to the - * given width and height, using the given interpolation filter. - * - * @param width width of the re-sampled image - * @param height height of the re-sampled image - * @param filterType interpolation filter algorithm - * @see filter type constants - */ - public ResampleOp(int width, int height, int filterType) { - if (width <= 0 || height <= 0) { - // NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P - throw new IllegalArgumentException("width and height must be positive"); - } - - this.width = width; - this.height = height; - - this.filterType = validateFilterType(filterType); - } - - private static int validateFilterType(int pFilterType) { - switch (pFilterType) { - case FILTER_UNDEFINED: - case FILTER_POINT: - case FILTER_BOX: - case FILTER_TRIANGLE: - case FILTER_HERMITE: - case FILTER_HANNING: - case FILTER_HAMMING: - case FILTER_BLACKMAN: - case FILTER_GAUSSIAN: - case FILTER_QUADRATIC: - case FILTER_CUBIC: - case FILTER_CATROM: - case FILTER_MITCHELL: - case FILTER_LANCZOS: - case FILTER_BLACKMAN_BESSEL: - case FILTER_BLACKMAN_SINC: - return pFilterType; - default: - throw new IllegalArgumentException("Unknown filter type: " + pFilterType); - } - } - - /** - * Gets the filter type specified by the given hints. - * - * @param pHints rendering hints - * @return a filter type constant - */ - private static int getFilterType(RenderingHints pHints) { - if (pHints == null) { - return FILTER_UNDEFINED; - } - - if (pHints.containsKey(KEY_RESAMPLE_INTERPOLATION)) { - Object value = pHints.get(KEY_RESAMPLE_INTERPOLATION); - // NOTE: Workaround for a bug in RenderingHints constructor (Bug id# 5084832) - if (!KEY_RESAMPLE_INTERPOLATION.isCompatibleValue(value)) { - throw new IllegalArgumentException(value + " incompatible with key " + KEY_RESAMPLE_INTERPOLATION); - } - return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED; - } - else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION)) - || (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION) - && (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING)) - || RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) { - // Nearest neighbour, or prioritize speed - return FILTER_POINT; - } - else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) { - // Triangle equals bi-linear interpolation - return FILTER_TRIANGLE; - } - else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) { - return FILTER_QUADRATIC;// No idea if this is correct..? - } - else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING)) - || RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) { - // Prioritize quality - return FILTER_MITCHELL; - } - - // NOTE: Other hints are ignored - return FILTER_UNDEFINED; - } - - /** - * Re-samples (scales) the image to the size, and using the algorithm - * specified in the constructor. - * - * @param input The {@code BufferedImage} to be filtered - * @param output The {@code BufferedImage} in which to store the resampled - * image - * @return The re-sampled {@code BufferedImage}. - * @throws NullPointerException if {@code input} is {@code null} - * @throws IllegalArgumentException if {@code input == output}. - * @see #ResampleOp(int,int,int) - */ - public final BufferedImage filter(final BufferedImage input, final BufferedImage output) { - if (input == null) { - throw new NullPointerException("Input == null"); - } - if (input == output) { - throw new IllegalArgumentException("Output image cannot be the same as the input image"); - } - - InterpolationFilter filter; - - // Special case for POINT, TRIANGLE and QUADRATIC filter, as standard - // Java implementation is very fast (possibly H/W accelerated) - switch (filterType) { - case FILTER_POINT: - if (input.getType() != BufferedImage.TYPE_CUSTOM) { - return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); - } - // Else fall through - case FILTER_TRIANGLE: - if (input.getType() != BufferedImage.TYPE_CUSTOM) { - return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR); - } - // Else fall through - case FILTER_QUADRATIC: - if (input.getType() != BufferedImage.TYPE_CUSTOM) { - return fastResample(input, output, width, height, AffineTransformOp.TYPE_BICUBIC); - } - // Else fall through - default: - filter = createFilter(filterType); - // NOTE: Workaround for filter throwing exceptions when input or output is less than support... - if (Math.min(input.getWidth(), input.getHeight()) <= filter.support() || Math.min(width, height) <= filter.support()) { - return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR); - } - // Fall through - } - - // Try to use native ImageMagick code - BufferedImage result = MagickAccelerator.filter(this, input, output); - if (result != null) { - return result; - } - - // Otherwise, continue in pure Java mode - - // TODO: What if output != null and wrong size? Create new? Render on only a part? Document? - - // If filter type != POINT or BOX and input has IndexColorModel, convert - // to true color, with alpha reflecting that of the original color model. - BufferedImage temp; - ColorModel cm; - if (filterType != FILTER_POINT && filterType != FILTER_BOX && (cm = input.getColorModel()) instanceof IndexColorModel) { - // TODO: OPTIMIZE: If color model has only b/w or gray, we could skip color info - temp = ImageUtil.toBuffered(input, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); - } - else { - temp = input; - } - - // Create or convert output to a suitable image - // TODO: OPTIMIZE: Don't really need to convert all types to same as input - result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null); - - resample(temp, result, filter); - - // If output != null and needed to be converted, draw it back - if (output != null && output != result) { - //output.setData(output.getRaster()); - ImageUtil.drawOnto(output, result); - result = output; - } - - return result; - } - - /* - private static BufferedImage pointResample(final BufferedImage pInput, final BufferedImage pOutput, final int pWidth, final int pHeight) { - double xScale = pWidth / (double) pInput.getWidth(); - double yScale = pHeight / (double) pInput.getHeight(); - - // NOTE: This is extremely fast, native, possibly H/W accelerated code - AffineTransform transform = AffineTransform.getScaleInstance(xScale, yScale); - AffineTransformOp scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); - return scale.filter(pInput, pOutput); - } - */ - - /* - // TODO: This idea from Chet and Romain is actually not too bad.. - // It reuses the image/raster/graphics... - // However, they don't end with a halve operation.. - private static BufferedImage getFasterScaledInstance(BufferedImage img, - int targetWidth, int targetHeight, Object hint, - boolean progressiveBilinear) { - int type = (img.getTransparency() == Transparency.OPAQUE) ? - BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; - BufferedImage ret = img; - BufferedImage scratchImage = null; - Graphics2D g2 = null; - int w, h; - int prevW = ret.getWidth(); - int prevH = ret.getHeight(); - boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE; - - if (progressiveBilinear) { - // Use multi-step technique: start with original size, then - // scale down in multiple passes with drawImage() - // until the target size is reached - w = img.getWidth(); - h = img.getHeight(); - } else { - // Use one-step technique: scale directly from original - // size to target size with a single drawImage() call - w = targetWidth; - h = targetHeight; - } - - do { - if (progressiveBilinear && w > targetWidth) { - w /= 2; - if (w < targetWidth) { - w = targetWidth; - } - } - - if (progressiveBilinear && h > targetHeight) { - h /= 2; - if (h < targetHeight) { - h = targetHeight; - } - } - - if (scratchImage == null || isTranslucent) { - // Use a single scratch buffer for all iterations - // and then copy to the final, correctly-sized image - // before returning - scratchImage = new BufferedImage(w, h, type); - g2 = scratchImage.createGraphics(); - } - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); - g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null); - prevW = w; - prevH = h; - - ret = scratchImage; - } while (w != targetWidth || h != targetHeight); - - if (g2 != null) { - g2.dispose(); - } - - // If we used a scratch buffer that is larger than our target size, - // create an image of the right size and copy the results into it - if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) { - scratchImage = new BufferedImage(targetWidth, targetHeight, type); - g2 = scratchImage.createGraphics(); - g2.drawImage(ret, 0, 0, null); - g2.dispose(); - ret = scratchImage; - } - - return ret; - } - */ - - private static BufferedImage fastResample(final BufferedImage input, final BufferedImage output, final int width, final int height, final int type) { - BufferedImage temp = input; - - double xScale; - double yScale; - - AffineTransform transform; - AffineTransformOp scale; - - if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { - // Initially scale so all remaining operations will halve the image - if (width < input.getWidth() || height < input.getHeight()) { - int w = width; - int h = height; - while (w < input.getWidth() / 2) { - w *= 2; - } - while (h < input.getHeight() / 2) { - h *= 2; - } - - xScale = w / (double) input.getWidth(); - yScale = h / (double) input.getHeight(); - - //System.out.println("First scale by x=" + xScale + ", y=" + yScale); - - transform = AffineTransform.getScaleInstance(xScale, yScale); - scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); - temp = scale.filter(temp, null); - } - } - - scale = null; // NOTE: This resets! - - xScale = width / (double) temp.getWidth(); - yScale = height / (double) temp.getHeight(); - - if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { - // TODO: Test skipping first scale (above), and instead scale once - // more here, and a little less than .5 each time... - // That would probably make the scaling smoother... - while (xScale < 0.5 || yScale < 0.5) { - if (xScale >= 0.5) { - //System.out.println("Halving by y=" + (yScale * 2.0)); - transform = AffineTransform.getScaleInstance(1.0, 0.5); - scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); - - yScale *= 2.0; - } - else if (yScale >= 0.5) { - //System.out.println("Halving by x=" + (xScale * 2.0)); - transform = AffineTransform.getScaleInstance(0.5, 1.0); - scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); - - xScale *= 2.0; - } - else { - //System.out.println("Halving by x=" + (xScale * 2.0) + ", y=" + (yScale * 2.0)); - xScale *= 2.0; - yScale *= 2.0; - } - - if (scale == null) { - transform = AffineTransform.getScaleInstance(0.5, 0.5); - scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); - } - - temp = scale.filter(temp, null); - } - } - - //System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale); - - transform = AffineTransform.getScaleInstance(xScale, yScale); - scale = new AffineTransformOp(transform, type); - - return scale.filter(temp, output); - } - - /** - * Returns the current filter type constant. - * - * @return the current filter type constant. - * @see filter type constants - */ - public int getFilterType() { - return filterType; - } - - private static InterpolationFilter createFilter(int pFilterType) { - // TODO: Select correct filter based on scale up or down, if undefined! - if (pFilterType == FILTER_UNDEFINED) { - pFilterType = FILTER_LANCZOS; - } - - switch (pFilterType) { - case FILTER_POINT: - return new PointFilter(); - case FILTER_BOX: - return new BoxFilter(); - case FILTER_TRIANGLE: - return new TriangleFilter(); - case FILTER_HERMITE: - return new HermiteFilter(); - case FILTER_HANNING: - return new HanningFilter(); - case FILTER_HAMMING: - return new HammingFilter(); - case FILTER_BLACKMAN: - return new BlacmanFilter(); - case FILTER_GAUSSIAN: - return new GaussianFilter(); - case FILTER_QUADRATIC: - return new QuadraticFilter(); - case FILTER_CUBIC: - return new CubicFilter(); - case FILTER_CATROM: - return new CatromFilter(); - case FILTER_MITCHELL: - return new MitchellFilter(); - case FILTER_LANCZOS: - return new LanczosFilter(); - case FILTER_BLACKMAN_BESSEL: - return new BlackmanBesselFilter(); - case FILTER_BLACKMAN_SINC: - return new BlackmanSincFilter(); - default: - throw new IllegalStateException("Unknown filter type: " + pFilterType); - } - } - - public final BufferedImage createCompatibleDestImage(final BufferedImage pInput, final ColorModel pModel) { - if (pInput == null) { - throw new NullPointerException("pInput == null"); - } - - ColorModel cm = pModel != null ? pModel : pInput.getColorModel(); - - // TODO: Might not work with all colormodels.. - // If indexcolormodel, we probably don't want to use that... - // NOTE: Either BOTH or NONE of the images must have ALPHA - - return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, width, height), - cm.isAlphaPremultiplied(), null); - } - - public RenderingHints getRenderingHints() { - Object value; - switch (filterType) { - case FILTER_UNDEFINED: - return null; - case FILTER_POINT: - value = VALUE_INTERPOLATION_POINT; - break; - case FILTER_BOX: - value = VALUE_INTERPOLATION_BOX; - break; - case FILTER_TRIANGLE: - value = VALUE_INTERPOLATION_TRIANGLE; - break; - case FILTER_HERMITE: - value = VALUE_INTERPOLATION_HERMITE; - break; - case FILTER_HANNING: - value = VALUE_INTERPOLATION_HANNING; - break; - case FILTER_HAMMING: - value = VALUE_INTERPOLATION_HAMMING; - break; - case FILTER_BLACKMAN: - value = VALUE_INTERPOLATION_BLACKMAN; - break; - case FILTER_GAUSSIAN: - value = VALUE_INTERPOLATION_GAUSSIAN; - break; - case FILTER_QUADRATIC: - value = VALUE_INTERPOLATION_QUADRATIC; - break; - case FILTER_CUBIC: - value = VALUE_INTERPOLATION_CUBIC; - break; - case FILTER_CATROM: - value = VALUE_INTERPOLATION_CATROM; - break; - case FILTER_MITCHELL: - value = VALUE_INTERPOLATION_MITCHELL; - break; - case FILTER_LANCZOS: - value = VALUE_INTERPOLATION_LANCZOS; - break; - case FILTER_BLACKMAN_BESSEL: - value = VALUE_INTERPOLATION_BLACKMAN_BESSEL; - break; - case FILTER_BLACKMAN_SINC: - value = VALUE_INTERPOLATION_BLACKMAN_SINC; - break; - default: - throw new IllegalStateException("Unknown filter type: " + filterType); - } - - return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value); - } - - public Rectangle2D getBounds2D(BufferedImage src) { - return new Rectangle(width, height); - } - - public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { - // TODO: This is wrong... - // How can I possible know how much one point is scaled, without first knowing the ration?! - // TODO: Maybe set all points outside of bounds, inside? - // TODO: Assume input image of Integer.MAX_VAL x Integer.MAX_VAL?! ;-) - if (dstPt == null) { - if (srcPt instanceof Point2D.Double) { - dstPt = new Point2D.Double(); - } - else { - dstPt = new Point2D.Float(); - } - dstPt.setLocation(srcPt); - } - return dstPt; - } - - /* -- Java port of filter_rcg.c below... -- */ - - /* - * filter function definitions - */ - - interface InterpolationFilter { - double filter(double t); - - double support(); - } - - static class HermiteFilter implements InterpolationFilter { - public final double filter(double t) { - /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ - if (t < 0.0) { - t = -t; - } - if (t < 1.0) { - return (2.0 * t - 3.0) * t * t + 1.0; - } - return 0.0; - } - - public final double support() { - return 1.0; - } - } - - static class PointFilter extends BoxFilter { - public PointFilter() { - super(0.0); - } - } - - static class BoxFilter implements InterpolationFilter { - private final double mSupport; - - public BoxFilter() { - mSupport = 0.5; - } - - protected BoxFilter(double pSupport) { - mSupport = pSupport; - } - - public final double filter(final double t) { - //if ((t > -0.5) && (t <= 0.5)) { - if ((t >= -0.5) && (t < 0.5)) {// ImageMagick resample.c - return 1.0; - } - return 0.0; - } - - public final double support() { - return mSupport; - } - } - - static class TriangleFilter implements InterpolationFilter { - public final double filter(double t) { - if (t < 0.0) { - t = -t; - } - if (t < 1.0) { - return 1.0 - t; - } - return 0.0; - } - - public final double support() { - return 1.0; - } - } - - static class QuadraticFilter implements InterpolationFilter { - // AKA Bell - public final double filter(double t)/* box (*) box (*) box */ { - if (t < 0) { - t = -t; - } - if (t < .5) { - return .75 - (t * t); - } - if (t < 1.5) { - t = (t - 1.5); - return .5 * (t * t); - } - return 0.0; - } - - public final double support() { - return 1.5; - } - } - - static class CubicFilter implements InterpolationFilter { - // AKA B-Spline - public final double filter(double t)/* box (*) box (*) box (*) box */ { - final double tt; - - if (t < 0) { - t = -t; - } - if (t < 1) { - tt = t * t; - return (.5 * tt * t) - tt + (2.0 / 3.0); - } - else if (t < 2) { - t = 2 - t; - return (1.0 / 6.0) * (t * t * t); - } - return 0.0; - } - - public final double support() { - return 2.0; - } - } - - private static double sinc(double x) { - x *= Math.PI; - if (x != 0.0) { - return Math.sin(x) / x; - } - return 1.0; - } - - static class LanczosFilter implements InterpolationFilter { - // AKA Lanczos3 - public final double filter(double t) { - if (t < 0) { - t = -t; - } - if (t < 3.0) { - return sinc(t) * sinc(t / 3.0); - } - return 0.0; - } - - public final double support() { - return 3.0; - } - } - - private final static double B = 1.0 / 3.0; - private final static double C = 1.0 / 3.0; - private final static double P0 = (6.0 - 2.0 * B) / 6.0; - private final static double P2 = (-18.0 + 12.0 * B + 6.0 * C) / 6.0; - private final static double P3 = (12.0 - 9.0 * B - 6.0 * C) / 6.0; - private final static double Q0 = (8.0 * B + 24.0 * C) / 6.0; - private final static double Q1 = (-12.0 * B - 48.0 * C) / 6.0; - private final static double Q2 = (6.0 * B + 30.0 * C) / 6.0; - private final static double Q3 = (-1.0 * B - 6.0 * C) / 6.0; - - static class MitchellFilter implements InterpolationFilter { - public final double filter(double t) { - if (t < -2.0) { - return 0.0; - } - if (t < -1.0) { - return Q0 - t * (Q1 - t * (Q2 - t * Q3)); - } - if (t < 0.0) { - return P0 + t * t * (P2 - t * P3); - } - if (t < 1.0) { - return P0 + t * t * (P2 + t * P3); - } - if (t < 2.0) { - return Q0 + t * (Q1 + t * (Q2 + t * Q3)); - } - return 0.0; - } - - public final double support() { - return 2.0; - } - } - - private static double j1(final double t) { - final double[] pOne = { - 0.581199354001606143928050809e+21, - -0.6672106568924916298020941484e+20, - 0.2316433580634002297931815435e+19, - -0.3588817569910106050743641413e+17, - 0.2908795263834775409737601689e+15, - -0.1322983480332126453125473247e+13, - 0.3413234182301700539091292655e+10, - -0.4695753530642995859767162166e+7, - 0.270112271089232341485679099e+4 - }; - final double[] qOne = { - 0.11623987080032122878585294e+22, - 0.1185770712190320999837113348e+20, - 0.6092061398917521746105196863e+17, - 0.2081661221307607351240184229e+15, - 0.5243710262167649715406728642e+12, - 0.1013863514358673989967045588e+10, - 0.1501793594998585505921097578e+7, - 0.1606931573481487801970916749e+4, - 0.1e+1 - }; - - double p = pOne[8]; - double q = qOne[8]; - for (int i = 7; i >= 0; i--) { - p = p * t * t + pOne[i]; - q = q * t * t + qOne[i]; - } - return p / q; - } - - private static double p1(final double t) { - final double[] pOne = { - 0.352246649133679798341724373e+5, - 0.62758845247161281269005675e+5, - 0.313539631109159574238669888e+5, - 0.49854832060594338434500455e+4, - 0.2111529182853962382105718e+3, - 0.12571716929145341558495e+1 - }; - final double[] qOne = { - 0.352246649133679798068390431e+5, - 0.626943469593560511888833731e+5, - 0.312404063819041039923015703e+5, - 0.4930396490181088979386097e+4, - 0.2030775189134759322293574e+3, - 0.1e+1 - }; - - double p = pOne[5]; - double q = qOne[5]; - for (int i = 4; i >= 0; i--) { - p = p * (8.0 / t) * (8.0 / t) + pOne[i]; - q = q * (8.0 / t) * (8.0 / t) + qOne[i]; - } - return p / q; - } - - private static double q1(final double t) { - final double[] pOne = { - 0.3511751914303552822533318e+3, - 0.7210391804904475039280863e+3, - 0.4259873011654442389886993e+3, - 0.831898957673850827325226e+2, - 0.45681716295512267064405e+1, - 0.3532840052740123642735e-1 - }; - final double[] qOne = { - 0.74917374171809127714519505e+4, - 0.154141773392650970499848051e+5, - 0.91522317015169922705904727e+4, - 0.18111867005523513506724158e+4, - 0.1038187585462133728776636e+3, - 0.1e+1 - }; - - double p = pOne[5]; - double q = qOne[5]; - for (int i = 4; i >= 0; i--) { - p = p * (8.0 / t) * (8.0 / t) + pOne[i]; - q = q * (8.0 / t) * (8.0 / t) + qOne[i]; - } - return p / q; - } - - static double besselOrderOne(double t) { - double p, q; - - if (t == 0.0) { - return 0.0; - } - p = t; - if (t < 0.0) { - t = -t; - } - if (t < 8.0) { - return p * j1(t); - } - q = Math.sqrt(2.0 / (Math.PI * t)) * (p1(t) * (1.0 / Math.sqrt(2.0) * (Math.sin(t) - Math.cos(t))) - 8.0 / t * q1(t) * - (-1.0 / Math.sqrt(2.0) * (Math.sin(t) + Math.cos(t)))); - if (p < 0.0) { - q = -q; - } - return q; - } - - private static double bessel(final double t) { - if (t == 0.0) { - return Math.PI / 4.0; - } - return besselOrderOne(Math.PI * t) / (2.0 * t); - } - - private static double blackman(final double t) { - return 0.42 + 0.50 * Math.cos(Math.PI * t) + 0.08 * Math.cos(2.0 * Math.PI * t); - } - - static class BlacmanFilter implements InterpolationFilter { - public final double filter(final double t) { - return blackman(t); - } - - public final double support() { - return 1.0; - } - } - - static class CatromFilter implements InterpolationFilter { - public final double filter(double t) { - if (t < 0) { - t = -t; - } - if (t < 1.0) { - return 0.5 * (2.0 + t * t * (-5.0 + t * 3.0)); - } - if (t < 2.0) { - return 0.5 * (4.0 + t * (-8.0 + t * (5.0 - t))); - } - return 0.0; - } - - public final double support() { - return 2.0; - } - } - - static class GaussianFilter implements InterpolationFilter { - public final double filter(final double t) { - return Math.exp(-2.0 * t * t) * Math.sqrt(2.0 / Math.PI); - } - - public final double support() { - return 1.25; - } - } - - static class HanningFilter implements InterpolationFilter { - public final double filter(final double t) { - return 0.5 + 0.5 * Math.cos(Math.PI * t); - } - - public final double support() { - return 1.0; - } - } - - static class HammingFilter implements InterpolationFilter { - public final double filter(final double t) { - return 0.54 + 0.46 * Math.cos(Math.PI * t); - } - - public final double support() { - return 1.0; - } - } - - static class BlackmanBesselFilter implements InterpolationFilter { - public final double filter(final double t) { - return blackman(t / support()) * bessel(t); - } - - public final double support() { - return 3.2383; - } - } - - static class BlackmanSincFilter implements InterpolationFilter { - public final double filter(final double t) { - return blackman(t / support()) * sinc(t); - } - - public final double support() { - return 4.0; - } - } - - /* - * image rescaling routine - */ - class Contributor { - int pixel; - double weight; - } - - class ContributorList { - int n;/* number of contributors (may be < p.length) */ - Contributor[] p;/* pointer to list of contributions */ - } - - /* - round() - - Round an FP value to its closest int representation. - General routine; ideally belongs in general math lib file. - */ - - static int round(double d) { - // NOTE: This code seems to be faster than Math.round(double)... - // Version that uses no function calls at all. - int n = (int) d; - double diff = d - (double) n; - if (diff < 0) { - diff = -diff; - } - if (diff >= 0.5) { - if (d < 0) { - n--; - } - else { - n++; - } - } - return n; - }/* round */ - - /* - calcXContrib() - - Calculates the filter weights for a single target column. - contribX->p must be freed afterwards. - - Returns -1 if error, 0 otherwise. - */ - private ContributorList calcXContrib(double xscale, double fwidth, int srcwidth, InterpolationFilter pFilter, int i) { - // TODO: What to do when fwidth > srcwidyj or dstwidth - - double width; - double fscale; - double center; - double weight; - - ContributorList contribX = new ContributorList(); - - if (xscale < 1.0) { - /* Shrinking image */ - width = fwidth / xscale; - fscale = 1.0 / xscale; - - if (width <= .5) { - // Reduce to point sampling. - width = .5 + 1.0e-6; - fscale = 1.0; - } - - //contribX.n = 0; - contribX.p = new Contributor[(int) (width * 2.0 + 1.0 + 0.5)]; - - center = (double) i / xscale; - int left = (int) Math.ceil(center - width);// Note: Assumes width <= .5 - int right = (int) Math.floor(center + width); - - double density = 0.0; - - for (int j = left; j <= right; j++) { - weight = center - (double) j; - weight = pFilter.filter(weight / fscale) / fscale; - int n; - if (j < 0) { - n = -j; - } - else if (j >= srcwidth) { - n = (srcwidth - j) + srcwidth - 1; - } - else { - n = j; - } - - /**/ - if (n >= srcwidth) { - n = n % srcwidth; - } - else if (n < 0) { - n = srcwidth - 1; - } - /**/ - - int k = contribX.n++; - contribX.p[k] = new Contributor(); - contribX.p[k].pixel = n; - contribX.p[k].weight = weight; - - density += weight; - - } - - if ((density != 0.0) && (density != 1.0)) { - //Normalize. - density = 1.0 / density; - for (int k = 0; k < contribX.n; k++) { - contribX.p[k].weight *= density; - } - } - } - else { - /* Expanding image */ - //contribX.n = 0; - contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0 + 0.5)]; - - center = (double) i / xscale; - int left = (int) Math.ceil(center - fwidth); - int right = (int) Math.floor(center + fwidth); - - for (int j = left; j <= right; j++) { - weight = center - (double) j; - weight = pFilter.filter(weight); - - int n; - if (j < 0) { - n = -j; - } - else if (j >= srcwidth) { - n = (srcwidth - j) + srcwidth - 1; - } - else { - n = j; - } - - /**/ - if (n >= srcwidth) { - n = n % srcwidth; - } - else if (n < 0) { - n = srcwidth - 1; - } - /**/ - - int k = contribX.n++; - contribX.p[k] = new Contributor(); - contribX.p[k].pixel = n; - contribX.p[k].weight = weight; - } - } - return contribX; - }/* calcXContrib */ - - /* - resample() - - Resizes bitmaps while resampling them. - */ - private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) { - final int dstWidth = pDest.getWidth(); - final int dstHeight = pDest.getHeight(); - - final int srcWidth = pSource.getWidth(); - final int srcHeight = pSource.getHeight(); - - /* create intermediate column to hold horizontal dst column zoom */ - final ColorModel cm = pSource.getColorModel(); -// final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight); - final WritableRaster work = ImageUtil.createCompatibleWritableRaster(pSource, cm, 1, srcHeight); - - double xscale = (double) dstWidth / (double) srcWidth; - double yscale = (double) dstHeight / (double) srcHeight; - - ContributorList[] contribY = new ContributorList[dstHeight]; - for (int i = 0; i < contribY.length; i++) { - contribY[i] = new ContributorList(); - } - - // TODO: What to do when fwidth > srcHeight or dstHeight - double fwidth = pFilter.support(); - if (yscale < 1.0) { - double width = fwidth / yscale; - double fscale = 1.0 / yscale; - - if (width <= .5) { - // Reduce to point sampling. - width = .5 + 1.0e-6; - fscale = 1.0; - } - - for (int i = 0; i < dstHeight; i++) { - //contribY[i].n = 0; - contribY[i].p = new Contributor[(int) (width * 2.0 + 1 + 0.5)]; - - double center = (double) i / yscale; - int left = (int) Math.ceil(center - width); - int right = (int) Math.floor(center + width); - - double density = 0.0; - - for (int j = left; j <= right; j++) { - double weight = center - (double) j; - weight = pFilter.filter(weight / fscale) / fscale; - int n; - if (j < 0) { - n = -j; - } - else if (j >= srcHeight) { - n = (srcHeight - j) + srcHeight - 1; - } - else { - n = j; - } - - /**/ - if (n >= srcHeight) { - n = n % srcHeight; - } - else if (n < 0) { - n = srcHeight - 1; - } - /**/ - - int k = contribY[i].n++; - contribY[i].p[k] = new Contributor(); - contribY[i].p[k].pixel = n; - contribY[i].p[k].weight = weight; - - density += weight; - } - - if ((density != 0.0) && (density != 1.0)) { - //Normalize. - density = 1.0 / density; - for (int k = 0; k < contribY[i].n; k++) { - contribY[i].p[k].weight *= density; - } - } - } - } - else { - for (int i = 0; i < dstHeight; ++i) { - //contribY[i].n = 0; - contribY[i].p = new Contributor[(int) (fwidth * 2 + 1 + 0.5)]; - - double center = (double) i / yscale; - double left = Math.ceil(center - fwidth); - double right = Math.floor(center + fwidth); - for (int j = (int) left; j <= right; ++j) { - double weight = center - (double) j; - weight = pFilter.filter(weight); - int n; - if (j < 0) { - n = -j; - } - else if (j >= srcHeight) { - n = (srcHeight - j) + srcHeight - 1; - } - else { - n = j; - } - - /**/ - if (n >= srcHeight) { - n = n % srcHeight; - } - else if (n < 0) { - n = srcHeight - 1; - } - /**/ - - int k = contribY[i].n++; - contribY[i].p[k] = new Contributor(); - contribY[i].p[k].pixel = n; - contribY[i].p[k].weight = weight; - } - } - } - - final Raster raster = pSource.getRaster(); - final WritableRaster out = pDest.getRaster(); - - // TODO: This is not optimal for non-byte-packed rasters... - // (What? Maybe I implemented the fix, but forgot to remove the TODO?) - final int numChannels = raster.getNumBands(); - final int[] channelMax = new int[numChannels]; - for (int k = 0; k < numChannels; k++) { - channelMax[k] = (1 << pSource.getColorModel().getComponentSize(k)) - 1; - } - - for (int xx = 0; xx < dstWidth; xx++) { - ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx); - /* Apply horiz filter to make dst column in tmp. */ - for (int k = 0; k < srcHeight; k++) { - for (int channel = 0; channel < numChannels; channel++) { - - double weight = 0.0; - boolean bPelDelta = false; - // TODO: This line throws index out of bounds, if the image - // is smaller than filter.support() - double pel = raster.getSample(contribX.p[0].pixel, k, channel); - for (int j = 0; j < contribX.n; j++) { - double pel2 = j == 0 ? pel : raster.getSample(contribX.p[j].pixel, k, channel); - if (pel2 != pel) { - bPelDelta = true; - } - weight += pel2 * contribX.p[j].weight; - } - weight = bPelDelta ? round(weight) : pel; - - if (weight < 0) { - weight = 0; - } - else if (weight > channelMax[channel]) { - weight = channelMax[channel]; - } - - work.setSample(0, k, channel, weight); - - } - }/* next row in temp column */ - - /* The temp column has been built. Now stretch it vertically into dst column. */ - for (int i = 0; i < dstHeight; i++) { - for (int channel = 0; channel < numChannels; channel++) { - - double weight = 0.0; - boolean bPelDelta = false; - double pel = work.getSample(0, contribY[i].p[0].pixel, channel); - - for (int j = 0; j < contribY[i].n; j++) { - // TODO: This line throws index out of bounds, if the image - // is smaller than filter.support() - double pel2 = j == 0 ? pel : work.getSample(0, contribY[i].p[j].pixel, channel); - if (pel2 != pel) { - bPelDelta = true; - } - weight += pel2 * contribY[i].p[j].weight; - } - weight = bPelDelta ? round(weight) : pel; - if (weight < 0) { - weight = 0; - } - else if (weight > channelMax[channel]) { - weight = channelMax[channel]; - } - - out.setSample(xx, i, channel, weight); - } - }/* next dst row */ - }/* next dst column */ - return pDest; - }/* resample */ +/* + * 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. + */ +/* + ******************************************************************************* + * + * Based on example code found in Graphics Gems III, Filtered Image Rescaling + * (filter_rcg.c), available from http://www.acm.org/tog/GraphicsGems/. + * + * Public Domain 1991 by Dale Schumacher. Mods by Ray Gardener + * + * Original by Dale Schumacher (fzoom) + * + * Additional changes by Ray Gardener, Daylon Graphics Ltd. + * December 4, 1999 + * + ******************************************************************************* + * + * Aditional changes inspired by ImageMagick's resize.c. + * + ******************************************************************************* + * + * Java port and additional changes/bugfixes by Harald Kuhr, Twelvemonkeys. + * February 20, 2006 + * + ******************************************************************************* + */ + +package com.twelvemonkeys.image; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.*; + +/** + * Resamples (scales) a {@code BufferedImage} to a new width and height, using + * high performance and high quality algorithms. + * Several different interpolation algorithms may be specifed in the + * constructor, either using the + * filter type constants, or one of the + * {@code RendereingHints}. + *

+ * For fastest results, use {@link #FILTER_POINT} or {@link #FILTER_BOX}. + * In most cases, {@link #FILTER_TRIANGLE} will produce acceptable results, while + * being relatively fast. + * For higher quality output, use more sophisticated interpolation algorithms, + * like {@link #FILTER_MITCHELL} or {@link #FILTER_LANCZOS}. + *

+ *

+ * Example: + *

+ *
+ * BufferedImage image;
+ *
+ * //...
+ *
+ * ResampleOp resampler = new ResampleOp(100, 100, ResampleOp.FILTER_TRIANGLE);
+ * BufferedImage thumbnail = resampler.filter(image, null);
+ * 
+ *

+ * If your input image is very large, it's possible to first resample using the + * very fast {@code FILTER_POINT} algorithm, then resample to the wanted size, + * using a higher quality algorithm: + *

+ *
+ * BufferedImage verylLarge;
+ *
+ * //...
+ *
+ * int w = 300;
+ * int h = 200;
+ *
+ * BufferedImage temp = new ResampleOp(w * 2, h * 2, FILTER_POINT).filter(verylLarge, null);
+ *
+ * BufferedImage scaled = new ResampleOp(w, h).filter(temp, null);
+ * 
+ *

+ * For maximum performance, this class will use native code, through + * JMagick, when available. + * Otherwise, the class will silently fall back to pure Java mode. + * Native code may be disabled globally, by setting the system property + * {@code com.twelvemonkeys.image.accel} to {@code false}. + * To allow debug of the native code, set the system property + * {@code com.twelvemonkeys.image.magick.debug} to {@code true}. + *

+ *

+ * This {@code BufferedImageOp} is based on C example code found in + * Graphics Gems III, + * Filtered Image Rescaling, by Dale Schumacher (with additional improvments by + * Ray Gardener). + * Additional changes are inspired by + * ImageMagick and + * Marco Schmidt's Java Imaging Utilities + * (which are also adaptions of the same original code from Graphics Gems III). + *

+ *

+ * For a description of the various interpolation algorithms, see + * General Filtered Image Rescaling in Graphics Gems III, + * Academic Press, 1994. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ResampleOp.java#1 $ + * @see #ResampleOp(int,int,int) + * @see #ResampleOp(int,int,java.awt.RenderingHints) + * @see BufferedImage + * @see RenderingHints + * @see AffineTransformOp + */ +// TODO: Consider using AffineTransformOp for more operations!? +public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { + + // NOTE: These MUST correspond to ImageMagick filter types, for the + // MagickAccelerator to work consistently (see magick.FilterType). + + /** + * Undefined interpolation, filter method will use default filter. + */ + public final static int FILTER_UNDEFINED = 0; + /** + * Point interpolation (also known as "nearest neighbour"). + * Very fast, but low quality + * (similar to {@link RenderingHints#VALUE_INTERPOLATION_NEAREST_NEIGHBOR} + * and {@link Image#SCALE_REPLICATE}). + */ + public final static int FILTER_POINT = 1; + /** + * Box interpolation. Fast, but low quality. + */ + public final static int FILTER_BOX = 2; + /** + * Triangle interpolation (also known as "linear" or "bilinear"). + * Quite fast, with acceptable quality + * (similar to {@link RenderingHints#VALUE_INTERPOLATION_BILINEAR} and + * {@link Image#SCALE_AREA_AVERAGING}). + */ + public final static int FILTER_TRIANGLE = 3; + /** + * Hermite interpolation. + */ + public final static int FILTER_HERMITE = 4; + /** + * Hanning interpolation. + */ + public final static int FILTER_HANNING = 5; + /** + * Hamming interpolation. + */ + public final static int FILTER_HAMMING = 6; + /** + * Blackman interpolation.. + */ + public final static int FILTER_BLACKMAN = 7; + /** + * Gaussian interpolation. + */ + public final static int FILTER_GAUSSIAN = 8; + /** + * Quadratic interpolation. + */ + public final static int FILTER_QUADRATIC = 9; + /** + * Cubic interpolation. + */ + public final static int FILTER_CUBIC = 10; + /** + * Catrom interpolation. + */ + public final static int FILTER_CATROM = 11; + /** + * Mitchell interpolation. High quality. + */ + public final static int FILTER_MITCHELL = 12; // IM default scale with palette or alpha, or scale up + /** + * Lanczos interpolation. High quality. + */ + public final static int FILTER_LANCZOS = 13; // IM default + /** + * Blackman-Bessel interpolation. High quality. + */ + public final static int FILTER_BLACKMAN_BESSEL = 14; + /** + * Blackman-Sinc interpolation. High quality. + */ + public final static int FILTER_BLACKMAN_SINC = 15; + + /** + * RenderingHints.Key specifying resampling interpolation algorithm. + */ + public final static RenderingHints.Key KEY_RESAMPLE_INTERPOLATION = new Key("ResampleInterpolation"); + + /** + * @see #FILTER_POINT + */ + public final static Object VALUE_INTERPOLATION_POINT = + new Value(KEY_RESAMPLE_INTERPOLATION, "Point", FILTER_POINT); + /** + * @see #FILTER_BOX + */ + public final static Object VALUE_INTERPOLATION_BOX = + new Value(KEY_RESAMPLE_INTERPOLATION, "Box", FILTER_BOX); + /** + * @see #FILTER_TRIANGLE + */ + public final static Object VALUE_INTERPOLATION_TRIANGLE = + new Value(KEY_RESAMPLE_INTERPOLATION, "Triangle", FILTER_TRIANGLE); + /** + * @see #FILTER_HERMITE + */ + public final static Object VALUE_INTERPOLATION_HERMITE = + new Value(KEY_RESAMPLE_INTERPOLATION, "Hermite", FILTER_HERMITE); + /** + * @see #FILTER_HANNING + */ + public final static Object VALUE_INTERPOLATION_HANNING = + new Value(KEY_RESAMPLE_INTERPOLATION, "Hanning", FILTER_HANNING); + /** + * @see #FILTER_HAMMING + */ + public final static Object VALUE_INTERPOLATION_HAMMING = + new Value(KEY_RESAMPLE_INTERPOLATION, "Hamming", FILTER_HAMMING); + /** + * @see #FILTER_BLACKMAN + */ + public final static Object VALUE_INTERPOLATION_BLACKMAN = + new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman", FILTER_BLACKMAN); + /** + * @see #FILTER_GAUSSIAN + */ + public final static Object VALUE_INTERPOLATION_GAUSSIAN = + new Value(KEY_RESAMPLE_INTERPOLATION, "Gaussian", FILTER_GAUSSIAN); + /** + * @see #FILTER_QUADRATIC + */ + public final static Object VALUE_INTERPOLATION_QUADRATIC = + new Value(KEY_RESAMPLE_INTERPOLATION, "Quadratic", FILTER_QUADRATIC); + /** + * @see #FILTER_CUBIC + */ + public final static Object VALUE_INTERPOLATION_CUBIC = + new Value(KEY_RESAMPLE_INTERPOLATION, "Cubic", FILTER_CUBIC); + /** + * @see #FILTER_CATROM + */ + public final static Object VALUE_INTERPOLATION_CATROM = + new Value(KEY_RESAMPLE_INTERPOLATION, "Catrom", FILTER_CATROM); + /** + * @see #FILTER_MITCHELL + */ + public final static Object VALUE_INTERPOLATION_MITCHELL = + new Value(KEY_RESAMPLE_INTERPOLATION, "Mitchell", FILTER_MITCHELL); + /** + * @see #FILTER_LANCZOS + */ + public final static Object VALUE_INTERPOLATION_LANCZOS = + new Value(KEY_RESAMPLE_INTERPOLATION, "Lanczos", FILTER_LANCZOS); + /** + * @see #FILTER_BLACKMAN_BESSEL + */ + public final static Object VALUE_INTERPOLATION_BLACKMAN_BESSEL = + new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Bessel", FILTER_BLACKMAN_BESSEL); + /** + * @see #FILTER_BLACKMAN_SINC + */ + public final static Object VALUE_INTERPOLATION_BLACKMAN_SINC = + new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC); + + // Member variables + // Package access, to allow access from MagickAccelerator + int width; + int height; + + int filterType; + + /** + * RendereingHints.Key implementation, works only with Value values. + */ + // TODO: Move to abstract class AbstractBufferedImageOp? + static class Key extends RenderingHints.Key { + static int sIndex = 10000; + + private final String name; + + public Key(final String pName) { + super(sIndex++); + name = pName; + } + + public boolean isCompatibleValue(Object pValue) { + return pValue instanceof Value && ((Value) pValue).isCompatibleKey(this); + } + + public String toString() { + return name; + } + } + + /** + * RenderingHints value implementation, works with Key keys. + */ + // TODO: Extract abstract Value class, and move to AbstractBufferedImageOp + static final class Value { + final private RenderingHints.Key key; + final private String name; + final private int type; + + public Value(final RenderingHints.Key pKey, final String pName, final int pType) { + key = pKey; + name = pName; + type = validateFilterType(pType); + } + + public boolean isCompatibleKey(Key pKey) { + return pKey == key; + } + + public int getFilterType() { + return type; + } + + public String toString() { + return name; + } + } + + /** + * Creates a {@code ResampleOp} that will resample input images to the + * given width and height, using the default interpolation filter. + * + * @param width width of the re-sampled image + * @param height height of the re-sampled image + */ + public ResampleOp(int width, int height) { + this(width, height, FILTER_UNDEFINED); + } + + /** + * Creates a {@code ResampleOp} that will resample input images to the + * given width and height, using the interpolation filter specified by + * the given hints. + *

+ * If using {@code RenderingHints}, the hints are mapped as follows: + *

+ *
    + *
  • {@code KEY_RESAMPLE_INTERPOLATION} takes precedence over any + * standard {@code java.awt} hints, and dictates interpolation + * directly, see + * {@code RenderingHints} constants.
  • + *
  • {@code KEY_INTERPOLATION} takes precedence over other hints. + *
      + *
    • {@link RenderingHints#VALUE_INTERPOLATION_NEAREST_NEIGHBOR} specifies + * {@code FILTER_POINT}
    • + *
    • {@link RenderingHints#VALUE_INTERPOLATION_BILINEAR} specifies + * {@code FILTER_TRIANGLE}
    • + *
    • {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} specifies + * {@code FILTER_QUADRATIC}
    • + *
    + *
  • + *
  • {@code KEY_RENDERING} or {@code KEY_COLOR_RENDERING} + *
      + *
    • {@link RenderingHints#VALUE_RENDER_SPEED} specifies + * {@code FILTER_POINT}
    • + *
    • {@link RenderingHints#VALUE_RENDER_QUALITY} specifies + * {@code FILTER_MITCHELL}
    • + *
    + *
  • + *
+ *

+ * Other hints have no effect on this filter. + *

+ * + * @param width width of the re-sampled image + * @param height height of the re-sampled image + * @param hints rendering hints, affecting interpolation algorithm + * @see #KEY_RESAMPLE_INTERPOLATION + * @see RenderingHints#KEY_INTERPOLATION + * @see RenderingHints#KEY_RENDERING + * @see RenderingHints#KEY_COLOR_RENDERING + */ + public ResampleOp(int width, int height, RenderingHints hints) { + this(width, height, getFilterType(hints)); + } + + /** + * Creates a {@code ResampleOp} that will resample input images to the + * given width and height, using the given interpolation filter. + * + * @param width width of the re-sampled image + * @param height height of the re-sampled image + * @param filterType interpolation filter algorithm + * @see filter type constants + */ + public ResampleOp(int width, int height, int filterType) { + if (width <= 0 || height <= 0) { + // NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P + throw new IllegalArgumentException("width and height must be positive"); + } + + this.width = width; + this.height = height; + + this.filterType = validateFilterType(filterType); + } + + private static int validateFilterType(int pFilterType) { + switch (pFilterType) { + case FILTER_UNDEFINED: + case FILTER_POINT: + case FILTER_BOX: + case FILTER_TRIANGLE: + case FILTER_HERMITE: + case FILTER_HANNING: + case FILTER_HAMMING: + case FILTER_BLACKMAN: + case FILTER_GAUSSIAN: + case FILTER_QUADRATIC: + case FILTER_CUBIC: + case FILTER_CATROM: + case FILTER_MITCHELL: + case FILTER_LANCZOS: + case FILTER_BLACKMAN_BESSEL: + case FILTER_BLACKMAN_SINC: + return pFilterType; + default: + throw new IllegalArgumentException("Unknown filter type: " + pFilterType); + } + } + + /** + * Gets the filter type specified by the given hints. + * + * @param pHints rendering hints + * @return a filter type constant + */ + private static int getFilterType(RenderingHints pHints) { + if (pHints == null) { + return FILTER_UNDEFINED; + } + + if (pHints.containsKey(KEY_RESAMPLE_INTERPOLATION)) { + Object value = pHints.get(KEY_RESAMPLE_INTERPOLATION); + // NOTE: Workaround for a bug in RenderingHints constructor (Bug id# 5084832) + if (!KEY_RESAMPLE_INTERPOLATION.isCompatibleValue(value)) { + throw new IllegalArgumentException(value + " incompatible with key " + KEY_RESAMPLE_INTERPOLATION); + } + return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED; + } + else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION)) + || (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION) + && (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING)) + || RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) { + // Nearest neighbour, or prioritize speed + return FILTER_POINT; + } + else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) { + // Triangle equals bi-linear interpolation + return FILTER_TRIANGLE; + } + else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) { + return FILTER_QUADRATIC;// No idea if this is correct..? + } + else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING)) + || RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) { + // Prioritize quality + return FILTER_MITCHELL; + } + + // NOTE: Other hints are ignored + return FILTER_UNDEFINED; + } + + /** + * Re-samples (scales) the image to the size, and using the algorithm + * specified in the constructor. + * + * @param input The {@code BufferedImage} to be filtered + * @param output The {@code BufferedImage} in which to store the resampled + * image + * @return The re-sampled {@code BufferedImage}. + * @throws NullPointerException if {@code input} is {@code null} + * @throws IllegalArgumentException if {@code input == output}. + * @see #ResampleOp(int,int,int) + */ + public final BufferedImage filter(final BufferedImage input, final BufferedImage output) { + if (input == null) { + throw new NullPointerException("Input == null"); + } + if (input == output) { + throw new IllegalArgumentException("Output image cannot be the same as the input image"); + } + + InterpolationFilter filter; + + // Special case for POINT, TRIANGLE and QUADRATIC filter, as standard + // Java implementation is very fast (possibly H/W accelerated) + switch (filterType) { + case FILTER_POINT: + if (input.getType() != BufferedImage.TYPE_CUSTOM) { + return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + } + // Else fall through + case FILTER_TRIANGLE: + if (input.getType() != BufferedImage.TYPE_CUSTOM) { + return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR); + } + // Else fall through + case FILTER_QUADRATIC: + if (input.getType() != BufferedImage.TYPE_CUSTOM) { + return fastResample(input, output, width, height, AffineTransformOp.TYPE_BICUBIC); + } + // Else fall through + default: + filter = createFilter(filterType); + // NOTE: Workaround for filter throwing exceptions when input or output is less than support... + if (Math.min(input.getWidth(), input.getHeight()) <= filter.support() || Math.min(width, height) <= filter.support()) { + return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR); + } + // Fall through + } + + // Try to use native ImageMagick code + BufferedImage result = MagickAccelerator.filter(this, input, output); + if (result != null) { + return result; + } + + // Otherwise, continue in pure Java mode + + // TODO: What if output != null and wrong size? Create new? Render on only a part? Document? + + // If filter type != POINT or BOX and input has IndexColorModel, convert + // to true color, with alpha reflecting that of the original color model. + BufferedImage temp; + ColorModel cm; + if (filterType != FILTER_POINT && filterType != FILTER_BOX && (cm = input.getColorModel()) instanceof IndexColorModel) { + // TODO: OPTIMIZE: If color model has only b/w or gray, we could skip color info + temp = ImageUtil.toBuffered(input, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); + } + else { + temp = input; + } + + // Create or convert output to a suitable image + // TODO: OPTIMIZE: Don't really need to convert all types to same as input + result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null); + + resample(temp, result, filter); + + // If output != null and needed to be converted, draw it back + if (output != null && output != result) { + //output.setData(output.getRaster()); + ImageUtil.drawOnto(output, result); + result = output; + } + + return result; + } + + /* + private static BufferedImage pointResample(final BufferedImage pInput, final BufferedImage pOutput, final int pWidth, final int pHeight) { + double xScale = pWidth / (double) pInput.getWidth(); + double yScale = pHeight / (double) pInput.getHeight(); + + // NOTE: This is extremely fast, native, possibly H/W accelerated code + AffineTransform transform = AffineTransform.getScaleInstance(xScale, yScale); + AffineTransformOp scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + return scale.filter(pInput, pOutput); + } + */ + + /* + // TODO: This idea from Chet and Romain is actually not too bad.. + // It reuses the image/raster/graphics... + // However, they don't end with a halve operation.. + private static BufferedImage getFasterScaledInstance(BufferedImage img, + int targetWidth, int targetHeight, Object hint, + boolean progressiveBilinear) { + int type = (img.getTransparency() == Transparency.OPAQUE) ? + BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; + BufferedImage ret = img; + BufferedImage scratchImage = null; + Graphics2D g2 = null; + int w, h; + int prevW = ret.getWidth(); + int prevH = ret.getHeight(); + boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE; + + if (progressiveBilinear) { + // Use multi-step technique: start with original size, then + // scale down in multiple passes with drawImage() + // until the target size is reached + w = img.getWidth(); + h = img.getHeight(); + } else { + // Use one-step technique: scale directly from original + // size to target size with a single drawImage() call + w = targetWidth; + h = targetHeight; + } + + do { + if (progressiveBilinear && w > targetWidth) { + w /= 2; + if (w < targetWidth) { + w = targetWidth; + } + } + + if (progressiveBilinear && h > targetHeight) { + h /= 2; + if (h < targetHeight) { + h = targetHeight; + } + } + + if (scratchImage == null || isTranslucent) { + // Use a single scratch buffer for all iterations + // and then copy to the final, correctly-sized image + // before returning + scratchImage = new BufferedImage(w, h, type); + g2 = scratchImage.createGraphics(); + } + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null); + prevW = w; + prevH = h; + + ret = scratchImage; + } while (w != targetWidth || h != targetHeight); + + if (g2 != null) { + g2.dispose(); + } + + // If we used a scratch buffer that is larger than our target size, + // create an image of the right size and copy the results into it + if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) { + scratchImage = new BufferedImage(targetWidth, targetHeight, type); + g2 = scratchImage.createGraphics(); + g2.drawImage(ret, 0, 0, null); + g2.dispose(); + ret = scratchImage; + } + + return ret; + } + */ + + private static BufferedImage fastResample(final BufferedImage input, final BufferedImage output, final int width, final int height, final int type) { + BufferedImage temp = input; + + double xScale; + double yScale; + + AffineTransform transform; + AffineTransformOp scale; + + if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { + // Initially scale so all remaining operations will halve the image + if (width < input.getWidth() || height < input.getHeight()) { + int w = width; + int h = height; + while (w < input.getWidth() / 2) { + w *= 2; + } + while (h < input.getHeight() / 2) { + h *= 2; + } + + xScale = w / (double) input.getWidth(); + yScale = h / (double) input.getHeight(); + + //System.out.println("First scale by x=" + xScale + ", y=" + yScale); + + transform = AffineTransform.getScaleInstance(xScale, yScale); + scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + temp = scale.filter(temp, null); + } + } + + scale = null; // NOTE: This resets! + + xScale = width / (double) temp.getWidth(); + yScale = height / (double) temp.getHeight(); + + if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { + // TODO: Test skipping first scale (above), and instead scale once + // more here, and a little less than .5 each time... + // That would probably make the scaling smoother... + while (xScale < 0.5 || yScale < 0.5) { + if (xScale >= 0.5) { + //System.out.println("Halving by y=" + (yScale * 2.0)); + transform = AffineTransform.getScaleInstance(1.0, 0.5); + scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + + yScale *= 2.0; + } + else if (yScale >= 0.5) { + //System.out.println("Halving by x=" + (xScale * 2.0)); + transform = AffineTransform.getScaleInstance(0.5, 1.0); + scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + + xScale *= 2.0; + } + else { + //System.out.println("Halving by x=" + (xScale * 2.0) + ", y=" + (yScale * 2.0)); + xScale *= 2.0; + yScale *= 2.0; + } + + if (scale == null) { + transform = AffineTransform.getScaleInstance(0.5, 0.5); + scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + } + + temp = scale.filter(temp, null); + } + } + + //System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale); + + transform = AffineTransform.getScaleInstance(xScale, yScale); + scale = new AffineTransformOp(transform, type); + + return scale.filter(temp, output); + } + + /** + * Returns the current filter type constant. + * + * @return the current filter type constant. + * @see filter type constants + */ + public int getFilterType() { + return filterType; + } + + private static InterpolationFilter createFilter(int pFilterType) { + // TODO: Select correct filter based on scale up or down, if undefined! + if (pFilterType == FILTER_UNDEFINED) { + pFilterType = FILTER_LANCZOS; + } + + switch (pFilterType) { + case FILTER_POINT: + return new PointFilter(); + case FILTER_BOX: + return new BoxFilter(); + case FILTER_TRIANGLE: + return new TriangleFilter(); + case FILTER_HERMITE: + return new HermiteFilter(); + case FILTER_HANNING: + return new HanningFilter(); + case FILTER_HAMMING: + return new HammingFilter(); + case FILTER_BLACKMAN: + return new BlacmanFilter(); + case FILTER_GAUSSIAN: + return new GaussianFilter(); + case FILTER_QUADRATIC: + return new QuadraticFilter(); + case FILTER_CUBIC: + return new CubicFilter(); + case FILTER_CATROM: + return new CatromFilter(); + case FILTER_MITCHELL: + return new MitchellFilter(); + case FILTER_LANCZOS: + return new LanczosFilter(); + case FILTER_BLACKMAN_BESSEL: + return new BlackmanBesselFilter(); + case FILTER_BLACKMAN_SINC: + return new BlackmanSincFilter(); + default: + throw new IllegalStateException("Unknown filter type: " + pFilterType); + } + } + + public final BufferedImage createCompatibleDestImage(final BufferedImage pInput, final ColorModel pModel) { + if (pInput == null) { + throw new NullPointerException("pInput == null"); + } + + ColorModel cm = pModel != null ? pModel : pInput.getColorModel(); + + // TODO: Might not work with all colormodels.. + // If indexcolormodel, we probably don't want to use that... + // NOTE: Either BOTH or NONE of the images must have ALPHA + + return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, width, height), + cm.isAlphaPremultiplied(), null); + } + + public RenderingHints getRenderingHints() { + Object value; + switch (filterType) { + case FILTER_UNDEFINED: + return null; + case FILTER_POINT: + value = VALUE_INTERPOLATION_POINT; + break; + case FILTER_BOX: + value = VALUE_INTERPOLATION_BOX; + break; + case FILTER_TRIANGLE: + value = VALUE_INTERPOLATION_TRIANGLE; + break; + case FILTER_HERMITE: + value = VALUE_INTERPOLATION_HERMITE; + break; + case FILTER_HANNING: + value = VALUE_INTERPOLATION_HANNING; + break; + case FILTER_HAMMING: + value = VALUE_INTERPOLATION_HAMMING; + break; + case FILTER_BLACKMAN: + value = VALUE_INTERPOLATION_BLACKMAN; + break; + case FILTER_GAUSSIAN: + value = VALUE_INTERPOLATION_GAUSSIAN; + break; + case FILTER_QUADRATIC: + value = VALUE_INTERPOLATION_QUADRATIC; + break; + case FILTER_CUBIC: + value = VALUE_INTERPOLATION_CUBIC; + break; + case FILTER_CATROM: + value = VALUE_INTERPOLATION_CATROM; + break; + case FILTER_MITCHELL: + value = VALUE_INTERPOLATION_MITCHELL; + break; + case FILTER_LANCZOS: + value = VALUE_INTERPOLATION_LANCZOS; + break; + case FILTER_BLACKMAN_BESSEL: + value = VALUE_INTERPOLATION_BLACKMAN_BESSEL; + break; + case FILTER_BLACKMAN_SINC: + value = VALUE_INTERPOLATION_BLACKMAN_SINC; + break; + default: + throw new IllegalStateException("Unknown filter type: " + filterType); + } + + return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value); + } + + public Rectangle2D getBounds2D(BufferedImage src) { + return new Rectangle(width, height); + } + + public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { + // TODO: This is wrong... + // How can I possible know how much one point is scaled, without first knowing the ration?! + // TODO: Maybe set all points outside of bounds, inside? + // TODO: Assume input image of Integer.MAX_VAL x Integer.MAX_VAL?! ;-) + if (dstPt == null) { + if (srcPt instanceof Point2D.Double) { + dstPt = new Point2D.Double(); + } + else { + dstPt = new Point2D.Float(); + } + dstPt.setLocation(srcPt); + } + return dstPt; + } + + /* -- Java port of filter_rcg.c below... -- */ + + /* + * filter function definitions + */ + + interface InterpolationFilter { + double filter(double t); + + double support(); + } + + static class HermiteFilter implements InterpolationFilter { + public final double filter(double t) { + /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ + if (t < 0.0) { + t = -t; + } + if (t < 1.0) { + return (2.0 * t - 3.0) * t * t + 1.0; + } + return 0.0; + } + + public final double support() { + return 1.0; + } + } + + static class PointFilter extends BoxFilter { + public PointFilter() { + super(0.0); + } + } + + static class BoxFilter implements InterpolationFilter { + private final double mSupport; + + public BoxFilter() { + mSupport = 0.5; + } + + protected BoxFilter(double pSupport) { + mSupport = pSupport; + } + + public final double filter(final double t) { + //if ((t > -0.5) && (t <= 0.5)) { + if ((t >= -0.5) && (t < 0.5)) {// ImageMagick resample.c + return 1.0; + } + return 0.0; + } + + public final double support() { + return mSupport; + } + } + + static class TriangleFilter implements InterpolationFilter { + public final double filter(double t) { + if (t < 0.0) { + t = -t; + } + if (t < 1.0) { + return 1.0 - t; + } + return 0.0; + } + + public final double support() { + return 1.0; + } + } + + static class QuadraticFilter implements InterpolationFilter { + // AKA Bell + public final double filter(double t)/* box (*) box (*) box */ { + if (t < 0) { + t = -t; + } + if (t < .5) { + return .75 - (t * t); + } + if (t < 1.5) { + t = (t - 1.5); + return .5 * (t * t); + } + return 0.0; + } + + public final double support() { + return 1.5; + } + } + + static class CubicFilter implements InterpolationFilter { + // AKA B-Spline + public final double filter(double t)/* box (*) box (*) box (*) box */ { + final double tt; + + if (t < 0) { + t = -t; + } + if (t < 1) { + tt = t * t; + return (.5 * tt * t) - tt + (2.0 / 3.0); + } + else if (t < 2) { + t = 2 - t; + return (1.0 / 6.0) * (t * t * t); + } + return 0.0; + } + + public final double support() { + return 2.0; + } + } + + private static double sinc(double x) { + x *= Math.PI; + if (x != 0.0) { + return Math.sin(x) / x; + } + return 1.0; + } + + static class LanczosFilter implements InterpolationFilter { + // AKA Lanczos3 + public final double filter(double t) { + if (t < 0) { + t = -t; + } + if (t < 3.0) { + return sinc(t) * sinc(t / 3.0); + } + return 0.0; + } + + public final double support() { + return 3.0; + } + } + + private final static double B = 1.0 / 3.0; + private final static double C = 1.0 / 3.0; + private final static double P0 = (6.0 - 2.0 * B) / 6.0; + private final static double P2 = (-18.0 + 12.0 * B + 6.0 * C) / 6.0; + private final static double P3 = (12.0 - 9.0 * B - 6.0 * C) / 6.0; + private final static double Q0 = (8.0 * B + 24.0 * C) / 6.0; + private final static double Q1 = (-12.0 * B - 48.0 * C) / 6.0; + private final static double Q2 = (6.0 * B + 30.0 * C) / 6.0; + private final static double Q3 = (-1.0 * B - 6.0 * C) / 6.0; + + static class MitchellFilter implements InterpolationFilter { + public final double filter(double t) { + if (t < -2.0) { + return 0.0; + } + if (t < -1.0) { + return Q0 - t * (Q1 - t * (Q2 - t * Q3)); + } + if (t < 0.0) { + return P0 + t * t * (P2 - t * P3); + } + if (t < 1.0) { + return P0 + t * t * (P2 + t * P3); + } + if (t < 2.0) { + return Q0 + t * (Q1 + t * (Q2 + t * Q3)); + } + return 0.0; + } + + public final double support() { + return 2.0; + } + } + + private static double j1(final double t) { + final double[] pOne = { + 0.581199354001606143928050809e+21, + -0.6672106568924916298020941484e+20, + 0.2316433580634002297931815435e+19, + -0.3588817569910106050743641413e+17, + 0.2908795263834775409737601689e+15, + -0.1322983480332126453125473247e+13, + 0.3413234182301700539091292655e+10, + -0.4695753530642995859767162166e+7, + 0.270112271089232341485679099e+4 + }; + final double[] qOne = { + 0.11623987080032122878585294e+22, + 0.1185770712190320999837113348e+20, + 0.6092061398917521746105196863e+17, + 0.2081661221307607351240184229e+15, + 0.5243710262167649715406728642e+12, + 0.1013863514358673989967045588e+10, + 0.1501793594998585505921097578e+7, + 0.1606931573481487801970916749e+4, + 0.1e+1 + }; + + double p = pOne[8]; + double q = qOne[8]; + for (int i = 7; i >= 0; i--) { + p = p * t * t + pOne[i]; + q = q * t * t + qOne[i]; + } + return p / q; + } + + private static double p1(final double t) { + final double[] pOne = { + 0.352246649133679798341724373e+5, + 0.62758845247161281269005675e+5, + 0.313539631109159574238669888e+5, + 0.49854832060594338434500455e+4, + 0.2111529182853962382105718e+3, + 0.12571716929145341558495e+1 + }; + final double[] qOne = { + 0.352246649133679798068390431e+5, + 0.626943469593560511888833731e+5, + 0.312404063819041039923015703e+5, + 0.4930396490181088979386097e+4, + 0.2030775189134759322293574e+3, + 0.1e+1 + }; + + double p = pOne[5]; + double q = qOne[5]; + for (int i = 4; i >= 0; i--) { + p = p * (8.0 / t) * (8.0 / t) + pOne[i]; + q = q * (8.0 / t) * (8.0 / t) + qOne[i]; + } + return p / q; + } + + private static double q1(final double t) { + final double[] pOne = { + 0.3511751914303552822533318e+3, + 0.7210391804904475039280863e+3, + 0.4259873011654442389886993e+3, + 0.831898957673850827325226e+2, + 0.45681716295512267064405e+1, + 0.3532840052740123642735e-1 + }; + final double[] qOne = { + 0.74917374171809127714519505e+4, + 0.154141773392650970499848051e+5, + 0.91522317015169922705904727e+4, + 0.18111867005523513506724158e+4, + 0.1038187585462133728776636e+3, + 0.1e+1 + }; + + double p = pOne[5]; + double q = qOne[5]; + for (int i = 4; i >= 0; i--) { + p = p * (8.0 / t) * (8.0 / t) + pOne[i]; + q = q * (8.0 / t) * (8.0 / t) + qOne[i]; + } + return p / q; + } + + static double besselOrderOne(double t) { + double p, q; + + if (t == 0.0) { + return 0.0; + } + p = t; + if (t < 0.0) { + t = -t; + } + if (t < 8.0) { + return p * j1(t); + } + q = Math.sqrt(2.0 / (Math.PI * t)) * (p1(t) * (1.0 / Math.sqrt(2.0) * (Math.sin(t) - Math.cos(t))) - 8.0 / t * q1(t) * + (-1.0 / Math.sqrt(2.0) * (Math.sin(t) + Math.cos(t)))); + if (p < 0.0) { + q = -q; + } + return q; + } + + private static double bessel(final double t) { + if (t == 0.0) { + return Math.PI / 4.0; + } + return besselOrderOne(Math.PI * t) / (2.0 * t); + } + + private static double blackman(final double t) { + return 0.42 + 0.50 * Math.cos(Math.PI * t) + 0.08 * Math.cos(2.0 * Math.PI * t); + } + + static class BlacmanFilter implements InterpolationFilter { + public final double filter(final double t) { + return blackman(t); + } + + public final double support() { + return 1.0; + } + } + + static class CatromFilter implements InterpolationFilter { + public final double filter(double t) { + if (t < 0) { + t = -t; + } + if (t < 1.0) { + return 0.5 * (2.0 + t * t * (-5.0 + t * 3.0)); + } + if (t < 2.0) { + return 0.5 * (4.0 + t * (-8.0 + t * (5.0 - t))); + } + return 0.0; + } + + public final double support() { + return 2.0; + } + } + + static class GaussianFilter implements InterpolationFilter { + public final double filter(final double t) { + return Math.exp(-2.0 * t * t) * Math.sqrt(2.0 / Math.PI); + } + + public final double support() { + return 1.25; + } + } + + static class HanningFilter implements InterpolationFilter { + public final double filter(final double t) { + return 0.5 + 0.5 * Math.cos(Math.PI * t); + } + + public final double support() { + return 1.0; + } + } + + static class HammingFilter implements InterpolationFilter { + public final double filter(final double t) { + return 0.54 + 0.46 * Math.cos(Math.PI * t); + } + + public final double support() { + return 1.0; + } + } + + static class BlackmanBesselFilter implements InterpolationFilter { + public final double filter(final double t) { + return blackman(t / support()) * bessel(t); + } + + public final double support() { + return 3.2383; + } + } + + static class BlackmanSincFilter implements InterpolationFilter { + public final double filter(final double t) { + return blackman(t / support()) * sinc(t); + } + + public final double support() { + return 4.0; + } + } + + /* + * image rescaling routine + */ + class Contributor { + int pixel; + double weight; + } + + class ContributorList { + int n;/* number of contributors (may be < p.length) */ + Contributor[] p;/* pointer to list of contributions */ + } + + /* + round() + + Round an FP value to its closest int representation. + General routine; ideally belongs in general math lib file. + */ + + static int round(double d) { + // NOTE: This code seems to be faster than Math.round(double)... + // Version that uses no function calls at all. + int n = (int) d; + double diff = d - (double) n; + if (diff < 0) { + diff = -diff; + } + if (diff >= 0.5) { + if (d < 0) { + n--; + } + else { + n++; + } + } + return n; + }/* round */ + + /* + calcXContrib() + + Calculates the filter weights for a single target column. + contribX->p must be freed afterwards. + + Returns -1 if error, 0 otherwise. + */ + private ContributorList calcXContrib(double xscale, double fwidth, int srcwidth, InterpolationFilter pFilter, int i) { + // TODO: What to do when fwidth > srcwidyj or dstwidth + + double width; + double fscale; + double center; + double weight; + + ContributorList contribX = new ContributorList(); + + if (xscale < 1.0) { + /* Shrinking image */ + width = fwidth / xscale; + fscale = 1.0 / xscale; + + if (width <= .5) { + // Reduce to point sampling. + width = .5 + 1.0e-6; + fscale = 1.0; + } + + //contribX.n = 0; + contribX.p = new Contributor[(int) (width * 2.0 + 1.0 + 0.5)]; + + center = (double) i / xscale; + int left = (int) Math.ceil(center - width);// Note: Assumes width <= .5 + int right = (int) Math.floor(center + width); + + double density = 0.0; + + for (int j = left; j <= right; j++) { + weight = center - (double) j; + weight = pFilter.filter(weight / fscale) / fscale; + int n; + if (j < 0) { + n = -j; + } + else if (j >= srcwidth) { + n = (srcwidth - j) + srcwidth - 1; + } + else { + n = j; + } + + /**/ + if (n >= srcwidth) { + n = n % srcwidth; + } + else if (n < 0) { + n = srcwidth - 1; + } + /**/ + + int k = contribX.n++; + contribX.p[k] = new Contributor(); + contribX.p[k].pixel = n; + contribX.p[k].weight = weight; + + density += weight; + + } + + if ((density != 0.0) && (density != 1.0)) { + //Normalize. + density = 1.0 / density; + for (int k = 0; k < contribX.n; k++) { + contribX.p[k].weight *= density; + } + } + } + else { + /* Expanding image */ + //contribX.n = 0; + contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0 + 0.5)]; + + center = (double) i / xscale; + int left = (int) Math.ceil(center - fwidth); + int right = (int) Math.floor(center + fwidth); + + for (int j = left; j <= right; j++) { + weight = center - (double) j; + weight = pFilter.filter(weight); + + int n; + if (j < 0) { + n = -j; + } + else if (j >= srcwidth) { + n = (srcwidth - j) + srcwidth - 1; + } + else { + n = j; + } + + /**/ + if (n >= srcwidth) { + n = n % srcwidth; + } + else if (n < 0) { + n = srcwidth - 1; + } + /**/ + + int k = contribX.n++; + contribX.p[k] = new Contributor(); + contribX.p[k].pixel = n; + contribX.p[k].weight = weight; + } + } + return contribX; + }/* calcXContrib */ + + /* + resample() + + Resizes bitmaps while resampling them. + */ + private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) { + final int dstWidth = pDest.getWidth(); + final int dstHeight = pDest.getHeight(); + + final int srcWidth = pSource.getWidth(); + final int srcHeight = pSource.getHeight(); + + /* create intermediate column to hold horizontal dst column zoom */ + final ColorModel cm = pSource.getColorModel(); +// final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight); + final WritableRaster work = ImageUtil.createCompatibleWritableRaster(pSource, cm, 1, srcHeight); + + double xscale = (double) dstWidth / (double) srcWidth; + double yscale = (double) dstHeight / (double) srcHeight; + + ContributorList[] contribY = new ContributorList[dstHeight]; + for (int i = 0; i < contribY.length; i++) { + contribY[i] = new ContributorList(); + } + + // TODO: What to do when fwidth > srcHeight or dstHeight + double fwidth = pFilter.support(); + if (yscale < 1.0) { + double width = fwidth / yscale; + double fscale = 1.0 / yscale; + + if (width <= .5) { + // Reduce to point sampling. + width = .5 + 1.0e-6; + fscale = 1.0; + } + + for (int i = 0; i < dstHeight; i++) { + //contribY[i].n = 0; + contribY[i].p = new Contributor[(int) (width * 2.0 + 1 + 0.5)]; + + double center = (double) i / yscale; + int left = (int) Math.ceil(center - width); + int right = (int) Math.floor(center + width); + + double density = 0.0; + + for (int j = left; j <= right; j++) { + double weight = center - (double) j; + weight = pFilter.filter(weight / fscale) / fscale; + int n; + if (j < 0) { + n = -j; + } + else if (j >= srcHeight) { + n = (srcHeight - j) + srcHeight - 1; + } + else { + n = j; + } + + /**/ + if (n >= srcHeight) { + n = n % srcHeight; + } + else if (n < 0) { + n = srcHeight - 1; + } + /**/ + + int k = contribY[i].n++; + contribY[i].p[k] = new Contributor(); + contribY[i].p[k].pixel = n; + contribY[i].p[k].weight = weight; + + density += weight; + } + + if ((density != 0.0) && (density != 1.0)) { + //Normalize. + density = 1.0 / density; + for (int k = 0; k < contribY[i].n; k++) { + contribY[i].p[k].weight *= density; + } + } + } + } + else { + for (int i = 0; i < dstHeight; ++i) { + //contribY[i].n = 0; + contribY[i].p = new Contributor[(int) (fwidth * 2 + 1 + 0.5)]; + + double center = (double) i / yscale; + double left = Math.ceil(center - fwidth); + double right = Math.floor(center + fwidth); + for (int j = (int) left; j <= right; ++j) { + double weight = center - (double) j; + weight = pFilter.filter(weight); + int n; + if (j < 0) { + n = -j; + } + else if (j >= srcHeight) { + n = (srcHeight - j) + srcHeight - 1; + } + else { + n = j; + } + + /**/ + if (n >= srcHeight) { + n = n % srcHeight; + } + else if (n < 0) { + n = srcHeight - 1; + } + /**/ + + int k = contribY[i].n++; + contribY[i].p[k] = new Contributor(); + contribY[i].p[k].pixel = n; + contribY[i].p[k].weight = weight; + } + } + } + + final Raster raster = pSource.getRaster(); + final WritableRaster out = pDest.getRaster(); + + // TODO: This is not optimal for non-byte-packed rasters... + // (What? Maybe I implemented the fix, but forgot to remove the TODO?) + final int numChannels = raster.getNumBands(); + final int[] channelMax = new int[numChannels]; + for (int k = 0; k < numChannels; k++) { + channelMax[k] = (1 << pSource.getColorModel().getComponentSize(k)) - 1; + } + + for (int xx = 0; xx < dstWidth; xx++) { + ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx); + /* Apply horiz filter to make dst column in tmp. */ + for (int k = 0; k < srcHeight; k++) { + for (int channel = 0; channel < numChannels; channel++) { + + double weight = 0.0; + boolean bPelDelta = false; + // TODO: This line throws index out of bounds, if the image + // is smaller than filter.support() + double pel = raster.getSample(contribX.p[0].pixel, k, channel); + for (int j = 0; j < contribX.n; j++) { + double pel2 = j == 0 ? pel : raster.getSample(contribX.p[j].pixel, k, channel); + if (pel2 != pel) { + bPelDelta = true; + } + weight += pel2 * contribX.p[j].weight; + } + weight = bPelDelta ? round(weight) : pel; + + if (weight < 0) { + weight = 0; + } + else if (weight > channelMax[channel]) { + weight = channelMax[channel]; + } + + work.setSample(0, k, channel, weight); + + } + }/* next row in temp column */ + + /* The temp column has been built. Now stretch it vertically into dst column. */ + for (int i = 0; i < dstHeight; i++) { + for (int channel = 0; channel < numChannels; channel++) { + + double weight = 0.0; + boolean bPelDelta = false; + double pel = work.getSample(0, contribY[i].p[0].pixel, channel); + + for (int j = 0; j < contribY[i].n; j++) { + // TODO: This line throws index out of bounds, if the image + // is smaller than filter.support() + double pel2 = j == 0 ? pel : work.getSample(0, contribY[i].p[j].pixel, channel); + if (pel2 != pel) { + bPelDelta = true; + } + weight += pel2 * contribY[i].p[j].weight; + } + weight = bPelDelta ? round(weight) : pel; + if (weight < 0) { + weight = 0; + } + else if (weight > channelMax[channel]) { + weight = channelMax[channel]; + } + + out.setSample(xx, i, channel, weight); + } + }/* next dst row */ + }/* next dst column */ + return pDest; + }/* resample */ } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java b/common/common-image/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java index 839a5b5d..226f4594 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java @@ -1,77 +1,80 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.image; - -import java.awt.image.ReplicateScaleFilter; - -/** - * An {@code ImageFilter} class for subsampling images. - *

- * It is meant to be used in conjunction with a {@code FilteredImageSource} - * object to produce subsampled versions of existing images. - * - * @see java.awt.image.FilteredImageSource - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java#1 $ - */ -public class SubsamplingFilter extends ReplicateScaleFilter { - private int xSub; - private int ySub; - - /** - * Creates a {@code SubsamplingFilter}. - * - * @param pXSub - * @param pYSub - * - * @throws IllegalArgumentException if {@code pXSub} or {@code pYSub} is - * less than 1. - */ - public SubsamplingFilter(int pXSub, int pYSub) { - super(1, 1); // These are NOT REAL values, but we have to defer setting - // until w/h is available, in setDimensions below - - if (pXSub < 1 || pYSub < 1) { - throw new IllegalArgumentException("Subsampling factors must be positive."); - } - - xSub = pXSub; - ySub = pYSub; - } - - /** {@code ImageFilter} implementation, do not invoke. */ - public void setDimensions(int pWidth, int pHeight) { - destWidth = (pWidth + xSub - 1) / xSub; - destHeight = (pHeight + ySub - 1) / ySub; - - //System.out.println("Subsampling: " + xSub + "," + ySub + "-> " + destWidth + ", " + destHeight); - super.setDimensions(pWidth, pHeight); - } -} +/* + * 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.image; + +import java.awt.image.ReplicateScaleFilter; + +/** + * An {@code ImageFilter} class for subsampling images. + *

+ * It is meant to be used in conjunction with a {@code FilteredImageSource} + * object to produce subsampled versions of existing images. + *

+ * + * @see java.awt.image.FilteredImageSource + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java#1 $ + */ +public class SubsamplingFilter extends ReplicateScaleFilter { + private int xSub; + private int ySub; + + /** + * Creates a {@code SubsamplingFilter}. + * + * @param pXSub + * @param pYSub + * + * @throws IllegalArgumentException if {@code pXSub} or {@code pYSub} is + * less than 1. + */ + public SubsamplingFilter(int pXSub, int pYSub) { + super(1, 1); // These are NOT REAL values, but we have to defer setting + // until w/h is available, in setDimensions below + + if (pXSub < 1 || pYSub < 1) { + throw new IllegalArgumentException("Subsampling factors must be positive."); + } + + xSub = pXSub; + ySub = pYSub; + } + + /** {@code ImageFilter} implementation, do not invoke. */ + public void setDimensions(int pWidth, int pHeight) { + destWidth = (pWidth + xSub - 1) / xSub; + destHeight = (pHeight + ySub - 1) / ySub; + + //System.out.println("Subsampling: " + xSub + "," + ySub + "-> " + destWidth + ", " + destHeight); + super.setDimensions(pWidth, pHeight); + } +} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/package-info.java b/common/common-image/src/main/java/com/twelvemonkeys/image/package-info.java index 9ee76555..fa01928e 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/package-info.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/package-info.java @@ -1,7 +1,38 @@ +/* + * 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. + */ + /** * Classes for image manipulation. - *

+ *

* See the class {@link com.twelvemonkeys.image.ImageUtil}. + *

* * @version 1.0 * @author Harald Kuhr diff --git a/common/common-image/src/test/java/com/twelvemonkeys/image/AffineTransformOpTest.java b/common/common-image/src/test/java/com/twelvemonkeys/image/AffineTransformOpTest.java index edbd6218..c1c53f67 100644 --- a/common/common-image/src/test/java/com/twelvemonkeys/image/AffineTransformOpTest.java +++ b/common/common-image/src/test/java/com/twelvemonkeys/image/AffineTransformOpTest.java @@ -1,3 +1,33 @@ +/* + * 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.image; import org.junit.Test; @@ -10,9 +40,7 @@ import java.awt.image.*; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * AffineTransformOpTest. diff --git a/common/common-image/src/test/java/com/twelvemonkeys/image/BufferedImageFactoryTestCase.java b/common/common-image/src/test/java/com/twelvemonkeys/image/BufferedImageFactoryTest.java similarity index 89% rename from common/common-image/src/test/java/com/twelvemonkeys/image/BufferedImageFactoryTestCase.java rename to common/common-image/src/test/java/com/twelvemonkeys/image/BufferedImageFactoryTest.java index fa6cb48a..dbde4c0e 100644 --- a/common/common-image/src/test/java/com/twelvemonkeys/image/BufferedImageFactoryTestCase.java +++ b/common/common-image/src/test/java/com/twelvemonkeys/image/BufferedImageFactoryTest.java @@ -1,3 +1,33 @@ +/* + * 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.image; import org.junit.Ignore; @@ -12,7 +42,6 @@ import java.awt.image.IndexColorModel; import java.net.URL; import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; /** * BufferedImageFactoryTestCase @@ -21,7 +50,7 @@ import static org.junit.Assert.assertEquals; * @author last modified by $Author: haraldk$ * @version $Id: BufferedImageFactoryTestCase.java,v 1.0 May 7, 2010 12:40:08 PM haraldk Exp$ */ -public class BufferedImageFactoryTestCase { +public class BufferedImageFactoryTest { @Test(expected = IllegalArgumentException.class) public void testCreateNullImage() { new BufferedImageFactory((Image) null); diff --git a/common/common-image/src/test/java/com/twelvemonkeys/image/ImageUtilTestCase.java b/common/common-image/src/test/java/com/twelvemonkeys/image/ImageUtilTest.java similarity index 92% rename from common/common-image/src/test/java/com/twelvemonkeys/image/ImageUtilTestCase.java rename to common/common-image/src/test/java/com/twelvemonkeys/image/ImageUtilTest.java index 4f8cd92d..7c14be3a 100755 --- a/common/common-image/src/test/java/com/twelvemonkeys/image/ImageUtilTestCase.java +++ b/common/common-image/src/test/java/com/twelvemonkeys/image/ImageUtilTest.java @@ -1,505 +1,534 @@ - -package com.twelvemonkeys.image; - -import org.junit.Test; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.IndexColorModel; -import java.awt.image.RenderedImage; -import java.io.InputStream; - -import static org.junit.Assert.*; - -public class ImageUtilTestCase { - - private final static String IMAGE_NAME = "/sunflower.jpg"; - private BufferedImage original; - private BufferedImage image; - private Image scaled; - - public ImageUtilTestCase() throws Exception { - image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); - scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST); - - // Read image from class path - InputStream is = getClass().getResourceAsStream(IMAGE_NAME); - original = ImageIO.read(is); - - assertNotNull(original); - } - - /* - public void setUp() throws Exception { - image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); - scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST); - - // Read image from class path - InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME); - original = ImageIO.read(is); - - assertNotNull(original); - } - - protected void tearDown() throws Exception { - original = null; - } - */ - - @Test - public void testToBufferedImageNull() { - BufferedImage img = null; - boolean threwRuntimeException = false; - - try { - img = ImageUtil.toBuffered((Image) null); - } - catch (RuntimeException ne) { - threwRuntimeException = true; - } - // No input should return null - assertNull(img); - - // Should have thrown an exception - assertTrue(threwRuntimeException); - } - - @Test - public void testToBufferedImageTypeNull() { - BufferedImage img = null; - boolean threwRuntimeException = false; - - try { - img = ImageUtil.toBuffered(null, BufferedImage.TYPE_INT_ARGB); - } - catch (RuntimeException ne) { - threwRuntimeException = true; - } - // No input should return null - assertNull(img); - - // Should have thrown an exception - assertTrue(threwRuntimeException); - } - - @Test - public void testImageIsNotBufferedImage() { - // Should not be a buffered image - assertFalse( - "FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.", - scaled instanceof BufferedImage - ); - } - - @Test - public void testToBufferedImage() { - BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) image); - BufferedImage bufferedScaled = ImageUtil.toBuffered(scaled); - - // Should be no need to convert - assertSame(image, sameAsImage); - - // Should have same dimensions - assertEquals(scaled.getWidth(null), bufferedScaled.getWidth()); - assertEquals(scaled.getHeight(null), bufferedScaled.getHeight()); - } - - @Test - public void testToBufferedImageType() { - // Assumes image is TYPE_INT_ARGB - BufferedImage converted = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_INDEXED); - BufferedImage convertedToo = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_BINARY); - - // Should not be the same - assertNotSame(image, converted); - assertNotSame(image, convertedToo); - - // Correct type - assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED); - assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY); - - // Should have same dimensions - assertEquals(image.getWidth(), converted.getWidth()); - assertEquals(image.getHeight(), converted.getHeight()); - - assertEquals(image.getWidth(), convertedToo.getWidth()); - assertEquals(image.getHeight(), convertedToo.getHeight()); - } - - @Test - public void testBrightness() { - final BufferedImage original = this.original; - assertNotNull(original); - - final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f)); - // Assumed: Images should be equal - if (original != notBrightened) { // Don't care to test if images are same - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - assertEquals(original.getRGB(x, y), notBrightened.getRGB(x, y)); - } - } - } - - // Assumed: All pixels should be brighter or equal to original - final BufferedImage brightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0.4f)); - final BufferedImage brightenedMore = ImageUtil.toBuffered(ImageUtil.brightness(original, 0.9f)); - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - assertTrue(original.getRGB(x, y) <= brightened.getRGB(x, y)); - assertTrue(brightened.getRGB(x, y) <= brightenedMore.getRGB(x, y)); - } - } - - // Assumed: Image should be all white - final BufferedImage brightenedMax = ImageUtil.toBuffered(ImageUtil.brightness(original, 2f)); - for (int y = 0; y < brightenedMax.getHeight(); y++) { - for (int x = 0; x < brightenedMax.getWidth(); x++) { - assertEquals(0x00FFFFFF, brightenedMax.getRGB(x, y) & 0x00FFFFFF); - } - } - - // Assumed: All pixels should be darker or equal to originial - final BufferedImage brightenedNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -0.4f)); - final BufferedImage brightenedNegativeMore = ImageUtil.toBuffered(ImageUtil.brightness(original, -0.9f)); - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - assertTrue(original.getRGB(x, y) >= brightenedNegative.getRGB(x, y)); - assertTrue(brightenedNegative.getRGB(x, y) >= brightenedNegativeMore.getRGB(x, y)); - } - } - // Assumed: Image should be all black - final BufferedImage brightenedMaxNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -2f)); - for (int y = 0; y < brightenedMaxNegative.getHeight(); y++) { - for (int x = 0; x < brightenedMaxNegative.getWidth(); x++) { - assertEquals(0x0, brightenedMaxNegative.getRGB(x, y) & 0x00FFFFFF); - } - } - - /* - JFrame frame = new JFrame("Sunflower - brightness"); - frame.setSize(sunflower.getWidth() * 4, sunflower.getHeight() * 2); - - Canvas canvas = new Canvas() { - public void paint(Graphics g) { - // Draw original for comparison - g.drawImage(original, 0, 0, null); - - // This should look like original - g.drawImage(notBrightened, 0, original.getHeight(), null); - - // Different versions - g.drawImage(brightened, original.getWidth(), 0, null); - g.drawImage(brightenedMore, original.getWidth() * 2, 0, null); - g.drawImage(brightenedMax, original.getWidth() * 3, 0, null); - - g.drawImage(brightenedNegative, original.getWidth(), original.getHeight(), null); - g.drawImage(brightenedNegativeMore, original.getWidth() * 2, original.getHeight(), null); - g.drawImage(brightenedMaxNegative, original.getWidth() * 3, original.getHeight(), null); - } - }; - - frame.getContentPane().add(canvas); - frame.setVisible(true); - - assertTrue(true); - */ - } - - @Test - public void testContrast() { - final BufferedImage original = this.original; - - assertNotNull(original); - - final BufferedImage notContrasted = ImageUtil.toBuffered(ImageUtil.contrast(original, 0f)); - // Assumed: Images should be equal - if (original != notContrasted) { // Don't care to test if images are same - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - assertEquals("0 constrast should not change image", original.getRGB(x, y), notContrasted.getRGB(x, y)); - } - } - } - - // Assumed: Contrast should be greater or equal to original - final BufferedImage contrasted = ImageUtil.toBuffered(ImageUtil.contrast(original)); - final BufferedImage contrastedDefault = ImageUtil.toBuffered(ImageUtil.contrast(original, 0.5f)); - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - int oRGB = original.getRGB(x, y); - int cRGB = contrasted.getRGB(x, y); - int dRGB = contrastedDefault.getRGB(x, y); - - int oR = oRGB >> 16 & 0xFF; - int oG = oRGB >> 8 & 0xFF; - int oB = oRGB & 0xFF; - - int cR = cRGB >> 16 & 0xFF; - int cG = cRGB >> 8 & 0xFF; - int cB = cRGB & 0xFF; - - int dR = dRGB >> 16 & 0xFF; - int dG = dRGB >> 8 & 0xFF; - int dB = dRGB & 0xFF; - - // RED - if (oR < 127) { - assertTrue("Contrast should be decreased or same", oR >= cR && cR >= dR); - } - else { - assertTrue("Contrast should be increased or same", oR <= cR && cR <= dR); - } - // GREEN - if (oG < 127) { - assertTrue("Contrast should be decreased or same", oG >= cG && cG >= dG); - } - else { - assertTrue("Contrast should be increased or same", oG <= cG && cG <= dG); - } - // BLUE - if (oB < 127) { - assertTrue("Contrast should be decreased or same", oB >= cB && cB >= dB); - } - else { - assertTrue("Contrast should be increased or same", oB <= cB && cB <= dB); - } - } - } - // Assumed: Only primary colors (w/b/r/g/b/c/y/m) - final BufferedImage contrastedMax = ImageUtil.toBuffered(ImageUtil.contrast(original, 1f)); - for (int y = 0; y < contrastedMax.getHeight(); y++) { - for (int x = 0; x < contrastedMax.getWidth(); x++) { - int rgb = contrastedMax.getRGB(x, y); - int r = rgb >> 16 & 0xFF; - int g = rgb >> 8 & 0xFF; - int b = rgb & 0xFF; - assertTrue("Max contrast should only produce primary colors", r == 0 || r == 255); - assertTrue("Max contrast should only produce primary colors", g == 0 || g == 255); - assertTrue("Max contrast should only produce primary colors", b == 0 || b == 255); - } - } - - // Assumed: Contrasts should be less than or equal to original - final BufferedImage contrastedNegative = ImageUtil.toBuffered(ImageUtil.contrast(original, -0.5f)); - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - int oRGB = original.getRGB(x, y); - int cRGB = contrastedNegative.getRGB(x, y); - - int oR = oRGB >> 16 & 0xFF; - int oG = oRGB >> 8 & 0xFF; - int oB = oRGB & 0xFF; - - int cR = cRGB >> 16 & 0xFF; - int cG = cRGB >> 8 & 0xFF; - int cB = cRGB & 0xFF; - - // RED - if (oR >= 127) { - assertTrue("Contrast should be decreased or same", oR >= cR); - } - else { - assertTrue("Contrast should be increased or same", oR <= cR); - } - // GREEN - if (oG >= 127) { - assertTrue("Contrast should be decreased or same", oG >= cG); - } - else { - assertTrue("Contrast should be increased or same", oG <= cG); - } - // BLUE - if (oB >= 127) { - assertTrue("Contrast should be decreased or same", oB >= cB); - } - else { - assertTrue("Contrast should be increased or same", oB <= cB); - } - } - } - - // Assumed: All gray (127)! - final BufferedImage contrastedMoreNegative = ImageUtil.toBuffered(ImageUtil.contrast(original, -1.0f)); - for (int y = 0; y < contrastedMoreNegative.getHeight(); y++) { - for (int x = 0; x < contrastedMoreNegative.getWidth(); x++) { - int rgb = contrastedMoreNegative.getRGB(x, y); - int r = rgb >> 16 & 0xFF; - int g = rgb >> 8 & 0xFF; - int b = rgb & 0xFF; - assertTrue("Minimum contrast should be all gray", r == 127 && g == 127 && b == 127); - } - } - - /* - JFrame frame = new JFrame("Sunflower - contrast"); - frame.setSize(sunflower.getWidth() * 4, sunflower.getHeight() * 2); - - Canvas canvas = new Canvas() { - public void paint(Graphics g) { - // Draw original for comparison - g.drawImage(original, 0, 0, null); - - // This should look like original - g.drawImage(notContrasted, 0, original.getHeight(), null); - - // Different versions - g.drawImage(contrasted, original.getWidth(), 0, null); - g.drawImage(contrastedDefault, original.getWidth() * 2, 0, null); - g.drawImage(contrastedMax, original.getWidth() * 3, 0, null); - g.drawImage(contrastedNegative, original.getWidth() * 2, original.getHeight(), null); - g.drawImage(contrastedMoreNegative, original.getWidth() * 3, original.getHeight(), null); - } - }; - - frame.getContentPane().add(canvas); - frame.setVisible(true); - - assertTrue(true); - */ - } - - @Test - public void testSharpen() { - final BufferedImage original = this.original; - - assertNotNull(original); - - final BufferedImage notSharpened = ImageUtil.sharpen(original, 0f); - // Assumed: Images should be equal - if (original != notSharpened) { // Don't care to test if images are same - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - assertEquals("0 sharpen should not change image", original.getRGB(x, y), notSharpened.getRGB(x, y)); - } - } - } - - // Assumed: Difference between neighbouring pixels should increase for higher sharpen values - // Assumed: Dynamics of entire image should not change - final BufferedImage sharpened = ImageUtil.sharpen(original); - final BufferedImage sharpenedDefault = ImageUtil.sharpen(original, 0.3f); - final BufferedImage sharpenedMore = ImageUtil.sharpen(original, 1.3f); - -// long diffOriginal = 0; -// long diffSharpened = 0; -// long diffDefault = 0; -// long diffMore = 0; - - long absDiffOriginal = 0; - long absDiffSharpened = 0; - long absDiffDefault = 0; - long absDiffMore = 0; - - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 1; x < original.getWidth(); x++) { - int oRGB = 0x00FFFFFF & original.getRGB(x, y); - int sRGB = 0x00FFFFFF & sharpened.getRGB(x, y); - int dRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x, y); - int mRGB = 0x00FFFFFF & sharpenedMore.getRGB(x, y); - - int poRGB = 0x00FFFFFF & original.getRGB(x - 1, y); - int psRGB = 0x00FFFFFF & sharpened.getRGB(x - 1, y); - int pdRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x - 1, y); - int pmRGB = 0x00FFFFFF & sharpenedMore.getRGB(x - 1, y); - -// diffOriginal += poRGB - oRGB; -// diffSharpened += psRGB - sRGB; -// diffDefault += pdRGB - dRGB; -// diffMore += pmRGB - mRGB; - - absDiffOriginal += Math.abs(poRGB - oRGB); - absDiffSharpened += Math.abs(psRGB - sRGB); - absDiffDefault += Math.abs(pdRGB - dRGB); - absDiffMore += Math.abs(pmRGB - mRGB); - } - } - -// assertEquals("Difference should not change", diffOriginal, diffSharpened); - assertTrue("Abs difference should increase", absDiffOriginal < absDiffSharpened); -// assertEquals("Difference should not change", diffOriginal, diffDefault); - assertTrue("Abs difference should increase", absDiffOriginal < absDiffDefault); -// assertEquals("Difference should not change", diffOriginal, diffMore); - assertTrue("Abs difference should increase", absDiffOriginal < absDiffMore); -// assertEquals("Difference should not change", diffSharpened, diffMore); - assertTrue("Abs difference should increase", absDiffSharpened < absDiffMore); - } - - @Test - public void testBlur() { - final BufferedImage original = this.original; - - assertNotNull(original); - - final BufferedImage notBlurred = ImageUtil.blur(original, 0f); - // Assumed: Images should be equal - if (original != notBlurred) { // Don't care to test if images are same - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 0; x < original.getWidth(); x++) { - assertEquals("0 blur should not change image", original.getRGB(x, y), notBlurred.getRGB(x, y)); - } - } - } - - // Assumed: Difference between neighbouring pixels should decrease for higher blur values - // Assumed: Dynamics of entire image should not change - final BufferedImage blurred = ImageUtil.blur(original); - final BufferedImage blurredDefault = ImageUtil.blur(original, 1.5f); - final BufferedImage blurredMore = ImageUtil.blur(original, 3f); - -// long diffOriginal = 0; -// long diffBlurred = 0; -// long diffDefault = 0; -// long diffMore = 0; - - long absDiffOriginal = 0; - long absDiffBlurred = 0; - long absDiffDefault = 0; - long absDiffMore = 0; - - for (int y = 0; y < original.getHeight(); y++) { - for (int x = 1; x < original.getWidth(); x++) { - int oRGB = 0x00FFFFFF & original.getRGB(x, y); - int bRGB = 0x00FFFFFF & blurred.getRGB(x, y); - int dRGB = 0x00FFFFFF & blurredDefault.getRGB(x, y); - int mRGB = 0x00FFFFFF & blurredMore.getRGB(x, y); - - int poRGB = 0x00FFFFFF & original.getRGB(x - 1, y); - int pbRGB = 0x00FFFFFF & blurred.getRGB(x - 1, y); - int pdRGB = 0x00FFFFFF & blurredDefault.getRGB(x - 1, y); - int pmRGB = 0x00FFFFFF & blurredMore.getRGB(x - 1, y); - -// diffOriginal += poRGB - oRGB; -// diffBlurred += pbRGB - bRGB; -// diffDefault += pdRGB - dRGB; -// diffMore += pmRGB - mRGB; - - absDiffOriginal += Math.abs(poRGB - oRGB); - absDiffBlurred += Math.abs(pbRGB - bRGB); - absDiffDefault += Math.abs(pdRGB - dRGB); - absDiffMore += Math.abs(pmRGB - mRGB); - } - } - -// assertEquals("Difference should not change", diffOriginal, diffBlurred); - assertTrue(String.format("Abs difference should decrease: %s <= %s", absDiffOriginal, absDiffBlurred), absDiffOriginal > absDiffBlurred); -// assertEquals("Difference should not change", diffOriginal, diffDefault); - assertTrue("Abs difference should decrease", absDiffOriginal > absDiffDefault); -// assertEquals("Difference should not change", diffOriginal, diffMore); - assertTrue("Abs difference should decrease", absDiffOriginal > absDiffMore); -// assertEquals("Difference should not change", diffBlurred, diffMore); - assertTrue("Abs difference should decrease", absDiffBlurred > absDiffMore); - } - - @Test - public void testIndexImage() { - BufferedImage sunflower = original; - - assertNotNull(sunflower); - - BufferedImage image = ImageUtil.createIndexed(sunflower); - assertNotNull("Image was null", image); - assertTrue(image.getColorModel() instanceof IndexColorModel); - } -} +/* + * 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.image; + +import org.junit.Test; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.io.InputStream; + +import static org.junit.Assert.*; + +public class ImageUtilTest { + + private final static String IMAGE_NAME = "/sunflower.jpg"; + private BufferedImage original; + private BufferedImage image; + private Image scaled; + + public ImageUtilTest() throws Exception { + image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST); + + // Read image from class path + InputStream is = getClass().getResourceAsStream(IMAGE_NAME); + original = ImageIO.read(is); + + assertNotNull(original); + } + + /* + public void setUp() throws Exception { + image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST); + + // Read image from class path + InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME); + original = ImageIO.read(is); + + assertNotNull(original); + } + + protected void tearDown() throws Exception { + original = null; + } + */ + + @Test + public void testToBufferedImageNull() { + BufferedImage img = null; + boolean threwRuntimeException = false; + + try { + img = ImageUtil.toBuffered((Image) null); + } + catch (RuntimeException ne) { + threwRuntimeException = true; + } + // No input should return null + assertNull(img); + + // Should have thrown an exception + assertTrue(threwRuntimeException); + } + + @Test + public void testToBufferedImageTypeNull() { + BufferedImage img = null; + boolean threwRuntimeException = false; + + try { + img = ImageUtil.toBuffered(null, BufferedImage.TYPE_INT_ARGB); + } + catch (RuntimeException ne) { + threwRuntimeException = true; + } + // No input should return null + assertNull(img); + + // Should have thrown an exception + assertTrue(threwRuntimeException); + } + + @Test + public void testImageIsNotBufferedImage() { + // Should not be a buffered image + assertFalse( + "FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.", + scaled instanceof BufferedImage + ); + } + + @Test + public void testToBufferedImage() { + BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) image); + BufferedImage bufferedScaled = ImageUtil.toBuffered(scaled); + + // Should be no need to convert + assertSame(image, sameAsImage); + + // Should have same dimensions + assertEquals(scaled.getWidth(null), bufferedScaled.getWidth()); + assertEquals(scaled.getHeight(null), bufferedScaled.getHeight()); + } + + @Test + public void testToBufferedImageType() { + // Assumes image is TYPE_INT_ARGB + BufferedImage converted = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_INDEXED); + BufferedImage convertedToo = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_BINARY); + + // Should not be the same + assertNotSame(image, converted); + assertNotSame(image, convertedToo); + + // Correct type + assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED); + assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY); + + // Should have same dimensions + assertEquals(image.getWidth(), converted.getWidth()); + assertEquals(image.getHeight(), converted.getHeight()); + + assertEquals(image.getWidth(), convertedToo.getWidth()); + assertEquals(image.getHeight(), convertedToo.getHeight()); + } + + @Test + public void testBrightness() { + final BufferedImage original = this.original; + assertNotNull(original); + + final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f)); + // Assumed: Images should be equal + if (original != notBrightened) { // Don't care to test if images are same + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + assertEquals(original.getRGB(x, y), notBrightened.getRGB(x, y)); + } + } + } + + // Assumed: All pixels should be brighter or equal to original + final BufferedImage brightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0.4f)); + final BufferedImage brightenedMore = ImageUtil.toBuffered(ImageUtil.brightness(original, 0.9f)); + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + assertTrue(original.getRGB(x, y) <= brightened.getRGB(x, y)); + assertTrue(brightened.getRGB(x, y) <= brightenedMore.getRGB(x, y)); + } + } + + // Assumed: Image should be all white + final BufferedImage brightenedMax = ImageUtil.toBuffered(ImageUtil.brightness(original, 2f)); + for (int y = 0; y < brightenedMax.getHeight(); y++) { + for (int x = 0; x < brightenedMax.getWidth(); x++) { + assertEquals(0x00FFFFFF, brightenedMax.getRGB(x, y) & 0x00FFFFFF); + } + } + + // Assumed: All pixels should be darker or equal to originial + final BufferedImage brightenedNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -0.4f)); + final BufferedImage brightenedNegativeMore = ImageUtil.toBuffered(ImageUtil.brightness(original, -0.9f)); + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + assertTrue(original.getRGB(x, y) >= brightenedNegative.getRGB(x, y)); + assertTrue(brightenedNegative.getRGB(x, y) >= brightenedNegativeMore.getRGB(x, y)); + } + } + // Assumed: Image should be all black + final BufferedImage brightenedMaxNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -2f)); + for (int y = 0; y < brightenedMaxNegative.getHeight(); y++) { + for (int x = 0; x < brightenedMaxNegative.getWidth(); x++) { + assertEquals(0x0, brightenedMaxNegative.getRGB(x, y) & 0x00FFFFFF); + } + } + + /* + JFrame frame = new JFrame("Sunflower - brightness"); + frame.setSize(sunflower.getWidth() * 4, sunflower.getHeight() * 2); + + Canvas canvas = new Canvas() { + public void paint(Graphics g) { + // Draw original for comparison + g.drawImage(original, 0, 0, null); + + // This should look like original + g.drawImage(notBrightened, 0, original.getHeight(), null); + + // Different versions + g.drawImage(brightened, original.getWidth(), 0, null); + g.drawImage(brightenedMore, original.getWidth() * 2, 0, null); + g.drawImage(brightenedMax, original.getWidth() * 3, 0, null); + + g.drawImage(brightenedNegative, original.getWidth(), original.getHeight(), null); + g.drawImage(brightenedNegativeMore, original.getWidth() * 2, original.getHeight(), null); + g.drawImage(brightenedMaxNegative, original.getWidth() * 3, original.getHeight(), null); + } + }; + + frame.getContentPane().add(canvas); + frame.setVisible(true); + + assertTrue(true); + */ + } + + @Test + public void testContrast() { + final BufferedImage original = this.original; + + assertNotNull(original); + + final BufferedImage notContrasted = ImageUtil.toBuffered(ImageUtil.contrast(original, 0f)); + // Assumed: Images should be equal + if (original != notContrasted) { // Don't care to test if images are same + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + assertEquals("0 constrast should not change image", original.getRGB(x, y), notContrasted.getRGB(x, y)); + } + } + } + + // Assumed: Contrast should be greater or equal to original + final BufferedImage contrasted = ImageUtil.toBuffered(ImageUtil.contrast(original)); + final BufferedImage contrastedDefault = ImageUtil.toBuffered(ImageUtil.contrast(original, 0.5f)); + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + int oRGB = original.getRGB(x, y); + int cRGB = contrasted.getRGB(x, y); + int dRGB = contrastedDefault.getRGB(x, y); + + int oR = oRGB >> 16 & 0xFF; + int oG = oRGB >> 8 & 0xFF; + int oB = oRGB & 0xFF; + + int cR = cRGB >> 16 & 0xFF; + int cG = cRGB >> 8 & 0xFF; + int cB = cRGB & 0xFF; + + int dR = dRGB >> 16 & 0xFF; + int dG = dRGB >> 8 & 0xFF; + int dB = dRGB & 0xFF; + + // RED + if (oR < 127) { + assertTrue("Contrast should be decreased or same", oR >= cR && cR >= dR); + } + else { + assertTrue("Contrast should be increased or same", oR <= cR && cR <= dR); + } + // GREEN + if (oG < 127) { + assertTrue("Contrast should be decreased or same", oG >= cG && cG >= dG); + } + else { + assertTrue("Contrast should be increased or same", oG <= cG && cG <= dG); + } + // BLUE + if (oB < 127) { + assertTrue("Contrast should be decreased or same", oB >= cB && cB >= dB); + } + else { + assertTrue("Contrast should be increased or same", oB <= cB && cB <= dB); + } + } + } + // Assumed: Only primary colors (w/b/r/g/b/c/y/m) + final BufferedImage contrastedMax = ImageUtil.toBuffered(ImageUtil.contrast(original, 1f)); + for (int y = 0; y < contrastedMax.getHeight(); y++) { + for (int x = 0; x < contrastedMax.getWidth(); x++) { + int rgb = contrastedMax.getRGB(x, y); + int r = rgb >> 16 & 0xFF; + int g = rgb >> 8 & 0xFF; + int b = rgb & 0xFF; + assertTrue("Max contrast should only produce primary colors", r == 0 || r == 255); + assertTrue("Max contrast should only produce primary colors", g == 0 || g == 255); + assertTrue("Max contrast should only produce primary colors", b == 0 || b == 255); + } + } + + // Assumed: Contrasts should be less than or equal to original + final BufferedImage contrastedNegative = ImageUtil.toBuffered(ImageUtil.contrast(original, -0.5f)); + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + int oRGB = original.getRGB(x, y); + int cRGB = contrastedNegative.getRGB(x, y); + + int oR = oRGB >> 16 & 0xFF; + int oG = oRGB >> 8 & 0xFF; + int oB = oRGB & 0xFF; + + int cR = cRGB >> 16 & 0xFF; + int cG = cRGB >> 8 & 0xFF; + int cB = cRGB & 0xFF; + + // RED + if (oR >= 127) { + assertTrue("Contrast should be decreased or same", oR >= cR); + } + else { + assertTrue("Contrast should be increased or same", oR <= cR); + } + // GREEN + if (oG >= 127) { + assertTrue("Contrast should be decreased or same", oG >= cG); + } + else { + assertTrue("Contrast should be increased or same", oG <= cG); + } + // BLUE + if (oB >= 127) { + assertTrue("Contrast should be decreased or same", oB >= cB); + } + else { + assertTrue("Contrast should be increased or same", oB <= cB); + } + } + } + + // Assumed: All gray (127)! + final BufferedImage contrastedMoreNegative = ImageUtil.toBuffered(ImageUtil.contrast(original, -1.0f)); + for (int y = 0; y < contrastedMoreNegative.getHeight(); y++) { + for (int x = 0; x < contrastedMoreNegative.getWidth(); x++) { + int rgb = contrastedMoreNegative.getRGB(x, y); + int r = rgb >> 16 & 0xFF; + int g = rgb >> 8 & 0xFF; + int b = rgb & 0xFF; + assertTrue("Minimum contrast should be all gray", r == 127 && g == 127 && b == 127); + } + } + + /* + JFrame frame = new JFrame("Sunflower - contrast"); + frame.setSize(sunflower.getWidth() * 4, sunflower.getHeight() * 2); + + Canvas canvas = new Canvas() { + public void paint(Graphics g) { + // Draw original for comparison + g.drawImage(original, 0, 0, null); + + // This should look like original + g.drawImage(notContrasted, 0, original.getHeight(), null); + + // Different versions + g.drawImage(contrasted, original.getWidth(), 0, null); + g.drawImage(contrastedDefault, original.getWidth() * 2, 0, null); + g.drawImage(contrastedMax, original.getWidth() * 3, 0, null); + g.drawImage(contrastedNegative, original.getWidth() * 2, original.getHeight(), null); + g.drawImage(contrastedMoreNegative, original.getWidth() * 3, original.getHeight(), null); + } + }; + + frame.getContentPane().add(canvas); + frame.setVisible(true); + + assertTrue(true); + */ + } + + @Test + public void testSharpen() { + final BufferedImage original = this.original; + + assertNotNull(original); + + final BufferedImage notSharpened = ImageUtil.sharpen(original, 0f); + // Assumed: Images should be equal + if (original != notSharpened) { // Don't care to test if images are same + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + assertEquals("0 sharpen should not change image", original.getRGB(x, y), notSharpened.getRGB(x, y)); + } + } + } + + // Assumed: Difference between neighbouring pixels should increase for higher sharpen values + // Assumed: Dynamics of entire image should not change + final BufferedImage sharpened = ImageUtil.sharpen(original); + final BufferedImage sharpenedDefault = ImageUtil.sharpen(original, 0.3f); + final BufferedImage sharpenedMore = ImageUtil.sharpen(original, 1.3f); + +// long diffOriginal = 0; +// long diffSharpened = 0; +// long diffDefault = 0; +// long diffMore = 0; + + long absDiffOriginal = 0; + long absDiffSharpened = 0; + long absDiffDefault = 0; + long absDiffMore = 0; + + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 1; x < original.getWidth(); x++) { + int oRGB = 0x00FFFFFF & original.getRGB(x, y); + int sRGB = 0x00FFFFFF & sharpened.getRGB(x, y); + int dRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x, y); + int mRGB = 0x00FFFFFF & sharpenedMore.getRGB(x, y); + + int poRGB = 0x00FFFFFF & original.getRGB(x - 1, y); + int psRGB = 0x00FFFFFF & sharpened.getRGB(x - 1, y); + int pdRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x - 1, y); + int pmRGB = 0x00FFFFFF & sharpenedMore.getRGB(x - 1, y); + +// diffOriginal += poRGB - oRGB; +// diffSharpened += psRGB - sRGB; +// diffDefault += pdRGB - dRGB; +// diffMore += pmRGB - mRGB; + + absDiffOriginal += Math.abs(poRGB - oRGB); + absDiffSharpened += Math.abs(psRGB - sRGB); + absDiffDefault += Math.abs(pdRGB - dRGB); + absDiffMore += Math.abs(pmRGB - mRGB); + } + } + +// assertEquals("Difference should not change", diffOriginal, diffSharpened); + assertTrue("Abs difference should increase", absDiffOriginal < absDiffSharpened); +// assertEquals("Difference should not change", diffOriginal, diffDefault); + assertTrue("Abs difference should increase", absDiffOriginal < absDiffDefault); +// assertEquals("Difference should not change", diffOriginal, diffMore); + assertTrue("Abs difference should increase", absDiffOriginal < absDiffMore); +// assertEquals("Difference should not change", diffSharpened, diffMore); + assertTrue("Abs difference should increase", absDiffSharpened < absDiffMore); + } + + @Test + public void testBlur() { + final BufferedImage original = this.original; + + assertNotNull(original); + + final BufferedImage notBlurred = ImageUtil.blur(original, 0f); + // Assumed: Images should be equal + if (original != notBlurred) { // Don't care to test if images are same + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 0; x < original.getWidth(); x++) { + assertEquals("0 blur should not change image", original.getRGB(x, y), notBlurred.getRGB(x, y)); + } + } + } + + // Assumed: Difference between neighbouring pixels should decrease for higher blur values + // Assumed: Dynamics of entire image should not change + final BufferedImage blurred = ImageUtil.blur(original); + final BufferedImage blurredDefault = ImageUtil.blur(original, 1.5f); + final BufferedImage blurredMore = ImageUtil.blur(original, 3f); + +// long diffOriginal = 0; +// long diffBlurred = 0; +// long diffDefault = 0; +// long diffMore = 0; + + long absDiffOriginal = 0; + long absDiffBlurred = 0; + long absDiffDefault = 0; + long absDiffMore = 0; + + for (int y = 0; y < original.getHeight(); y++) { + for (int x = 1; x < original.getWidth(); x++) { + int oRGB = 0x00FFFFFF & original.getRGB(x, y); + int bRGB = 0x00FFFFFF & blurred.getRGB(x, y); + int dRGB = 0x00FFFFFF & blurredDefault.getRGB(x, y); + int mRGB = 0x00FFFFFF & blurredMore.getRGB(x, y); + + int poRGB = 0x00FFFFFF & original.getRGB(x - 1, y); + int pbRGB = 0x00FFFFFF & blurred.getRGB(x - 1, y); + int pdRGB = 0x00FFFFFF & blurredDefault.getRGB(x - 1, y); + int pmRGB = 0x00FFFFFF & blurredMore.getRGB(x - 1, y); + +// diffOriginal += poRGB - oRGB; +// diffBlurred += pbRGB - bRGB; +// diffDefault += pdRGB - dRGB; +// diffMore += pmRGB - mRGB; + + absDiffOriginal += Math.abs(poRGB - oRGB); + absDiffBlurred += Math.abs(pbRGB - bRGB); + absDiffDefault += Math.abs(pdRGB - dRGB); + absDiffMore += Math.abs(pmRGB - mRGB); + } + } + +// assertEquals("Difference should not change", diffOriginal, diffBlurred); + assertTrue(String.format("Abs difference should decrease: %s <= %s", absDiffOriginal, absDiffBlurred), absDiffOriginal > absDiffBlurred); +// assertEquals("Difference should not change", diffOriginal, diffDefault); + assertTrue("Abs difference should decrease", absDiffOriginal > absDiffDefault); +// assertEquals("Difference should not change", diffOriginal, diffMore); + assertTrue("Abs difference should decrease", absDiffOriginal > absDiffMore); +// assertEquals("Difference should not change", diffBlurred, diffMore); + assertTrue("Abs difference should decrease", absDiffBlurred > absDiffMore); + } + + @Test + public void testIndexImage() { + BufferedImage sunflower = original; + + assertNotNull(sunflower); + + BufferedImage image = ImageUtil.createIndexed(sunflower); + assertNotNull("Image was null", image); + assertTrue(image.getColorModel() instanceof IndexColorModel); + } +} diff --git a/common/common-image/src/test/java/com/twelvemonkeys/image/ResampleOpTestCase.java b/common/common-image/src/test/java/com/twelvemonkeys/image/ResampleOpTest.java similarity index 87% rename from common/common-image/src/test/java/com/twelvemonkeys/image/ResampleOpTestCase.java rename to common/common-image/src/test/java/com/twelvemonkeys/image/ResampleOpTest.java index 316fac6e..c6b43bf3 100644 --- a/common/common-image/src/test/java/com/twelvemonkeys/image/ResampleOpTestCase.java +++ b/common/common-image/src/test/java/com/twelvemonkeys/image/ResampleOpTest.java @@ -1,3 +1,33 @@ +/* + * 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.image; import org.junit.Ignore; @@ -19,7 +49,7 @@ import static org.junit.Assert.*; * @author last modified by $Author: haku $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/image/ResampleOpTestCase.java#1 $ */ -public class ResampleOpTestCase { +public class ResampleOpTest { protected BufferedImage createImage(final int pWidth, final int pHeigth) { return createImage(pWidth, pHeigth, BufferedImage.TYPE_INT_ARGB); diff --git a/common/common-io/pom.xml b/common/common-io/pom.xml index 19f61ccc..64a3c91d 100644 --- a/common/common-io/pom.xml +++ b/common/common-io/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.common common - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT common-io jar @@ -13,6 +13,10 @@ The TwelveMonkeys Common IO support + + com.twelvemonkeys.common.io + + ${project.groupId} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/AbstractCachedSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/AbstractCachedSeekableStream.java index 69f07bba..afc87f14 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/AbstractCachedSeekableStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/AbstractCachedSeekableStream.java @@ -1,3 +1,33 @@ +/* + * 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.io; import com.twelvemonkeys.lang.Validate; @@ -156,7 +186,7 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream { } protected void closeImpl() throws IOException { - cache.flush(position); + cache.close(); cache = null; stream.close(); } @@ -168,12 +198,12 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream { * @author last modified by $Author: haku $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/AbstractCachedSeekableStream.java#2 $ */ - public static abstract class StreamCache { + static abstract class StreamCache { /** * Creates a {@code StreamCache}. */ - protected StreamCache() { + StreamCache() { } /** @@ -188,9 +218,10 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream { /** * Writes a series of bytes at the current read/write position. The read/write position will be increased by * {@code pLength}. - *

+ *

* This implementation invokes {@link #write(int)} {@code pLength} times. * Subclasses may override this method for performance. + *

* * @param pBuffer the bytes to write. * @param pOffset the starting offset into the buffer. @@ -216,9 +247,10 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream { /** * Writes a series of bytes at the current read/write position. The read/write position will be increased by * {@code pLength}. - *

+ *

* This implementation invokes {@link #read()} {@code pLength} times. * Subclasses may override this method for performance. + *

* * @param pBuffer the bytes to write * @param pOffset the starting offset into the buffer. @@ -253,12 +285,14 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream { /** * Optionally flushes any data prior to the given position. - *

+ *

* Attempting to perform a seek operation, and/or a read or write operation to a position equal to or before * the flushed position may result in exceptions or undefined behaviour. - *

+ *

+ *

* Subclasses should override this method for performance reasons, to avoid holding on to unnecessary resources. * This implementation does nothing. + *

* * @param pPosition the last position to flush. */ @@ -273,5 +307,7 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream { * @throws IOException if the position can't be determined because of a problem in the cache backing mechanism. */ abstract long getPosition() throws IOException; + + abstract void close() throws IOException; } } diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/CompoundReader.java b/common/common-io/src/main/java/com/twelvemonkeys/io/CompoundReader.java index e41562f1..47279114 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/CompoundReader.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/CompoundReader.java @@ -1,220 +1,221 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.lang.Validate; - -import java.io.IOException; -import java.io.Reader; -import java.util.Iterator; -import java.util.ArrayList; -import java.util.List; - -/** - * A Reader implementation that can read from multiple sources. - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/CompoundReader.java#2 $ - */ -public class CompoundReader extends Reader { - - private Reader current; - private List readers; - - protected final Object finalLock; - - protected final boolean markSupported; - - private int currentReader; - private int markedReader; - private int mark; - private int mNext; - - /** - * Create a new compound reader. - * - * @param pReaders {@code Iterator} containting {@code Reader}s, - * providing the character stream. - * - * @throws NullPointerException if {@code pReaders} is {@code null}, or - * any of the elements in the iterator is {@code null}. - * @throws ClassCastException if any element of the iterator is not a - * {@code java.io.Reader} - */ - public CompoundReader(final Iterator pReaders) { - super(Validate.notNull(pReaders, "readers")); - - finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the - // reference can't change, only it's elements - - readers = new ArrayList(); - - boolean markSupported = true; - while (pReaders.hasNext()) { - Reader reader = pReaders.next(); - if (reader == null) { - throw new NullPointerException("readers cannot contain null-elements"); - } - readers.add(reader); - markSupported = markSupported && reader.markSupported(); - } - this.markSupported = markSupported; - - current = nextReader(); - } - - protected final Reader nextReader() { - if (currentReader >= readers.size()) { - current = new EmptyReader(); - } - else { - current = readers.get(currentReader++); - } - - // NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods! - mNext = 0; - return current; - } - - /** - * Check to make sure that the stream has not been closed - * - * @throws IOException if the stream is closed - */ - protected final void ensureOpen() throws IOException { - if (readers == null) { - throw new IOException("Stream closed"); - } - } - - public void close() throws IOException { - // Close all readers - for (Reader reader : readers) { - reader.close(); - } - - readers = null; - } - - @Override - public void mark(int pReadLimit) throws IOException { - if (pReadLimit < 0) { - throw new IllegalArgumentException("Read limit < 0"); - } - - // TODO: It would be nice if we could actually close some readers now - - synchronized (finalLock) { - ensureOpen(); - mark = mNext; - markedReader = currentReader; - - current.mark(pReadLimit); - } - } - - @Override - public void reset() throws IOException { - synchronized (finalLock) { - ensureOpen(); - - if (currentReader != markedReader) { - // Reset any reader before this - for (int i = currentReader; i >= markedReader; i--) { - readers.get(i).reset(); - } - - currentReader = markedReader - 1; - nextReader(); - } - current.reset(); - - mNext = mark; - } - } - - @Override - public boolean markSupported() { - return markSupported; - } - - @Override - public int read() throws IOException { - synchronized (finalLock) { - int read = current.read(); - - if (read < 0 && currentReader < readers.size()) { - nextReader(); - return read(); // In case of 0-length readers - } - - mNext++; - - return read; - } - } - - public int read(char pBuffer[], int pOffset, int pLength) throws IOException { - synchronized (finalLock) { - int read = current.read(pBuffer, pOffset, pLength); - - if (read < 0 && currentReader < readers.size()) { - nextReader(); - return read(pBuffer, pOffset, pLength); // In case of 0-length readers - } - - mNext += read; - - return read; - } - } - - @Override - public boolean ready() throws IOException { - return current.ready(); - } - - @Override - public long skip(long pChars) throws IOException { - synchronized (finalLock) { - long skipped = current.skip(pChars); - - if (skipped == 0 && currentReader < readers.size()) { - nextReader(); - return skip(pChars); // In case of 0-length readers - } - - mNext += skipped; - - return skipped; - } - } -} +/* + * 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.io; + +import com.twelvemonkeys.lang.Validate; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A Reader implementation that can read from multiple sources. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/CompoundReader.java#2 $ + */ +public class CompoundReader extends Reader { + + private Reader current; + private List readers; + + protected final Object finalLock; + + protected final boolean markSupported; + + private int currentReader; + private int markedReader; + private int mark; + private int mNext; + + /** + * Create a new compound reader. + * + * @param pReaders {@code Iterator} containting {@code Reader}s, + * providing the character stream. + * + * @throws NullPointerException if {@code pReaders} is {@code null}, or + * any of the elements in the iterator is {@code null}. + * @throws ClassCastException if any element of the iterator is not a + * {@code java.io.Reader} + */ + public CompoundReader(final Iterator pReaders) { + super(Validate.notNull(pReaders, "readers")); + + finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the + // reference can't change, only it's elements + + readers = new ArrayList(); + + boolean markSupported = true; + while (pReaders.hasNext()) { + Reader reader = pReaders.next(); + if (reader == null) { + throw new NullPointerException("readers cannot contain null-elements"); + } + readers.add(reader); + markSupported = markSupported && reader.markSupported(); + } + this.markSupported = markSupported; + + current = nextReader(); + } + + protected final Reader nextReader() { + if (currentReader >= readers.size()) { + current = new EmptyReader(); + } + else { + current = readers.get(currentReader++); + } + + // NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods! + mNext = 0; + return current; + } + + /** + * Check to make sure that the stream has not been closed + * + * @throws IOException if the stream is closed + */ + protected final void ensureOpen() throws IOException { + if (readers == null) { + throw new IOException("Stream closed"); + } + } + + public void close() throws IOException { + // Close all readers + for (Reader reader : readers) { + reader.close(); + } + + readers = null; + } + + @Override + public void mark(int pReadLimit) throws IOException { + if (pReadLimit < 0) { + throw new IllegalArgumentException("Read limit < 0"); + } + + // TODO: It would be nice if we could actually close some readers now + + synchronized (finalLock) { + ensureOpen(); + mark = mNext; + markedReader = currentReader; + + current.mark(pReadLimit); + } + } + + @Override + public void reset() throws IOException { + synchronized (finalLock) { + ensureOpen(); + + if (currentReader != markedReader) { + // Reset any reader before this + for (int i = currentReader; i >= markedReader; i--) { + readers.get(i).reset(); + } + + currentReader = markedReader - 1; + nextReader(); + } + current.reset(); + + mNext = mark; + } + } + + @Override + public boolean markSupported() { + return markSupported; + } + + @Override + public int read() throws IOException { + synchronized (finalLock) { + int read = current.read(); + + if (read < 0 && currentReader < readers.size()) { + nextReader(); + return read(); // In case of 0-length readers + } + + mNext++; + + return read; + } + } + + public int read(char pBuffer[], int pOffset, int pLength) throws IOException { + synchronized (finalLock) { + int read = current.read(pBuffer, pOffset, pLength); + + if (read < 0 && currentReader < readers.size()) { + nextReader(); + return read(pBuffer, pOffset, pLength); // In case of 0-length readers + } + + mNext += read; + + return read; + } + } + + @Override + public boolean ready() throws IOException { + return current.ready(); + } + + @Override + public long skip(long pChars) throws IOException { + synchronized (finalLock) { + long skipped = current.skip(pChars); + + if (skipped == 0 && currentReader < readers.size()) { + nextReader(); + return skip(pChars); // In case of 0-length readers + } + + mNext += skipped; + + return skipped; + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java b/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java index d83c9053..6ff1a7a4 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java @@ -1,45 +1,46 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.StringReader; - -/** - * EmptyReader - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/EmptyReader.java#1 $ - */ -final class EmptyReader extends StringReader { - public EmptyReader() { - super(""); - } -} +/* + * 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.io; + +import java.io.StringReader; + +/** + * EmptyReader + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/EmptyReader.java#1 $ + */ +final class EmptyReader extends StringReader { + public EmptyReader() { + super(""); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java index bf6c5b5f..87b8fade 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java @@ -1,134 +1,137 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.ByteArrayInputStream; - -/** - * An unsynchronized {@code ByteArrayOutputStream} implementation. This version - * also has a constructor that lets you create a stream with initial content. - *

- * - * @author Harald Kuhr - * @version $Id: FastByteArrayOutputStream.java#2 $ - */ -// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block -public final class FastByteArrayOutputStream extends ByteArrayOutputStream { - /** Max grow size (unless if writing more than this amount of bytes) */ - protected int maxGrowSize = 1024 * 1024; // 1 MB - - /** - * Creates a {@code ByteArrayOutputStream} with the given initial buffer - * size. - * - * @param pSize initial buffer size - */ - public FastByteArrayOutputStream(int pSize) { - super(pSize); - } - - /** - * Creates a {@code ByteArrayOutputStream} with the given initial content. - *

- * Note that the buffer is not cloned, for maximum performance. - * - * @param pBuffer initial buffer - */ - public FastByteArrayOutputStream(byte[] pBuffer) { - super(0); // Don't allocate array - buf = pBuffer; - count = pBuffer.length; - } - - @Override - public void write(byte pBytes[], int pOffset, int pLength) { - if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || - ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { - throw new IndexOutOfBoundsException(); - } - else if (pLength == 0) { - return; - } - - int newCount = count + pLength; - growIfNeeded(newCount); - System.arraycopy(pBytes, pOffset, buf, count, pLength); - count = newCount; - } - - @Override - public void write(int pByte) { - int newCount = count + 1; - growIfNeeded(newCount); - buf[count] = (byte) pByte; - count = newCount; - } - - private void growIfNeeded(int pNewCount) { - if (pNewCount > buf.length) { - int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount); - byte newBuf[] = new byte[newSize]; - System.arraycopy(buf, 0, newBuf, 0, count); - buf = newBuf; - } - } - - // Non-synchronized version of writeTo - @Override - public void writeTo(OutputStream pOut) throws IOException { - pOut.write(buf, 0, count); - } - - // Non-synchronized version of toByteArray - @Override - public byte[] toByteArray() { - byte newBuf[] = new byte[count]; - System.arraycopy(buf, 0, newBuf, 0, count); - - return newBuf; - } - - /** - * Creates a {@code ByteArrayInputStream} that reads directly from this - * {@code FastByteArrayOutputStream}'s byte buffer. - * The buffer is not cloned, for maximum performance. - *

- * Note that care needs to be taken to avoid writes to - * this output stream after the input stream is created. - * Failing to do so, may result in unpredictable behaviour. - * - * @return a new {@code ByteArrayInputStream}, reading from this stream's buffer. - */ - public ByteArrayInputStream createInputStream() { - return new ByteArrayInputStream(buf, 0, count); - } +/* + * 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.io; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An unsynchronized {@code ByteArrayOutputStream} implementation. This version + * also has a constructor that lets you create a stream with initial content. + * + * @author Harald Kuhr + * @version $Id: FastByteArrayOutputStream.java#2 $ + */ +// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block +public final class FastByteArrayOutputStream extends ByteArrayOutputStream { + /** Max grow size (unless if writing more than this amount of bytes) */ + protected int maxGrowSize = 1024 * 1024; // 1 MB + + /** + * Creates a {@code ByteArrayOutputStream} with the given initial buffer + * size. + * + * @param pSize initial buffer size + */ + public FastByteArrayOutputStream(int pSize) { + super(pSize); + } + + /** + * Creates a {@code ByteArrayOutputStream} with the given initial content. + *

+ * Note that the buffer is not cloned, for maximum performance. + *

+ * + * @param pBuffer initial buffer + */ + public FastByteArrayOutputStream(byte[] pBuffer) { + super(0); // Don't allocate array + buf = pBuffer; + count = pBuffer.length; + } + + @Override + public void write(byte[] pBytes, int pOffset, int pLength) { + if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || + ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { + throw new IndexOutOfBoundsException(); + } + else if (pLength == 0) { + return; + } + + int newCount = count + pLength; + growIfNeeded(newCount); + System.arraycopy(pBytes, pOffset, buf, count, pLength); + count = newCount; + } + + @Override + public void write(int pByte) { + int newCount = count + 1; + growIfNeeded(newCount); + buf[count] = (byte) pByte; + count = newCount; + } + + private void growIfNeeded(int pNewCount) { + if (pNewCount > buf.length) { + int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount); + byte[] newBuf = new byte[newSize]; + System.arraycopy(buf, 0, newBuf, 0, count); + buf = newBuf; + } + } + + // Non-synchronized version of writeTo + @Override + public void writeTo(OutputStream pOut) throws IOException { + pOut.write(buf, 0, count); + } + + // Non-synchronized version of toByteArray + @Override + public byte[] toByteArray() { + byte[] newBuf = new byte[count]; + System.arraycopy(buf, 0, newBuf, 0, count); + + return newBuf; + } + + /** + * Creates a {@code ByteArrayInputStream} that reads directly from this + * {@code FastByteArrayOutputStream}'s byte buffer. + * The buffer is not cloned, for maximum performance. + *

+ * Note that care needs to be taken to avoid writes to + * this output stream after the input stream is created. + * Failing to do so, may result in unpredictable behaviour. + *

+ * + * @return a new {@code ByteArrayInputStream}, reading from this stream's buffer. + */ + public ByteArrayInputStream createInputStream() { + return new ByteArrayInputStream(buf, 0, count); + } } \ No newline at end of file diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java index e514c3c8..d02225d9 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java @@ -1,230 +1,241 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.lang.Validate; - -import java.io.*; - -/** - * A {@code SeekableInputStream} implementation that caches data in a temporary {@code File}. - *

- * Temporary files are created as specified in {@link File#createTempFile(String, String, java.io.File)}. - * - * @see MemoryCacheSeekableStream - * @see FileSeekableStream - * - * @see File#createTempFile(String, String) - * @see RandomAccessFile - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java#5 $ - */ -public final class FileCacheSeekableStream extends AbstractCachedSeekableStream { - - private byte[] buffer; - - /** - * Creates a {@code FileCacheSeekableStream} reading from the given - * {@code InputStream}. Data will be cached in a temporary file. - * - * @param pStream the {@code InputStream} to read from - * - * @throws IOException if the temporary file cannot be created, - * or cannot be opened for random access. - */ - public FileCacheSeekableStream(final InputStream pStream) throws IOException { - this(pStream, "iocache", null); - } - - /** - * Creates a {@code FileCacheSeekableStream} reading from the given - * {@code InputStream}. Data will be cached in a temporary file, with - * the given base name. - * - * @param pStream the {@code InputStream} to read from - * @param pTempBaseName optional base name for the temporary file - * - * @throws IOException if the temporary file cannot be created, - * or cannot be opened for random access. - */ - public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName) throws IOException { - this(pStream, pTempBaseName, null); - } - - /** - * Creates a {@code FileCacheSeekableStream} reading from the given - * {@code InputStream}. Data will be cached in a temporary file, with - * the given base name, in the given directory - * - * @param pStream the {@code InputStream} to read from - * @param pTempBaseName optional base name for the temporary file - * @param pTempDir optional temp directory - * - * @throws IOException if the temporary file cannot be created, - * or cannot be opened for random access. - */ - public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName, final File pTempDir) throws IOException { - // NOTE: We do validation BEFORE we create temp file, to avoid orphan files - this(Validate.notNull(pStream, "stream"), createTempFile(pTempBaseName, pTempDir)); - } - - /*protected*/ static File createTempFile(String pTempBaseName, File pTempDir) throws IOException { - Validate.notNull(pTempBaseName, "tempBaseName"); - - File file = File.createTempFile(pTempBaseName, null, pTempDir); - file.deleteOnExit(); - - return file; - } - - // TODO: Consider exposing this for external use - /*protected*/ FileCacheSeekableStream(final InputStream pStream, final File pFile) throws FileNotFoundException { - super(pStream, new FileCache(pFile)); - - // TODO: Allow for custom buffer sizes? - buffer = new byte[1024]; - } - - public final boolean isCachedMemory() { - return false; - } - - public final boolean isCachedFile() { - return true; - } - - @Override - protected void closeImpl() throws IOException { - super.closeImpl(); - buffer = null; - } - - @Override - public int read() throws IOException { - checkOpen(); - - int read; - if (position == streamPosition) { - // Read ahead into buffer, for performance - read = readAhead(buffer, 0, buffer.length); - if (read >= 0) { - read = buffer[0] & 0xff; - } - - //System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff)); - } - else { - // ..or read byte from the cache - syncPosition(); - read = getCache().read(); - - //System.out.println("Read 1 byte from cache: " + Integer.toHexString(read & 0xff)); - } - - // TODO: This field is not REALLY considered accessible.. :-P - if (read != -1) { - position++; - } - return read; - } - - @Override - public int read(byte[] pBytes, int pOffset, int pLength) throws IOException { - checkOpen(); - - int length; - if (position == streamPosition) { - // Read bytes from the stream - length = readAhead(pBytes, pOffset, pLength); - - //System.out.println("Read " + length + " byte from stream"); - } - else { - // ...or read bytes from the cache - syncPosition(); - length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, streamPosition - position)); - - //System.out.println("Read " + length + " byte from cache"); - } - - // TODO: This field is not REALLY considered accessible.. :-P - if (length > 0) { - position += length; - } - return length; - } - - private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - int length; - length = stream.read(pBytes, pOffset, pLength); - - if (length > 0) { - streamPosition += length; - getCache().write(pBytes, pOffset, length); - } - return length; - } - - final static class FileCache extends StreamCache { - private RandomAccessFile mCacheFile; - - public FileCache(final File pFile) throws FileNotFoundException { - Validate.notNull(pFile, "file"); - mCacheFile = new RandomAccessFile(pFile, "rw"); - } - - public void write(final int pByte) throws IOException { - mCacheFile.write(pByte); - } - - @Override - public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { - mCacheFile.write(pBuffer, pOffset, pLength); - } - - public int read() throws IOException { - return mCacheFile.read(); - } - - @Override - public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { - return mCacheFile.read(pBuffer, pOffset, pLength); - } - - public void seek(final long pPosition) throws IOException { - mCacheFile.seek(pPosition); - } - - public long getPosition() throws IOException { - return mCacheFile.getFilePointer(); - } - } - -} +/* + * 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.io; + +import com.twelvemonkeys.lang.Validate; + +import java.io.*; + +/** + * A {@code SeekableInputStream} implementation that caches data in a temporary {@code File}. + *

+ * Temporary files are created as specified in {@link File#createTempFile(String, String, java.io.File)}. + *

+ * + * @see MemoryCacheSeekableStream + * @see FileSeekableStream + * + * @see File#createTempFile(String, String) + * @see RandomAccessFile + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java#5 $ + */ +public final class FileCacheSeekableStream extends AbstractCachedSeekableStream { + + private byte[] buffer; + + /** + * Creates a {@code FileCacheSeekableStream} reading from the given + * {@code InputStream}. Data will be cached in a temporary file. + * + * @param pStream the {@code InputStream} to read from + * + * @throws IOException if the temporary file cannot be created, + * or cannot be opened for random access. + */ + public FileCacheSeekableStream(final InputStream pStream) throws IOException { + this(pStream, "iocache", null); + } + + /** + * Creates a {@code FileCacheSeekableStream} reading from the given + * {@code InputStream}. Data will be cached in a temporary file, with + * the given base name. + * + * @param pStream the {@code InputStream} to read from + * @param pTempBaseName optional base name for the temporary file + * + * @throws IOException if the temporary file cannot be created, + * or cannot be opened for random access. + */ + public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName) throws IOException { + this(pStream, pTempBaseName, null); + } + + /** + * Creates a {@code FileCacheSeekableStream} reading from the given + * {@code InputStream}. Data will be cached in a temporary file, with + * the given base name, in the given directory + * + * @param pStream the {@code InputStream} to read from + * @param pTempBaseName optional base name for the temporary file + * @param pTempDir optional temp directory + * + * @throws IOException if the temporary file cannot be created, + * or cannot be opened for random access. + */ + public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName, final File pTempDir) throws IOException { + // NOTE: We do validation BEFORE we create temp file, to avoid orphan files + this(Validate.notNull(pStream, "stream"), createTempFile(pTempBaseName, pTempDir)); + } + + /*protected*/ static File createTempFile(String pTempBaseName, File pTempDir) throws IOException { + Validate.notNull(pTempBaseName, "tempBaseName"); + + File file = File.createTempFile(pTempBaseName, null, pTempDir); + file.deleteOnExit(); + + return file; + } + + // TODO: Consider exposing this for external use + /*protected*/ FileCacheSeekableStream(final InputStream pStream, final File pFile) throws FileNotFoundException { + super(pStream, new FileCache(pFile)); + + // TODO: Allow for custom buffer sizes? + buffer = new byte[1024]; + } + + public final boolean isCachedMemory() { + return false; + } + + public final boolean isCachedFile() { + return true; + } + + @Override + protected void closeImpl() throws IOException { + // TODO: Close cache file + super.closeImpl(); + + buffer = null; + } + + @Override + public int read() throws IOException { + checkOpen(); + + int read; + if (position == streamPosition) { + // Read ahead into buffer, for performance + read = readAhead(buffer, 0, buffer.length); + if (read >= 0) { + read = buffer[0] & 0xff; + } + + //System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff)); + } + else { + // ..or read byte from the cache + syncPosition(); + read = getCache().read(); + + //System.out.println("Read 1 byte from cache: " + Integer.toHexString(read & 0xff)); + } + + // TODO: This field is not REALLY considered accessible.. :-P + if (read != -1) { + position++; + } + return read; + } + + @Override + public int read(byte[] pBytes, int pOffset, int pLength) throws IOException { + checkOpen(); + + int length; + if (position == streamPosition) { + // Read bytes from the stream + length = readAhead(pBytes, pOffset, pLength); + + //System.out.println("Read " + length + " byte from stream"); + } + else { + // ...or read bytes from the cache + syncPosition(); + length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, streamPosition - position)); + + //System.out.println("Read " + length + " byte from cache"); + } + + // TODO: This field is not REALLY considered accessible.. :-P + if (length > 0) { + position += length; + } + return length; + } + + private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + int length; + length = stream.read(pBytes, pOffset, pLength); + + if (length > 0) { + streamPosition += length; + getCache().write(pBytes, pOffset, length); + } + return length; + } + + // TODO: We need to close the cache file!!! Otherwise we are leaking file descriptors + + final static class FileCache extends StreamCache { + private RandomAccessFile cacheFile; + + public FileCache(final File pFile) throws FileNotFoundException { + Validate.notNull(pFile, "file"); + cacheFile = new RandomAccessFile(pFile, "rw"); + } + + public void write(final int pByte) throws IOException { + cacheFile.write(pByte); + } + + @Override + public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + cacheFile.write(pBuffer, pOffset, pLength); + } + + public int read() throws IOException { + return cacheFile.read(); + } + + @Override + public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + return cacheFile.read(pBuffer, pOffset, pLength); + } + + public void seek(final long pPosition) throws IOException { + cacheFile.seek(pPosition); + } + + public long getPosition() throws IOException { + return cacheFile.getFilePointer(); + } + + @Override + void close() throws IOException { + cacheFile.close(); + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java index e86ad5f0..c4be9cfd 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java @@ -1,130 +1,135 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.*; - -/** - * A {@code SeekableInputStream} implementation that uses random access directly to a {@code File}. - *

- * @see FileCacheSeekableStream - * @see MemoryCacheSeekableStream - * @see RandomAccessFile - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java#4 $ - */ -public final class FileSeekableStream extends SeekableInputStream { - - // TODO: Figure out why this class is SLOWER than FileCacheSeekableStream in - // my tests..? - - final RandomAccessFile mRandomAccess; - - /** - * Creates a {@code FileSeekableStream} that reads from the given - * {@code File}. - * - * @param pInput file to read from - * @throws FileNotFoundException if {@code pInput} does not exist - */ - public FileSeekableStream(final File pInput) throws FileNotFoundException { - this(new RandomAccessFile(pInput, "r")); - } - - /** - * Creates a {@code FileSeekableStream} that reads from the given file. - * The {@code RandomAccessFile} needs only to be open in read - * ({@code "r"}) mode. - * - * @param pInput file to read from - */ - public FileSeekableStream(final RandomAccessFile pInput) { - mRandomAccess = pInput; - } - - /// Seekable - - public boolean isCached() { - return false; - } - - public boolean isCachedFile() { - return false; - } - - public boolean isCachedMemory() { - return false; - } - - /// InputStream - - @Override - public int available() throws IOException { - long length = mRandomAccess.length() - position; - return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length; - } - - public void closeImpl() throws IOException { - mRandomAccess.close(); - } - - public int read() throws IOException { - checkOpen(); - - int read = mRandomAccess.read(); - if (read >= 0) { - position++; - } - return read; - } - - @Override - public int read(byte pBytes[], int pOffset, int pLength) throws IOException { - checkOpen(); - - int read = mRandomAccess.read(pBytes, pOffset, pLength); - if (read > 0) { - position += read; - } - return read; - } - - /** - * Does nothing, as we don't really do any caching here. - * - * @param pPosition the position to flush to - */ - protected void flushBeforeImpl(long pPosition) { - } - - protected void seekImpl(long pPosition) throws IOException { - mRandomAccess.seek(pPosition); - } -} +/* + * 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.io; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * A {@code SeekableInputStream} implementation that uses random access directly to a {@code File}. + + * @see FileCacheSeekableStream + * @see MemoryCacheSeekableStream + * @see RandomAccessFile + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java#4 $ + */ +public final class FileSeekableStream extends SeekableInputStream { + + // TODO: Figure out why this class is SLOWER than FileCacheSeekableStream in + // my tests..? + + final RandomAccessFile mRandomAccess; + + /** + * Creates a {@code FileSeekableStream} that reads from the given + * {@code File}. + * + * @param pInput file to read from + * @throws FileNotFoundException if {@code pInput} does not exist + */ + public FileSeekableStream(final File pInput) throws FileNotFoundException { + this(new RandomAccessFile(pInput, "r")); + } + + /** + * Creates a {@code FileSeekableStream} that reads from the given file. + * The {@code RandomAccessFile} needs only to be open in read + * ({@code "r"}) mode. + * + * @param pInput file to read from + */ + public FileSeekableStream(final RandomAccessFile pInput) { + mRandomAccess = pInput; + } + + /// Seekable + + public boolean isCached() { + return false; + } + + public boolean isCachedFile() { + return false; + } + + public boolean isCachedMemory() { + return false; + } + + /// InputStream + + @Override + public int available() throws IOException { + long length = mRandomAccess.length() - position; + return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length; + } + + public void closeImpl() throws IOException { + mRandomAccess.close(); + } + + public int read() throws IOException { + checkOpen(); + + int read = mRandomAccess.read(); + if (read >= 0) { + position++; + } + return read; + } + + @Override + public int read(byte pBytes[], int pOffset, int pLength) throws IOException { + checkOpen(); + + int read = mRandomAccess.read(pBytes, pOffset, pLength); + if (read > 0) { + position += read; + } + return read; + } + + /** + * Does nothing, as we don't really do any caching here. + * + * @param pPosition the position to flush to + */ + protected void flushBeforeImpl(long pPosition) { + } + + protected void seekImpl(long pPosition) throws IOException { + mRandomAccess.seek(pPosition); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java index 9b18f789..81668a88 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java @@ -1,101 +1,102 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; - -/** - * FileSystem - *

- * - * @author Harald Kuhr - * @version $Id: FileSystem.java#1 $ - */ -abstract class FileSystem { - abstract long getFreeSpace(File pPath); - - abstract long getTotalSpace(File pPath); - - abstract String getName(); - - static BufferedReader exec(String[] pArgs) throws IOException { - Process cmd = Runtime.getRuntime().exec(pArgs); - return new BufferedReader(new InputStreamReader(cmd.getInputStream())); - } - - static FileSystem get() { - String os = System.getProperty("os.name"); - //System.out.println("os = " + os); - - os = os.toLowerCase(); - if (os.contains("windows")) { - return new Win32FileSystem(); - } - else if (os.contains("linux") || - os.contains("sun os") || - os.contains("sunos") || - os.contains("solaris") || - os.contains("mpe/ix") || - os.contains("hp-ux") || - os.contains("aix") || - os.contains("freebsd") || - os.contains("irix") || - os.contains("digital unix") || - os.contains("unix") || - os.contains("mac os x")) { - return new UnixFileSystem(); - } - else { - return new UnknownFileSystem(os); - } - } - - private static class UnknownFileSystem extends FileSystem { - private final String osName; - - UnknownFileSystem(String pOSName) { - osName = pOSName; - } - - long getFreeSpace(File pPath) { - return 0l; - } - - long getTotalSpace(File pPath) { - return 0l; - } - - String getName() { - return "Unknown (" + osName + ")"; - } - } -} +/* + * 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.io; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * FileSystem + * + * @author Harald Kuhr + * @version $Id: FileSystem.java#1 $ + */ +abstract class FileSystem { + abstract long getFreeSpace(File pPath); + + abstract long getTotalSpace(File pPath); + + abstract String getName(); + + static BufferedReader exec(String[] pArgs) throws IOException { + Process cmd = Runtime.getRuntime().exec(pArgs); + return new BufferedReader(new InputStreamReader(cmd.getInputStream())); + } + + static FileSystem get() { + String os = System.getProperty("os.name"); + //System.out.println("os = " + os); + + os = os.toLowerCase(); + if (os.contains("windows")) { + return new Win32FileSystem(); + } + else if (os.contains("linux") || + os.contains("sun os") || + os.contains("sunos") || + os.contains("solaris") || + os.contains("mpe/ix") || + os.contains("hp-ux") || + os.contains("aix") || + os.contains("freebsd") || + os.contains("irix") || + os.contains("digital unix") || + os.contains("unix") || + os.contains("mac os x")) { + return new UnixFileSystem(); + } + else { + return new UnknownFileSystem(os); + } + } + + private static class UnknownFileSystem extends FileSystem { + private final String osName; + + UnknownFileSystem(String pOSName) { + osName = pOSName; + } + + long getFreeSpace(File pPath) { + return 0l; + } + + long getTotalSpace(File pPath) { + return 0l; + } + + String getName() { + return "Unknown (" + osName + ")"; + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java index 8cad4665..5f2953ee 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java @@ -1,1081 +1,1082 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.lang.StringUtil; -import com.twelvemonkeys.lang.Validate; -import com.twelvemonkeys.util.Visitor; - -import java.io.*; -import java.net.URL; -import java.text.NumberFormat; -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.UndeclaredThrowableException; - -/** - * A utility class with some useful file and i/o related methods. - *

- * Versions exists take Input and OutputStreams as parameters, to - * allow for copying streams (URL's etc.). - * - * @author Harald Kuhr - * @author Eirik Torske - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileUtil.java#3 $ - */ -public final class FileUtil { - // TODO: Be more cosequent using resolve() all places where File objects are involved - // TODO: Parameter handling (allow null vs IllegalArgument) - // TODO: Exception handling - - /** - * The size of the buffer used for copying - */ - public final static int BUF_SIZE = 1024; - private static String TEMP_DIR = null; - - private final static FileSystem FS = FileSystem.get(); - - public static void main(String[] pArgs) throws IOException { - File file; - if (pArgs[0].startsWith("file:")) { - file = toFile(new URL(pArgs[0])); - System.out.println(file); - } - else { - file = new File(pArgs[0]); - System.out.println(file.toURL()); - } - - System.out.println("Free space: " + getFreeSpace(file) + "/" + getTotalSpace(file) + " bytes"); - } - - /* - * Method main for test only. - * - public static void main0(String[] pArgs) { - if (pArgs.length != 2) { - System.out.println("usage: java Copy in out"); - return; - } - try { - if (!copy(pArgs[0], pArgs[1])) { - System.out.println("Error copying"); - } - } - catch (IOException e) { - System.out.println(e.getMessage()); - } - } - //*/ - - // Avoid instances/constructor showing up in API doc - private FileUtil() {} - - /** - * Copies the fromFile to the toFile location. If toFile is a directory, a - * new file is created in that directory, with the name of the fromFile. - * If the toFile exists, the file will not be copied, unless owerWrite is - * true. - * - * @param pFromFileName The name of the file to copy from - * @param pToFileName The name of the file to copy to - * @return true if the file was copied successfully, - * false if the output file exists. In all other cases, an - * IOException is thrown, and the method does not return a value. - * @throws IOException if an i/o error occurs during copy - */ - public static boolean copy(String pFromFileName, String pToFileName) throws IOException { - return copy(new File(pFromFileName), new File(pToFileName), false); - } - - /** - * Copies the fromFile to the toFile location. If toFile is a directory, a - * new file is created in that directory, with the name of the fromFile. - * If the toFile exists, the file will not be copied, unless owerWrite is - * true. - * - * @param pFromFileName The name of the file to copy from - * @param pToFileName The name of the file to copy to - * @param pOverWrite Specifies if the toFile should be overwritten, if it - * exists. - * @return true if the file was copied successfully, - * false if the output file exists, and the owerWrite parameter is - * false. In all other cases, an - * IOException is thrown, and the method does not return a value. - * @throws IOException if an i/o error occurs during copy - */ - public static boolean copy(String pFromFileName, String pToFileName, boolean pOverWrite) throws IOException { - return copy(new File(pFromFileName), new File(pToFileName), pOverWrite); - } - - /** - * Copies the fromFile to the toFile location. If toFile is a directory, a - * new file is created in that directory, with the name of the fromFile. - * If the toFile exists, the file will not be copied, unless owerWrite is - * true. - * - * @param pFromFile The file to copy from - * @param pToFile The file to copy to - * @return true if the file was copied successfully, - * false if the output file exists. In all other cases, an - * IOException is thrown, and the method does not return a value. - * @throws IOException if an i/o error occurs during copy - */ - public static boolean copy(File pFromFile, File pToFile) throws IOException { - return copy(pFromFile, pToFile, false); - } - - /** - * Copies the fromFile to the toFile location. If toFile is a directory, a - * new file is created in that directory, with the name of the fromFile. - * If the toFile exists, the file will not be copied, unless owerWrite is - * true. - * - * @param pFromFile The file to copy from - * @param pToFile The file to copy to - * @param pOverWrite Specifies if the toFile should be overwritten, if it - * exists. - * @return {@code true} if the file was copied successfully, - * {@code false} if the output file exists, and the - * {@code pOwerWrite} parameter is - * {@code false}. In all other cases, an - * {@code IOExceptio}n is thrown, and the method does not return. - * @throws IOException if an i/o error occurs during copy - * @todo Test copyDir functionality! - */ - public static boolean copy(File pFromFile, File pToFile, boolean pOverWrite) throws IOException { - // Copy all directory structure - if (pFromFile.isDirectory()) { - return copyDir(pFromFile, pToFile, pOverWrite); - } - - // Check if destination is a directory - if (pToFile.isDirectory()) { - // Create a new file with same name as from - pToFile = new File(pToFile, pFromFile.getName()); - } - - // Check if file exists, and return false if overWrite is false - if (!pOverWrite && pToFile.exists()) { - return false; - } - - InputStream in = null; - OutputStream out = null; - - try { - // Use buffer size two times byte array, to avoid i/o bottleneck - in = new FileInputStream(pFromFile); - out = new FileOutputStream(pToFile); - - // Copy from inputStream to outputStream - copy(in, out); - } - //Just pass any IOException on up the stack - finally { - close(in); - close(out); - } - - return true; // If we got here, everything is probably okay.. ;-) - } - - /** - * Tries to close the given stream. - * NOTE: If the stream cannot be closed, the IOException thrown is silently - * ignored. - * - * @param pInput the stream to close - */ - public static void close(InputStream pInput) { - try { - if (pInput != null) { - pInput.close(); - } - } - catch (IOException ignore) { - // Non critical error - } - } - - /** - * Tries to close the given stream. - * NOTE: If the stream cannot be closed, the IOException thrown is silently - * ignored. - * - * @param pOutput the stream to close - */ - public static void close(OutputStream pOutput) { - try { - if (pOutput != null) { - pOutput.close(); - } - } - catch (IOException ignore) { - // Non critical error - } - } - - static void close(Reader pReader) { - try { - if (pReader != null) { - pReader.close(); - } - } - catch (IOException ignore) { - // Non critical error - } - } - - static void close(Writer pWriter) { - try { - if (pWriter != null) { - pWriter.close(); - } - } - catch (IOException ignore) { - // Non critical error - } - } - - /** - * Copies a directory recursively. If the destination folder does not exist, - * it is created - * - * @param pFrom the source directory - * @param pTo the destination directory - * @param pOverWrite {@code true} if we should allow overwrting existing files - * @return {@code true} if all files were copied sucessfully - * @throws IOException if {@code pTo} exists, and it not a directory, - * or if copying of any of the files in the folder fails - */ - private static boolean copyDir(File pFrom, File pTo, boolean pOverWrite) throws IOException { - if (pTo.exists() && !pTo.isDirectory()) { - throw new IOException("A directory may only be copied to another directory, not to a file"); - } - pTo.mkdirs(); // mkdir? - boolean allOkay = true; - File[] files = pFrom.listFiles(); - - for (File file : files) { - if (!copy(file, new File(pTo, file.getName()), pOverWrite)) { - allOkay = false; - } - } - return allOkay; - } - - /** - * Copies all data from one stream to another. - * The data is copied from the fromStream to the toStream using buffered - * streams for efficiency. - * - * @param pFrom The input srteam to copy from - * @param pTo The output stream to copy to - * @return true. Otherwise, an - * IOException is thrown, and the method does not return a value. - * @throws IOException if an i/o error occurs during copy - * @throws IllegalArgumentException if either {@code pFrom} or {@code pTo} is - * {@code null} - */ - public static boolean copy(InputStream pFrom, OutputStream pTo) throws IOException { - Validate.notNull(pFrom, "from"); - Validate.notNull(pTo, "to"); - - // TODO: Consider using file channels for faster copy where possible - - // Use buffer size two times byte array, to avoid i/o bottleneck - // TODO: Consider letting the client decide as this is sometimes not a good thing! - InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2); - OutputStream out = new BufferedOutputStream(pTo, BUF_SIZE * 2); - - byte[] buffer = new byte[BUF_SIZE]; - int count; - - while ((count = in.read(buffer)) != -1) { - out.write(buffer, 0, count); - } - - // Flush out stream, to write any remaining buffered data - out.flush(); - - return true; // If we got here, everything is probably okay.. ;-) - } - - /** - * Gets the file (type) extension of the given file. - * A file extension is the part of the filename, after the last occurence - * of a period {@code '.'}. - * If the filename contains no period, {@code null} is returned. - * - * @param pFileName the full filename with extension - * @return the extension (type) of the file, or {@code null} - */ - public static String getExtension(final String pFileName) { - return getExtension0(getFilename(pFileName)); - } - - /** - * Gets the file (type) extension of the given file. - * A file extension is the part of the filename, after the last occurence - * of a period {@code '.'}. - * If the filename contains no period, {@code null} is returned. - * - * @param pFile the file - * @return the extension (type) of the file, or {@code null} - */ - public static String getExtension(final File pFile) { - return getExtension0(pFile.getName()); - } - - // NOTE: Assumes filename and no path - private static String getExtension0(final String pFileName) { - int index = pFileName.lastIndexOf('.'); - - if (index >= 0) { - return pFileName.substring(index + 1); - } - - // No period found - return null; - } - - - /** - * Gets the file name of the given file, without the extension (type). - * A file extension is the part of the filename, after the last occurence - * of a period {@code '.'}. - * If the filename contains no period, the complete file name is returned - * (same as {@code pFileName}, if the string contains no path elements). - * - * @param pFileName the full filename with extension - * @return the base name of the file - */ - public static String getBasename(final String pFileName) { - return getBasename0(getFilename(pFileName)); - } - - /** - * Gets the file name of the given file, without the extension (type). - * A file extension is the part of the filename, after the last occurence - * of a period {@code '.'}. - * If the filename contains no period, {@code pFile.getName()} is returned. - * - * @param pFile the file - * @return the base name of the file - */ - public static String getBasename(final File pFile) { - return getBasename0(pFile.getName()); - } - - // NOTE: Assumes filename and no path - public static String getBasename0(final String pFileName) { - int index = pFileName.lastIndexOf('.'); - - if (index >= 0) { - return pFileName.substring(0, index); - } - - // No period found - return pFileName; - } - - /** - * Extracts the directory path without the filename, from a complete - * filename path. - * - * @param pPath The full filename path. - * @return the path without the filename. - * @see File#getParent - * @see #getFilename - */ - public static String getDirectoryname(final String pPath) { - return getDirectoryname(pPath, File.separatorChar); - } - - /** - * Extracts the directory path without the filename, from a complete - * filename path. - * - * @param pPath The full filename path. - * @param pSeparator the separator char used in {@code pPath} - * @return the path without the filename. - * @see File#getParent - * @see #getFilename - */ - public static String getDirectoryname(final String pPath, final char pSeparator) { - int index = pPath.lastIndexOf(pSeparator); - - if (index < 0) { - return ""; // Assume only filename - } - return pPath.substring(0, index); - } - - /** - * Extracts the filename of a complete filename path. - * - * @param pPath The full filename path. - * @return the extracted filename. - * @see File#getName - * @see #getDirectoryname - */ - public static String getFilename(final String pPath) { - return getFilename(pPath, File.separatorChar); - } - - /** - * Extracts the filename of a complete filename path. - * - * @param pPath The full filename path. - * @param pSeparator The file separator. - * @return the extracted filename. - * @see File#getName - * @see #getDirectoryname - */ - public static String getFilename(final String pPath, final char pSeparator) { - int index = pPath.lastIndexOf(pSeparator); - - if (index < 0) { - return pPath; // Assume only filename - } - - return pPath.substring(index + 1); - } - - - /** - * Tests if a file or directory has no content. - * A file is empty if it has a length of 0L. A non-existing file is also - * considered empty. - * A directory is considered empty if it contains no files. - * - * @param pFile The file to test - * @return {@code true} if the file is empty, otherwise - * {@code false}. - */ - public static boolean isEmpty(File pFile) { - if (pFile.isDirectory()) { - return (pFile.list().length == 0); - } - return (pFile.length() == 0); - } - - /** - * Gets the default temp directory for the system as a File. - * - * @return a {@code File}, representing the default temp directory. - * @see File#createTempFile - */ - public static File getTempDirFile() { - return new File(getTempDir()); - } - - /** - * Gets the default temp directory for the system. - * - * @return a {@code String}, representing the path to the default temp - * directory. - * @see File#createTempFile - */ - public static String getTempDir() { - synchronized (FileUtil.class) { - if (TEMP_DIR == null) { - // Get the 'java.io.tmpdir' property - String tmpDir = System.getProperty("java.io.tmpdir"); - - if (StringUtil.isEmpty(tmpDir)) { - // Stupid fallback... - // TODO: Delegate to FileSystem? - if (new File("/temp").exists()) { - tmpDir = "/temp"; // Windows - } - else { - tmpDir = "/tmp"; // Unix - } - } - TEMP_DIR = tmpDir; - } - } - return TEMP_DIR; - } - - /** - * Gets the contents of the given file, as a byte array. - * - * @param pFilename the name of the file to get content from - * @return the content of the file as a byte array. - * @throws IOException if the read operation fails - */ - public static byte[] read(String pFilename) throws IOException { - return read(new File(pFilename)); - } - - /** - * Gets the contents of the given file, as a byte array. - * - * @param pFile the file to get content from - * @return the content of the file as a byte array. - * @throws IOException if the read operation fails - */ - public static byte[] read(File pFile) throws IOException { - // Custom implementation, as we know the size of a file - if (!pFile.exists()) { - throw new FileNotFoundException(pFile.toString()); - } - - byte[] bytes = new byte[(int) pFile.length()]; - InputStream in = null; - - try { - // Use buffer size two times byte array, to avoid i/o bottleneck - in = new BufferedInputStream(new FileInputStream(pFile), BUF_SIZE * 2); - - int off = 0; - int len; - while ((len = in.read(bytes, off, in.available())) != -1 && (off < bytes.length)) { - off += len; - // System.out.println("read:" + len); - } - } - // Just pass any IOException on up the stack - finally { - close(in); - } - - return bytes; - } - - /** - * Reads all data from the input stream to a byte array. - * - * @param pInput The input stream to read from - * @return The content of the stream as a byte array. - * @throws IOException if an i/o error occurs during read. - */ - public static byte[] read(InputStream pInput) throws IOException { - // Create byte array - ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE); - - // Copy from stream to byte array - copy(pInput, bytes); - - return bytes.toByteArray(); - } - - /** - * Writes the contents from a byte array to an output stream. - * - * @param pOutput The output stream to write to - * @param pData The byte array to write - * @return {@code true}, otherwise an IOException is thrown. - * @throws IOException if an i/o error occurs during write. - */ - public static boolean write(OutputStream pOutput, byte[] pData) throws IOException { - // Write data - pOutput.write(pData); - - // If we got here, all is okay - return true; - } - - /** - * Writes the contents from a byte array to a file. - * - * @param pFile The file to write to - * @param pData The byte array to write - * @return {@code true}, otherwise an IOException is thrown. - * @throws IOException if an i/o error occurs during write. - */ - public static boolean write(File pFile, byte[] pData) throws IOException { - boolean success = false; - OutputStream out = null; - - try { - out = new BufferedOutputStream(new FileOutputStream(pFile)); - success = write(out, pData); - } - finally { - close(out); - } - return success; - } - - /** - * Writes the contents from a byte array to a file. - * - * @param pFilename The name of the file to write to - * @param pData The byte array to write - * @return {@code true}, otherwise an IOException is thrown. - * @throws IOException if an i/o error occurs during write. - */ - public static boolean write(String pFilename, byte[] pData) throws IOException { - return write(new File(pFilename), pData); - } - - /** - * Deletes the specified file. - * - * @param pFile The file to delete - * @param pForce Forces delete, even if the parameter is a directory, and - * is not empty. Be careful! - * @return {@code true}, if the file existed and was deleted. - * @throws IOException if an i/o error occurs during delete. - */ - public static boolean delete(final File pFile, final boolean pForce) throws IOException { - if (pForce && pFile.isDirectory()) { - return deleteDir(pFile); - } - return pFile.exists() && pFile.delete(); - } - - /** - * Deletes a directory recursively. - * - * @param pFile the file to delete - * @return {@code true} if the file was deleted sucessfully - * @throws IOException if an i/o error occurs during delete. - */ - private static boolean deleteDir(final File pFile) throws IOException { - // Recusively delete all files/subfolders - // Deletes the files using visitor pattern, to avoid allocating - // a file array, which may throw OutOfMemoryExceptions for - // large directories/in low memory situations - class DeleteFilesVisitor implements Visitor { - private int failedCount = 0; - private IOException exception = null; - - public void visit(final File pFile) { - try { - if (!delete(pFile, true)) { - failedCount++; - } - } - catch (IOException e) { - failedCount++; - if (exception == null) { - exception = e; - } - } - } - - boolean succeeded() throws IOException { - if (exception != null) { - throw exception; - } - return failedCount == 0; - } - } - DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor(); - visitFiles(pFile, null, fileDeleter); - - // If any of the deletes above failed, this will fail (or return false) - return fileDeleter.succeeded() && pFile.delete(); - } - - /** - * Deletes the specified file. - * - * @param pFilename The name of file to delete - * @param pForce Forces delete, even if the parameter is a directory, and - * is not empty. Careful! - * @return {@code true}, if the file existed and was deleted. - * @throws java.io.IOException if deletion fails - */ - public static boolean delete(String pFilename, boolean pForce) throws IOException { - return delete(new File(pFilename), pForce); - } - - /** - * Deletes the specified file. - * - * @param pFile The file to delete - * @return {@code true}, if the file existed and was deleted. - * @throws java.io.IOException if deletion fails - */ - public static boolean delete(File pFile) throws IOException { - return delete(pFile, false); - } - - /** - * Deletes the specified file. - * - * @param pFilename The name of file to delete - * @return {@code true}, if the file existed and was deleted. - * @throws java.io.IOException if deletion fails - */ - public static boolean delete(String pFilename) throws IOException { - return delete(new File(pFilename), false); - } - - /** - * Renames the specified file. - * If the destination is a directory (and the source is not), the source - * file is simply moved to the destination directory. - * - * @param pFrom The file to rename - * @param pTo The new file - * @param pOverWrite Specifies if the tofile should be overwritten, if it - * exists - * @return {@code true}, if the file was renamed. - * - * @throws FileNotFoundException if {@code pFrom} does not exist. - */ - public static boolean rename(File pFrom, File pTo, boolean pOverWrite) throws IOException { - if (!pFrom.exists()) { - throw new FileNotFoundException(pFrom.getAbsolutePath()); - } - - if (pFrom.isFile() && pTo.isDirectory()) { - pTo = new File(pTo, pFrom.getName()); - } - return (pOverWrite || !pTo.exists()) && pFrom.renameTo(pTo); - - } - - /** - * Renames the specified file, if the destination does not exist. - * If the destination is a directory (and the source is not), the source - * file is simply moved to the destination directory. - * - * @param pFrom The file to rename - * @param pTo The new file - * @return {@code true}, if the file was renamed. - * @throws java.io.IOException if rename fails - */ - public static boolean rename(File pFrom, File pTo) throws IOException { - return rename(pFrom, pTo, false); - } - - /** - * Renames the specified file. - * If the destination is a directory (and the source is not), the source - * file is simply moved to the destination directory. - * - * @param pFrom The file to rename - * @param pTo The new name of the file - * @param pOverWrite Specifies if the tofile should be overwritten, if it - * exists - * @return {@code true}, if the file was renamed. - * @throws java.io.IOException if rename fails - */ - public static boolean rename(File pFrom, String pTo, boolean pOverWrite) throws IOException { - return rename(pFrom, new File(pTo), pOverWrite); - } - - /** - * Renames the specified file, if the destination does not exist. - * If the destination is a directory (and the source is not), the source - * file is simply moved to the destination directory. - * - * @param pFrom The file to rename - * @param pTo The new name of the file - * @return {@code true}, if the file was renamed. - * @throws java.io.IOException if rename fails - */ - public static boolean rename(File pFrom, String pTo) throws IOException { - return rename(pFrom, new File(pTo), false); - } - - /** - * Renames the specified file. - * If the destination is a directory (and the source is not), the source - * file is simply moved to the destination directory. - * - * @param pFrom The name of the file to rename - * @param pTo The new name of the file - * @param pOverWrite Specifies if the tofile should be overwritten, if it - * exists - * @return {@code true}, if the file was renamed. - * @throws java.io.IOException if rename fails - */ - public static boolean rename(String pFrom, String pTo, boolean pOverWrite) throws IOException { - return rename(new File(pFrom), new File(pTo), pOverWrite); - } - - /** - * Renames the specified file, if the destination does not exist. - * If the destination is a directory (and the source is not), the source - * file is simply moved to the destination directory. - * - * @param pFrom The name of the file to rename - * @param pTo The new name of the file - * @return {@code true}, if the file was renamed. - * @throws java.io.IOException if rename fails - */ - public static boolean rename(String pFrom, String pTo) throws IOException { - return rename(new File(pFrom), new File(pTo), false); - } - - /** - * Lists all files (and directories) in a specific folder. - * - * @param pFolder The folder to list - * @return a list of {@code java.io.File} objects. - * @throws FileNotFoundException if {@code pFolder} is not a readable file - */ - public static File[] list(final String pFolder) throws FileNotFoundException { - return list(pFolder, null); - } - - /** - * Lists all files (and directories) in a specific folder which are - * embraced by the wildcard filename mask provided. - * - * @param pFolder The folder to list - * @param pFilenameMask The wildcard filename mask - * @return a list of {@code java.io.File} objects. - * @see File#listFiles(FilenameFilter) - * @throws FileNotFoundException if {@code pFolder} is not a readable file - */ - public static File[] list(final String pFolder, final String pFilenameMask) throws FileNotFoundException { - if (StringUtil.isEmpty(pFolder)) { - return null; - } - - File folder = resolve(pFolder); - if (!(/*folder.exists() &&*/folder.isDirectory() && folder.canRead())) { - // NOTE: exists is implicitly called by isDirectory - throw new FileNotFoundException("\"" + pFolder + "\" is not a directory or is not readable."); - } - - if (StringUtil.isEmpty(pFilenameMask)) { - return folder.listFiles(); - } - - // TODO: Rewrite to use regexp - - FilenameFilter filter = new FilenameMaskFilter(pFilenameMask); - return folder.listFiles(filter); - } - - /** - * Creates a {@code File} based on the path part of the URL, for - * file-protocol ({@code file:}) based URLs. - * - * @param pURL the {@code file:} URL - * @return a new {@code File} object representing the URL - * - * @throws NullPointerException if {@code pURL} is {@code null} - * @throws IllegalArgumentException if {@code pURL} is - * not a file-protocol URL. - * - * @see java.io.File#toURI() - * @see java.io.File#File(java.net.URI) - */ - public static File toFile(URL pURL) { - if (pURL == null) { - throw new NullPointerException("URL == null"); - } - - // NOTE: Precondition tests below is based on the File(URI) constructor, - // and is most likely overkill... - // NOTE: A URI is absolute iff it has a scheme component - // As the scheme has to be "file", this is implicitly tested below - // NOTE: A URI is opaque iff it is absolute and it's shceme-specific - // part does not begin with a '/', see below - if (!"file".equals(pURL.getProtocol())) { - // URL protocol => URI scheme - throw new IllegalArgumentException("URL scheme is not \"file\""); - } - if (pURL.getAuthority() != null) { - throw new IllegalArgumentException("URL has an authority component"); - } - if (pURL.getRef() != null) { - // URL ref (anchor) => URI fragment - throw new IllegalArgumentException("URI has a fragment component"); - } - if (pURL.getQuery() != null) { - throw new IllegalArgumentException("URL has a query component"); - } - String path = pURL.getPath(); - if (!path.startsWith("/")) { - // A URL should never be able to represent an opaque URI, test anyway - throw new IllegalArgumentException("URI is not hierarchical"); - } - if (path.equals("")) { - throw new IllegalArgumentException("URI path component is empty"); - } - - // Convert separator, doesn't seem to be neccessary on Windows/Unix, - // but do it anyway to be compatible... - if (File.separatorChar != '/') { - path = path.replace('/', File.separatorChar); - } - - return resolve(path); - } - - public static File resolve(String pPath) { - return Win32File.wrap(new File(pPath)); - } - - public static File resolve(File pPath) { - return Win32File.wrap(pPath); - } - - public static File resolve(File pParent, String pChild) { - return Win32File.wrap(new File(pParent, pChild)); - } - - public static File[] resolve(File[] pPaths) { - return Win32File.wrap(pPaths); - } - - // TODO: Handle SecurityManagers in a deterministic way - // TODO: Exception handling - // TODO: What happens if the file does not exist? - public static long getFreeSpace(final File pPath) { - // NOTE: Allow null, to get space in current/system volume - File path = pPath != null ? pPath : new File("."); - - Long space = getSpace16("getFreeSpace", path); - if (space != null) { - return space; - } - - return FS.getFreeSpace(path); - } - - public static long getUsableSpace(final File pPath) { - // NOTE: Allow null, to get space in current/system volume - File path = pPath != null ? pPath : new File("."); - - Long space = getSpace16("getUsableSpace", path); - if (space != null) { - return space; - } - - return getTotalSpace(path); - } - - // TODO: FixMe for Windows, before making it public... - public static long getTotalSpace(final File pPath) { - // NOTE: Allow null, to get space in current/system volume - File path = pPath != null ? pPath : new File("."); - - Long space = getSpace16("getTotalSpace", path); - if (space != null) { - return space; - } - - return FS.getTotalSpace(path); - } - - private static Long getSpace16(final String pMethodName, final File pPath) { - try { - Method freeSpace = File.class.getMethod(pMethodName); - return (Long) freeSpace.invoke(pPath); - } - catch (NoSuchMethodException ignore) {} - catch (IllegalAccessException ignore) {} - catch (InvocationTargetException e) { - Throwable throwable = e.getTargetException(); - if (throwable instanceof SecurityException) { - throw (SecurityException) throwable; - } - throw new UndeclaredThrowableException(throwable); - } - - return null; - } - - /** - * Formats the given number to a human readable format. - * Kind of like {@code df -h}. - * - * @param pSizeInBytes the size in byte - * @return a human readable string representation - */ - public static String toHumanReadableSize(final long pSizeInBytes) { - // TODO: Rewrite to use String.format? - if (pSizeInBytes < 1024L) { - return pSizeInBytes + " Bytes"; - } - else if (pSizeInBytes < (1024L << 10)) { - return getSizeFormat().format(pSizeInBytes / (double) (1024L)) + " KB"; - } - else if (pSizeInBytes < (1024L << 20)) { - return getSizeFormat().format(pSizeInBytes / (double) (1024L << 10)) + " MB"; - } - else if (pSizeInBytes < (1024L << 30)) { - return getSizeFormat().format(pSizeInBytes / (double) (1024L << 20)) + " GB"; - } - else if (pSizeInBytes < (1024L << 40)) { - return getSizeFormat().format(pSizeInBytes / (double) (1024L << 30)) + " TB"; - } - else { - return getSizeFormat().format(pSizeInBytes / (double) (1024L << 40)) + " PB"; - } - } - - // NumberFormat is not thread-safe, so we stick to thread-confined instances - private static ThreadLocal sNumberFormat = new ThreadLocal() { - protected NumberFormat initialValue() { - NumberFormat format = NumberFormat.getNumberInstance(); - // TODO: Consider making this locale/platform specific, OR a method parameter... -// format.setMaximumFractionDigits(2); - format.setMaximumFractionDigits(0); - return format; - } - }; - - private static NumberFormat getSizeFormat() { - return sNumberFormat.get(); - } - - /** - * Visits all files in {@code pDirectory}. Optionally filtered through a {@link FileFilter}. - * - * @param pDirectory the directory to visit files in - * @param pFilter the filter, may be {@code null}, meaning all files will be visited - * @param pVisitor the visitor - * - * @throws IllegalArgumentException if either {@code pDirectory} or {@code pVisitor} are {@code null} - * - * @see com.twelvemonkeys.util.Visitor - */ - @SuppressWarnings({"ResultOfMethodCallIgnored"}) - public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor pVisitor) { - Validate.notNull(pDirectory, "directory"); - Validate.notNull(pVisitor, "visitor"); - - pDirectory.listFiles(new FileFilter() { - public boolean accept(final File pFile) { - if (pFilter == null || pFilter.accept(pFile)) { - pVisitor.visit(pFile); - } - - return false; - } - }); - } -} +/* + * 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.io; + +import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.lang.Validate; +import com.twelvemonkeys.util.Visitor; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URL; +import java.text.NumberFormat; + +/** + * A utility class with some useful file and i/o related methods. + *

+ * Versions exists take Input and OutputStreams as parameters, to + * allow for copying streams (URL's etc.). + * + * @author Harald Kuhr + * @author Eirik Torske + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileUtil.java#3 $ + */ +public final class FileUtil { + // TODO: Be more cosequent using resolve() all places where File objects are involved + // TODO: Parameter handling (allow null vs IllegalArgument) + // TODO: Exception handling + + /** + * The size of the buffer used for copying + */ + public final static int BUF_SIZE = 1024; + private static String TEMP_DIR = null; + + private final static FileSystem FS = FileSystem.get(); + + public static void main(String[] pArgs) throws IOException { + File file; + if (pArgs[0].startsWith("file:")) { + file = toFile(new URL(pArgs[0])); + System.out.println(file); + } + else { + file = new File(pArgs[0]); + System.out.println(file.toURL()); + } + + System.out.println("Free space: " + getFreeSpace(file) + "/" + getTotalSpace(file) + " bytes"); + } + + /* + * Method main for test only. + * + public static void main0(String[] pArgs) { + if (pArgs.length != 2) { + System.out.println("usage: java Copy in out"); + return; + } + try { + if (!copy(pArgs[0], pArgs[1])) { + System.out.println("Error copying"); + } + } + catch (IOException e) { + System.out.println(e.getMessage()); + } + } + //*/ + + // Avoid instances/constructor showing up in API doc + private FileUtil() {} + + /** + * Copies the fromFile to the toFile location. If toFile is a directory, a + * new file is created in that directory, with the name of the fromFile. + * If the toFile exists, the file will not be copied, unless owerWrite is + * true. + * + * @param pFromFileName The name of the file to copy from + * @param pToFileName The name of the file to copy to + * @return true if the file was copied successfully, + * false if the output file exists. In all other cases, an + * IOException is thrown, and the method does not return a value. + * @throws IOException if an i/o error occurs during copy + */ + public static boolean copy(String pFromFileName, String pToFileName) throws IOException { + return copy(new File(pFromFileName), new File(pToFileName), false); + } + + /** + * Copies the fromFile to the toFile location. If toFile is a directory, a + * new file is created in that directory, with the name of the fromFile. + * If the toFile exists, the file will not be copied, unless owerWrite is + * true. + * + * @param pFromFileName The name of the file to copy from + * @param pToFileName The name of the file to copy to + * @param pOverWrite Specifies if the toFile should be overwritten, if it + * exists. + * @return true if the file was copied successfully, + * false if the output file exists, and the owerWrite parameter is + * false. In all other cases, an + * IOException is thrown, and the method does not return a value. + * @throws IOException if an i/o error occurs during copy + */ + public static boolean copy(String pFromFileName, String pToFileName, boolean pOverWrite) throws IOException { + return copy(new File(pFromFileName), new File(pToFileName), pOverWrite); + } + + /** + * Copies the fromFile to the toFile location. If toFile is a directory, a + * new file is created in that directory, with the name of the fromFile. + * If the toFile exists, the file will not be copied, unless owerWrite is + * true. + * + * @param pFromFile The file to copy from + * @param pToFile The file to copy to + * @return true if the file was copied successfully, + * false if the output file exists. In all other cases, an + * IOException is thrown, and the method does not return a value. + * @throws IOException if an i/o error occurs during copy + */ + public static boolean copy(File pFromFile, File pToFile) throws IOException { + return copy(pFromFile, pToFile, false); + } + + /** + * Copies the fromFile to the toFile location. If toFile is a directory, a + * new file is created in that directory, with the name of the fromFile. + * If the toFile exists, the file will not be copied, unless owerWrite is + * true. + * + * @param pFromFile The file to copy from + * @param pToFile The file to copy to + * @param pOverWrite Specifies if the toFile should be overwritten, if it + * exists. + * @return {@code true} if the file was copied successfully, + * {@code false} if the output file exists, and the + * {@code pOwerWrite} parameter is + * {@code false}. In all other cases, an + * {@code IOExceptio}n is thrown, and the method does not return. + * @throws IOException if an i/o error occurs during copy + */ + public static boolean copy(File pFromFile, File pToFile, boolean pOverWrite) throws IOException { + // Copy all directory structure + if (pFromFile.isDirectory()) { + return copyDir(pFromFile, pToFile, pOverWrite); + } + + // Check if destination is a directory + if (pToFile.isDirectory()) { + // Create a new file with same name as from + pToFile = new File(pToFile, pFromFile.getName()); + } + + // Check if file exists, and return false if overWrite is false + if (!pOverWrite && pToFile.exists()) { + return false; + } + + InputStream in = null; + OutputStream out = null; + + try { + // Use buffer size two times byte array, to avoid i/o bottleneck + in = new FileInputStream(pFromFile); + out = new FileOutputStream(pToFile); + + // Copy from inputStream to outputStream + copy(in, out); + } + //Just pass any IOException on up the stack + finally { + close(in); + close(out); + } + + return true; // If we got here, everything is probably okay.. ;-) + } + + /** + * Tries to close the given stream. + * NOTE: If the stream cannot be closed, the IOException thrown is silently + * ignored. + * + * @param pInput the stream to close + */ + public static void close(InputStream pInput) { + try { + if (pInput != null) { + pInput.close(); + } + } + catch (IOException ignore) { + // Non critical error + } + } + + /** + * Tries to close the given stream. + * NOTE: If the stream cannot be closed, the IOException thrown is silently + * ignored. + * + * @param pOutput the stream to close + */ + public static void close(OutputStream pOutput) { + try { + if (pOutput != null) { + pOutput.close(); + } + } + catch (IOException ignore) { + // Non critical error + } + } + + static void close(Reader pReader) { + try { + if (pReader != null) { + pReader.close(); + } + } + catch (IOException ignore) { + // Non critical error + } + } + + static void close(Writer pWriter) { + try { + if (pWriter != null) { + pWriter.close(); + } + } + catch (IOException ignore) { + // Non critical error + } + } + + /** + * Copies a directory recursively. If the destination folder does not exist, + * it is created + * + * @param pFrom the source directory + * @param pTo the destination directory + * @param pOverWrite {@code true} if we should allow overwrting existing files + * @return {@code true} if all files were copied sucessfully + * @throws IOException if {@code pTo} exists, and it not a directory, + * or if copying of any of the files in the folder fails + */ + private static boolean copyDir(File pFrom, File pTo, boolean pOverWrite) throws IOException { + if (pTo.exists() && !pTo.isDirectory()) { + throw new IOException("A directory may only be copied to another directory, not to a file"); + } + pTo.mkdirs(); // mkdir? + boolean allOkay = true; + File[] files = pFrom.listFiles(); + + for (File file : files) { + if (!copy(file, new File(pTo, file.getName()), pOverWrite)) { + allOkay = false; + } + } + return allOkay; + } + + /** + * Copies all data from one stream to another. + * The data is copied from the fromStream to the toStream using buffered + * streams for efficiency. + * + * @param pFrom The input srteam to copy from + * @param pTo The output stream to copy to + * @return true. Otherwise, an + * IOException is thrown, and the method does not return a value. + * @throws IOException if an i/o error occurs during copy + * @throws IllegalArgumentException if either {@code pFrom} or {@code pTo} is + * {@code null} + */ + public static boolean copy(InputStream pFrom, OutputStream pTo) throws IOException { + Validate.notNull(pFrom, "from"); + Validate.notNull(pTo, "to"); + + // TODO: Consider using file channels for faster copy where possible + + // Use buffer size two times byte array, to avoid i/o bottleneck + // TODO: Consider letting the client decide as this is sometimes not a good thing! + InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2); + OutputStream out = new BufferedOutputStream(pTo, BUF_SIZE * 2); + + byte[] buffer = new byte[BUF_SIZE]; + int count; + + while ((count = in.read(buffer)) != -1) { + out.write(buffer, 0, count); + } + + // Flush out stream, to write any remaining buffered data + out.flush(); + + return true; // If we got here, everything is probably okay.. ;-) + } + + /** + * Gets the file (type) extension of the given file. + * A file extension is the part of the filename, after the last occurence + * of a period {@code '.'}. + * If the filename contains no period, {@code null} is returned. + * + * @param pFileName the full filename with extension + * @return the extension (type) of the file, or {@code null} + */ + public static String getExtension(final String pFileName) { + return getExtension0(getFilename(pFileName)); + } + + /** + * Gets the file (type) extension of the given file. + * A file extension is the part of the filename, after the last occurrence + * of a period {@code '.'}. + * If the filename contains no period, {@code null} is returned. + * + * @param pFile the file + * @return the extension (type) of the file, or {@code null} + */ + public static String getExtension(final File pFile) { + return getExtension0(pFile.getName()); + } + + // NOTE: Assumes filename and no path + private static String getExtension0(final String pFileName) { + int index = pFileName.lastIndexOf('.'); + + if (index >= 0) { + return pFileName.substring(index + 1); + } + + // No period found + return null; + } + + + /** + * Gets the file name of the given file, without the extension (type). + * A file extension is the part of the filename, after the last occurence + * of a period {@code '.'}. + * If the filename contains no period, the complete file name is returned + * (same as {@code pFileName}, if the string contains no path elements). + * + * @param pFileName the full filename with extension + * @return the base name of the file + */ + public static String getBasename(final String pFileName) { + return getBasename0(getFilename(pFileName)); + } + + /** + * Gets the file name of the given file, without the extension (type). + * A file extension is the part of the filename, after the last occurence + * of a period {@code '.'}. + * If the filename contains no period, {@code pFile.getName()} is returned. + * + * @param pFile the file + * @return the base name of the file + */ + public static String getBasename(final File pFile) { + return getBasename0(pFile.getName()); + } + + // NOTE: Assumes filename and no path + public static String getBasename0(final String pFileName) { + int index = pFileName.lastIndexOf('.'); + + if (index >= 0) { + return pFileName.substring(0, index); + } + + // No period found + return pFileName; + } + + /** + * Extracts the directory path without the filename, from a complete + * filename path. + * + * @param pPath The full filename path. + * @return the path without the filename. + * @see File#getParent + * @see #getFilename + */ + public static String getDirectoryname(final String pPath) { + return getDirectoryname(pPath, File.separatorChar); + } + + /** + * Extracts the directory path without the filename, from a complete + * filename path. + * + * @param pPath The full filename path. + * @param pSeparator the separator char used in {@code pPath} + * @return the path without the filename. + * @see File#getParent + * @see #getFilename + */ + public static String getDirectoryname(final String pPath, final char pSeparator) { + int index = pPath.lastIndexOf(pSeparator); + + if (index < 0) { + return ""; // Assume only filename + } + return pPath.substring(0, index); + } + + /** + * Extracts the filename of a complete filename path. + * + * @param pPath The full filename path. + * @return the extracted filename. + * @see File#getName + * @see #getDirectoryname + */ + public static String getFilename(final String pPath) { + return getFilename(pPath, File.separatorChar); + } + + /** + * Extracts the filename of a complete filename path. + * + * @param pPath The full filename path. + * @param pSeparator The file separator. + * @return the extracted filename. + * @see File#getName + * @see #getDirectoryname + */ + public static String getFilename(final String pPath, final char pSeparator) { + int index = pPath.lastIndexOf(pSeparator); + + if (index < 0) { + return pPath; // Assume only filename + } + + return pPath.substring(index + 1); + } + + + /** + * Tests if a file or directory has no content. + * A file is empty if it has a length of 0L. A non-existing file is also + * considered empty. + * A directory is considered empty if it contains no files. + * + * @param pFile The file to test + * @return {@code true} if the file is empty, otherwise + * {@code false}. + */ + public static boolean isEmpty(File pFile) { + if (pFile.isDirectory()) { + return (pFile.list().length == 0); + } + return (pFile.length() == 0); + } + + /** + * Gets the default temp directory for the system as a File. + * + * @return a {@code File}, representing the default temp directory. + * @see File#createTempFile + */ + public static File getTempDirFile() { + return new File(getTempDir()); + } + + /** + * Gets the default temp directory for the system. + * + * @return a {@code String}, representing the path to the default temp + * directory. + * @see File#createTempFile + */ + public static String getTempDir() { + synchronized (FileUtil.class) { + if (TEMP_DIR == null) { + // Get the 'java.io.tmpdir' property + String tmpDir = System.getProperty("java.io.tmpdir"); + + if (StringUtil.isEmpty(tmpDir)) { + // Stupid fallback... + // TODO: Delegate to FileSystem? + if (new File("/temp").exists()) { + tmpDir = "/temp"; // Windows + } + else { + tmpDir = "/tmp"; // Unix + } + } + TEMP_DIR = tmpDir; + } + } + return TEMP_DIR; + } + + /** + * Gets the contents of the given file, as a byte array. + * + * @param pFilename the name of the file to get content from + * @return the content of the file as a byte array. + * @throws IOException if the read operation fails + */ + public static byte[] read(String pFilename) throws IOException { + return read(new File(pFilename)); + } + + /** + * Gets the contents of the given file, as a byte array. + * + * @param pFile the file to get content from + * @return the content of the file as a byte array. + * @throws IOException if the read operation fails + */ + public static byte[] read(File pFile) throws IOException { + // Custom implementation, as we know the size of a file + if (!pFile.exists()) { + throw new FileNotFoundException(pFile.toString()); + } + + byte[] bytes = new byte[(int) pFile.length()]; + InputStream in = null; + + try { + // Use buffer size two times byte array, to avoid i/o bottleneck + in = new BufferedInputStream(new FileInputStream(pFile), BUF_SIZE * 2); + + int off = 0; + int len; + while ((len = in.read(bytes, off, in.available())) != -1 && (off < bytes.length)) { + off += len; + // System.out.println("read:" + len); + } + } + // Just pass any IOException on up the stack + finally { + close(in); + } + + return bytes; + } + + /** + * Reads all data from the input stream to a byte array. + * + * @param pInput The input stream to read from + * @return The content of the stream as a byte array. + * @throws IOException if an i/o error occurs during read. + */ + public static byte[] read(InputStream pInput) throws IOException { + // Create byte array + ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE); + + // Copy from stream to byte array + copy(pInput, bytes); + + return bytes.toByteArray(); + } + + /** + * Writes the contents from a byte array to an output stream. + * + * @param pOutput The output stream to write to + * @param pData The byte array to write + * @return {@code true}, otherwise an IOException is thrown. + * @throws IOException if an i/o error occurs during write. + */ + public static boolean write(OutputStream pOutput, byte[] pData) throws IOException { + // Write data + pOutput.write(pData); + + // If we got here, all is okay + return true; + } + + /** + * Writes the contents from a byte array to a file. + * + * @param pFile The file to write to + * @param pData The byte array to write + * @return {@code true}, otherwise an IOException is thrown. + * @throws IOException if an i/o error occurs during write. + */ + public static boolean write(File pFile, byte[] pData) throws IOException { + boolean success = false; + OutputStream out = null; + + try { + out = new BufferedOutputStream(new FileOutputStream(pFile)); + success = write(out, pData); + } + finally { + close(out); + } + return success; + } + + /** + * Writes the contents from a byte array to a file. + * + * @param pFilename The name of the file to write to + * @param pData The byte array to write + * @return {@code true}, otherwise an IOException is thrown. + * @throws IOException if an i/o error occurs during write. + */ + public static boolean write(String pFilename, byte[] pData) throws IOException { + return write(new File(pFilename), pData); + } + + /** + * Deletes the specified file. + * + * @param pFile The file to delete + * @param pForce Forces delete, even if the parameter is a directory, and + * is not empty. Be careful! + * @return {@code true}, if the file existed and was deleted. + * @throws IOException if an i/o error occurs during delete. + */ + public static boolean delete(final File pFile, final boolean pForce) throws IOException { + if (pForce && pFile.isDirectory()) { + return deleteDir(pFile); + } + return pFile.exists() && pFile.delete(); + } + + /** + * Deletes a directory recursively. + * + * @param pFile the file to delete + * @return {@code true} if the file was deleted sucessfully + * @throws IOException if an i/o error occurs during delete. + */ + private static boolean deleteDir(final File pFile) throws IOException { + // Recusively delete all files/subfolders + // Deletes the files using visitor pattern, to avoid allocating + // a file array, which may throw OutOfMemoryExceptions for + // large directories/in low memory situations + class DeleteFilesVisitor implements Visitor { + private int failedCount = 0; + private IOException exception = null; + + public void visit(final File pFile) { + try { + if (!delete(pFile, true)) { + failedCount++; + } + } + catch (IOException e) { + failedCount++; + if (exception == null) { + exception = e; + } + } + } + + boolean succeeded() throws IOException { + if (exception != null) { + throw exception; + } + return failedCount == 0; + } + } + DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor(); + visitFiles(pFile, null, fileDeleter); + + // If any of the deletes above failed, this will fail (or return false) + return fileDeleter.succeeded() && pFile.delete(); + } + + /** + * Deletes the specified file. + * + * @param pFilename The name of file to delete + * @param pForce Forces delete, even if the parameter is a directory, and + * is not empty. Careful! + * @return {@code true}, if the file existed and was deleted. + * @throws java.io.IOException if deletion fails + */ + public static boolean delete(String pFilename, boolean pForce) throws IOException { + return delete(new File(pFilename), pForce); + } + + /** + * Deletes the specified file. + * + * @param pFile The file to delete + * @return {@code true}, if the file existed and was deleted. + * @throws java.io.IOException if deletion fails + */ + public static boolean delete(File pFile) throws IOException { + return delete(pFile, false); + } + + /** + * Deletes the specified file. + * + * @param pFilename The name of file to delete + * @return {@code true}, if the file existed and was deleted. + * @throws java.io.IOException if deletion fails + */ + public static boolean delete(String pFilename) throws IOException { + return delete(new File(pFilename), false); + } + + /** + * Renames the specified file. + * If the destination is a directory (and the source is not), the source + * file is simply moved to the destination directory. + * + * @param pFrom The file to rename + * @param pTo The new file + * @param pOverWrite Specifies if the tofile should be overwritten, if it + * exists + * @return {@code true}, if the file was renamed. + * + * @throws FileNotFoundException if {@code pFrom} does not exist. + */ + public static boolean rename(File pFrom, File pTo, boolean pOverWrite) throws IOException { + if (!pFrom.exists()) { + throw new FileNotFoundException(pFrom.getAbsolutePath()); + } + + if (pFrom.isFile() && pTo.isDirectory()) { + pTo = new File(pTo, pFrom.getName()); + } + return (pOverWrite || !pTo.exists()) && pFrom.renameTo(pTo); + + } + + /** + * Renames the specified file, if the destination does not exist. + * If the destination is a directory (and the source is not), the source + * file is simply moved to the destination directory. + * + * @param pFrom The file to rename + * @param pTo The new file + * @return {@code true}, if the file was renamed. + * @throws java.io.IOException if rename fails + */ + public static boolean rename(File pFrom, File pTo) throws IOException { + return rename(pFrom, pTo, false); + } + + /** + * Renames the specified file. + * If the destination is a directory (and the source is not), the source + * file is simply moved to the destination directory. + * + * @param pFrom The file to rename + * @param pTo The new name of the file + * @param pOverWrite Specifies if the tofile should be overwritten, if it + * exists + * @return {@code true}, if the file was renamed. + * @throws java.io.IOException if rename fails + */ + public static boolean rename(File pFrom, String pTo, boolean pOverWrite) throws IOException { + return rename(pFrom, new File(pTo), pOverWrite); + } + + /** + * Renames the specified file, if the destination does not exist. + * If the destination is a directory (and the source is not), the source + * file is simply moved to the destination directory. + * + * @param pFrom The file to rename + * @param pTo The new name of the file + * @return {@code true}, if the file was renamed. + * @throws java.io.IOException if rename fails + */ + public static boolean rename(File pFrom, String pTo) throws IOException { + return rename(pFrom, new File(pTo), false); + } + + /** + * Renames the specified file. + * If the destination is a directory (and the source is not), the source + * file is simply moved to the destination directory. + * + * @param pFrom The name of the file to rename + * @param pTo The new name of the file + * @param pOverWrite Specifies if the tofile should be overwritten, if it + * exists + * @return {@code true}, if the file was renamed. + * @throws java.io.IOException if rename fails + */ + public static boolean rename(String pFrom, String pTo, boolean pOverWrite) throws IOException { + return rename(new File(pFrom), new File(pTo), pOverWrite); + } + + /** + * Renames the specified file, if the destination does not exist. + * If the destination is a directory (and the source is not), the source + * file is simply moved to the destination directory. + * + * @param pFrom The name of the file to rename + * @param pTo The new name of the file + * @return {@code true}, if the file was renamed. + * @throws java.io.IOException if rename fails + */ + public static boolean rename(String pFrom, String pTo) throws IOException { + return rename(new File(pFrom), new File(pTo), false); + } + + /** + * Lists all files (and directories) in a specific folder. + * + * @param pFolder The folder to list + * @return a list of {@code java.io.File} objects. + * @throws FileNotFoundException if {@code pFolder} is not a readable file + */ + public static File[] list(final String pFolder) throws FileNotFoundException { + return list(pFolder, null); + } + + /** + * Lists all files (and directories) in a specific folder which are + * embraced by the wildcard filename mask provided. + * + * @param pFolder The folder to list + * @param pFilenameMask The wildcard filename mask + * @return a list of {@code java.io.File} objects. + * @see File#listFiles(FilenameFilter) + * @throws FileNotFoundException if {@code pFolder} is not a readable file + */ + public static File[] list(final String pFolder, final String pFilenameMask) throws FileNotFoundException { + if (StringUtil.isEmpty(pFolder)) { + return null; + } + + File folder = resolve(pFolder); + if (!(/*folder.exists() &&*/folder.isDirectory() && folder.canRead())) { + // NOTE: exists is implicitly called by isDirectory + throw new FileNotFoundException("\"" + pFolder + "\" is not a directory or is not readable."); + } + + if (StringUtil.isEmpty(pFilenameMask)) { + return folder.listFiles(); + } + + // TODO: Rewrite to use regexp + + FilenameFilter filter = new FilenameMaskFilter(pFilenameMask); + return folder.listFiles(filter); + } + + /** + * Creates a {@code File} based on the path part of the URL, for + * file-protocol ({@code file:}) based URLs. + * + * @param pURL the {@code file:} URL + * @return a new {@code File} object representing the URL + * + * @throws NullPointerException if {@code pURL} is {@code null} + * @throws IllegalArgumentException if {@code pURL} is + * not a file-protocol URL. + * + * @see java.io.File#toURI() + * @see java.io.File#File(java.net.URI) + */ + public static File toFile(URL pURL) { + if (pURL == null) { + throw new NullPointerException("URL == null"); + } + + // NOTE: Precondition tests below is based on the File(URI) constructor, + // and is most likely overkill... + // NOTE: A URI is absolute iff it has a scheme component + // As the scheme has to be "file", this is implicitly tested below + // NOTE: A URI is opaque iff it is absolute and it's shceme-specific + // part does not begin with a '/', see below + if (!"file".equals(pURL.getProtocol())) { + // URL protocol => URI scheme + throw new IllegalArgumentException("URL scheme is not \"file\""); + } + if (pURL.getAuthority() != null) { + throw new IllegalArgumentException("URL has an authority component"); + } + if (pURL.getRef() != null) { + // URL ref (anchor) => URI fragment + throw new IllegalArgumentException("URI has a fragment component"); + } + if (pURL.getQuery() != null) { + throw new IllegalArgumentException("URL has a query component"); + } + String path = pURL.getPath(); + if (!path.startsWith("/")) { + // A URL should never be able to represent an opaque URI, test anyway + throw new IllegalArgumentException("URI is not hierarchical"); + } + if (path.isEmpty()) { + throw new IllegalArgumentException("URI path component is empty"); + } + + // Convert separator, doesn't seem to be neccessary on Windows/Unix, + // but do it anyway to be compatible... + if (File.separatorChar != '/') { + path = path.replace('/', File.separatorChar); + } + + return resolve(path); + } + + public static File resolve(String pPath) { + return Win32File.wrap(new File(pPath)); + } + + public static File resolve(File pPath) { + return Win32File.wrap(pPath); + } + + public static File resolve(File pParent, String pChild) { + return Win32File.wrap(new File(pParent, pChild)); + } + + public static File[] resolve(File[] pPaths) { + return Win32File.wrap(pPaths); + } + + // TODO: Handle SecurityManagers in a deterministic way + // TODO: Exception handling + // TODO: What happens if the file does not exist? + public static long getFreeSpace(final File pPath) { + // NOTE: Allow null, to get space in current/system volume + File path = pPath != null ? pPath : new File("."); + + Long space = getSpace16("getFreeSpace", path); + if (space != null) { + return space; + } + + return FS.getFreeSpace(path); + } + + public static long getUsableSpace(final File pPath) { + // NOTE: Allow null, to get space in current/system volume + File path = pPath != null ? pPath : new File("."); + + Long space = getSpace16("getUsableSpace", path); + if (space != null) { + return space; + } + + return getTotalSpace(path); + } + + // TODO: FixMe for Windows, before making it public... + public static long getTotalSpace(final File pPath) { + // NOTE: Allow null, to get space in current/system volume + File path = pPath != null ? pPath : new File("."); + + Long space = getSpace16("getTotalSpace", path); + if (space != null) { + return space; + } + + return FS.getTotalSpace(path); + } + + private static Long getSpace16(final String pMethodName, final File pPath) { + try { + Method freeSpace = File.class.getMethod(pMethodName); + return (Long) freeSpace.invoke(pPath); + } + catch (NoSuchMethodException ignore) {} + catch (IllegalAccessException ignore) {} + catch (InvocationTargetException e) { + Throwable throwable = e.getTargetException(); + if (throwable instanceof SecurityException) { + throw (SecurityException) throwable; + } + throw new UndeclaredThrowableException(throwable); + } + + return null; + } + + /** + * Formats the given number to a human readable format. + * Kind of like {@code df -h}. + * + * @param pSizeInBytes the size in byte + * @return a human readable string representation + */ + public static String toHumanReadableSize(final long pSizeInBytes) { + // TODO: Rewrite to use String.format? + if (pSizeInBytes < 1024L) { + return pSizeInBytes + " Bytes"; + } + else if (pSizeInBytes < (1024L << 10)) { + return getSizeFormat().format(pSizeInBytes / (double) (1024L)) + " KB"; + } + else if (pSizeInBytes < (1024L << 20)) { + return getSizeFormat().format(pSizeInBytes / (double) (1024L << 10)) + " MB"; + } + else if (pSizeInBytes < (1024L << 30)) { + return getSizeFormat().format(pSizeInBytes / (double) (1024L << 20)) + " GB"; + } + else if (pSizeInBytes < (1024L << 40)) { + return getSizeFormat().format(pSizeInBytes / (double) (1024L << 30)) + " TB"; + } + else { + return getSizeFormat().format(pSizeInBytes / (double) (1024L << 40)) + " PB"; + } + } + + // NumberFormat is not thread-safe, so we stick to thread-confined instances + private static ThreadLocal sNumberFormat = new ThreadLocal() { + protected NumberFormat initialValue() { + NumberFormat format = NumberFormat.getNumberInstance(); + // TODO: Consider making this locale/platform specific, OR a method parameter... +// format.setMaximumFractionDigits(2); + format.setMaximumFractionDigits(0); + return format; + } + }; + + private static NumberFormat getSizeFormat() { + return sNumberFormat.get(); + } + + /** + * Visits all files in {@code pDirectory}. Optionally filtered through a {@link FileFilter}. + * + * @param pDirectory the directory to visit files in + * @param pFilter the filter, may be {@code null}, meaning all files will be visited + * @param pVisitor the visitor + * + * @throws IllegalArgumentException if either {@code pDirectory} or {@code pVisitor} are {@code null} + * + * @see com.twelvemonkeys.util.Visitor + */ + @SuppressWarnings({"ResultOfMethodCallIgnored"}) + public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor pVisitor) { + Validate.notNull(pDirectory, "directory"); + Validate.notNull(pVisitor, "visitor"); + + pDirectory.listFiles(new FileFilter() { + public boolean accept(final File pFile) { + if (pFilter == null || pFilter.accept(pFile)) { + pVisitor.visit(pFile); + } + + return false; + } + }); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java index 6a541621..65e9e2de 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java @@ -1,242 +1,249 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.util.regex.WildcardStringParser; - -import java.io.File; -import java.io.FilenameFilter; - -/** - * A Java Bean used for approving file names which are to be included in a - * {@code java.io.File} listing. - * The mask is given as a well-known DOS filename format, with '*' and '?' as - * wildcards. - * All other characters counts as ordinary characters. - *

- * The file name masks are used as a filter input and is given to the class via - * the string array property:
- *

{@code filenameMasksForInclusion} - Filename mask for exclusion of - * files (default if both properties are defined) - *
{@code filenameMasksForExclusion} - Filename mask for exclusion of - * files. - *

- * A recommended way of doing this is by referencing to the component which uses - * this class for file listing. In this way all properties are set in the same - * component and this utility component is kept in the background with only - * initial configuration necessary. - * - * @author Eirik Torske - * @see File#list(java.io.FilenameFilter) java.io.File.list - * @see FilenameFilter java.io.FilenameFilter - * @see WildcardStringParser - * @deprecated - */ -public class FilenameMaskFilter implements FilenameFilter { - - // TODO: Rewrite to use regexp, or create new class - - // Members - private String[] filenameMasksForInclusion; - private String[] filenameMasksForExclusion; - private boolean inclusion = true; - - - /** - * Creates a {@code FilenameMaskFilter} - */ - public FilenameMaskFilter() { - } - - /** - * Creates a {@code FilenameMaskFilter} - * - * @param pFilenameMask the filename mask - */ - public FilenameMaskFilter(final String pFilenameMask) { - String[] filenameMask = {pFilenameMask}; - setFilenameMasksForInclusion(filenameMask); - } - - /** - * Creates a {@code FilenameMaskFilter} - * - * @param pFilenameMasks the filename masks - */ - public FilenameMaskFilter(final String[] pFilenameMasks) { - this(pFilenameMasks, false); - } - - /** - * Creates a {@code FilenameMaskFilter} - * - * @param pFilenameMask the filename masks - * @param pExclusion if {@code true}, the masks will be excluded - */ - public FilenameMaskFilter(final String pFilenameMask, final boolean pExclusion) { - String[] filenameMask = {pFilenameMask}; - - if (pExclusion) { - setFilenameMasksForExclusion(filenameMask); - } - else { - setFilenameMasksForInclusion(filenameMask); - } - } - - /** - * Creates a {@code FilenameMaskFilter} - * - * @param pFilenameMasks the filename masks - * @param pExclusion if {@code true}, the masks will be excluded - */ - public FilenameMaskFilter(final String[] pFilenameMasks, final boolean pExclusion) { - if (pExclusion) { - setFilenameMasksForExclusion(pFilenameMasks); - } - else { - setFilenameMasksForInclusion(pFilenameMasks); - } - } - - /** - * - * @param pFilenameMasksForInclusion the filename masks to include - */ - public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) { - filenameMasksForInclusion = pFilenameMasksForInclusion; - } - - /** - * @return the current inclusion masks - */ - public String[] getFilenameMasksForInclusion() { - return filenameMasksForInclusion.clone(); - } - - /** - * @param pFilenameMasksForExclusion the filename masks to exclude - */ - public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) { - filenameMasksForExclusion = pFilenameMasksForExclusion; - inclusion = false; - } - - /** - * @return the current exclusion masks - */ - public String[] getFilenameMasksForExclusion() { - return filenameMasksForExclusion.clone(); - } - - /** - * This method implements the {@code java.io.FilenameFilter} interface. - * - * @param pDir the directory in which the file was found. - * @param pName the name of the file. - * @return {@code true} if the file {@code pName} should be included in the file - * list; {@code false} otherwise. - */ - public boolean accept(File pDir, String pName) { - WildcardStringParser parser; - - // Check each filename string mask whether the file is to be accepted - if (inclusion) { // Inclusion - for (String mask : filenameMasksForInclusion) { - parser = new WildcardStringParser(mask); - if (parser.parseString(pName)) { - - // The filename was accepted by the filename masks provided - // - include it in filename list - return true; - } - } - - // The filename not was accepted by any of the filename masks - // provided - NOT to be included in the filename list - return false; - } - else { - // Exclusion - for (String mask : filenameMasksForExclusion) { - parser = new WildcardStringParser(mask); - if (parser.parseString(pName)) { - - // The filename was accepted by the filename masks provided - // - NOT to be included in the filename list - return false; - } - } - - // The filename was not accepted by any of the filename masks - // provided - include it in filename list - return true; - } - } - - /** - * @return a string representation for debug purposes - */ - public String toString() { - StringBuilder retVal = new StringBuilder(); - int i; - - if (inclusion) { - // Inclusion - if (filenameMasksForInclusion == null) { - retVal.append("No filename masks set - property filenameMasksForInclusion is null!"); - } - else { - retVal.append(filenameMasksForInclusion.length); - retVal.append(" filename mask(s) - "); - for (i = 0; i < filenameMasksForInclusion.length; i++) { - retVal.append("\""); - retVal.append(filenameMasksForInclusion[i]); - retVal.append("\", \""); - } - } - } - else { - // Exclusion - if (filenameMasksForExclusion == null) { - retVal.append("No filename masks set - property filenameMasksForExclusion is null!"); - } - else { - retVal.append(filenameMasksForExclusion.length); - retVal.append(" exclusion filename mask(s) - "); - for (i = 0; i < filenameMasksForExclusion.length; i++) { - retVal.append("\""); - retVal.append(filenameMasksForExclusion[i]); - retVal.append("\", \""); - } - } - } - return retVal.toString(); - } -} +/* + * 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.io; + +import com.twelvemonkeys.util.regex.WildcardStringParser; + +import java.io.File; +import java.io.FilenameFilter; + +/** + * A Java Bean used for approving file names which are to be included in a + * {@code java.io.File} listing. + * The mask is given as a well-known DOS filename format, with '*' and '?' as + * wildcards. + * All other characters counts as ordinary characters. + *

+ * The file name masks are used as a filter input and is given to the class via + * the string array property: + *

+ *
+ *
{@code filenameMasksForInclusion}
+ *
Filename mask for exclusion of + * files (default if both properties are defined).
+ *
{@code filenameMasksForExclusion}
+ *
Filename mask for exclusion of files.
+ *
+ *

+ * A recommended way of doing this is by referencing to the component which uses + * this class for file listing. In this way all properties are set in the same + * component and this utility component is kept in the background with only + * initial configuration necessary. + *

+ * + * @author Eirik Torske + * @see File#list(java.io.FilenameFilter) java.io.File.list + * @see FilenameFilter java.io.FilenameFilter + * @see WildcardStringParser + * @deprecated + */ +public class FilenameMaskFilter implements FilenameFilter { + + // TODO: Rewrite to use regexp, or create new class + + // Members + private String[] filenameMasksForInclusion; + private String[] filenameMasksForExclusion; + private boolean inclusion = true; + + + /** + * Creates a {@code FilenameMaskFilter} + */ + public FilenameMaskFilter() { + } + + /** + * Creates a {@code FilenameMaskFilter} + * + * @param pFilenameMask the filename mask + */ + public FilenameMaskFilter(final String pFilenameMask) { + String[] filenameMask = {pFilenameMask}; + setFilenameMasksForInclusion(filenameMask); + } + + /** + * Creates a {@code FilenameMaskFilter} + * + * @param pFilenameMasks the filename masks + */ + public FilenameMaskFilter(final String[] pFilenameMasks) { + this(pFilenameMasks, false); + } + + /** + * Creates a {@code FilenameMaskFilter} + * + * @param pFilenameMask the filename masks + * @param pExclusion if {@code true}, the masks will be excluded + */ + public FilenameMaskFilter(final String pFilenameMask, final boolean pExclusion) { + String[] filenameMask = {pFilenameMask}; + + if (pExclusion) { + setFilenameMasksForExclusion(filenameMask); + } + else { + setFilenameMasksForInclusion(filenameMask); + } + } + + /** + * Creates a {@code FilenameMaskFilter} + * + * @param pFilenameMasks the filename masks + * @param pExclusion if {@code true}, the masks will be excluded + */ + public FilenameMaskFilter(final String[] pFilenameMasks, final boolean pExclusion) { + if (pExclusion) { + setFilenameMasksForExclusion(pFilenameMasks); + } + else { + setFilenameMasksForInclusion(pFilenameMasks); + } + } + + /** + * + * @param pFilenameMasksForInclusion the filename masks to include + */ + public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) { + filenameMasksForInclusion = pFilenameMasksForInclusion; + } + + /** + * @return the current inclusion masks + */ + public String[] getFilenameMasksForInclusion() { + return filenameMasksForInclusion.clone(); + } + + /** + * @param pFilenameMasksForExclusion the filename masks to exclude + */ + public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) { + filenameMasksForExclusion = pFilenameMasksForExclusion; + inclusion = false; + } + + /** + * @return the current exclusion masks + */ + public String[] getFilenameMasksForExclusion() { + return filenameMasksForExclusion.clone(); + } + + /** + * This method implements the {@code java.io.FilenameFilter} interface. + * + * @param pDir the directory in which the file was found. + * @param pName the name of the file. + * @return {@code true} if the file {@code pName} should be included in the file + * list; {@code false} otherwise. + */ + public boolean accept(File pDir, String pName) { + WildcardStringParser parser; + + // Check each filename string mask whether the file is to be accepted + if (inclusion) { // Inclusion + for (String mask : filenameMasksForInclusion) { + parser = new WildcardStringParser(mask); + if (parser.parseString(pName)) { + + // The filename was accepted by the filename masks provided + // - include it in filename list + return true; + } + } + + // The filename not was accepted by any of the filename masks + // provided - NOT to be included in the filename list + return false; + } + else { + // Exclusion + for (String mask : filenameMasksForExclusion) { + parser = new WildcardStringParser(mask); + if (parser.parseString(pName)) { + + // The filename was accepted by the filename masks provided + // - NOT to be included in the filename list + return false; + } + } + + // The filename was not accepted by any of the filename masks + // provided - include it in filename list + return true; + } + } + + /** + * @return a string representation for debug purposes + */ + public String toString() { + StringBuilder retVal = new StringBuilder(); + int i; + + if (inclusion) { + // Inclusion + if (filenameMasksForInclusion == null) { + retVal.append("No filename masks set - property filenameMasksForInclusion is null!"); + } + else { + retVal.append(filenameMasksForInclusion.length); + retVal.append(" filename mask(s) - "); + for (i = 0; i < filenameMasksForInclusion.length; i++) { + retVal.append("\""); + retVal.append(filenameMasksForInclusion[i]); + retVal.append("\", \""); + } + } + } + else { + // Exclusion + if (filenameMasksForExclusion == null) { + retVal.append("No filename masks set - property filenameMasksForExclusion is null!"); + } + else { + retVal.append(filenameMasksForExclusion.length); + retVal.append(" exclusion filename mask(s) - "); + for (i = 0; i < filenameMasksForExclusion.length; i++) { + retVal.append("\""); + retVal.append(filenameMasksForExclusion[i]); + retVal.append("\", \""); + } + } + } + return retVal.toString(); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java index 7eb31923..2cb94a03 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java @@ -1,451 +1,449 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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. - */ -/* - * From http://www.cafeaulait.org/books/javaio/ioexamples/index.html: - * - * Please feel free to use any fragment of this code you need in your own work. - * As far as I am concerned, it's in the public domain. No permission is necessary - * or required. Credit is always appreciated if you use a large chunk or base a - * significant product on one of my examples, but that's not required either. - * - * Elliotte Rusty Harold - */ - -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.Validate; - -import java.io.*; - -/** - * A little endian input stream reads two's complement, - * little endian integers, floating point numbers, and characters - * and returns them as Java primitive types. - *

- * The standard {@code java.io.DataInputStream} class - * which this class imitates reads big endian quantities. - *

- * Warning: - * - * The {@code DataInput} and {@code DataOutput} interfaces - * specifies big endian byte order in their documentation. - * This means that this class is, strictly speaking, not a proper - * implementation. However, I don't see a reason for the these interfaces to - * specify the byte order of their underlying representations. - * - * - * @see com.twelvemonkeys.io.LittleEndianRandomAccessFile - * @see java.io.DataInputStream - * @see java.io.DataInput - * @see java.io.DataOutput - * - * @author Elliotte Rusty Harold - * @author Harald Kuhr - * @version 2 - */ -public class LittleEndianDataInputStream extends FilterInputStream implements DataInput { - // TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations? - /** - * Creates a new little endian input stream and chains it to the - * input stream specified by the {@code pStream} argument. - * - * @param pStream the underlying input stream. - * @see java.io.FilterInputStream#in - */ - public LittleEndianDataInputStream(final InputStream pStream) { - super(Validate.notNull(pStream, "stream")); - } - - /** - * Reads a {@code boolean} from the underlying input stream by - * reading a single byte. If the byte is zero, false is returned. - * If the byte is positive, true is returned. - * - * @return the {@code boolean} value read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public boolean readBoolean() throws IOException { - int b = in.read(); - - if (b < 0) { - throw new EOFException(); - } - - return b != 0; - } - - /** - * Reads a signed {@code byte} from the underlying input stream - * with value between -128 and 127 - * - * @return the {@code byte} value read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public byte readByte() throws IOException { - int b = in.read(); - - if (b < 0) { - throw new EOFException(); - } - - return (byte) b; - - } - - /** - * Reads an unsigned {@code byte} from the underlying - * input stream with value between 0 and 255 - * - * @return the {@code byte} value read. - * @throws EOFException if the end of the underlying input - * stream has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public int readUnsignedByte() throws IOException { - int b = in.read(); - - if (b < 0) { - throw new EOFException(); - } - - return b; - } - - /** - * Reads a two byte signed {@code short} from the underlying - * input stream in little endian order, low byte first. - * - * @return the {@code short} read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public short readShort() throws IOException { - int byte1 = in.read(); - int byte2 = in.read(); - - // only need to test last byte read - // if byte1 is -1 so is byte2 - if (byte2 < 0) { - throw new EOFException(); - } - - return (short) (((byte2 << 24) >>> 16) | (byte1 << 24) >>> 24); - } - - /** - * Reads a two byte unsigned {@code short} from the underlying - * input stream in little endian order, low byte first. - * - * @return the int value of the unsigned short read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public int readUnsignedShort() throws IOException { - int byte1 = in.read(); - int byte2 = in.read(); - - if (byte2 < 0) { - throw new EOFException(); - } - - return (byte2 << 8) + byte1; - } - - /** - * Reads a two byte Unicode {@code char} from the underlying - * input stream in little endian order, low byte first. - * - * @return the int value of the unsigned short read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public char readChar() throws IOException { - int byte1 = in.read(); - int byte2 = in.read(); - - if (byte2 < 0) { - throw new EOFException(); - } - - return (char) (((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24)); - } - - - /** - * Reads a four byte signed {@code int} from the underlying - * input stream in little endian order, low byte first. - * - * @return the {@code int} read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public int readInt() throws IOException { - int byte1 = in.read(); - int byte2 = in.read(); - int byte3 = in.read(); - int byte4 = in.read(); - - if (byte4 < 0) { - throw new EOFException(); - } - - return (byte4 << 24) | ((byte3 << 24) >>> 8) - | ((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24); - } - - /** - * Reads an eight byte signed {@code int} from the underlying - * input stream in little endian order, low byte first. - * - * @return the {@code int} read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public long readLong() throws IOException { - long byte1 = in.read(); - long byte2 = in.read(); - long byte3 = in.read(); - long byte4 = in.read(); - long byte5 = in.read(); - long byte6 = in.read(); - long byte7 = in.read(); - long byte8 = in.read(); - - if (byte8 < 0) { - throw new EOFException(); - } - - return (byte8 << 56) | ((byte7 << 56) >>> 8) - | ((byte6 << 56) >>> 16) | ((byte5 << 56) >>> 24) - | ((byte4 << 56) >>> 32) | ((byte3 << 56) >>> 40) - | ((byte2 << 56) >>> 48) | ((byte1 << 56) >>> 56); - } - - /** - * Reads a string of no more than 65,535 characters - * from the underlying input stream using UTF-8 - * encoding. This method first reads a two byte short - * in big endian order as required by the - * UTF-8 specification. This gives the number of bytes in - * the UTF-8 encoded version of the string. - * Next this many bytes are read and decoded as UTF-8 - * encoded characters. - * - * @return the decoded string - * @throws UTFDataFormatException if the string cannot be decoded - * @throws IOException if the underlying stream throws an IOException. - */ - public String readUTF() throws IOException { - int byte1 = in.read(); - int byte2 = in.read(); - - if (byte2 < 0) { - throw new EOFException(); - } - - int numbytes = (byte1 << 8) + byte2; - char result[] = new char[numbytes]; - int numread = 0; - int numchars = 0; - - while (numread < numbytes) { - int c1 = readUnsignedByte(); - int c2, c3; - - // The first four bits of c1 determine how many bytes are in this char - int test = c1 >> 4; - if (test < 8) { // one byte - numread++; - result[numchars++] = (char) c1; - } - else if (test == 12 || test == 13) { // two bytes - numread += 2; - - if (numread > numbytes) { - throw new UTFDataFormatException(); - } - - c2 = readUnsignedByte(); - - if ((c2 & 0xC0) != 0x80) { - throw new UTFDataFormatException(); - } - - result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); - } - else if (test == 14) { // three bytes - numread += 3; - - if (numread > numbytes) { - throw new UTFDataFormatException(); - } - - c2 = readUnsignedByte(); - c3 = readUnsignedByte(); - - if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { - throw new UTFDataFormatException(); - } - - result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); - } - else { // malformed - throw new UTFDataFormatException(); - } - - } // end while - - return new String(result, 0, numchars); - - } - - /** - * @return the next eight bytes of this input stream, interpreted as a - * little endian {@code double}. - * @throws EOFException if end of stream occurs before eight bytes - * have been read. - * @throws IOException if an I/O error occurs. - */ - public final double readDouble() throws IOException { - return Double.longBitsToDouble(readLong()); - } - - /** - * @return the next four bytes of this input stream, interpreted as a - * little endian {@code int}. - * @throws EOFException if end of stream occurs before four bytes - * have been read. - * @throws IOException if an I/O error occurs. - */ - public final float readFloat() throws IOException { - return Float.intBitsToFloat(readInt()); - } - - /** - * See the general contract of the {@code skipBytes} - * method of {@code DataInput}. - *

- * Bytes for this operation are read from the contained input stream. - * - * @param pLength the number of bytes to be skipped. - * @return the actual number of bytes skipped. - * @exception IOException if an I/O error occurs. - */ - public final int skipBytes(int pLength) throws IOException { - // NOTE: There was probably a bug in ERH's original code here, as skip - // never returns -1, but returns 0 if no more bytes can be skipped... - int total = 0; - int skipped; - - while ((total < pLength) && ((skipped = (int) in.skip(pLength - total)) > 0)) { - total += skipped; - } - - return total; - } - - /** - * See the general contract of the {@code readFully} - * method of {@code DataInput}. - *

- * Bytes - * for this operation are read from the contained - * input stream. - * - * @param pBytes the buffer into which the data is read. - * @throws EOFException if this input stream reaches the end before - * reading all the bytes. - * @throws IOException if an I/O error occurs. - * @see java.io.FilterInputStream#in - */ - public final void readFully(byte pBytes[]) throws IOException { - readFully(pBytes, 0, pBytes.length); - } - - /** - * See the general contract of the {@code readFully} - * method of {@code DataInput}. - *

- * Bytes - * for this operation are read from the contained - * input stream. - * - * @param pBytes the buffer into which the data is read. - * @param pOffset the start offset of the data. - * @param pLength the number of bytes to read. - * @throws EOFException if this input stream reaches the end before - * reading all the bytes. - * @throws IOException if an I/O error occurs. - * @see java.io.FilterInputStream#in - */ - public final void readFully(byte pBytes[], int pOffset, int pLength) throws IOException { - if (pLength < 0) { - throw new IndexOutOfBoundsException(); - } - - int count = 0; - - while (count < pLength) { - int read = in.read(pBytes, pOffset + count, pLength - count); - - if (read < 0) { - throw new EOFException(); - } - - count += read; - } - } - - /** - * See the general contract of the {@code readLine} - * method of {@code DataInput}. - *

- * Bytes for this operation are read from the contained input stream. - * - * @deprecated This method does not properly convert bytes to characters. - * - * @return the next line of text from this input stream. - * @exception IOException if an I/O error occurs. - * @see java.io.BufferedReader#readLine() - * @see java.io.DataInputStream#readLine() - * @noinspection deprecation - */ - public String readLine() throws IOException { - DataInputStream ds = new DataInputStream(in); - return ds.readLine(); - } -} +/* + * 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. + */ +/* + * From http://www.cafeaulait.org/books/javaio/ioexamples/index.html: + * + * Please feel free to use any fragment of this code you need in your own work. + * As far as I am concerned, it's in the public domain. No permission is necessary + * or required. Credit is always appreciated if you use a large chunk or base a + * significant product on one of my examples, but that's not required either. + * + * Elliotte Rusty Harold + */ + +package com.twelvemonkeys.io; + +import com.twelvemonkeys.lang.Validate; + +import java.io.*; + +/** + * A little endian input stream reads two's complement, + * little endian integers, floating point numbers, and characters + * and returns them as Java primitive types. + *

+ * The standard {@code java.io.DataInputStream} class + * which this class imitates reads big endian quantities. + *

+ *

+ * Warning: + * The {@code DataInput} and {@code DataOutput} interfaces + * specifies big endian byte order in their documentation. + * This means that this class is, strictly speaking, not a proper + * implementation. However, I don't see a reason for the these interfaces to + * specify the byte order of their underlying representations. + * + *

+ * + * @see com.twelvemonkeys.io.LittleEndianRandomAccessFile + * @see java.io.DataInputStream + * @see java.io.DataInput + * @see java.io.DataOutput + * + * @author Elliotte Rusty Harold + * @author Harald Kuhr + * @version 2 + */ +public class LittleEndianDataInputStream extends FilterInputStream implements DataInput { + // TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations? + /** + * Creates a new little endian input stream and chains it to the + * input stream specified by the {@code pStream} argument. + * + * @param pStream the underlying input stream. + * @see java.io.FilterInputStream#in + */ + public LittleEndianDataInputStream(final InputStream pStream) { + super(Validate.notNull(pStream, "stream")); + } + + /** + * Reads a {@code boolean} from the underlying input stream by + * reading a single byte. If the byte is zero, false is returned. + * If the byte is positive, true is returned. + * + * @return the {@code boolean} value read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public boolean readBoolean() throws IOException { + int b = in.read(); + + if (b < 0) { + throw new EOFException(); + } + + return b != 0; + } + + /** + * Reads a signed {@code byte} from the underlying input stream + * with value between -128 and 127 + * + * @return the {@code byte} value read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public byte readByte() throws IOException { + int b = in.read(); + + if (b < 0) { + throw new EOFException(); + } + + return (byte) b; + + } + + /** + * Reads an unsigned {@code byte} from the underlying + * input stream with value between 0 and 255 + * + * @return the {@code byte} value read. + * @throws EOFException if the end of the underlying input + * stream has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public int readUnsignedByte() throws IOException { + int b = in.read(); + + if (b < 0) { + throw new EOFException(); + } + + return b; + } + + /** + * Reads a two byte signed {@code short} from the underlying + * input stream in little endian order, low byte first. + * + * @return the {@code short} read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public short readShort() throws IOException { + int byte1 = in.read(); + int byte2 = in.read(); + + // only need to test last byte read + // if byte1 is -1 so is byte2 + if (byte2 < 0) { + throw new EOFException(); + } + + return (short) (((byte2 << 24) >>> 16) | (byte1 << 24) >>> 24); + } + + /** + * Reads a two byte unsigned {@code short} from the underlying + * input stream in little endian order, low byte first. + * + * @return the int value of the unsigned short read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public int readUnsignedShort() throws IOException { + int byte1 = in.read(); + int byte2 = in.read(); + + if (byte2 < 0) { + throw new EOFException(); + } + + return (byte2 << 8) + byte1; + } + + /** + * Reads a two byte Unicode {@code char} from the underlying + * input stream in little endian order, low byte first. + * + * @return the int value of the unsigned short read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public char readChar() throws IOException { + int byte1 = in.read(); + int byte2 = in.read(); + + if (byte2 < 0) { + throw new EOFException(); + } + + return (char) (((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24)); + } + + + /** + * Reads a four byte signed {@code int} from the underlying + * input stream in little endian order, low byte first. + * + * @return the {@code int} read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public int readInt() throws IOException { + int byte1 = in.read(); + int byte2 = in.read(); + int byte3 = in.read(); + int byte4 = in.read(); + + if (byte4 < 0) { + throw new EOFException(); + } + + return (byte4 << 24) | ((byte3 << 24) >>> 8) + | ((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24); + } + + /** + * Reads an eight byte signed {@code int} from the underlying + * input stream in little endian order, low byte first. + * + * @return the {@code int} read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public long readLong() throws IOException { + long byte1 = in.read(); + long byte2 = in.read(); + long byte3 = in.read(); + long byte4 = in.read(); + long byte5 = in.read(); + long byte6 = in.read(); + long byte7 = in.read(); + long byte8 = in.read(); + + if (byte8 < 0) { + throw new EOFException(); + } + + return (byte8 << 56) | ((byte7 << 56) >>> 8) + | ((byte6 << 56) >>> 16) | ((byte5 << 56) >>> 24) + | ((byte4 << 56) >>> 32) | ((byte3 << 56) >>> 40) + | ((byte2 << 56) >>> 48) | ((byte1 << 56) >>> 56); + } + + /** + * Reads a string of no more than 65,535 characters + * from the underlying input stream using UTF-8 + * encoding. This method first reads a two byte short + * in big endian order as required by the + * UTF-8 specification. This gives the number of bytes in + * the UTF-8 encoded version of the string. + * Next this many bytes are read and decoded as UTF-8 + * encoded characters. + * + * @return the decoded string + * @throws UTFDataFormatException if the string cannot be decoded + * @throws IOException if the underlying stream throws an IOException. + */ + public String readUTF() throws IOException { + int byte1 = in.read(); + int byte2 = in.read(); + + if (byte2 < 0) { + throw new EOFException(); + } + + int numbytes = (byte1 << 8) + byte2; + char result[] = new char[numbytes]; + int numread = 0; + int numchars = 0; + + while (numread < numbytes) { + int c1 = readUnsignedByte(); + int c2, c3; + + // The first four bits of c1 determine how many bytes are in this char + int test = c1 >> 4; + if (test < 8) { // one byte + numread++; + result[numchars++] = (char) c1; + } + else if (test == 12 || test == 13) { // two bytes + numread += 2; + + if (numread > numbytes) { + throw new UTFDataFormatException(); + } + + c2 = readUnsignedByte(); + + if ((c2 & 0xC0) != 0x80) { + throw new UTFDataFormatException(); + } + + result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); + } + else if (test == 14) { // three bytes + numread += 3; + + if (numread > numbytes) { + throw new UTFDataFormatException(); + } + + c2 = readUnsignedByte(); + c3 = readUnsignedByte(); + + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { + throw new UTFDataFormatException(); + } + + result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); + } + else { // malformed + throw new UTFDataFormatException(); + } + + } // end while + + return new String(result, 0, numchars); + + } + + /** + * @return the next eight bytes of this input stream, interpreted as a + * little endian {@code double}. + * @throws EOFException if end of stream occurs before eight bytes + * have been read. + * @throws IOException if an I/O error occurs. + */ + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * @return the next four bytes of this input stream, interpreted as a + * little endian {@code int}. + * @throws EOFException if end of stream occurs before four bytes + * have been read. + * @throws IOException if an I/O error occurs. + */ + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * See the general contract of the {@code skipBytes} + * method of {@code DataInput}. + *

+ * Bytes for this operation are read from the contained input stream. + * + * @param pLength the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if an I/O error occurs. + */ + public final int skipBytes(int pLength) throws IOException { + // NOTE: There was probably a bug in ERH's original code here, as skip + // never returns -1, but returns 0 if no more bytes can be skipped... + int total = 0; + int skipped; + + while ((total < pLength) && ((skipped = (int) in.skip(pLength - total)) > 0)) { + total += skipped; + } + + return total; + } + + /** + * See the general contract of the {@code readFully} method of {@code DataInput}. + *

+ * Bytes for this operation are read from the contained input stream. + *

+ * + * @param pBytes the buffer into which the data is read. + * @throws EOFException if this input stream reaches the end before + * reading all the bytes. + * @throws IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public final void readFully(byte pBytes[]) throws IOException { + readFully(pBytes, 0, pBytes.length); + } + + /** + * See the general contract of the {@code readFully} method of {@code DataInput}. + *

+ * Bytes for this operation are read from the contained input stream. + *

+ * + * @param pBytes the buffer into which the data is read. + * @param pOffset the start offset of the data. + * @param pLength the number of bytes to read. + * @throws EOFException if this input stream reaches the end before + * reading all the bytes. + * @throws IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public final void readFully(byte pBytes[], int pOffset, int pLength) throws IOException { + if (pLength < 0) { + throw new IndexOutOfBoundsException(); + } + + int count = 0; + + while (count < pLength) { + int read = in.read(pBytes, pOffset + count, pLength - count); + + if (read < 0) { + throw new EOFException(); + } + + count += read; + } + } + + /** + * See the general contract of the {@code readLine} + * method of {@code DataInput}. + *

+ * Bytes for this operation are read from the contained input stream. + * + * @deprecated This method does not properly convert bytes to characters. + * + * @return the next line of text from this input stream. + * @exception IOException if an I/O error occurs. + * @see java.io.BufferedReader#readLine() + * @see java.io.DataInputStream#readLine() + */ + public String readLine() throws IOException { + DataInputStream ds = new DataInputStream(in); + return ds.readLine(); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java index 78699d73..68905fa7 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java @@ -1,339 +1,342 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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. - */ -/* - * From http://www.cafeaulait.org/books/javaio/ioexamples/index.html: - * - * Please feel free to use any fragment of this code you need in your own work. - * As far as I am concerned, it's in the public domain. No permission is necessary - * or required. Credit is always appreciated if you use a large chunk or base a - * significant product on one of my examples, but that's not required either. - * - * Elliotte Rusty Harold - */ - -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.Validate; - -import java.io.*; - -/** - * A little endian output stream writes primitive Java numbers - * and characters to an output stream in a little endian format. - *

- * The standard {@code java.io.DataOutputStream} class which this class - * imitates uses big endian integers. - *

- * Warning: - * - * The {@code DataInput} and {@code DataOutput} interfaces - * specifies big endian byte order in their documentation. - * This means that this class is, strictly speaking, not a proper - * implementation. However, I don't see a reason for the these interfaces to - * specify the byte order of their underlying representations. - * - * - * @see com.twelvemonkeys.io.LittleEndianRandomAccessFile - * @see java.io.DataOutputStream - * @see java.io.DataInput - * @see java.io.DataOutput - * - * @author Elliotte Rusty Harold - * @version 1.0.1, 19 May 1999 - */ -public class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput { - - /** - * The number of bytes written so far to the little endian output stream. - */ - protected int bytesWritten; - - /** - * Creates a new little endian output stream and chains it to the - * output stream specified by the {@code pStream} argument. - * - * @param pStream the underlying output stream. - * @see java.io.FilterOutputStream#out - */ - public LittleEndianDataOutputStream(OutputStream pStream) { - super(Validate.notNull(pStream, "stream")); - } - - /** - * Writes the specified byte value to the underlying output stream. - * - * @param pByte the {@code byte} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public synchronized void write(int pByte) throws IOException { - out.write(pByte); - bytesWritten++; - } - - /** - * Writes {@code pLength} bytes from the specified byte array - * starting at {@code pOffset} to the underlying output stream. - * - * @param pBytes the data. - * @param pOffset the start offset in the data. - * @param pLength the number of bytes to write. - * @throws IOException if the underlying stream throws an IOException. - */ - public synchronized void write(byte[] pBytes, int pOffset, int pLength) throws IOException { - out.write(pBytes, pOffset, pLength); - bytesWritten += pLength; - } - - - /** - * Writes a {@code boolean} to the underlying output stream as - * a single byte. If the argument is true, the byte value 1 is written. - * If the argument is false, the byte value {@code 0} in written. - * - * @param pBoolean the {@code boolean} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeBoolean(boolean pBoolean) throws IOException { - if (pBoolean) { - write(1); - } - else { - write(0); - } - } - - /** - * Writes out a {@code byte} to the underlying output stream - * - * @param pByte the {@code byte} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeByte(int pByte) throws IOException { - out.write(pByte); - bytesWritten++; - } - - /** - * Writes a two byte {@code short} to the underlying output stream in - * little endian order, low byte first. - * - * @param pShort the {@code short} to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeShort(int pShort) throws IOException { - out.write(pShort & 0xFF); - out.write((pShort >>> 8) & 0xFF); - bytesWritten += 2; - } - - /** - * Writes a two byte {@code char} to the underlying output stream - * in little endian order, low byte first. - * - * @param pChar the {@code char} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeChar(int pChar) throws IOException { - out.write(pChar & 0xFF); - out.write((pChar >>> 8) & 0xFF); - bytesWritten += 2; - } - - /** - * Writes a four-byte {@code int} to the underlying output stream - * in little endian order, low byte first, high byte last - * - * @param pInt the {@code int} to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeInt(int pInt) throws IOException { - out.write(pInt & 0xFF); - out.write((pInt >>> 8) & 0xFF); - out.write((pInt >>> 16) & 0xFF); - out.write((pInt >>> 24) & 0xFF); - bytesWritten += 4; - - } - - /** - * Writes an eight-byte {@code long} to the underlying output stream - * in little endian order, low byte first, high byte last - * - * @param pLong the {@code long} to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeLong(long pLong) throws IOException { - out.write((int) pLong & 0xFF); - out.write((int) (pLong >>> 8) & 0xFF); - out.write((int) (pLong >>> 16) & 0xFF); - out.write((int) (pLong >>> 24) & 0xFF); - out.write((int) (pLong >>> 32) & 0xFF); - out.write((int) (pLong >>> 40) & 0xFF); - out.write((int) (pLong >>> 48) & 0xFF); - out.write((int) (pLong >>> 56) & 0xFF); - bytesWritten += 8; - } - - /** - * Writes a 4 byte Java float to the underlying output stream in - * little endian order. - * - * @param f the {@code float} value to be written. - * @throws IOException if an I/O error occurs. - */ - public final void writeFloat(float f) throws IOException { - writeInt(Float.floatToIntBits(f)); - } - - /** - * Writes an 8 byte Java double to the underlying output stream in - * little endian order. - * - * @param d the {@code double} value to be written. - * @throws IOException if an I/O error occurs. - */ - public final void writeDouble(double d) throws IOException { - writeLong(Double.doubleToLongBits(d)); - } - - /** - * Writes a string to the underlying output stream as a sequence of - * bytes. Each character is written to the data output stream as - * if by the {@link #writeByte(int)} method. - * - * @param pString the {@code String} value to be written. - * @throws IOException if the underlying stream throws an IOException. - * @see #writeByte(int) - * @see #out - */ - public void writeBytes(String pString) throws IOException { - int length = pString.length(); - - for (int i = 0; i < length; i++) { - out.write((byte) pString.charAt(i)); - } - - bytesWritten += length; - } - - /** - * Writes a string to the underlying output stream as a sequence of - * characters. Each character is written to the data output stream as - * if by the {@code writeChar} method. - * - * @param pString a {@code String} value to be written. - * @throws IOException if the underlying stream throws an IOException. - * @see #writeChar(int) - * @see #out - */ - public void writeChars(String pString) throws IOException { - int length = pString.length(); - - for (int i = 0; i < length; i++) { - int c = pString.charAt(i); - out.write(c & 0xFF); - out.write((c >>> 8) & 0xFF); - } - - bytesWritten += length * 2; - } - - /** - * Writes a string of no more than 65,535 characters - * to the underlying output stream using UTF-8 - * encoding. This method first writes a two byte short - * in big endian order as required by the - * UTF-8 specification. This gives the number of bytes in the - * UTF-8 encoded version of the string, not the number of characters - * in the string. Next each character of the string is written - * using the UTF-8 encoding for the character. - * - * @param pString the string to be written. - * @throws UTFDataFormatException if the string is longer than - * 65,535 characters. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeUTF(String pString) throws IOException { - int numchars = pString.length(); - int numbytes = 0; - - for (int i = 0; i < numchars; i++) { - int c = pString.charAt(i); - - if ((c >= 0x0001) && (c <= 0x007F)) { - numbytes++; - } - else if (c > 0x07FF) { - numbytes += 3; - } - else { - numbytes += 2; - } - } - - if (numbytes > 65535) { - throw new UTFDataFormatException(); - } - - out.write((numbytes >>> 8) & 0xFF); - out.write(numbytes & 0xFF); - - for (int i = 0; i < numchars; i++) { - int c = pString.charAt(i); - - if ((c >= 0x0001) && (c <= 0x007F)) { - out.write(c); - } - else if (c > 0x07FF) { - out.write(0xE0 | ((c >> 12) & 0x0F)); - out.write(0x80 | ((c >> 6) & 0x3F)); - out.write(0x80 | (c & 0x3F)); - bytesWritten += 2; - } - else { - out.write(0xC0 | ((c >> 6) & 0x1F)); - out.write(0x80 | (c & 0x3F)); - bytesWritten += 1; - } - } - - bytesWritten += numchars + 2; - } - - /** - * Returns the number of bytes written to this little endian output stream. - * (This class is not thread-safe with respect to this method. It is - * possible that this number is temporarily less than the actual - * number of bytes written.) - * @return the value of the {@code written} field. - * @see #bytesWritten - */ - public int size() { - return bytesWritten; - } +/* + * 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. + */ +/* + * From http://www.cafeaulait.org/books/javaio/ioexamples/index.html: + * + * Please feel free to use any fragment of this code you need in your own work. + * As far as I am concerned, it's in the public domain. No permission is necessary + * or required. Credit is always appreciated if you use a large chunk or base a + * significant product on one of my examples, but that's not required either. + * + * Elliotte Rusty Harold + */ + +package com.twelvemonkeys.io; + +import com.twelvemonkeys.lang.Validate; + +import java.io.*; + +/** + * A little endian output stream writes primitive Java numbers + * and characters to an output stream in a little endian format. + *

+ * The standard {@code java.io.DataOutputStream} class which this class + * imitates uses big endian integers. + *

+ *

+ * Warning: + * The {@code DataInput} and {@code DataOutput} interfaces + * specifies big endian byte order in their documentation. + * This means that this class is, strictly speaking, not a proper + * implementation. However, I don't see a reason for the these interfaces to + * specify the byte order of their underlying representations. + * + *

+ * + * @see com.twelvemonkeys.io.LittleEndianRandomAccessFile + * @see java.io.DataOutputStream + * @see java.io.DataInput + * @see java.io.DataOutput + * + * @author Elliotte Rusty Harold + * @version 1.0.1, 19 May 1999 + */ +public class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput { + + /** + * The number of bytes written so far to the little endian output stream. + */ + protected int bytesWritten; + + /** + * Creates a new little endian output stream and chains it to the + * output stream specified by the {@code pStream} argument. + * + * @param pStream the underlying output stream. + * @see java.io.FilterOutputStream#out + */ + public LittleEndianDataOutputStream(OutputStream pStream) { + super(Validate.notNull(pStream, "stream")); + } + + /** + * Writes the specified byte value to the underlying output stream. + * + * @param pByte the {@code byte} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public synchronized void write(int pByte) throws IOException { + out.write(pByte); + bytesWritten++; + } + + /** + * Writes {@code pLength} bytes from the specified byte array + * starting at {@code pOffset} to the underlying output stream. + * + * @param pBytes the data. + * @param pOffset the start offset in the data. + * @param pLength the number of bytes to write. + * @throws IOException if the underlying stream throws an IOException. + */ + public synchronized void write(byte[] pBytes, int pOffset, int pLength) throws IOException { + out.write(pBytes, pOffset, pLength); + bytesWritten += pLength; + } + + + /** + * Writes a {@code boolean} to the underlying output stream as + * a single byte. If the argument is true, the byte value 1 is written. + * If the argument is false, the byte value {@code 0} in written. + * + * @param pBoolean the {@code boolean} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeBoolean(boolean pBoolean) throws IOException { + if (pBoolean) { + write(1); + } + else { + write(0); + } + } + + /** + * Writes out a {@code byte} to the underlying output stream + * + * @param pByte the {@code byte} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeByte(int pByte) throws IOException { + out.write(pByte); + bytesWritten++; + } + + /** + * Writes a two byte {@code short} to the underlying output stream in + * little endian order, low byte first. + * + * @param pShort the {@code short} to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeShort(int pShort) throws IOException { + out.write(pShort & 0xFF); + out.write((pShort >>> 8) & 0xFF); + bytesWritten += 2; + } + + /** + * Writes a two byte {@code char} to the underlying output stream + * in little endian order, low byte first. + * + * @param pChar the {@code char} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeChar(int pChar) throws IOException { + out.write(pChar & 0xFF); + out.write((pChar >>> 8) & 0xFF); + bytesWritten += 2; + } + + /** + * Writes a four-byte {@code int} to the underlying output stream + * in little endian order, low byte first, high byte last + * + * @param pInt the {@code int} to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeInt(int pInt) throws IOException { + out.write(pInt & 0xFF); + out.write((pInt >>> 8) & 0xFF); + out.write((pInt >>> 16) & 0xFF); + out.write((pInt >>> 24) & 0xFF); + bytesWritten += 4; + + } + + /** + * Writes an eight-byte {@code long} to the underlying output stream + * in little endian order, low byte first, high byte last + * + * @param pLong the {@code long} to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeLong(long pLong) throws IOException { + out.write((int) pLong & 0xFF); + out.write((int) (pLong >>> 8) & 0xFF); + out.write((int) (pLong >>> 16) & 0xFF); + out.write((int) (pLong >>> 24) & 0xFF); + out.write((int) (pLong >>> 32) & 0xFF); + out.write((int) (pLong >>> 40) & 0xFF); + out.write((int) (pLong >>> 48) & 0xFF); + out.write((int) (pLong >>> 56) & 0xFF); + bytesWritten += 8; + } + + /** + * Writes a 4 byte Java float to the underlying output stream in + * little endian order. + * + * @param f the {@code float} value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeFloat(float f) throws IOException { + writeInt(Float.floatToIntBits(f)); + } + + /** + * Writes an 8 byte Java double to the underlying output stream in + * little endian order. + * + * @param d the {@code double} value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeDouble(double d) throws IOException { + writeLong(Double.doubleToLongBits(d)); + } + + /** + * Writes a string to the underlying output stream as a sequence of + * bytes. Each character is written to the data output stream as + * if by the {@link #writeByte(int)} method. + * + * @param pString the {@code String} value to be written. + * @throws IOException if the underlying stream throws an IOException. + * @see #writeByte(int) + * @see #out + */ + public void writeBytes(String pString) throws IOException { + int length = pString.length(); + + for (int i = 0; i < length; i++) { + out.write((byte) pString.charAt(i)); + } + + bytesWritten += length; + } + + /** + * Writes a string to the underlying output stream as a sequence of + * characters. Each character is written to the data output stream as + * if by the {@code writeChar} method. + * + * @param pString a {@code String} value to be written. + * @throws IOException if the underlying stream throws an IOException. + * @see #writeChar(int) + * @see #out + */ + public void writeChars(String pString) throws IOException { + int length = pString.length(); + + for (int i = 0; i < length; i++) { + int c = pString.charAt(i); + out.write(c & 0xFF); + out.write((c >>> 8) & 0xFF); + } + + bytesWritten += length * 2; + } + + /** + * Writes a string of no more than 65,535 characters + * to the underlying output stream using UTF-8 + * encoding. This method first writes a two byte short + * in big endian order as required by the + * UTF-8 specification. This gives the number of bytes in the + * UTF-8 encoded version of the string, not the number of characters + * in the string. Next each character of the string is written + * using the UTF-8 encoding for the character. + * + * @param pString the string to be written. + * @throws UTFDataFormatException if the string is longer than + * 65,535 characters. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeUTF(String pString) throws IOException { + int numchars = pString.length(); + int numbytes = 0; + + for (int i = 0; i < numchars; i++) { + int c = pString.charAt(i); + + if ((c >= 0x0001) && (c <= 0x007F)) { + numbytes++; + } + else if (c > 0x07FF) { + numbytes += 3; + } + else { + numbytes += 2; + } + } + + if (numbytes > 65535) { + throw new UTFDataFormatException(); + } + + out.write((numbytes >>> 8) & 0xFF); + out.write(numbytes & 0xFF); + + for (int i = 0; i < numchars; i++) { + int c = pString.charAt(i); + + if ((c >= 0x0001) && (c <= 0x007F)) { + out.write(c); + } + else if (c > 0x07FF) { + out.write(0xE0 | ((c >> 12) & 0x0F)); + out.write(0x80 | ((c >> 6) & 0x3F)); + out.write(0x80 | (c & 0x3F)); + bytesWritten += 2; + } + else { + out.write(0xC0 | ((c >> 6) & 0x1F)); + out.write(0x80 | (c & 0x3F)); + bytesWritten += 1; + } + } + + bytesWritten += numchars + 2; + } + + /** + * Returns the number of bytes written to this little endian output stream. + * (This class is not thread-safe with respect to this method. It is + * possible that this number is temporarily less than the actual + * number of bytes written.) + * @return the value of the {@code written} field. + * @see #bytesWritten + */ + public int size() { + return bytesWritten; + } } \ No newline at end of file diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java index 6cb701dc..98e54489 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java @@ -1,626 +1,628 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.*; -import java.nio.channels.FileChannel; - -/** - * A replacement for {@link java.io.RandomAccessFile} that is capable of reading - * and writing data in little endian byte order. - *

- * Warning: - * - * The {@code DataInput} and {@code DataOutput} interfaces - * specifies big endian byte order in their documentation. - * This means that this class is, strictly speaking, not a proper - * implementation. However, I don't see a reason for the these interfaces to - * specify the byte order of their underlying representations. - * - * - * @see com.twelvemonkeys.io.LittleEndianDataInputStream - * @see com.twelvemonkeys.io.LittleEndianDataOutputStream - * @see java.io.RandomAccessFile - * @see java.io.DataInput - * @see java.io.DataOutput - * - * @author Elliotte Rusty Harold - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java#1 $ - */ -public class LittleEndianRandomAccessFile implements DataInput, DataOutput { - private RandomAccessFile file; - - public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException { - this(FileUtil.resolve(pName), pMode); - } - - public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException { - file = new RandomAccessFile(pFile, pMode); - } - - public void close() throws IOException { - file.close(); - } - - public FileChannel getChannel() { - return file.getChannel(); - } - - public FileDescriptor getFD() throws IOException { - return file.getFD(); - } - - public long getFilePointer() throws IOException { - return file.getFilePointer(); - } - - public long length() throws IOException { - return file.length(); - } - - public int read() throws IOException { - return file.read(); - } - - public int read(final byte[] b) throws IOException { - return file.read(b); - } - - public int read(final byte[] b, final int off, final int len) throws IOException { - return file.read(b, off, len); - } - - public void readFully(final byte[] b) throws IOException { - file.readFully(b); - } - - public void readFully(final byte[] b, final int off, final int len) throws IOException { - file.readFully(b, off, len); - } - - public String readLine() throws IOException { - return file.readLine(); - } - - /** - * Reads a {@code boolean} from the underlying input stream by - * reading a single byte. If the byte is zero, false is returned. - * If the byte is positive, true is returned. - * - * @return the {@code boolean} value read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public boolean readBoolean() throws IOException { - int b = file.read(); - - if (b < 0) { - throw new EOFException(); - } - - return b != 0; - } - - /** - * Reads a signed {@code byte} from the underlying input stream - * with value between -128 and 127 - * - * @return the {@code byte} value read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public byte readByte() throws IOException { - int b = file.read(); - - if (b < 0) { - throw new EOFException(); - } - - return (byte) b; - - } - - /** - * Reads an unsigned {@code byte} from the underlying - * input stream with value between 0 and 255 - * - * @return the {@code byte} value read. - * @throws EOFException if the end of the underlying input - * stream has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public int readUnsignedByte() throws IOException { - int b = file.read(); - - if (b < 0) { - throw new EOFException(); - } - - return b; - } - - /** - * Reads a two byte signed {@code short} from the underlying - * input stream in little endian order, low byte first. - * - * @return the {@code short} read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public short readShort() throws IOException { - int byte1 = file.read(); - int byte2 = file.read(); - - // only need to test last byte read - // if byte1 is -1 so is byte2 - if (byte2 < 0) { - throw new EOFException(); - } - - return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24); - } - - /** - * Reads a two byte unsigned {@code short} from the underlying - * input stream in little endian order, low byte first. - * - * @return the int value of the unsigned short read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public int readUnsignedShort() throws IOException { - int byte1 = file.read(); - int byte2 = file.read(); - - if (byte2 < 0) { - throw new EOFException(); - } - - //return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24); - return (byte2 << 8) + byte1; - } - - /** - * Reads a two byte Unicode {@code char} from the underlying - * input stream in little endian order, low byte first. - * - * @return the int value of the unsigned short read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public char readChar() throws IOException { - int byte1 = file.read(); - int byte2 = file.read(); - - if (byte2 < 0) { - throw new EOFException(); - } - - return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24)); - } - - - /** - * Reads a four byte signed {@code int} from the underlying - * input stream in little endian order, low byte first. - * - * @return the {@code int} read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public int readInt() throws IOException { - int byte1 = file.read(); - int byte2 = file.read(); - int byte3 = file.read(); - int byte4 = file.read(); - - if (byte4 < 0) { - throw new EOFException(); - } - - return (byte4 << 24) + ((byte3 << 24) >>> 8) + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24); - } - - /** - * Reads an eight byte signed {@code int} from the underlying - * input stream in little endian order, low byte first. - * - * @return the {@code int} read. - * @throws EOFException if the end of the underlying input stream - * has been reached - * @throws IOException if the underlying stream throws an IOException. - */ - public long readLong() throws IOException { - long byte1 = file.read(); - long byte2 = file.read(); - long byte3 = file.read(); - long byte4 = file.read(); - long byte5 = file.read(); - long byte6 = file.read(); - long byte7 = file.read(); - long byte8 = file.read(); - - if (byte8 < 0) { - throw new EOFException(); - } - - return (byte8 << 56) + ((byte7 << 56) >>> 8) - + ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24) - + ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40) - + ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56); - - } - - /** - * Reads a string of no more than 65,535 characters - * from the underlying input stream using UTF-8 - * encoding. This method first reads a two byte short - * in big endian order as required by the - * UTF-8 specification. This gives the number of bytes in - * the UTF-8 encoded version of the string. - * Next this many bytes are read and decoded as UTF-8 - * encoded characters. - * - * @return the decoded string - * @throws UTFDataFormatException if the string cannot be decoded - * @throws IOException if the underlying stream throws an IOException. - */ - public String readUTF() throws IOException { - int byte1 = file.read(); - int byte2 = file.read(); - - if (byte2 < 0) { - throw new EOFException(); - } - - int numbytes = (byte1 << 8) + byte2; - char result[] = new char[numbytes]; - int numread = 0; - int numchars = 0; - - while (numread < numbytes) { - - int c1 = readUnsignedByte(); - int c2, c3; - - // The first four bits of c1 determine how many bytes are in this char - int test = c1 >> 4; - if (test < 8) { // one byte - numread++; - result[numchars++] = (char) c1; - } - else if (test == 12 || test == 13) { // two bytes - numread += 2; - - if (numread > numbytes) { - throw new UTFDataFormatException(); - } - - c2 = readUnsignedByte(); - - if ((c2 & 0xC0) != 0x80) { - throw new UTFDataFormatException(); - } - - result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); - } - else if (test == 14) { // three bytes - numread += 3; - - if (numread > numbytes) { - throw new UTFDataFormatException(); - } - - c2 = readUnsignedByte(); - c3 = readUnsignedByte(); - - if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { - throw new UTFDataFormatException(); - } - - result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); - } - else { // malformed - throw new UTFDataFormatException(); - } - - } // end while - - return new String(result, 0, numchars); - } - - /** - * @return the next eight bytes of this input stream, interpreted as a - * little endian {@code double}. - * @throws EOFException if end of stream occurs before eight bytes - * have been read. - * @throws IOException if an I/O error occurs. - */ - public final double readDouble() throws IOException { - return Double.longBitsToDouble(readLong()); - } - - /** - * @return the next four bytes of this input stream, interpreted as a - * little endian {@code int}. - * @throws EOFException if end of stream occurs before four bytes - * have been read. - * @throws IOException if an I/O error occurs. - */ - public final float readFloat() throws IOException { - return Float.intBitsToFloat(readInt()); - } - - /** - * Sets the file-pointer offset, measured from the beginning of this - * file, at which the next read or write occurs. The offset may be - * set beyond the end of the file. Setting the offset beyond the end - * of the file does not change the file length. The file length will - * change only by writing after the offset has been set beyond the end - * of the file. - * - * @param pos the offset position, measured in bytes from the - * beginning of the file, at which to set the file - * pointer. - * @exception IOException if {@code pos} is less than - * {@code 0} or if an I/O error occurs. - */ - public void seek(final long pos) throws IOException { - file.seek(pos); - } - - public void setLength(final long newLength) throws IOException { - file.setLength(newLength); - } - - public int skipBytes(final int n) throws IOException { - return file.skipBytes(n); - } - - public void write(final byte[] b) throws IOException { - file.write(b); - } - - public void write(final byte[] b, final int off, final int len) throws IOException { - file.write(b, off, len); - } - - public void write(final int b) throws IOException { - file.write(b); - } - - /** - * Writes a {@code boolean} to the underlying output stream as - * a single byte. If the argument is true, the byte value 1 is written. - * If the argument is false, the byte value {@code 0} in written. - * - * @param pBoolean the {@code boolean} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeBoolean(boolean pBoolean) throws IOException { - if (pBoolean) { - write(1); - } - else { - write(0); - } - } - - /** - * Writes out a {@code byte} to the underlying output stream - * - * @param pByte the {@code byte} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeByte(int pByte) throws IOException { - file.write(pByte); - } - - /** - * Writes a two byte {@code short} to the underlying output stream in - * little endian order, low byte first. - * - * @param pShort the {@code short} to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeShort(int pShort) throws IOException { - file.write(pShort & 0xFF); - file.write((pShort >>> 8) & 0xFF); - } - - /** - * Writes a two byte {@code char} to the underlying output stream - * in little endian order, low byte first. - * - * @param pChar the {@code char} value to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeChar(int pChar) throws IOException { - file.write(pChar & 0xFF); - file.write((pChar >>> 8) & 0xFF); - } - - /** - * Writes a four-byte {@code int} to the underlying output stream - * in little endian order, low byte first, high byte last - * - * @param pInt the {@code int} to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeInt(int pInt) throws IOException { - file.write(pInt & 0xFF); - file.write((pInt >>> 8) & 0xFF); - file.write((pInt >>> 16) & 0xFF); - file.write((pInt >>> 24) & 0xFF); - } - - /** - * Writes an eight-byte {@code long} to the underlying output stream - * in little endian order, low byte first, high byte last - * - * @param pLong the {@code long} to be written. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeLong(long pLong) throws IOException { - file.write((int) pLong & 0xFF); - file.write((int) (pLong >>> 8) & 0xFF); - file.write((int) (pLong >>> 16) & 0xFF); - file.write((int) (pLong >>> 24) & 0xFF); - file.write((int) (pLong >>> 32) & 0xFF); - file.write((int) (pLong >>> 40) & 0xFF); - file.write((int) (pLong >>> 48) & 0xFF); - file.write((int) (pLong >>> 56) & 0xFF); - } - - /** - * Writes a 4 byte Java float to the underlying output stream in - * little endian order. - * - * @param f the {@code float} value to be written. - * @throws IOException if an I/O error occurs. - */ - public final void writeFloat(float f) throws IOException { - writeInt(Float.floatToIntBits(f)); - } - - /** - * Writes an 8 byte Java double to the underlying output stream in - * little endian order. - * - * @param d the {@code double} value to be written. - * @throws IOException if an I/O error occurs. - */ - public final void writeDouble(double d) throws IOException { - writeLong(Double.doubleToLongBits(d)); - } - - /** - * Writes a string to the underlying output stream as a sequence of - * bytes. Each character is written to the data output stream as - * if by the {@code writeByte()} method. - * - * @param pString the {@code String} value to be written. - * @throws IOException if the underlying stream throws an IOException. - * @see #writeByte(int) - * @see #file - */ - public void writeBytes(String pString) throws IOException { - int length = pString.length(); - - for (int i = 0; i < length; i++) { - file.write((byte) pString.charAt(i)); - } - } - - /** - * Writes a string to the underlying output stream as a sequence of - * characters. Each character is written to the data output stream as - * if by the {@code writeChar} method. - * - * @param pString a {@code String} value to be written. - * @throws IOException if the underlying stream throws an IOException. - * @see #writeChar(int) - * @see #file - */ - public void writeChars(String pString) throws IOException { - int length = pString.length(); - - for (int i = 0; i < length; i++) { - int c = pString.charAt(i); - file.write(c & 0xFF); - file.write((c >>> 8) & 0xFF); - } - } - - /** - * Writes a string of no more than 65,535 characters - * to the underlying output stream using UTF-8 - * encoding. This method first writes a two byte short - * in big endian order as required by the - * UTF-8 specification. This gives the number of bytes in the - * UTF-8 encoded version of the string, not the number of characters - * in the string. Next each character of the string is written - * using the UTF-8 encoding for the character. - * - * @param pString the string to be written. - * @throws UTFDataFormatException if the string is longer than - * 65,535 characters. - * @throws IOException if the underlying stream throws an IOException. - */ - public void writeUTF(String pString) throws IOException { - int numchars = pString.length(); - int numbytes = 0; - - for (int i = 0; i < numchars; i++) { - int c = pString.charAt(i); - - if ((c >= 0x0001) && (c <= 0x007F)) { - numbytes++; - } - else if (c > 0x07FF) { - numbytes += 3; - } - else { - numbytes += 2; - } - } - - if (numbytes > 65535) { - throw new UTFDataFormatException(); - } - - file.write((numbytes >>> 8) & 0xFF); - file.write(numbytes & 0xFF); - - for (int i = 0; i < numchars; i++) { - int c = pString.charAt(i); - - if ((c >= 0x0001) && (c <= 0x007F)) { - file.write(c); - } - else if (c > 0x07FF) { - file.write(0xE0 | ((c >> 12) & 0x0F)); - file.write(0x80 | ((c >> 6) & 0x3F)); - file.write(0x80 | (c & 0x3F)); - } - else { - file.write(0xC0 | ((c >> 6) & 0x1F)); - file.write(0x80 | (c & 0x3F)); - } - } - } -} +/* + * 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.io; + +import java.io.*; +import java.nio.channels.FileChannel; + +/** + * A replacement for {@link java.io.RandomAccessFile} that is capable of reading + * and writing data in little endian byte order. + *

+ * Warning: + * The {@code DataInput} and {@code DataOutput} interfaces + * specifies big endian byte order in their documentation. + * This means that this class is, strictly speaking, not a proper + * implementation. However, I don't see a reason for the these interfaces to + * specify the byte order of their underlying representations. + * + *

+ * + * @see com.twelvemonkeys.io.LittleEndianDataInputStream + * @see com.twelvemonkeys.io.LittleEndianDataOutputStream + * @see java.io.RandomAccessFile + * @see java.io.DataInput + * @see java.io.DataOutput + * + * @author Elliotte Rusty Harold + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java#1 $ + */ +public class LittleEndianRandomAccessFile implements DataInput, DataOutput { + private RandomAccessFile file; + + public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException { + this(FileUtil.resolve(pName), pMode); + } + + public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException { + file = new RandomAccessFile(pFile, pMode); + } + + public void close() throws IOException { + file.close(); + } + + public FileChannel getChannel() { + return file.getChannel(); + } + + public FileDescriptor getFD() throws IOException { + return file.getFD(); + } + + public long getFilePointer() throws IOException { + return file.getFilePointer(); + } + + public long length() throws IOException { + return file.length(); + } + + public int read() throws IOException { + return file.read(); + } + + public int read(final byte[] b) throws IOException { + return file.read(b); + } + + public int read(final byte[] b, final int off, final int len) throws IOException { + return file.read(b, off, len); + } + + public void readFully(final byte[] b) throws IOException { + file.readFully(b); + } + + public void readFully(final byte[] b, final int off, final int len) throws IOException { + file.readFully(b, off, len); + } + + public String readLine() throws IOException { + return file.readLine(); + } + + /** + * Reads a {@code boolean} from the underlying input stream by + * reading a single byte. If the byte is zero, false is returned. + * If the byte is positive, true is returned. + * + * @return the {@code boolean} value read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public boolean readBoolean() throws IOException { + int b = file.read(); + + if (b < 0) { + throw new EOFException(); + } + + return b != 0; + } + + /** + * Reads a signed {@code byte} from the underlying input stream + * with value between -128 and 127 + * + * @return the {@code byte} value read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public byte readByte() throws IOException { + int b = file.read(); + + if (b < 0) { + throw new EOFException(); + } + + return (byte) b; + + } + + /** + * Reads an unsigned {@code byte} from the underlying + * input stream with value between 0 and 255 + * + * @return the {@code byte} value read. + * @throws EOFException if the end of the underlying input + * stream has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public int readUnsignedByte() throws IOException { + int b = file.read(); + + if (b < 0) { + throw new EOFException(); + } + + return b; + } + + /** + * Reads a two byte signed {@code short} from the underlying + * input stream in little endian order, low byte first. + * + * @return the {@code short} read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public short readShort() throws IOException { + int byte1 = file.read(); + int byte2 = file.read(); + + // only need to test last byte read + // if byte1 is -1 so is byte2 + if (byte2 < 0) { + throw new EOFException(); + } + + return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24); + } + + /** + * Reads a two byte unsigned {@code short} from the underlying + * input stream in little endian order, low byte first. + * + * @return the int value of the unsigned short read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public int readUnsignedShort() throws IOException { + int byte1 = file.read(); + int byte2 = file.read(); + + if (byte2 < 0) { + throw new EOFException(); + } + + //return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24); + return (byte2 << 8) + byte1; + } + + /** + * Reads a two byte Unicode {@code char} from the underlying + * input stream in little endian order, low byte first. + * + * @return the int value of the unsigned short read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public char readChar() throws IOException { + int byte1 = file.read(); + int byte2 = file.read(); + + if (byte2 < 0) { + throw new EOFException(); + } + + return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24)); + } + + + /** + * Reads a four byte signed {@code int} from the underlying + * input stream in little endian order, low byte first. + * + * @return the {@code int} read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public int readInt() throws IOException { + int byte1 = file.read(); + int byte2 = file.read(); + int byte3 = file.read(); + int byte4 = file.read(); + + if (byte4 < 0) { + throw new EOFException(); + } + + return (byte4 << 24) + ((byte3 << 24) >>> 8) + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24); + } + + /** + * Reads an eight byte signed {@code int} from the underlying + * input stream in little endian order, low byte first. + * + * @return the {@code int} read. + * @throws EOFException if the end of the underlying input stream + * has been reached + * @throws IOException if the underlying stream throws an IOException. + */ + public long readLong() throws IOException { + long byte1 = file.read(); + long byte2 = file.read(); + long byte3 = file.read(); + long byte4 = file.read(); + long byte5 = file.read(); + long byte6 = file.read(); + long byte7 = file.read(); + long byte8 = file.read(); + + if (byte8 < 0) { + throw new EOFException(); + } + + return (byte8 << 56) + ((byte7 << 56) >>> 8) + + ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24) + + ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40) + + ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56); + + } + + /** + * Reads a string of no more than 65,535 characters + * from the underlying input stream using UTF-8 + * encoding. This method first reads a two byte short + * in big endian order as required by the + * UTF-8 specification. This gives the number of bytes in + * the UTF-8 encoded version of the string. + * Next this many bytes are read and decoded as UTF-8 + * encoded characters. + * + * @return the decoded string + * @throws UTFDataFormatException if the string cannot be decoded + * @throws IOException if the underlying stream throws an IOException. + */ + public String readUTF() throws IOException { + int byte1 = file.read(); + int byte2 = file.read(); + + if (byte2 < 0) { + throw new EOFException(); + } + + int numbytes = (byte1 << 8) + byte2; + char result[] = new char[numbytes]; + int numread = 0; + int numchars = 0; + + while (numread < numbytes) { + + int c1 = readUnsignedByte(); + int c2, c3; + + // The first four bits of c1 determine how many bytes are in this char + int test = c1 >> 4; + if (test < 8) { // one byte + numread++; + result[numchars++] = (char) c1; + } + else if (test == 12 || test == 13) { // two bytes + numread += 2; + + if (numread > numbytes) { + throw new UTFDataFormatException(); + } + + c2 = readUnsignedByte(); + + if ((c2 & 0xC0) != 0x80) { + throw new UTFDataFormatException(); + } + + result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); + } + else if (test == 14) { // three bytes + numread += 3; + + if (numread > numbytes) { + throw new UTFDataFormatException(); + } + + c2 = readUnsignedByte(); + c3 = readUnsignedByte(); + + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { + throw new UTFDataFormatException(); + } + + result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); + } + else { // malformed + throw new UTFDataFormatException(); + } + + } // end while + + return new String(result, 0, numchars); + } + + /** + * @return the next eight bytes of this input stream, interpreted as a + * little endian {@code double}. + * @throws EOFException if end of stream occurs before eight bytes + * have been read. + * @throws IOException if an I/O error occurs. + */ + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * @return the next four bytes of this input stream, interpreted as a + * little endian {@code int}. + * @throws EOFException if end of stream occurs before four bytes + * have been read. + * @throws IOException if an I/O error occurs. + */ + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Sets the file-pointer offset, measured from the beginning of this + * file, at which the next read or write occurs. The offset may be + * set beyond the end of the file. Setting the offset beyond the end + * of the file does not change the file length. The file length will + * change only by writing after the offset has been set beyond the end + * of the file. + * + * @param pos the offset position, measured in bytes from the + * beginning of the file, at which to set the file + * pointer. + * @exception IOException if {@code pos} is less than + * {@code 0} or if an I/O error occurs. + */ + public void seek(final long pos) throws IOException { + file.seek(pos); + } + + public void setLength(final long newLength) throws IOException { + file.setLength(newLength); + } + + public int skipBytes(final int n) throws IOException { + return file.skipBytes(n); + } + + public void write(final byte[] b) throws IOException { + file.write(b); + } + + public void write(final byte[] b, final int off, final int len) throws IOException { + file.write(b, off, len); + } + + public void write(final int b) throws IOException { + file.write(b); + } + + /** + * Writes a {@code boolean} to the underlying output stream as + * a single byte. If the argument is true, the byte value 1 is written. + * If the argument is false, the byte value {@code 0} in written. + * + * @param pBoolean the {@code boolean} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeBoolean(boolean pBoolean) throws IOException { + if (pBoolean) { + write(1); + } + else { + write(0); + } + } + + /** + * Writes out a {@code byte} to the underlying output stream + * + * @param pByte the {@code byte} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeByte(int pByte) throws IOException { + file.write(pByte); + } + + /** + * Writes a two byte {@code short} to the underlying output stream in + * little endian order, low byte first. + * + * @param pShort the {@code short} to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeShort(int pShort) throws IOException { + file.write(pShort & 0xFF); + file.write((pShort >>> 8) & 0xFF); + } + + /** + * Writes a two byte {@code char} to the underlying output stream + * in little endian order, low byte first. + * + * @param pChar the {@code char} value to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeChar(int pChar) throws IOException { + file.write(pChar & 0xFF); + file.write((pChar >>> 8) & 0xFF); + } + + /** + * Writes a four-byte {@code int} to the underlying output stream + * in little endian order, low byte first, high byte last + * + * @param pInt the {@code int} to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeInt(int pInt) throws IOException { + file.write(pInt & 0xFF); + file.write((pInt >>> 8) & 0xFF); + file.write((pInt >>> 16) & 0xFF); + file.write((pInt >>> 24) & 0xFF); + } + + /** + * Writes an eight-byte {@code long} to the underlying output stream + * in little endian order, low byte first, high byte last + * + * @param pLong the {@code long} to be written. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeLong(long pLong) throws IOException { + file.write((int) pLong & 0xFF); + file.write((int) (pLong >>> 8) & 0xFF); + file.write((int) (pLong >>> 16) & 0xFF); + file.write((int) (pLong >>> 24) & 0xFF); + file.write((int) (pLong >>> 32) & 0xFF); + file.write((int) (pLong >>> 40) & 0xFF); + file.write((int) (pLong >>> 48) & 0xFF); + file.write((int) (pLong >>> 56) & 0xFF); + } + + /** + * Writes a 4 byte Java float to the underlying output stream in + * little endian order. + * + * @param f the {@code float} value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeFloat(float f) throws IOException { + writeInt(Float.floatToIntBits(f)); + } + + /** + * Writes an 8 byte Java double to the underlying output stream in + * little endian order. + * + * @param d the {@code double} value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeDouble(double d) throws IOException { + writeLong(Double.doubleToLongBits(d)); + } + + /** + * Writes a string to the underlying output stream as a sequence of + * bytes. Each character is written to the data output stream as + * if by the {@code writeByte()} method. + * + * @param pString the {@code String} value to be written. + * @throws IOException if the underlying stream throws an IOException. + * @see #writeByte(int) + * @see #file + */ + public void writeBytes(String pString) throws IOException { + int length = pString.length(); + + for (int i = 0; i < length; i++) { + file.write((byte) pString.charAt(i)); + } + } + + /** + * Writes a string to the underlying output stream as a sequence of + * characters. Each character is written to the data output stream as + * if by the {@code writeChar} method. + * + * @param pString a {@code String} value to be written. + * @throws IOException if the underlying stream throws an IOException. + * @see #writeChar(int) + * @see #file + */ + public void writeChars(String pString) throws IOException { + int length = pString.length(); + + for (int i = 0; i < length; i++) { + int c = pString.charAt(i); + file.write(c & 0xFF); + file.write((c >>> 8) & 0xFF); + } + } + + /** + * Writes a string of no more than 65,535 characters + * to the underlying output stream using UTF-8 + * encoding. This method first writes a two byte short + * in big endian order as required by the + * UTF-8 specification. This gives the number of bytes in the + * UTF-8 encoded version of the string, not the number of characters + * in the string. Next each character of the string is written + * using the UTF-8 encoding for the character. + * + * @param pString the string to be written. + * @throws UTFDataFormatException if the string is longer than + * 65,535 characters. + * @throws IOException if the underlying stream throws an IOException. + */ + public void writeUTF(String pString) throws IOException { + int numchars = pString.length(); + int numbytes = 0; + + for (int i = 0; i < numchars; i++) { + int c = pString.charAt(i); + + if ((c >= 0x0001) && (c <= 0x007F)) { + numbytes++; + } + else if (c > 0x07FF) { + numbytes += 3; + } + else { + numbytes += 2; + } + } + + if (numbytes > 65535) { + throw new UTFDataFormatException(); + } + + file.write((numbytes >>> 8) & 0xFF); + file.write(numbytes & 0xFF); + + for (int i = 0; i < numchars; i++) { + int c = pString.charAt(i); + + if ((c >= 0x0001) && (c <= 0x007F)) { + file.write(c); + } + else if (c > 0x07FF) { + file.write(0xE0 | ((c >> 12) & 0x0F)); + file.write(0x80 | ((c >> 6) & 0x3F)); + file.write(0x80 | (c & 0x3F)); + } + else { + file.write(0xC0 | ((c >> 6) & 0x1F)); + file.write(0x80 | (c & 0x3F)); + } + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java index 70bf86ff..3a8d89d3 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java @@ -1,191 +1,197 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * A {@code SeekableInputStream} implementation that caches data in memory. - *

- * - * @see FileCacheSeekableStream - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java#3 $ - */ -public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStream { - - /** - * Creates a {@code MemoryCacheSeekableStream}, reading from the given - * {@code InputStream}. Data will be cached in memory. - * - * @param pStream the {@code InputStream} to read from. - */ - public MemoryCacheSeekableStream(final InputStream pStream) { - super(pStream, new MemoryCache()); - } - - public final boolean isCachedMemory() { - return true; - } - - public final boolean isCachedFile() { - return false; - } - - final static class MemoryCache extends StreamCache { - final static int BLOCK_SIZE = 1 << 13; - - private final List cache = new ArrayList(); - private long length; - private long position; - private long start; - - private byte[] getBlock() throws IOException { - final long currPos = position - start; - if (currPos < 0) { - throw new IOException("StreamCache flushed before read position"); - } - - long index = currPos / BLOCK_SIZE; - - if (index >= Integer.MAX_VALUE) { - throw new IOException("Memory cache max size exceeded"); - } - - if (index >= cache.size()) { - try { - cache.add(new byte[BLOCK_SIZE]); -// System.out.println("Allocating new block, size: " + BLOCK_SIZE); -// System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)"); - } - catch (OutOfMemoryError e) { - throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE); - } - } - - //System.out.println("index: " + index); - - return cache.get((int) index); - } - - public void write(final int pByte) throws IOException { - byte[] buffer = getBlock(); - - int idx = (int) (position % BLOCK_SIZE); - buffer[idx] = (byte) pByte; - position++; - - if (position > length) { - length = position; - } - } - - // TODO: OptimizeMe!!! - @Override - public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { - byte[] buffer = getBlock(); - for (int i = 0; i < pLength; i++) { - int index = (int) position % BLOCK_SIZE; - if (index == 0) { - buffer = getBlock(); - } - buffer[index] = pBuffer[pOffset + i]; - - position++; - } - if (position > length) { - length = position; - } - } - - public int read() throws IOException { - if (position >= length) { - return -1; - } - - byte[] buffer = getBlock(); - - int idx = (int) (position % BLOCK_SIZE); - position++; - - return buffer[idx] & 0xff; - } - - // TODO: OptimizeMe!!! - @Override - public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (position >= length) { - return -1; - } - - byte[] buffer = getBlock(); - - int bufferPos = (int) (position % BLOCK_SIZE); - - // Find maxIdx and simplify test in for-loop - int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position); - - int i; - //for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) { - for (i = 0; i < maxLen; i++) { - pBytes[pOffset + i] = buffer[bufferPos + i]; - } - - position += i; - - return i; - } - - public void seek(final long pPosition) throws IOException { - if (pPosition < start) { - throw new IOException("Seek before flush position"); - } - position = pPosition; - } - - @Override - public void flush(final long pPosition) { - int firstPos = (int) (pPosition / BLOCK_SIZE) - 1; - - for (int i = 0; i < firstPos; i++) { - cache.remove(0); - } - - start = pPosition; - } - - public long getPosition() { - return position; - } - } -} +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@code SeekableInputStream} implementation that caches data in memory. + * + * @see FileCacheSeekableStream + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java#3 $ + */ +public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStream { + + /** + * Creates a {@code MemoryCacheSeekableStream}, reading from the given + * {@code InputStream}. Data will be cached in memory. + * + * @param pStream the {@code InputStream} to read from. + */ + public MemoryCacheSeekableStream(final InputStream pStream) { + super(pStream, new MemoryCache()); + } + + public final boolean isCachedMemory() { + return true; + } + + public final boolean isCachedFile() { + return false; + } + + final static class MemoryCache extends StreamCache { + final static int BLOCK_SIZE = 1 << 13; + + private final List cache = new ArrayList<>(); + private long length; + private long position; + private long start; + + private byte[] getBlock() throws IOException { + final long currPos = position - start; + if (currPos < 0) { + throw new IOException("StreamCache flushed before read position"); + } + + long index = currPos / BLOCK_SIZE; + + if (index >= Integer.MAX_VALUE) { + throw new IOException("Memory cache max size exceeded"); + } + + if (index >= cache.size()) { + try { + cache.add(new byte[BLOCK_SIZE]); +// System.out.println("Allocating new block, size: " + BLOCK_SIZE); +// System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)"); + } + catch (OutOfMemoryError e) { + throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE); + } + } + + //System.out.println("index: " + index); + + return cache.get((int) index); + } + + public void write(final int pByte) throws IOException { + byte[] buffer = getBlock(); + + int idx = (int) (position % BLOCK_SIZE); + buffer[idx] = (byte) pByte; + position++; + + if (position > length) { + length = position; + } + } + + // TODO: OptimizeMe!!! + @Override + public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + byte[] buffer = getBlock(); + for (int i = 0; i < pLength; i++) { + int index = (int) position % BLOCK_SIZE; + if (index == 0) { + buffer = getBlock(); + } + buffer[index] = pBuffer[pOffset + i]; + + position++; + } + if (position > length) { + length = position; + } + } + + public int read() throws IOException { + if (position >= length) { + return -1; + } + + byte[] buffer = getBlock(); + + int idx = (int) (position % BLOCK_SIZE); + position++; + + return buffer[idx] & 0xff; + } + + // TODO: OptimizeMe!!! + @Override + public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + if (position >= length) { + return -1; + } + + byte[] buffer = getBlock(); + + int bufferPos = (int) (position % BLOCK_SIZE); + + // Find maxIdx and simplify test in for-loop + int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position); + + int i; + //for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) { + for (i = 0; i < maxLen; i++) { + pBytes[pOffset + i] = buffer[bufferPos + i]; + } + + position += i; + + return i; + } + + public void seek(final long pPosition) throws IOException { + if (pPosition < start) { + throw new IOException("Seek before flush position"); + } + position = pPosition; + } + + @Override + public void flush(final long pPosition) { + int firstPos = (int) (pPosition / BLOCK_SIZE) - 1; + + for (int i = 0; i < firstPos; i++) { + cache.remove(0); + } + + start = pPosition; + } + + @Override + void close() throws IOException { + cache.clear(); + } + + public long getPosition() { + return position; + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java index aa27ac30..02e45387 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java @@ -1,80 +1,81 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.IOException; -import java.io.InputStream; - -/** - * An {@code InputStream} that contains no bytes. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/NullInputStream.java#2 $ - */ -public class NullInputStream extends InputStream { - - /** - * Creates a {@code NullInputStream}. - */ - public NullInputStream() { - } - - /** - * This implementation returns {@code -1} (EOF), always. - * - * @return {@code -1} - * @throws IOException - */ - public int read() throws IOException { - return -1; - } - - /** - * This implementation returns {@code 0}, always. - * - * @return {@code 0} - * @throws IOException - */ - @Override - public int available() throws IOException { - return 0; - } - - /** - * This implementation returns {@code 0}, always. - * - * @return {@code 0} - * @throws IOException - */ - @Override - public long skip(long pOffset) throws IOException { - return 0l; - } -} +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@code InputStream} that contains no bytes. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/NullInputStream.java#2 $ + */ +public class NullInputStream extends InputStream { + + /** + * Creates a {@code NullInputStream}. + */ + public NullInputStream() { + } + + /** + * This implementation returns {@code -1} (EOF), always. + * + * @return {@code -1} + * @throws IOException + */ + public int read() throws IOException { + return -1; + } + + /** + * This implementation returns {@code 0}, always. + * + * @return {@code 0} + * @throws IOException + */ + @Override + public int available() throws IOException { + return 0; + } + + /** + * This implementation returns {@code 0}, always. + * + * @return {@code 0} + * @throws IOException + */ + @Override + public long skip(long pOffset) throws IOException { + return 0l; + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java index ad99120b..b584631a 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java @@ -1,68 +1,69 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * An {@code OutputStream} implementation that works as a sink. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/NullOutputStream.java#2 $ - */ -public class NullOutputStream extends OutputStream { - - /** - * Creates a {@code NullOutputStream}. - */ - public NullOutputStream() { - } - - /** - * This implementation does nothing. - */ - public void write(int pByte) throws IOException { - } - - /** - * This implementation does nothing. - */ - @Override - public void write(byte pBytes[]) throws IOException { - } - - /** - * This implementation does nothing. - */ - @Override - public void write(byte pBytes[], int pOffset, int pLength) throws IOException { - } +/* + * 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.io; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An {@code OutputStream} implementation that works as a sink. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/NullOutputStream.java#2 $ + */ +public class NullOutputStream extends OutputStream { + + /** + * Creates a {@code NullOutputStream}. + */ + public NullOutputStream() { + } + + /** + * This implementation does nothing. + */ + public void write(int pByte) throws IOException { + } + + /** + * This implementation does nothing. + */ + @Override + public void write(byte pBytes[]) throws IOException { + } + + /** + * This implementation does nothing. + */ + @Override + public void write(byte pBytes[], int pOffset, int pLength) throws IOException { + } } \ No newline at end of file diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java index 196da6e9..e3e5051d 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java @@ -1,239 +1,242 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.io.EOFException; - -/** - * A data stream that is both readable and writable, much like a - * {@code RandomAccessFile}, except it may be backed by something other than a file. - *

- * - * @see java.io.RandomAccessFile - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java#3 $ - */ -public abstract class RandomAccessStream implements Seekable, DataInput, DataOutput { - // TODO: Use a RandomAcceessFile as backing in impl, probably - // TODO: Create an in-memory implementation too? - // TODO: Package private SeekableDelegate? - - // TODO: Both read and write must update stream position - //private int position = -1; - - /** This random access stream, wrapped in an {@code InputStream} */ - SeekableInputStream inputView = null; - /** This random access stream, wrapped in an {@code OutputStream} */ - SeekableOutputStream outputView = null; - - // TODO: Create an Input and an Output interface matching InputStream and OutputStream? - public int read() throws IOException { - try { - return readByte() & 0xff; - } - catch (EOFException e) { - return -1; - } - } - - public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (pBytes == null) { - throw new NullPointerException("bytes == null"); - } - else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || - ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { - throw new IndexOutOfBoundsException(); - } - else if (pLength == 0) { - return 0; - } - - // Special case, allready at EOF - int c = read(); - if (c == -1) { - return -1; - } - - // Otherwise, read as many as bytes as possible - pBytes[pOffset] = (byte) c; - - int i = 1; - try { - for (; i < pLength; i++) { - c = read(); - if (c == -1) { - break; - } - pBytes[pOffset + i] = (byte) c; - } - } - catch (IOException ignore) { - // Ignore exception, just return length - } - - return i; - } - - public final int read(byte[] pBytes) throws IOException { - return read(pBytes, 0, pBytes != null ? pBytes.length : 1); - } - - /** - * Returns an input view of this {@code RandomAccessStream}. - * Invoking this method several times, will return the same object. - *

- * Note that read access is NOT synchronized. - * - * @return a {@code SeekableInputStream} reading from this stream - */ - public final SeekableInputStream asInputStream() { - if (inputView == null) { - inputView = new InputStreamView(this); - } - return inputView; - } - - /** - * Returns an output view of this {@code RandomAccessStream}. - * Invoking this method several times, will return the same object. - *

- * Note that write access is NOT synchronized. - * - * @return a {@code SeekableOutputStream} writing to this stream - */ - public final SeekableOutputStream asOutputStream() { - if (outputView == null) { - outputView = new OutputStreamView(this); - } - return outputView; - } - - static final class InputStreamView extends SeekableInputStream { - // TODO: Consider adding synchonization (on stream) for all operations - // TODO: Is is a good thing that close/flush etc works on stream? - // - Or should it rather just work on the views? - // - Allow multiple views? - - final private RandomAccessStream mStream; - - public InputStreamView(RandomAccessStream pStream) { - if (pStream == null) { - throw new IllegalArgumentException("stream == null"); - } - mStream = pStream; - } - - public boolean isCached() { - return mStream.isCached(); - } - - public boolean isCachedFile() { - return mStream.isCachedFile(); - } - - public boolean isCachedMemory() { - return mStream.isCachedMemory(); - } - - protected void closeImpl() throws IOException { - mStream.close(); - } - - protected void flushBeforeImpl(long pPosition) throws IOException { - mStream.flushBefore(pPosition); - } - - protected void seekImpl(long pPosition) throws IOException { - mStream.seek(pPosition); - } - - public int read() throws IOException { - return mStream.read(); - } - - @Override - public int read(byte pBytes[], int pOffset, int pLength) throws IOException { - return mStream.read(pBytes, pOffset, pLength); - } - } - - static final class OutputStreamView extends SeekableOutputStream { - // TODO: Consider adding synchonization (on stream) for all operations - // TODO: Is is a good thing that close/flush etc works on stream? - // - Or should it rather just work on the views? - // - Allow multiple views? - - final private RandomAccessStream mStream; - - public OutputStreamView(RandomAccessStream pStream) { - if (pStream == null) { - throw new IllegalArgumentException("stream == null"); - } - mStream = pStream; - } - - public boolean isCached() { - return mStream.isCached(); - } - - public boolean isCachedFile() { - return mStream.isCachedFile(); - } - - public boolean isCachedMemory() { - return mStream.isCachedMemory(); - } - - protected void closeImpl() throws IOException { - mStream.close(); - } - - protected void flushBeforeImpl(long pPosition) throws IOException { - mStream.flushBefore(pPosition); - } - - protected void seekImpl(long pPosition) throws IOException { - mStream.seek(pPosition); - } - - public void write(int pByte) throws IOException { - mStream.write(pByte); - } - - @Override - public void write(byte pBytes[], int pOffset, int pLength) throws IOException { - mStream.write(pBytes, pOffset, pLength); - } - } -} +/* + * 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.io; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.EOFException; +import java.io.IOException; + +/** + * A data stream that is both readable and writable, much like a + * {@code RandomAccessFile}, except it may be backed by something other than a file. + * + * @see java.io.RandomAccessFile + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java#3 $ + */ +public abstract class RandomAccessStream implements Seekable, DataInput, DataOutput { + // TODO: Use a RandomAcceessFile as backing in impl, probably + // TODO: Create an in-memory implementation too? + // TODO: Package private SeekableDelegate? + + // TODO: Both read and write must update stream position + //private int position = -1; + + /** This random access stream, wrapped in an {@code InputStream} */ + SeekableInputStream inputView = null; + /** This random access stream, wrapped in an {@code OutputStream} */ + SeekableOutputStream outputView = null; + + // TODO: Create an Input and an Output interface matching InputStream and OutputStream? + public int read() throws IOException { + try { + return readByte() & 0xff; + } + catch (EOFException e) { + return -1; + } + } + + public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + if (pBytes == null) { + throw new NullPointerException("bytes == null"); + } + else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || + ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { + throw new IndexOutOfBoundsException(); + } + else if (pLength == 0) { + return 0; + } + + // Special case, allready at EOF + int c = read(); + if (c == -1) { + return -1; + } + + // Otherwise, read as many as bytes as possible + pBytes[pOffset] = (byte) c; + + int i = 1; + try { + for (; i < pLength; i++) { + c = read(); + if (c == -1) { + break; + } + pBytes[pOffset + i] = (byte) c; + } + } + catch (IOException ignore) { + // Ignore exception, just return length + } + + return i; + } + + public final int read(byte[] pBytes) throws IOException { + return read(pBytes, 0, pBytes != null ? pBytes.length : 1); + } + + /** + * Returns an input view of this {@code RandomAccessStream}. + * Invoking this method several times, will return the same object. + *

+ * Note that read access is NOT synchronized. + *

+ * + * @return a {@code SeekableInputStream} reading from this stream + */ + public final SeekableInputStream asInputStream() { + if (inputView == null) { + inputView = new InputStreamView(this); + } + return inputView; + } + + /** + * Returns an output view of this {@code RandomAccessStream}. + * Invoking this method several times, will return the same object. + *

+ * Note that write access is NOT synchronized. + *

+ * + * @return a {@code SeekableOutputStream} writing to this stream + */ + public final SeekableOutputStream asOutputStream() { + if (outputView == null) { + outputView = new OutputStreamView(this); + } + return outputView; + } + + static final class InputStreamView extends SeekableInputStream { + // TODO: Consider adding synchonization (on stream) for all operations + // TODO: Is is a good thing that close/flush etc works on stream? + // - Or should it rather just work on the views? + // - Allow multiple views? + + final private RandomAccessStream mStream; + + public InputStreamView(RandomAccessStream pStream) { + if (pStream == null) { + throw new IllegalArgumentException("stream == null"); + } + mStream = pStream; + } + + public boolean isCached() { + return mStream.isCached(); + } + + public boolean isCachedFile() { + return mStream.isCachedFile(); + } + + public boolean isCachedMemory() { + return mStream.isCachedMemory(); + } + + protected void closeImpl() throws IOException { + mStream.close(); + } + + protected void flushBeforeImpl(long pPosition) throws IOException { + mStream.flushBefore(pPosition); + } + + protected void seekImpl(long pPosition) throws IOException { + mStream.seek(pPosition); + } + + public int read() throws IOException { + return mStream.read(); + } + + @Override + public int read(byte pBytes[], int pOffset, int pLength) throws IOException { + return mStream.read(pBytes, pOffset, pLength); + } + } + + static final class OutputStreamView extends SeekableOutputStream { + // TODO: Consider adding synchonization (on stream) for all operations + // TODO: Is is a good thing that close/flush etc works on stream? + // - Or should it rather just work on the views? + // - Allow multiple views? + + final private RandomAccessStream mStream; + + public OutputStreamView(RandomAccessStream pStream) { + if (pStream == null) { + throw new IllegalArgumentException("stream == null"); + } + mStream = pStream; + } + + public boolean isCached() { + return mStream.isCached(); + } + + public boolean isCachedFile() { + return mStream.isCachedFile(); + } + + public boolean isCachedMemory() { + return mStream.isCachedMemory(); + } + + protected void closeImpl() throws IOException { + mStream.close(); + } + + protected void flushBeforeImpl(long pPosition) throws IOException { + mStream.flushBefore(pPosition); + } + + protected void seekImpl(long pPosition) throws IOException { + mStream.seek(pPosition); + } + + public void write(int pByte) throws IOException { + mStream.write(pByte); + } + + @Override + public void write(byte pBytes[], int pOffset, int pLength) throws IOException { + mStream.write(pBytes, pOffset, pLength); + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java index b84d5d3e..204d1e10 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java @@ -1,184 +1,193 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.IOException; - -/** - * Interface for seekable streams. - *

- * @see SeekableInputStream - * @see SeekableOutputStream - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Seekable.java#1 $ - */ -public interface Seekable { - - /** - * Returns the current byte position of the stream. The next read will take - * place starting at this offset. - * - * @return a {@code long} containing the position of the stream. - * @throws IOException if an I/O error occurs. - */ - long getStreamPosition() throws IOException; - - /** - * Sets the current stream position to the desired location. - * The next read will occur at this location. - *

- * An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller - * than the flushed position (as returned by {@link #getFlushedPosition()}). - *

- * It is legal to seek past the end of the file; an {@code EOFException} - * will be thrown only if a read is performed. - * - * @param pPosition a long containing the desired file pointer position. - * - * @throws IndexOutOfBoundsException if {@code pPosition} is smaller than - * the flushed position. - * @throws IOException if any other I/O error occurs. - */ - void seek(long pPosition) throws IOException; - - /** - * Marks a position in the stream to be returned to by a subsequent call to - * reset. - * Unlike a standard {@code InputStream}, all {@code Seekable} - * streams upport marking. Additionally, calls to {@code mark} and - * {@code reset} may be nested arbitrarily. - *

- * Unlike the {@code mark} methods declared by the {@code Reader} or - * {@code InputStream} - * interfaces, no {@code readLimit} parameter is used. An arbitrary amount - * of data may be read following the call to {@code mark}. - */ - void mark(); - - /** - * Returns the file pointer to its previous position, - * at the time of the most recent unmatched call to mark. - *

- * Calls to reset without a corresponding call to mark will either: - *

    - *
  • throw an {@code IOException}
  • - *
  • or, reset to the beginning of the stream.
  • - *
- * An {@code IOException} will be thrown if the previous marked position - * lies in the discarded portion of the stream. - * - * @throws IOException if an I/O error occurs. - * @see java.io.InputStream#reset() - */ - void reset() throws IOException; - - /** - * Discards the initial portion of the stream prior to the indicated - * postion. Attempting to seek to an offset within the flushed portion of - * the stream will result in an {@code IndexOutOfBoundsException}. - *

- * Calling {@code flushBefore} may allow classes implementing this - * interface to free up resources such as memory or disk space that are - * being used to store data from the stream. - * - * @param pPosition a long containing the length of the file prefix that - * may be flushed. - * - * @throws IndexOutOfBoundsException if {@code pPosition} lies in the - * flushed portion of the stream or past the current stream position. - * @throws IOException if an I/O error occurs. - */ - void flushBefore(long pPosition) throws IOException; - - /** - * Discards the initial position of the stream prior to the current stream - * position. Equivalent to {@code flushBefore(getStreamPosition())}. - * - * @throws IOException if an I/O error occurs. - */ - void flush() throws IOException; - - /** - * Returns the earliest position in the stream to which seeking may be - * performed. The returned value will be the maximum of all values passed - * into previous calls to {@code flushBefore}. - * - * @return the earliest legal position for seeking, as a {@code long}. - * - * @throws IOException if an I/O error occurs. - */ - long getFlushedPosition() throws IOException; - - /** - * Returns true if this {@code Seekable} stream caches data itself in order - * to allow seeking backwards. Applications may consult this in order to - * decide how frequently, or whether, to flush in order to conserve cache - * resources. - * - * @return {@code true} if this {@code Seekable} caches data. - * @see #isCachedMemory() - * @see #isCachedFile() - */ - boolean isCached(); - - /** - * Returns true if this {@code Seekable} stream caches data itself in order - * to allow seeking backwards, and the cache is kept in main memory. - * Applications may consult this in order to decide how frequently, or - * whether, to flush in order to conserve cache resources. - * - * @return {@code true} if this {@code Seekable} caches data in main - * memory. - * @see #isCached() - * @see #isCachedFile() - */ - boolean isCachedMemory(); - - /** - * Returns true if this {@code Seekable} stream caches data itself in - * order to allow seeking backwards, and the cache is kept in a - * temporary file. - * Applications may consult this in order to decide how frequently, - * or whether, to flush in order to conserve cache resources. - * - * @return {@code true} if this {@code Seekable} caches data in a - * temporary file. - * @see #isCached - * @see #isCachedMemory - */ - boolean isCachedFile(); - - /** - * Closes the stream. - * - * @throws java.io.IOException if the stream can't be closed. - */ - void close() throws IOException; -} +/* + * 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.io; + +import java.io.IOException; + +/** + * Interface for seekable streams. + * + * @see SeekableInputStream + * @see SeekableOutputStream + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Seekable.java#1 $ + */ +public interface Seekable { + + /** + * Returns the current byte position of the stream. The next read will take + * place starting at this offset. + * + * @return a {@code long} containing the position of the stream. + * @throws IOException if an I/O error occurs. + */ + long getStreamPosition() throws IOException; + + /** + * Sets the current stream position to the desired location. + * The next read will occur at this location. + *

+ * An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller + * than the flushed position (as returned by {@link #getFlushedPosition()}). + *

+ *

+ * It is legal to seek past the end of the file; an {@code EOFException} + * will be thrown only if a read is performed. + *

+ * + * @param pPosition a long containing the desired file pointer position. + * + * @throws IndexOutOfBoundsException if {@code pPosition} is smaller than + * the flushed position. + * @throws IOException if any other I/O error occurs. + */ + void seek(long pPosition) throws IOException; + + /** + * Marks a position in the stream to be returned to by a subsequent call to + * reset. + * Unlike a standard {@code InputStream}, all {@code Seekable} + * streams upport marking. Additionally, calls to {@code mark} and + * {@code reset} may be nested arbitrarily. + *

+ * Unlike the {@code mark} methods declared by the {@code Reader} or + * {@code InputStream} + * interfaces, no {@code readLimit} parameter is used. An arbitrary amount + * of data may be read following the call to {@code mark}. + *

+ */ + void mark(); + + /** + * Returns the file pointer to its previous position, + * at the time of the most recent unmatched call to mark. + *

+ * Calls to reset without a corresponding call to mark will either: + *

+ *
    + *
  • throw an {@code IOException}
  • + *
  • or, reset to the beginning of the stream.
  • + *
+ *

+ * An {@code IOException} will be thrown if the previous marked position + * lies in the discarded portion of the stream. + *

+ * + * @throws IOException if an I/O error occurs. + * @see java.io.InputStream#reset() + */ + void reset() throws IOException; + + /** + * Discards the initial portion of the stream prior to the indicated + * postion. Attempting to seek to an offset within the flushed portion of + * the stream will result in an {@code IndexOutOfBoundsException}. + *

+ * Calling {@code flushBefore} may allow classes implementing this + * interface to free up resources such as memory or disk space that are + * being used to store data from the stream. + *

+ * + * @param pPosition a long containing the length of the file prefix that + * may be flushed. + * + * @throws IndexOutOfBoundsException if {@code pPosition} lies in the + * flushed portion of the stream or past the current stream position. + * @throws IOException if an I/O error occurs. + */ + void flushBefore(long pPosition) throws IOException; + + /** + * Discards the initial position of the stream prior to the current stream + * position. Equivalent to {@code flushBefore(getStreamPosition())}. + * + * @throws IOException if an I/O error occurs. + */ + void flush() throws IOException; + + /** + * Returns the earliest position in the stream to which seeking may be + * performed. The returned value will be the maximum of all values passed + * into previous calls to {@code flushBefore}. + * + * @return the earliest legal position for seeking, as a {@code long}. + * + * @throws IOException if an I/O error occurs. + */ + long getFlushedPosition() throws IOException; + + /** + * Returns true if this {@code Seekable} stream caches data itself in order + * to allow seeking backwards. Applications may consult this in order to + * decide how frequently, or whether, to flush in order to conserve cache + * resources. + * + * @return {@code true} if this {@code Seekable} caches data. + * @see #isCachedMemory() + * @see #isCachedFile() + */ + boolean isCached(); + + /** + * Returns true if this {@code Seekable} stream caches data itself in order + * to allow seeking backwards, and the cache is kept in main memory. + * Applications may consult this in order to decide how frequently, or + * whether, to flush in order to conserve cache resources. + * + * @return {@code true} if this {@code Seekable} caches data in main + * memory. + * @see #isCached() + * @see #isCachedFile() + */ + boolean isCachedMemory(); + + /** + * Returns true if this {@code Seekable} stream caches data itself in + * order to allow seeking backwards, and the cache is kept in a + * temporary file. + * Applications may consult this in order to decide how frequently, + * or whether, to flush in order to conserve cache resources. + * + * @return {@code true} if this {@code Seekable} caches data in a + * temporary file. + * @see #isCached + * @see #isCachedMemory + */ + boolean isCachedFile(); + + /** + * Closes the stream. + * + * @throws java.io.IOException if the stream can't be closed. + */ + void close() throws IOException; +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java index 0a605174..f585ebea 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java @@ -1,236 +1,238 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Stack; - -/** - * Abstract base class for {@code InputStream}s implementing the {@code Seekable} interface. - *

- * @see SeekableOutputStream - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java#4 $ - */ -public abstract class SeekableInputStream extends InputStream implements Seekable { - - // TODO: It's at the moment not possible to create subclasses outside this - // package, as there's no access to position. position needs to be - // updated from the read/read/read methods... - - /** The stream position in this stream */ - long position; - long flushedPosition; - boolean closed; - - protected Stack markedPositions = new Stack(); - - /// InputStream overrides - @Override - public final int read(byte[] pBytes) throws IOException { - return read(pBytes, 0, pBytes != null ? pBytes.length : 1); - } - - /** - * Implemented using {@code seek(currentPos + pLength)}. - * - * @param pLength the number of bytes to skip - * @return the actual number of bytes skipped (may be equal to or less - * than {@code pLength}) - * - * @throws IOException if an I/O exception occurs during skip - */ - @Override - public final long skip(final long pLength) throws IOException { - long pos = position; - long wantedPosition = pos + pLength; - if (wantedPosition < flushedPosition) { - throw new IOException("position < flushedPosition"); - } - - // Stop at stream length for compatibility, even though it might be allowed - // to seek past end of stream - int available = available(); - if (available > 0) { - seek(Math.min(wantedPosition, pos + available)); - } - // TODO: Add optimization for streams with known length! - else { - // Slow mode... - int toSkip = (int) Math.max(Math.min(pLength, 512), -512); - while (toSkip > 0 && read() >= 0) { - toSkip--; - } - } - - return position - pos; - } - - @Override - public final void mark(int pLimit) { - mark(); - - // TODO: We don't really need to do this.. Is it a good idea? - try { - flushBefore(Math.max(position - pLimit, flushedPosition)); - } - catch (IOException ignore) { - // Ignore, as it's not really critical - } - } - - /** - * Returns {@code true}, as marking is always supported. - * - * @return {@code true}. - */ - @Override - public final boolean markSupported() { - return true; - } - - /// Seekable implementation - public final void seek(long pPosition) throws IOException { - checkOpen(); - - // NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException), - // but it's kind of inconsistent with reset that throws IOException... - if (pPosition < flushedPosition) { - throw new IndexOutOfBoundsException("position < flushedPosition"); - } - - seekImpl(pPosition); - position = pPosition; - } - - protected abstract void seekImpl(long pPosition) throws IOException; - - public final void mark() { - markedPositions.push(position); - } - - @Override - public final void reset() throws IOException { - checkOpen(); - if (!markedPositions.isEmpty()) { - long newPos = markedPositions.pop(); - - // NOTE: This is correct according to javax.imageio (IOException), - // but it's kind of inconsistent with seek that throws IndexOutOfBoundsException... - if (newPos < flushedPosition) { - throw new IOException("Previous marked position has been discarded"); - } - - seek(newPos); - } - else { - // TODO: To iron out some wrinkles due to conflicting contracts - // (InputStream and Seekable both declare reset), - // we might need to reset to the last marked position instead.. - // However, that becomes REALLY confusing if that position is after - // the current position... - seek(0); - } - } - - public final void flushBefore(long pPosition) throws IOException { - if (pPosition < flushedPosition) { - throw new IndexOutOfBoundsException("position < flushedPosition"); - } - if (pPosition > getStreamPosition()) { - throw new IndexOutOfBoundsException("position > stream position"); - } - checkOpen(); - flushBeforeImpl(pPosition); - flushedPosition = pPosition; - } - - /** - * Discards the initial portion of the stream prior to the indicated postion. - * - * @param pPosition the position to flush to - * @throws IOException if an I/O exception occurs during the flush operation - * - * @see #flushBefore(long) - */ - protected abstract void flushBeforeImpl(long pPosition) throws IOException; - - public final void flush() throws IOException { - flushBefore(flushedPosition); - } - - public final long getFlushedPosition() throws IOException { - checkOpen(); - return flushedPosition; - } - - public final long getStreamPosition() throws IOException { - checkOpen(); - return position; - } - - protected final void checkOpen() throws IOException { - if (closed) { - throw new IOException("closed"); - } - } - - @Override - public final void close() throws IOException { - checkOpen(); - closed = true; - closeImpl(); - } - - protected abstract void closeImpl() throws IOException; - - /** - * Finalizes this object prior to garbage collection. The - * {@code close} method is called to close any open input - * source. This method should not be called from application - * code. - * - * @exception Throwable if an error occurs during superclass - * finalization. - */ - @Override - protected void finalize() throws Throwable { - if (!closed) { - try { - close(); - } - catch (IOException ignore) { - // Ignroe - } - } - super.finalize(); - } -} +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Stack; + +/** + * Abstract base class for {@code InputStream}s implementing the {@code Seekable} interface. + * + * @see SeekableOutputStream + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java#4 $ + */ +public abstract class SeekableInputStream extends InputStream implements Seekable { + + // TODO: It's at the moment not possible to create subclasses outside this + // package, as there's no access to position. position needs to be + // updated from the read/read/read methods... + + /** The stream position in this stream */ + long position; + long flushedPosition; + boolean closed; + + protected Stack markedPositions = new Stack(); + + /// InputStream overrides + @Override + public final int read(byte[] pBytes) throws IOException { + return read(pBytes, 0, pBytes != null ? pBytes.length : 1); + } + + /** + * Implemented using {@code seek(currentPos + pLength)}. + * + * @param pLength the number of bytes to skip + * @return the actual number of bytes skipped (may be equal to or less + * than {@code pLength}) + * + * @throws IOException if an I/O exception occurs during skip + */ + @Override + public final long skip(final long pLength) throws IOException { + long pos = position; + long wantedPosition = pos + pLength; + if (wantedPosition < flushedPosition) { + throw new IOException("position < flushedPosition"); + } + + // Stop at stream length for compatibility, even though it might be allowed + // to seek past end of stream + int available = available(); + if (available > 0) { + seek(Math.min(wantedPosition, pos + available)); + } + // TODO: Add optimization for streams with known length! + else { + // Slow mode... + int toSkip = (int) Math.max(Math.min(pLength, 512), -512); + while (toSkip > 0 && read() >= 0) { + toSkip--; + } + } + + return position - pos; + } + + @Override + public final void mark(int pLimit) { + mark(); + + // TODO: We don't really need to do this.. Is it a good idea? + try { + flushBefore(Math.max(position - pLimit, flushedPosition)); + } + catch (IOException ignore) { + // Ignore, as it's not really critical + } + } + + /** + * Returns {@code true}, as marking is always supported. + * + * @return {@code true}. + */ + @Override + public final boolean markSupported() { + return true; + } + + /// Seekable implementation + public final void seek(long pPosition) throws IOException { + checkOpen(); + + // NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException), + // but it's kind of inconsistent with reset that throws IOException... + if (pPosition < flushedPosition) { + throw new IndexOutOfBoundsException("position < flushedPosition"); + } + + seekImpl(pPosition); + position = pPosition; + } + + protected abstract void seekImpl(long pPosition) throws IOException; + + public final void mark() { + markedPositions.push(position); + } + + @Override + public final void reset() throws IOException { + checkOpen(); + if (!markedPositions.isEmpty()) { + long newPos = markedPositions.pop(); + + // NOTE: This is correct according to javax.imageio (IOException), + // but it's kind of inconsistent with seek that throws IndexOutOfBoundsException... + if (newPos < flushedPosition) { + throw new IOException("Previous marked position has been discarded"); + } + + seek(newPos); + } + else { + // TODO: To iron out some wrinkles due to conflicting contracts + // (InputStream and Seekable both declare reset), + // we might need to reset to the last marked position instead.. + // However, that becomes REALLY confusing if that position is after + // the current position... + seek(0); + } + } + + public final void flushBefore(long pPosition) throws IOException { + if (pPosition < flushedPosition) { + throw new IndexOutOfBoundsException("position < flushedPosition"); + } + if (pPosition > getStreamPosition()) { + throw new IndexOutOfBoundsException("position > stream position"); + } + checkOpen(); + flushBeforeImpl(pPosition); + flushedPosition = pPosition; + } + + /** + * Discards the initial portion of the stream prior to the indicated postion. + * + * @param pPosition the position to flush to + * @throws IOException if an I/O exception occurs during the flush operation + * + * @see #flushBefore(long) + */ + protected abstract void flushBeforeImpl(long pPosition) throws IOException; + + public final void flush() throws IOException { + flushBefore(flushedPosition); + } + + public final long getFlushedPosition() throws IOException { + checkOpen(); + return flushedPosition; + } + + public final long getStreamPosition() throws IOException { + checkOpen(); + return position; + } + + protected final void checkOpen() throws IOException { + if (closed) { + throw new IOException("closed"); + } + } + + @Override + public final void close() throws IOException { + checkOpen(); + closed = true; + closeImpl(); + } + + protected abstract void closeImpl() throws IOException; + + /** + * Finalizes this object prior to garbage collection. The + * {@code close} method is called to close any open input + * source. This method should not be called from application + * code. + * + * @exception Throwable if an error occurs during superclass + * finalization. + */ + @Override + protected void finalize() throws Throwable { + if (!closed) { + try { + close(); + } + catch (IOException ignore) { + // Ignroe + } + } + super.finalize(); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java index 133597ec..bdce032b 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java @@ -1,138 +1,140 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.OutputStream; -import java.io.IOException; -import java.util.Stack; - -/** - * Abstract base class for {@code OutputStream}s implementing the - * {@code Seekable} interface. - *

- * @see SeekableInputStream - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java#2 $ - */ -public abstract class SeekableOutputStream extends OutputStream implements Seekable { - // TODO: Implement - long position; - long flushedPosition; - boolean closed; - - protected Stack markedPositions = new Stack(); - - /// Outputstream overrides - @Override - public final void write(byte pBytes[]) throws IOException { - write(pBytes, 0, pBytes != null ? pBytes.length : 1); - } - - /// Seekable implementation - // TODO: This is common behaviour/implementation with SeekableInputStream, - // probably a good idea to extract a delegate..? - public final void seek(long pPosition) throws IOException { - checkOpen(); - - // TODO: This is correct according to javax.imageio (IndexOutOfBoundsException), - // but it's inconsistent with reset that throws IOException... - if (pPosition < flushedPosition) { - throw new IndexOutOfBoundsException("position < flushedPosition!"); - } - - seekImpl(pPosition); - position = pPosition; - } - - protected abstract void seekImpl(long pPosition) throws IOException; - - public final void mark() { - markedPositions.push(position); - } - - public final void reset() throws IOException { - checkOpen(); - if (!markedPositions.isEmpty()) { - long newPos = markedPositions.pop(); - - // TODO: This is correct according to javax.imageio (IOException), - // but it's inconsistent with seek that throws IndexOutOfBoundsException... - if (newPos < flushedPosition) { - throw new IOException("Previous marked position has been discarded!"); - } - - seek(newPos); - } - } - - public final void flushBefore(long pPosition) throws IOException { - if (pPosition < flushedPosition) { - throw new IndexOutOfBoundsException("position < flushedPosition!"); - } - if (pPosition > getStreamPosition()) { - throw new IndexOutOfBoundsException("position > getStreamPosition()!"); - } - checkOpen(); - flushBeforeImpl(pPosition); - flushedPosition = pPosition; - } - - protected abstract void flushBeforeImpl(long pPosition) throws IOException; - - @Override - public final void flush() throws IOException { - flushBefore(flushedPosition); - } - - public final long getFlushedPosition() throws IOException { - checkOpen(); - return flushedPosition; - } - - public final long getStreamPosition() throws IOException { - checkOpen(); - return position; - } - - protected final void checkOpen() throws IOException { - if (closed) { - throw new IOException("closed"); - } - } - - @Override - public final void close() throws IOException { - checkOpen(); - closed = true; - closeImpl(); - } - - protected abstract void closeImpl() throws IOException; -} +/* + * 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.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Stack; + +/** + * Abstract base class for {@code OutputStream}s implementing the + * {@code Seekable} interface. + * + * @see SeekableInputStream + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java#2 $ + */ +public abstract class SeekableOutputStream extends OutputStream implements Seekable { + // TODO: Implement + long position; + long flushedPosition; + boolean closed; + + protected Stack markedPositions = new Stack(); + + /// Outputstream overrides + @Override + public final void write(byte pBytes[]) throws IOException { + write(pBytes, 0, pBytes != null ? pBytes.length : 1); + } + + /// Seekable implementation + // TODO: This is common behaviour/implementation with SeekableInputStream, + // probably a good idea to extract a delegate..? + public final void seek(long pPosition) throws IOException { + checkOpen(); + + // TODO: This is correct according to javax.imageio (IndexOutOfBoundsException), + // but it's inconsistent with reset that throws IOException... + if (pPosition < flushedPosition) { + throw new IndexOutOfBoundsException("position < flushedPosition!"); + } + + seekImpl(pPosition); + position = pPosition; + } + + protected abstract void seekImpl(long pPosition) throws IOException; + + public final void mark() { + markedPositions.push(position); + } + + public final void reset() throws IOException { + checkOpen(); + if (!markedPositions.isEmpty()) { + long newPos = markedPositions.pop(); + + // TODO: This is correct according to javax.imageio (IOException), + // but it's inconsistent with seek that throws IndexOutOfBoundsException... + if (newPos < flushedPosition) { + throw new IOException("Previous marked position has been discarded!"); + } + + seek(newPos); + } + } + + public final void flushBefore(long pPosition) throws IOException { + if (pPosition < flushedPosition) { + throw new IndexOutOfBoundsException("position < flushedPosition!"); + } + if (pPosition > getStreamPosition()) { + throw new IndexOutOfBoundsException("position > getStreamPosition()!"); + } + checkOpen(); + flushBeforeImpl(pPosition); + flushedPosition = pPosition; + } + + protected abstract void flushBeforeImpl(long pPosition) throws IOException; + + @Override + public final void flush() throws IOException { + flushBefore(flushedPosition); + } + + public final long getFlushedPosition() throws IOException { + checkOpen(); + return flushedPosition; + } + + public final long getStreamPosition() throws IOException { + checkOpen(); + return position; + } + + protected final void checkOpen() throws IOException { + if (closed) { + throw new IOException("closed"); + } + } + + @Override + public final void close() throws IOException { + checkOpen(); + closed = true; + closeImpl(); + } + + protected abstract void closeImpl() throws IOException; +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java b/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java index d90b3e1b..45ec3f9b 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java @@ -1,187 +1,188 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.lang.Validate; - -import java.io.StringReader; -import java.io.IOException; -import java.io.Reader; - -/** - * StringArrayReader - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/StringArrayReader.java#2 $ - */ -public class StringArrayReader extends StringReader { - - private StringReader current; - private String[] strings; - protected final Object finalLock; - private int currentSting; - private int markedString; - private int mark; - private int next; - - /** - * Create a new string array reader. - * - * @param pStrings {@code String}s providing the character stream. - */ - public StringArrayReader(final String[] pStrings) { - super(""); - - Validate.notNull(pStrings, "strings"); - - finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the - // reference can't change, only it's elements - - strings = pStrings.clone(); // Defensive copy for content - nextReader(); - } - - protected final Reader nextReader() { - if (currentSting >= strings.length) { - current = new EmptyReader(); - } - else { - current = new StringReader(strings[currentSting++]); - } - - // NOTE: Reset next for every reader, and record marked reader in mark/reset methods! - next = 0; - - return current; - } - - /** - * Check to make sure that the stream has not been closed - * - * @throws IOException if the stream is closed - */ - protected final void ensureOpen() throws IOException { - if (strings == null) { - throw new IOException("Stream closed"); - } - } - - public void close() { - super.close(); - strings = null; - current.close(); - } - - public void mark(int pReadLimit) throws IOException { - if (pReadLimit < 0){ - throw new IllegalArgumentException("Read limit < 0"); - } - - synchronized (finalLock) { - ensureOpen(); - mark = next; - markedString = currentSting; - - current.mark(pReadLimit); - } - } - - public void reset() throws IOException { - synchronized (finalLock) { - ensureOpen(); - - if (currentSting != markedString) { - currentSting = markedString - 1; - nextReader(); - current.skip(mark); - } - else { - current.reset(); - } - - next = mark; - } - } - - public boolean markSupported() { - return true; - } - - public int read() throws IOException { - synchronized (finalLock) { - int read = current.read(); - - if (read < 0 && currentSting < strings.length) { - nextReader(); - return read(); // In case of empty strings - } - - next++; - - return read; - } - } - - public int read(char pBuffer[], int pOffset, int pLength) throws IOException { - synchronized (finalLock) { - int read = current.read(pBuffer, pOffset, pLength); - - if (read < 0 && currentSting < strings.length) { - nextReader(); - return read(pBuffer, pOffset, pLength); // In case of empty strings - } - - next += read; - - return read; - } - } - - public boolean ready() throws IOException { - return current.ready(); - } - - public long skip(long pChars) throws IOException { - synchronized (finalLock) { - long skipped = current.skip(pChars); - - if (skipped == 0 && currentSting < strings.length) { - nextReader(); - return skip(pChars); - } - - next += skipped; - - return skipped; - } - } - -} +/* + * 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.io; + +import com.twelvemonkeys.lang.Validate; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +/** + * StringArrayReader + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/StringArrayReader.java#2 $ + */ +public class StringArrayReader extends StringReader { + + private StringReader current; + private String[] strings; + protected final Object finalLock; + private int currentSting; + private int markedString; + private int mark; + private int next; + + /** + * Create a new string array reader. + * + * @param pStrings {@code String}s providing the character stream. + */ + public StringArrayReader(final String[] pStrings) { + super(""); + + Validate.notNull(pStrings, "strings"); + + finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the + // reference can't change, only it's elements + + strings = pStrings.clone(); // Defensive copy for content + nextReader(); + } + + protected final Reader nextReader() { + if (currentSting >= strings.length) { + current = new EmptyReader(); + } + else { + current = new StringReader(strings[currentSting++]); + } + + // NOTE: Reset next for every reader, and record marked reader in mark/reset methods! + next = 0; + + return current; + } + + /** + * Check to make sure that the stream has not been closed + * + * @throws IOException if the stream is closed + */ + protected final void ensureOpen() throws IOException { + if (strings == null) { + throw new IOException("Stream closed"); + } + } + + public void close() { + super.close(); + strings = null; + current.close(); + } + + public void mark(int pReadLimit) throws IOException { + if (pReadLimit < 0){ + throw new IllegalArgumentException("Read limit < 0"); + } + + synchronized (finalLock) { + ensureOpen(); + mark = next; + markedString = currentSting; + + current.mark(pReadLimit); + } + } + + public void reset() throws IOException { + synchronized (finalLock) { + ensureOpen(); + + if (currentSting != markedString) { + currentSting = markedString - 1; + nextReader(); + current.skip(mark); + } + else { + current.reset(); + } + + next = mark; + } + } + + public boolean markSupported() { + return true; + } + + public int read() throws IOException { + synchronized (finalLock) { + int read = current.read(); + + if (read < 0 && currentSting < strings.length) { + nextReader(); + return read(); // In case of empty strings + } + + next++; + + return read; + } + } + + public int read(char pBuffer[], int pOffset, int pLength) throws IOException { + synchronized (finalLock) { + int read = current.read(pBuffer, pOffset, pLength); + + if (read < 0 && currentSting < strings.length) { + nextReader(); + return read(pBuffer, pOffset, pLength); // In case of empty strings + } + + next += read; + + return read; + } + } + + public boolean ready() throws IOException { + return current.ready(); + } + + public long skip(long pChars) throws IOException { + synchronized (finalLock) { + long skipped = current.skip(pChars); + + if (skipped == 0 && currentSting < strings.length) { + nextReader(); + return skip(pChars); + } + + next += skipped; + + return skipped; + } + } + +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java index d45d6b78..76a3c01f 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java @@ -1,135 +1,136 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.lang.Validate; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * An {@code InputStream} reading up to a specified number of bytes from an - * underlying stream. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $ - */ -public final class SubStream extends FilterInputStream { - private long bytesLeft; - private int markLimit; - - /** - * Creates a {@code SubStream} of the given {@code pStream}. - * - * @param pStream the underlying input stream - * @param pLength maximum number of bytes to read drom this stream - */ - public SubStream(final InputStream pStream, final long pLength) { - super(Validate.notNull(pStream, "stream")); - bytesLeft = pLength; - } - - /** - * Marks this stream as closed. - * This implementation does not close the underlying stream. - */ - @Override - public void close() throws IOException { - // NOTE: Do not close the underlying stream - while (bytesLeft > 0) { - //noinspection ResultOfMethodCallIgnored - skip(bytesLeft); - } - } - - @Override - public int available() throws IOException { - return (int) Math.min(super.available(), bytesLeft); - } - - @Override - public void mark(int pReadLimit) { - super.mark(pReadLimit);// This either succeeds or does nothing... - markLimit = pReadLimit; - } - - @Override - public void reset() throws IOException { - super.reset();// This either succeeds or throws IOException - bytesLeft += markLimit; - } - - @Override - public int read() throws IOException { - if (bytesLeft-- <= 0) { - return -1; - } - return super.read(); - } - - @Override - public final int read(byte[] pBytes) throws IOException { - return read(pBytes, 0, pBytes.length); - } - - @Override - public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (bytesLeft <= 0) { - return -1; - } - - int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength)); - bytesLeft = read < 0 ? 0 : bytesLeft - read; - return read; - } - - /** - * Finds the maximum number of bytes we can read or skip, from this stream. - * - * @param pLength the requested length - * @return the maximum number of bytes to read - */ - private long findMaxLen(long pLength) { - if (bytesLeft < pLength) { - return (int) Math.max(bytesLeft, 0); - } - else { - return pLength; - } - } - - @Override - public long skip(long pLength) throws IOException { - long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1 - bytesLeft -= skipped; - return skipped; - } -} +/* + * 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.io; + +import com.twelvemonkeys.lang.Validate; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@code InputStream} reading up to a specified number of bytes from an + * underlying stream. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $ + */ +public final class SubStream extends FilterInputStream { + private long bytesLeft; + private int markLimit; + + /** + * Creates a {@code SubStream} of the given {@code pStream}. + * + * @param pStream the underlying input stream + * @param pLength maximum number of bytes to read drom this stream + */ + public SubStream(final InputStream pStream, final long pLength) { + super(Validate.notNull(pStream, "stream")); + bytesLeft = pLength; + } + + /** + * Marks this stream as closed. + * This implementation does not close the underlying stream. + */ + @Override + public void close() throws IOException { + // NOTE: Do not close the underlying stream + while (bytesLeft > 0) { + //noinspection ResultOfMethodCallIgnored + skip(bytesLeft); + } + } + + @Override + public int available() throws IOException { + return (int) Math.min(super.available(), bytesLeft); + } + + @Override + public void mark(int pReadLimit) { + super.mark(pReadLimit);// This either succeeds or does nothing... + markLimit = pReadLimit; + } + + @Override + public void reset() throws IOException { + super.reset();// This either succeeds or throws IOException + bytesLeft += markLimit; + } + + @Override + public int read() throws IOException { + if (bytesLeft-- <= 0) { + return -1; + } + return super.read(); + } + + @Override + public final int read(byte[] pBytes) throws IOException { + return read(pBytes, 0, pBytes.length); + } + + @Override + public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + if (bytesLeft <= 0) { + return -1; + } + + int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength)); + bytesLeft = read < 0 ? 0 : bytesLeft - read; + return read; + } + + /** + * Finds the maximum number of bytes we can read or skip, from this stream. + * + * @param pLength the requested length + * @return the maximum number of bytes to read + */ + private long findMaxLen(long pLength) { + if (bytesLeft < pLength) { + return (int) Math.max(bytesLeft, 0); + } + else { + return pLength; + } + } + + @Override + public long skip(long pLength) throws IOException { + long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1 + bytesLeft -= skipped; + return skipped; + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java b/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java index 3675fee5..55caa5e9 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java @@ -1,105 +1,106 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.util.StringTokenIterator; - -import java.io.File; -import java.io.IOException; -import java.io.BufferedReader; - -/** - * UnixFileSystem - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java#1 $ - */ -final class UnixFileSystem extends FileSystem { - long getFreeSpace(File pPath) { - try { - return getNumber(pPath, 3); - } - catch (IOException e) { - return 0l; - } - } - - long getTotalSpace(File pPath) { - try { - return getNumber(pPath, 5); - } - catch (IOException e) { - return 0l; - } - } - - private long getNumber(File pPath, int pIndex) throws IOException { - // TODO: Test on other platforms - // Tested on Mac OS X, CygWin - BufferedReader reader = exec(new String[] {"df", "-k", pPath.getAbsolutePath()}); - - String last = null; - String line; - try { - while ((line = reader.readLine()) != null) { - last = line; - } - } - finally { - FileUtil.close(reader); - } - - if (last != null) { - String blocks = null; - StringTokenIterator tokens = new StringTokenIterator(last, " ", StringTokenIterator.REVERSE); - int count = 0; - // We want the 3rd last token - while (count < pIndex && tokens.hasNext()) { - blocks = tokens.nextToken(); - count++; - } - - if (blocks != null) { - try { - return Long.parseLong(blocks) * 1024L; - } - catch (NumberFormatException ignore) { - // Ignore - } - } - } - - return 0l; - } - - String getName() { - return "Unix"; - } -} +/* + * 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.io; + +import com.twelvemonkeys.util.StringTokenIterator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; + +/** + * UnixFileSystem + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java#1 $ + */ +final class UnixFileSystem extends FileSystem { + long getFreeSpace(File pPath) { + try { + return getNumber(pPath, 3); + } + catch (IOException e) { + return 0l; + } + } + + long getTotalSpace(File pPath) { + try { + return getNumber(pPath, 5); + } + catch (IOException e) { + return 0l; + } + } + + private long getNumber(File pPath, int pIndex) throws IOException { + // TODO: Test on other platforms + // Tested on Mac OS X, CygWin + BufferedReader reader = exec(new String[] {"df", "-k", pPath.getAbsolutePath()}); + + String last = null; + String line; + try { + while ((line = reader.readLine()) != null) { + last = line; + } + } + finally { + FileUtil.close(reader); + } + + if (last != null) { + String blocks = null; + StringTokenIterator tokens = new StringTokenIterator(last, " ", StringTokenIterator.REVERSE); + int count = 0; + // We want the 3rd last token + while (count < pIndex && tokens.hasNext()) { + blocks = tokens.nextToken(); + count++; + } + + if (blocks != null) { + try { + return Long.parseLong(blocks) * 1024L; + } + catch (NumberFormatException ignore) { + // Ignore + } + } + } + + return 0l; + } + + String getName() { + return "Unix"; + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java index 872ad480..fa6c82ef 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java @@ -1,190 +1,194 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.*; - -/** - * Win32File - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32File.java#2 $ - */ -final class Win32File extends File { - private final static boolean IS_WINDOWS = isWindows(); - - private static boolean isWindows() { - try { - String os = System.getProperty("os.name"); - return os.toLowerCase().indexOf("windows") >= 0; - } - catch (Throwable t) { - // Ignore - } - return false; - } - - private Win32File(File pPath) { - super(pPath.getPath()); - } - - public static void main(String[] pArgs) { - int argIdx = 0; - boolean recursive = false; - while (pArgs.length > argIdx + 1 && pArgs[argIdx].charAt(0) == '-' && pArgs[argIdx].length() > 1) { - if (pArgs[argIdx].charAt(1) == 'R' || pArgs[argIdx].equals("--recursive")) { - recursive = true; - } - else { - System.err.println("Unknown option: " + pArgs[argIdx]); - } - argIdx++; - } - - File file = wrap(new File(pArgs[argIdx])); - System.out.println("file: " + file); - System.out.println("file.getClass(): " + file.getClass()); - - listFiles(file, 0, recursive); - } - - private static void listFiles(File pFile, int pLevel, boolean pRecursive) { - if (pFile.isDirectory()) { - File[] files = pFile.listFiles(); - for (int l = 0; l < pLevel; l++) { - System.out.print(" "); - } - System.out.println("Contents of " + pFile + ": "); - for (File file : files) { - for (int l = 0; l < pLevel; l++) { - System.out.print(" "); - } - System.out.println(" " + file); - if (pRecursive) { - listFiles(file, pLevel + 1, pLevel < 4); - } - } - } - } - - /** - * Wraps a {@code File} object pointing to a Windows symbolic link - * ({@code .lnk} file) in a {@code Win32Lnk}. - * If the operating system is not Windows, the - * {@code pPath} parameter is returned unwrapped. - * - * @param pPath any path, possibly pointing to a Windows symbolic link file. - * May be {@code null}, in which case {@code null} is returned. - * - * @return a new {@code Win32Lnk} object if the current os is Windows, and - * the file is a Windows symbolic link ({@code .lnk} file), otherwise - * {@code pPath} - */ - public static File wrap(final File pPath) { - if (pPath == null) { - return null; - } - - if (IS_WINDOWS) { - // Don't wrap if allready wrapped - if (pPath instanceof Win32File || pPath instanceof Win32Lnk) { - return pPath; - } - - if (pPath.exists() && pPath.getName().endsWith(".lnk")) { - // If Win32 .lnk, let's wrap - try { - return new Win32Lnk(pPath); - } - catch (IOException e) { - // TODO: FixMe! - e.printStackTrace(); - } - } - - // Wwrap even if not a .lnk, as the listFiles() methods etc, - // could potentially return .lnk's, that we want to wrap later... - return new Win32File(pPath); - } - - return pPath; - } - - /** - * Wraps a {@code File} array, possibly pointing to Windows symbolic links - * ({@code .lnk} files) in {@code Win32Lnk}s. - * - * @param pPaths an array of {@code File}s, possibly pointing to Windows - * symbolic link files. - * May be {@code null}, in which case {@code null} is returned. - * - * @return {@code pPaths}, with any {@code File} representing a Windows - * symbolic link ({@code .lnk} file) wrapped in a {@code Win32Lnk}. - */ - public static File[] wrap(File[] pPaths) { - if (IS_WINDOWS) { - for (int i = 0; pPaths != null && i < pPaths.length; i++) { - pPaths[i] = wrap(pPaths[i]); - } - } - return pPaths; - } - - // File overrides - @Override - public File getAbsoluteFile() { - return wrap(super.getAbsoluteFile()); - } - - @Override - public File getCanonicalFile() throws IOException { - return wrap(super.getCanonicalFile()); - } - - @Override - public File getParentFile() { - return wrap(super.getParentFile()); - } - - @Override - public File[] listFiles() { - return wrap(super.listFiles()); - } - - @Override - public File[] listFiles(FileFilter filter) { - return wrap(super.listFiles(filter)); - } - - @Override - public File[] listFiles(FilenameFilter filter) { - return wrap(super.listFiles(filter)); - } -} +/* + * 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.io; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; + +/** + * Win32File + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32File.java#2 $ + */ +final class Win32File extends File { + private final static boolean IS_WINDOWS = isWindows(); + + private static boolean isWindows() { + try { + String os = System.getProperty("os.name"); + return os.toLowerCase().indexOf("windows") >= 0; + } + catch (Throwable t) { + // Ignore + } + return false; + } + + private Win32File(File pPath) { + super(pPath.getPath()); + } + + public static void main(String[] pArgs) { + int argIdx = 0; + boolean recursive = false; + while (pArgs.length > argIdx + 1 && pArgs[argIdx].charAt(0) == '-' && pArgs[argIdx].length() > 1) { + if (pArgs[argIdx].charAt(1) == 'R' || pArgs[argIdx].equals("--recursive")) { + recursive = true; + } + else { + System.err.println("Unknown option: " + pArgs[argIdx]); + } + argIdx++; + } + + File file = wrap(new File(pArgs[argIdx])); + System.out.println("file: " + file); + System.out.println("file.getClass(): " + file.getClass()); + + listFiles(file, 0, recursive); + } + + private static void listFiles(File pFile, int pLevel, boolean pRecursive) { + if (pFile.isDirectory()) { + File[] files = pFile.listFiles(); + for (int l = 0; l < pLevel; l++) { + System.out.print(" "); + } + System.out.println("Contents of " + pFile + ": "); + for (File file : files) { + for (int l = 0; l < pLevel; l++) { + System.out.print(" "); + } + System.out.println(" " + file); + if (pRecursive) { + listFiles(file, pLevel + 1, pLevel < 4); + } + } + } + } + + /** + * Wraps a {@code File} object pointing to a Windows symbolic link + * ({@code .lnk} file) in a {@code Win32Lnk}. + * If the operating system is not Windows, the + * {@code pPath} parameter is returned unwrapped. + * + * @param pPath any path, possibly pointing to a Windows symbolic link file. + * May be {@code null}, in which case {@code null} is returned. + * + * @return a new {@code Win32Lnk} object if the current os is Windows, and + * the file is a Windows symbolic link ({@code .lnk} file), otherwise + * {@code pPath} + */ + public static File wrap(final File pPath) { + if (pPath == null) { + return null; + } + + if (IS_WINDOWS) { + // Don't wrap if allready wrapped + if (pPath instanceof Win32File || pPath instanceof Win32Lnk) { + return pPath; + } + + if (pPath.exists() && pPath.getName().endsWith(".lnk")) { + // If Win32 .lnk, let's wrap + try { + return new Win32Lnk(pPath); + } + catch (IOException e) { + // TODO: FixMe! + e.printStackTrace(); + } + } + + // Wwrap even if not a .lnk, as the listFiles() methods etc, + // could potentially return .lnk's, that we want to wrap later... + return new Win32File(pPath); + } + + return pPath; + } + + /** + * Wraps a {@code File} array, possibly pointing to Windows symbolic links + * ({@code .lnk} files) in {@code Win32Lnk}s. + * + * @param pPaths an array of {@code File}s, possibly pointing to Windows + * symbolic link files. + * May be {@code null}, in which case {@code null} is returned. + * + * @return {@code pPaths}, with any {@code File} representing a Windows + * symbolic link ({@code .lnk} file) wrapped in a {@code Win32Lnk}. + */ + public static File[] wrap(File[] pPaths) { + if (IS_WINDOWS) { + for (int i = 0; pPaths != null && i < pPaths.length; i++) { + pPaths[i] = wrap(pPaths[i]); + } + } + return pPaths; + } + + // File overrides + @Override + public File getAbsoluteFile() { + return wrap(super.getAbsoluteFile()); + } + + @Override + public File getCanonicalFile() throws IOException { + return wrap(super.getCanonicalFile()); + } + + @Override + public File getParentFile() { + return wrap(super.getParentFile()); + } + + @Override + public File[] listFiles() { + return wrap(super.listFiles()); + } + + @Override + public File[] listFiles(FileFilter filter) { + return wrap(super.listFiles(filter)); + } + + @Override + public File[] listFiles(FilenameFilter filter) { + return wrap(super.listFiles(filter)); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java index 8e8ff5a9..ae3d3608 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java @@ -1,90 +1,91 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.File; -import java.io.BufferedReader; -import java.io.IOException; - -/** - * WindowsFileSystem - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java#2 $ - */ -final class Win32FileSystem extends FileSystem { - public long getFreeSpace(File pPath) { - try { - // Windows version - // TODO: Test on W2K/95/98/etc... (tested on XP) - BufferedReader reader = exec(new String[] {"CMD.EXE", "/C", "DIR", "/-C", pPath.getAbsolutePath()}); - - String last = null; - String line; - try { - while ((line = reader.readLine()) != null) { - last = line; - } - } - finally { - FileUtil.close(reader); - } - - if (last != null) { - int end = last.lastIndexOf(" bytes free"); - int start = last.lastIndexOf(' ', end - 1); - - if (start >= 0 && end >= 0) { - try { - return Long.parseLong(last.substring(start + 1, end)); - } - catch (NumberFormatException ignore) { - // Ignore - } - } - } - } - catch (IOException ignore) { - // Ignore - } - - return 0l; - } - - long getTotalSpace(File pPath) { - // TODO: Implement, probably need some JNI stuff... - // Distribute df.exe and execute from temp!? ;-) - return getFreeSpace(pPath); - } - - String getName() { - return "Win32"; - } -} +/* + * 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.io; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; + +/** + * WindowsFileSystem + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java#2 $ + */ +final class Win32FileSystem extends FileSystem { + public long getFreeSpace(File pPath) { + try { + // Windows version + // TODO: Test on W2K/95/98/etc... (tested on XP) + BufferedReader reader = exec(new String[] {"CMD.EXE", "/C", "DIR", "/-C", pPath.getAbsolutePath()}); + + String last = null; + String line; + try { + while ((line = reader.readLine()) != null) { + last = line; + } + } + finally { + FileUtil.close(reader); + } + + if (last != null) { + int end = last.lastIndexOf(" bytes free"); + int start = last.lastIndexOf(' ', end - 1); + + if (start >= 0 && end >= 0) { + try { + return Long.parseLong(last.substring(start + 1, end)); + } + catch (NumberFormatException ignore) { + // Ignore + } + } + } + } + catch (IOException ignore) { + // Ignore + } + + return 0l; + } + + long getTotalSpace(File pPath) { + // TODO: Implement, probably need some JNI stuff... + // Distribute df.exe and execute from temp!? ;-) + return getFreeSpace(pPath); + } + + String getName() { + return "Win32"; + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java index 738243e1..34eb0be6 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java @@ -1,473 +1,477 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import java.io.*; -import java.util.Arrays; - -/** - * A {@code File} implementation that resolves the Windows {@code .lnk} files as symbolic links. - *

- * This class is based on example code from - * Swing Hacks, - * By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30. - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32Lnk.java#2 $ - */ -final class Win32Lnk extends File { - private final static byte[] LNK_MAGIC = { - 'L', 0x00, 0x00, 0x00, // Magic - }; - private final static byte[] LNK_GUID = { - 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Shell Link GUID - (byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F' - }; - - private final File target; - - private static final int FLAG_ITEM_ID_LIST = 0x01; - private static final int FLAG_FILE_LOC_INFO = 0x02; - private static final int FLAG_DESC_STRING = 0x04; - private static final int FLAG_REL_PATH_STRING = 0x08; - private static final int FLAG_WORKING_DIRECTORY = 0x10; - private static final int FLAG_COMMAND_LINE_ARGS = 0x20; - private static final int FLAG_ICON_FILENAME = 0x40; - private static final int FLAG_ADDITIONAL_INFO = 0x80; - - private Win32Lnk(final String pPath) throws IOException { - super(pPath); - File target = parse(this); - if (target == this) { - // NOTE: This is a workaround - // target = this causes infinite loops in some methods - target = new File(pPath); - } - this.target = target; - } - - Win32Lnk(final File pPath) throws IOException { - this(pPath.getPath()); - } - - /** - * Parses a {@code .lnk} file to find the real file. - * - * @param pPath the path to the {@code .lnk} file - * @return a new file object that - * @throws java.io.IOException if the {@code .lnk} cannot be parsed - */ - static File parse(final File pPath) throws IOException { - if (!pPath.getName().endsWith(".lnk")) { - return pPath; - } - - File result = pPath; - - LittleEndianDataInputStream in = new LittleEndianDataInputStream(new BufferedInputStream(new FileInputStream(pPath))); - try { - byte[] magic = new byte[4]; - in.readFully(magic); - - byte[] guid = new byte[16]; - in.readFully(guid); - - if (!(Arrays.equals(LNK_MAGIC, magic) && Arrays.equals(LNK_GUID, guid))) { - //System.out.println("Not a symlink"); - // Not a symlink - return pPath; - } - - // Get the flags - int flags = in.readInt(); - //System.out.println("flags: " + Integer.toBinaryString(flags & 0xff)); - - // Get to the file settings - /*int attributes = */in.readInt(); - - // File attributes - // 0 Target is read only. - // 1 Target is hidden. - // 2 Target is a system file. - // 3 Target is a volume label. (Not possible) - // 4 Target is a directory. - // 5 Target has been modified since last backup. (archive) - // 6 Target is encrypted (NTFS EFS) - // 7 Target is Normal?? - // 8 Target is temporary. - // 9 Target is a sparse file. - // 10 Target has reparse point data. - // 11 Target is compressed. - // 12 Target is offline. - //System.out.println("attributes: " + Integer.toBinaryString(attributes)); - // NOTE: Cygwin .lnks are not directory links, can't rely on this.. :-/ - - in.skipBytes(48); // TODO: Make sense of this data... - - // Skipped data: - // long time 1 (creation) - // long time 2 (modification) - // long time 3 (last access) - // int file length - // int icon number - // int ShowVnd value - // int hotkey - // int, int - unknown: 0,0 - - // If the shell settings are present, skip them - if ((flags & FLAG_ITEM_ID_LIST) != 0) { - // Shell Item Id List present - //System.out.println("Shell Item Id List present"); - int shellLen = in.readShort(); // Short - //System.out.println("shellLen: " + shellLen); - - // TODO: Probably need to parse this data, to determine - // Cygwin folders... - - /* - int read = 2; - int itemLen = in.readShort(); - while (itemLen > 0) { - System.out.println("--> ITEM: " + itemLen); - - BufferedReader reader = new BufferedReader(new InputStreamReader(new SubStream(in, itemLen - 2))); - //byte[] itemBytes = new byte[itemLen - 2]; // NOTE: Lenght included - //in.readFully(itemBytes); - - String item = reader.readLine(); - System.out.println("item: \"" + item + "\""); - - itemLen = in.readShort(); - read += itemLen; - } - - System.out.println("read: " + read); - */ - - in.skipBytes(shellLen); - } - - if ((flags & FLAG_FILE_LOC_INFO) != 0) { - // File Location Info Table present - //System.out.println("File Location Info Table present"); - - // 0h 1 dword This is the total length of this structure and all following data - // 4h 1 dword This is a pointer to first offset after this structure. 1Ch - // 8h 1 dword Flags - // Ch 1 dword Offset of local volume info - // 10h 1 dword Offset of base pathname on local system - // 14h 1 dword Offset of network volume info - // 18h 1 dword Offset of remaining pathname - - // Flags: - // Bit Meaning - // 0 Available on a local volume - // 1 Available on a network share - // TODO: Make sure the path is on a local disk, etc.. - - int tableLen = in.readInt(); // Int - //System.out.println("tableLen: " + tableLen); - - in.readInt(); // Skip - - int locFlags = in.readInt(); - //System.out.println("locFlags: " + Integer.toBinaryString(locFlags)); - if ((locFlags & 0x01) != 0) { - //System.out.println("Available local"); - } - if ((locFlags & 0x02) != 0) { - //System.err.println("Available on network path"); - } - - // Get the local volume and local system values - in.skipBytes(4); // TODO: see above for structure - - int localSysOff = in.readInt(); - //System.out.println("localSysOff: " + localSysOff); - in.skipBytes(localSysOff - 20); // Relative to start of chunk - - byte[] pathBytes = new byte[tableLen - localSysOff - 1]; - in.readFully(pathBytes, 0, pathBytes.length); - String path = new String(pathBytes, 0, pathBytes.length - 1); - /* - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte read; - // Read bytes until the null (0) character - while (true) { - read = in.readByte(); - if (read == 0) { - break; - } - bytes.write(read & 0xff); - } - - String path = new String(bytes.toByteArray(), 0, bytes.size()); - //*/ - - // Recurse to end of link chain - // TODO: This may cause endless loop if cyclic chain... - //System.out.println("path: \"" + path + "\""); - try { - result = parse(new File(path)); - } - catch (StackOverflowError e) { - throw new IOException("Cannot resolve cyclic link: " + e.getMessage()); - } - } - - if ((flags & FLAG_DESC_STRING) != 0) { - // Description String present, skip it. - //System.out.println("Description String present"); - - // The string length is the first word which must also be skipped. - int descLen = in.readShort(); - //System.out.println("descLen: " + descLen); - - byte[] descBytes = new byte[descLen]; - in.readFully(descBytes, 0, descLen); - - //String desc = new String(descBytes, 0, descLen); - //System.out.println("desc: " + desc); - } - - if ((flags & FLAG_REL_PATH_STRING) != 0) { - // Relative Path String present - //System.out.println("Relative Path String present"); - - // The string length is the first word which must also be skipped. - int pathLen = in.readShort(); - //System.out.println("pathLen: " + pathLen); - - byte[] pathBytes = new byte[pathLen]; - in.readFully(pathBytes, 0, pathLen); - - String path = new String(pathBytes, 0, pathLen); - - // TODO: This may cause endless loop if cyclic chain... - //System.out.println("path: \"" + path + "\""); - if (result == pPath) { - try { - result = parse(new File(pPath.getParentFile(), path)); - } - catch (StackOverflowError e) { - throw new IOException("Cannot resolve cyclic link: " + e.getMessage()); - } - } - } - - if ((flags & FLAG_WORKING_DIRECTORY) != 0) { - //System.out.println("Working Directory present"); - } - if ((flags & FLAG_COMMAND_LINE_ARGS) != 0) { - //System.out.println("Command Line Arguments present"); - // NOTE: This means this .lnk is not a folder, don't follow - result = pPath; - } - if ((flags & FLAG_ICON_FILENAME) != 0) { - //System.out.println("Icon Filename present"); - } - if ((flags & FLAG_ADDITIONAL_INFO) != 0) { - //System.out.println("Additional Info present"); - } - } - finally { - in.close(); - } - - return result; - } - - /* - private static String getNullDelimitedString(byte[] bytes, int off) { - int len = 0; - // Count bytes until the null (0) character - while (true) { - if (bytes[off + len] == 0) { - break; - } - len++; - } - - System.err.println("--> " + len); - - return new String(bytes, off, len); - } - */ - - /** - * Converts two bytes into a short. - *

- * NOTE: this is little endian because it's for an - * Intel only OS - * - * @ param bytes - * @ param off - * @return the bytes as a short. - */ - /* - private static int bytes2short(byte[] bytes, int off) { - return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); - } - */ - - public File getTarget() { - return target; - } - - // java.io.File overrides below - - @Override - public boolean isDirectory() { - return target.isDirectory(); - } - - @Override - public boolean canRead() { - return target.canRead(); - } - - @Override - public boolean canWrite() { - return target.canWrite(); - } - - // NOTE: equals is implemented using compareto == 0 - /* - public int compareTo(File pathname) { - // TODO: Verify this - // Probably not a good idea, as it IS NOT THE SAME file - // It's probably better to not override - return target.compareTo(pathname); - } - */ - - // Should probably never allow creating a new .lnk - // public boolean createNewFile() throws IOException - - // Deletes only the .lnk - // public boolean delete() { - //public void deleteOnExit() { - - @Override - public boolean exists() { - return target.exists(); - } - - // A .lnk may be absolute - //public File getAbsoluteFile() { - //public String getAbsolutePath() { - - // Theses should be resolved according to the API (for Unix). - @Override - public File getCanonicalFile() throws IOException { - return target.getCanonicalFile(); - } - - @Override - public String getCanonicalPath() throws IOException { - return target.getCanonicalPath(); - } - - //public String getName() { - - // I guess the parent should be the parent of the .lnk, not the target - //public String getParent() { - //public File getParentFile() { - - // public boolean isAbsolute() { - @Override - public boolean isFile() { - return target.isFile(); - } - - @Override - public boolean isHidden() { - return target.isHidden(); - } - - @Override - public long lastModified() { - return target.lastModified(); - } - - @Override - public long length() { - return target.length(); - } - - @Override - public String[] list() { - return target.list(); - } - - @Override - public String[] list(final FilenameFilter filter) { - return target.list(filter); - } - - @Override - public File[] listFiles() { - return Win32File.wrap(target.listFiles()); - } - - @Override - public File[] listFiles(final FileFilter filter) { - return Win32File.wrap(target.listFiles(filter)); - } - - @Override - public File[] listFiles(final FilenameFilter filter) { - return Win32File.wrap(target.listFiles(filter)); - } - - // Makes no sense, does it? - //public boolean mkdir() { - //public boolean mkdirs() { - - // Only rename the lnk - //public boolean renameTo(File dest) { - - @Override - public boolean setLastModified(long time) { - return target.setLastModified(time); - } - - @Override - public boolean setReadOnly() { - return target.setReadOnly(); - } - - @Override - public String toString() { - if (target.equals(this)) { - return super.toString(); - } - return super.toString() + " -> " + target.toString(); - } -} +/* + * 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.io; + +import java.io.*; +import java.util.Arrays; + +/** + * A {@code File} implementation that resolves the Windows {@code .lnk} files as symbolic links. + *

+ * This class is based on example code from + * Swing Hacks, + * By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30. + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32Lnk.java#2 $ + */ +final class Win32Lnk extends File { + private final static byte[] LNK_MAGIC = { + 'L', 0x00, 0x00, 0x00, // Magic + }; + private final static byte[] LNK_GUID = { + 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Shell Link GUID + (byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F' + }; + + private final File target; + + private static final int FLAG_ITEM_ID_LIST = 0x01; + private static final int FLAG_FILE_LOC_INFO = 0x02; + private static final int FLAG_DESC_STRING = 0x04; + private static final int FLAG_REL_PATH_STRING = 0x08; + private static final int FLAG_WORKING_DIRECTORY = 0x10; + private static final int FLAG_COMMAND_LINE_ARGS = 0x20; + private static final int FLAG_ICON_FILENAME = 0x40; + private static final int FLAG_ADDITIONAL_INFO = 0x80; + + private Win32Lnk(final String pPath) throws IOException { + super(pPath); + File target = parse(this); + if (target == this) { + // NOTE: This is a workaround + // target = this causes infinite loops in some methods + target = new File(pPath); + } + this.target = target; + } + + Win32Lnk(final File pPath) throws IOException { + this(pPath.getPath()); + } + + /** + * Parses a {@code .lnk} file to find the real file. + * + * @param pPath the path to the {@code .lnk} file + * @return a new file object that + * @throws java.io.IOException if the {@code .lnk} cannot be parsed + */ + static File parse(final File pPath) throws IOException { + if (!pPath.getName().endsWith(".lnk")) { + return pPath; + } + + File result = pPath; + + LittleEndianDataInputStream in = new LittleEndianDataInputStream(new BufferedInputStream(new FileInputStream(pPath))); + try { + byte[] magic = new byte[4]; + in.readFully(magic); + + byte[] guid = new byte[16]; + in.readFully(guid); + + if (!(Arrays.equals(LNK_MAGIC, magic) && Arrays.equals(LNK_GUID, guid))) { + //System.out.println("Not a symlink"); + // Not a symlink + return pPath; + } + + // Get the flags + int flags = in.readInt(); + //System.out.println("flags: " + Integer.toBinaryString(flags & 0xff)); + + // Get to the file settings + /*int attributes = */in.readInt(); + + // File attributes + // 0 Target is read only. + // 1 Target is hidden. + // 2 Target is a system file. + // 3 Target is a volume label. (Not possible) + // 4 Target is a directory. + // 5 Target has been modified since last backup. (archive) + // 6 Target is encrypted (NTFS EFS) + // 7 Target is Normal?? + // 8 Target is temporary. + // 9 Target is a sparse file. + // 10 Target has reparse point data. + // 11 Target is compressed. + // 12 Target is offline. + //System.out.println("attributes: " + Integer.toBinaryString(attributes)); + // NOTE: Cygwin .lnks are not directory links, can't rely on this.. :-/ + + in.skipBytes(48); // TODO: Make sense of this data... + + // Skipped data: + // long time 1 (creation) + // long time 2 (modification) + // long time 3 (last access) + // int file length + // int icon number + // int ShowVnd value + // int hotkey + // int, int - unknown: 0,0 + + // If the shell settings are present, skip them + if ((flags & FLAG_ITEM_ID_LIST) != 0) { + // Shell Item Id List present + //System.out.println("Shell Item Id List present"); + int shellLen = in.readShort(); // Short + //System.out.println("shellLen: " + shellLen); + + // TODO: Probably need to parse this data, to determine + // Cygwin folders... + + /* + int read = 2; + int itemLen = in.readShort(); + while (itemLen > 0) { + System.out.println("--> ITEM: " + itemLen); + + BufferedReader reader = new BufferedReader(new InputStreamReader(new SubStream(in, itemLen - 2))); + //byte[] itemBytes = new byte[itemLen - 2]; // NOTE: Lenght included + //in.readFully(itemBytes); + + String item = reader.readLine(); + System.out.println("item: \"" + item + "\""); + + itemLen = in.readShort(); + read += itemLen; + } + + System.out.println("read: " + read); + */ + + in.skipBytes(shellLen); + } + + if ((flags & FLAG_FILE_LOC_INFO) != 0) { + // File Location Info Table present + //System.out.println("File Location Info Table present"); + + // 0h 1 dword This is the total length of this structure and all following data + // 4h 1 dword This is a pointer to first offset after this structure. 1Ch + // 8h 1 dword Flags + // Ch 1 dword Offset of local volume info + // 10h 1 dword Offset of base pathname on local system + // 14h 1 dword Offset of network volume info + // 18h 1 dword Offset of remaining pathname + + // Flags: + // Bit Meaning + // 0 Available on a local volume + // 1 Available on a network share + // TODO: Make sure the path is on a local disk, etc.. + + int tableLen = in.readInt(); // Int + //System.out.println("tableLen: " + tableLen); + + in.readInt(); // Skip + + int locFlags = in.readInt(); + //System.out.println("locFlags: " + Integer.toBinaryString(locFlags)); + if ((locFlags & 0x01) != 0) { + //System.out.println("Available local"); + } + if ((locFlags & 0x02) != 0) { + //System.err.println("Available on network path"); + } + + // Get the local volume and local system values + in.skipBytes(4); // TODO: see above for structure + + int localSysOff = in.readInt(); + //System.out.println("localSysOff: " + localSysOff); + in.skipBytes(localSysOff - 20); // Relative to start of chunk + + byte[] pathBytes = new byte[tableLen - localSysOff - 1]; + in.readFully(pathBytes, 0, pathBytes.length); + String path = new String(pathBytes, 0, pathBytes.length - 1); + /* + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte read; + // Read bytes until the null (0) character + while (true) { + read = in.readByte(); + if (read == 0) { + break; + } + bytes.write(read & 0xff); + } + + String path = new String(bytes.toByteArray(), 0, bytes.size()); + //*/ + + // Recurse to end of link chain + // TODO: This may cause endless loop if cyclic chain... + //System.out.println("path: \"" + path + "\""); + try { + result = parse(new File(path)); + } + catch (StackOverflowError e) { + throw new IOException("Cannot resolve cyclic link: " + e.getMessage()); + } + } + + if ((flags & FLAG_DESC_STRING) != 0) { + // Description String present, skip it. + //System.out.println("Description String present"); + + // The string length is the first word which must also be skipped. + int descLen = in.readShort(); + //System.out.println("descLen: " + descLen); + + byte[] descBytes = new byte[descLen]; + in.readFully(descBytes, 0, descLen); + + //String desc = new String(descBytes, 0, descLen); + //System.out.println("desc: " + desc); + } + + if ((flags & FLAG_REL_PATH_STRING) != 0) { + // Relative Path String present + //System.out.println("Relative Path String present"); + + // The string length is the first word which must also be skipped. + int pathLen = in.readShort(); + //System.out.println("pathLen: " + pathLen); + + byte[] pathBytes = new byte[pathLen]; + in.readFully(pathBytes, 0, pathLen); + + String path = new String(pathBytes, 0, pathLen); + + // TODO: This may cause endless loop if cyclic chain... + //System.out.println("path: \"" + path + "\""); + if (result == pPath) { + try { + result = parse(new File(pPath.getParentFile(), path)); + } + catch (StackOverflowError e) { + throw new IOException("Cannot resolve cyclic link: " + e.getMessage()); + } + } + } + + if ((flags & FLAG_WORKING_DIRECTORY) != 0) { + //System.out.println("Working Directory present"); + } + if ((flags & FLAG_COMMAND_LINE_ARGS) != 0) { + //System.out.println("Command Line Arguments present"); + // NOTE: This means this .lnk is not a folder, don't follow + result = pPath; + } + if ((flags & FLAG_ICON_FILENAME) != 0) { + //System.out.println("Icon Filename present"); + } + if ((flags & FLAG_ADDITIONAL_INFO) != 0) { + //System.out.println("Additional Info present"); + } + } + finally { + in.close(); + } + + return result; + } + + /* + private static String getNullDelimitedString(byte[] bytes, int off) { + int len = 0; + // Count bytes until the null (0) character + while (true) { + if (bytes[off + len] == 0) { + break; + } + len++; + } + + System.err.println("--> " + len); + + return new String(bytes, off, len); + } + */ + + /** + * Converts two bytes into a short. + *

+ * NOTE: this is little endian because it's for an + * Intel only OS + *

+ * + * @ param bytes + * @ param off + * @return the bytes as a short. + */ + /* + private static int bytes2short(byte[] bytes, int off) { + return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); + } + */ + + public File getTarget() { + return target; + } + + // java.io.File overrides below + + @Override + public boolean isDirectory() { + return target.isDirectory(); + } + + @Override + public boolean canRead() { + return target.canRead(); + } + + @Override + public boolean canWrite() { + return target.canWrite(); + } + + // NOTE: equals is implemented using compareto == 0 + /* + public int compareTo(File pathname) { + // TODO: Verify this + // Probably not a good idea, as it IS NOT THE SAME file + // It's probably better to not override + return target.compareTo(pathname); + } + */ + + // Should probably never allow creating a new .lnk + // public boolean createNewFile() throws IOException + + // Deletes only the .lnk + // public boolean delete() { + //public void deleteOnExit() { + + @Override + public boolean exists() { + return target.exists(); + } + + // A .lnk may be absolute + //public File getAbsoluteFile() { + //public String getAbsolutePath() { + + // Theses should be resolved according to the API (for Unix). + @Override + public File getCanonicalFile() throws IOException { + return target.getCanonicalFile(); + } + + @Override + public String getCanonicalPath() throws IOException { + return target.getCanonicalPath(); + } + + //public String getName() { + + // I guess the parent should be the parent of the .lnk, not the target + //public String getParent() { + //public File getParentFile() { + + // public boolean isAbsolute() { + @Override + public boolean isFile() { + return target.isFile(); + } + + @Override + public boolean isHidden() { + return target.isHidden(); + } + + @Override + public long lastModified() { + return target.lastModified(); + } + + @Override + public long length() { + return target.length(); + } + + @Override + public String[] list() { + return target.list(); + } + + @Override + public String[] list(final FilenameFilter filter) { + return target.list(filter); + } + + @Override + public File[] listFiles() { + return Win32File.wrap(target.listFiles()); + } + + @Override + public File[] listFiles(final FileFilter filter) { + return Win32File.wrap(target.listFiles(filter)); + } + + @Override + public File[] listFiles(final FilenameFilter filter) { + return Win32File.wrap(target.listFiles(filter)); + } + + // Makes no sense, does it? + //public boolean mkdir() { + //public boolean mkdirs() { + + // Only rename the lnk + //public boolean renameTo(File dest) { + + @Override + public boolean setLastModified(long time) { + return target.setLastModified(time); + } + + @Override + public boolean setReadOnly() { + return target.setReadOnly(); + } + + @Override + public String toString() { + if (target.equals(this)) { + return super.toString(); + } + return super.toString() + " -> " + target.toString(); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java index d3c0a1a7..ff0893ad 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java @@ -1,237 +1,240 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io; - -import com.twelvemonkeys.lang.DateUtil; - -import java.io.*; -import java.nio.charset.Charset; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; - -/** - * Wraps a {@code Writer} in an {@code OutputStream}. - *

- * Instances of this class are not thread-safe. - *

- * NOTE: This class is probably not the right way of solving your problem, - * however it might prove useful in JSPs etc. - * If possible, it's always better to use the {@code Writer}'s underlying - * {@code OutputStream}, or wrap it's native backing. - * - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java#2 $ - */ -public class WriterOutputStream extends OutputStream { - protected Writer writer; - final protected Decoder decoder; - final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024); - - private volatile boolean isFlushing = false; // Ugly but critical... - - private static final boolean NIO_AVAILABLE = isNIOAvailable(); - - private static boolean isNIOAvailable() { - try { - Class.forName("java.nio.charset.Charset"); - return true; - } - catch (Throwable t) { - // Ignore - } - - return false; - } - - public WriterOutputStream(final Writer pWriter, final String pCharset) { - writer = pWriter; - decoder = getDecoder(pCharset); - } - - public WriterOutputStream(final Writer pWriter) { - this(pWriter, null); - } - - private static Decoder getDecoder(final String pCharset) { - // NOTE: The CharsetDecoder is typically 10-20% faster than - // StringDecoder according to my tests - // StringEncoder is horribly slow on 1.2 systems, but there's no - // alternative... - if (NIO_AVAILABLE) { - return new CharsetDecoder(pCharset); - } - - return new StringDecoder(pCharset); - } - - @Override - public void close() throws IOException { - flush(); - writer.close(); - writer = null; - } - - @Override - public void flush() throws IOException { - flushBuffer(); - writer.flush(); - } - - @Override - public final void write(byte[] pBytes) throws IOException { - if (pBytes == null) { - throw new NullPointerException("bytes == null"); - } - write(pBytes, 0, pBytes.length); - } - - @Override - public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException { - flushBuffer(); - decoder.decodeTo(writer, pBytes, pOffset, pLength); - } - - @Override - public final void write(int pByte) { - // TODO: Is it possible to know if this is a good place in the stream to - // flush? It might be in the middle of a multi-byte encoded character.. - bufferStream.write(pByte); - } - - private void flushBuffer() throws IOException { - if (!isFlushing && bufferStream.size() > 0) { - isFlushing = true; - bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array - bufferStream.reset(); - isFlushing = false; - } - } - - /////////////////////////////////////////////////////////////////////////// - public static void main(String[] pArgs) throws IOException { - int iterations = 1000000; - - byte[] bytes = "������ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8"); - - Decoder d; - long start; - long time; - Writer sink = new PrintWriter(new NullOutputStream()); - StringWriter writer; - String str; - - d = new StringDecoder("UTF-8"); - for (int i = 0; i < 10000; i++) { - d.decodeTo(sink, bytes, 0, bytes.length); - } - start = System.currentTimeMillis(); - for (int i = 0; i < iterations; i++) { - d.decodeTo(sink, bytes, 0, bytes.length); - } - time = DateUtil.delta(start); - System.out.println("StringDecoder"); - System.out.println("time: " + time); - - writer = new StringWriter(); - d.decodeTo(writer, bytes, 0, bytes.length); - str = writer.toString(); - System.out.println("str: \"" + str + "\""); - System.out.println("chars.length: " + str.length()); - System.out.println(); - - if (NIO_AVAILABLE) { - d = new CharsetDecoder("UTF-8"); - for (int i = 0; i < 10000; i++) { - d.decodeTo(sink, bytes, 0, bytes.length); - } - start = System.currentTimeMillis(); - for (int i = 0; i < iterations; i++) { - d.decodeTo(sink, bytes, 0, bytes.length); - } - time = DateUtil.delta(start); - System.out.println("CharsetDecoder"); - System.out.println("time: " + time); - writer = new StringWriter(); - d.decodeTo(writer, bytes, 0, bytes.length); - str = writer.toString(); - System.out.println("str: \"" + str + "\""); - System.out.println("chars.length: " + str.length()); - System.out.println(); - } - - OutputStream os = new WriterOutputStream(new PrintWriter(System.out), "UTF-8"); - os.write(bytes); - os.flush(); - System.out.println(); - - for (byte b : bytes) { - os.write(b & 0xff); - } - os.flush(); - } - - /////////////////////////////////////////////////////////////////////////// - private static interface Decoder { - void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException; - } - - private static final class CharsetDecoder implements Decoder { - final Charset mCharset; - - CharsetDecoder(String pCharset) { - // Handle null-case, to get default charset - String charset = pCharset != null ? pCharset : - System.getProperty("file.encoding", "ISO-8859-1"); - mCharset = Charset.forName(charset); - } - - public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException { - CharBuffer cb = mCharset.decode(ByteBuffer.wrap(pBytes, pOffset, pLength)); - pWriter.write(cb.array(), 0, cb.length()); - } - } - - private static final class StringDecoder implements Decoder { - final String mCharset; - - StringDecoder(String pCharset) { - mCharset = pCharset; - } - - public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException { - String str = mCharset == null ? - new String(pBytes, pOffset, pLength) : - new String(pBytes, pOffset, pLength, mCharset); - - pWriter.write(str); - } - } +/* + * 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.io; + +import com.twelvemonkeys.lang.DateUtil; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + +/** + * Wraps a {@code Writer} in an {@code OutputStream}. + *

+ * Instances of this class are not thread-safe. + *

+ *

+ * NOTE: This class is probably not the right way of solving your problem, + * however it might prove useful in JSPs etc. + * If possible, it's always better to use the {@code Writer}'s underlying + * {@code OutputStream}, or wrap it's native backing. + * + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java#2 $ + */ +public class WriterOutputStream extends OutputStream { + protected Writer writer; + final protected Decoder decoder; + final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024); + + private volatile boolean isFlushing = false; // Ugly but critical... + + private static final boolean NIO_AVAILABLE = isNIOAvailable(); + + private static boolean isNIOAvailable() { + try { + Class.forName("java.nio.charset.Charset"); + return true; + } + catch (Throwable t) { + // Ignore + } + + return false; + } + + public WriterOutputStream(final Writer pWriter, final String pCharset) { + writer = pWriter; + decoder = getDecoder(pCharset); + } + + public WriterOutputStream(final Writer pWriter) { + this(pWriter, null); + } + + private static Decoder getDecoder(final String pCharset) { + // NOTE: The CharsetDecoder is typically 10-20% faster than + // StringDecoder according to my tests + // StringEncoder is horribly slow on 1.2 systems, but there's no + // alternative... + if (NIO_AVAILABLE) { + return new CharsetDecoder(pCharset); + } + + return new StringDecoder(pCharset); + } + + @Override + public void close() throws IOException { + flush(); + writer.close(); + writer = null; + } + + @Override + public void flush() throws IOException { + flushBuffer(); + writer.flush(); + } + + @Override + public final void write(byte[] pBytes) throws IOException { + if (pBytes == null) { + throw new NullPointerException("bytes == null"); + } + write(pBytes, 0, pBytes.length); + } + + @Override + public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException { + flushBuffer(); + decoder.decodeTo(writer, pBytes, pOffset, pLength); + } + + @Override + public final void write(int pByte) { + // TODO: Is it possible to know if this is a good place in the stream to + // flush? It might be in the middle of a multi-byte encoded character.. + bufferStream.write(pByte); + } + + private void flushBuffer() throws IOException { + if (!isFlushing && bufferStream.size() > 0) { + isFlushing = true; + bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array + bufferStream.reset(); + isFlushing = false; + } + } + + /////////////////////////////////////////////////////////////////////////// + public static void main(String[] pArgs) throws IOException { + int iterations = 1000000; + + byte[] bytes = "������ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8"); + + Decoder d; + long start; + long time; + Writer sink = new PrintWriter(new NullOutputStream()); + StringWriter writer; + String str; + + d = new StringDecoder("UTF-8"); + for (int i = 0; i < 10000; i++) { + d.decodeTo(sink, bytes, 0, bytes.length); + } + start = System.currentTimeMillis(); + for (int i = 0; i < iterations; i++) { + d.decodeTo(sink, bytes, 0, bytes.length); + } + time = DateUtil.delta(start); + System.out.println("StringDecoder"); + System.out.println("time: " + time); + + writer = new StringWriter(); + d.decodeTo(writer, bytes, 0, bytes.length); + str = writer.toString(); + System.out.println("str: \"" + str + "\""); + System.out.println("chars.length: " + str.length()); + System.out.println(); + + if (NIO_AVAILABLE) { + d = new CharsetDecoder("UTF-8"); + for (int i = 0; i < 10000; i++) { + d.decodeTo(sink, bytes, 0, bytes.length); + } + start = System.currentTimeMillis(); + for (int i = 0; i < iterations; i++) { + d.decodeTo(sink, bytes, 0, bytes.length); + } + time = DateUtil.delta(start); + System.out.println("CharsetDecoder"); + System.out.println("time: " + time); + writer = new StringWriter(); + d.decodeTo(writer, bytes, 0, bytes.length); + str = writer.toString(); + System.out.println("str: \"" + str + "\""); + System.out.println("chars.length: " + str.length()); + System.out.println(); + } + + OutputStream os = new WriterOutputStream(new PrintWriter(System.out), "UTF-8"); + os.write(bytes); + os.flush(); + System.out.println(); + + for (byte b : bytes) { + os.write(b & 0xff); + } + os.flush(); + } + + /////////////////////////////////////////////////////////////////////////// + private static interface Decoder { + void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException; + } + + private static final class CharsetDecoder implements Decoder { + final Charset mCharset; + + CharsetDecoder(String pCharset) { + // Handle null-case, to get default charset + String charset = pCharset != null ? pCharset : + System.getProperty("file.encoding", "ISO-8859-1"); + mCharset = Charset.forName(charset); + } + + public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException { + CharBuffer cb = mCharset.decode(ByteBuffer.wrap(pBytes, pOffset, pLength)); + pWriter.write(cb.array(), 0, cb.length()); + } + } + + private static final class StringDecoder implements Decoder { + final String mCharset; + + StringDecoder(String pCharset) { + mCharset = pCharset; + } + + public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException { + String str = mCharset == null ? + new String(pBytes, pOffset, pLength) : + new String(pBytes, pOffset, pLength, mCharset); + + pWriter.write(str); + } + } } \ No newline at end of file diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java index 7e385c3a..2660b2f7 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java @@ -1,186 +1,188 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * {@code Decoder} implementation for standard base64 encoding. - *

- * @see RFC 1421 - * @see - * - * @see Base64Encoder - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java#2 $ - */ -public final class Base64Decoder implements Decoder { - /** - * This array maps the characters to their 6 bit values - */ - final static byte[] PEM_ARRAY = { - //0 1 2 3 4 5 6 7 - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1 - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2 - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3 - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4 - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5 - 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6 - '4', '5', '6', '7', '8', '9', '+', '/' // 7 - }; - - final static byte[] PEM_CONVERT_ARRAY; - - private byte[] decodeBuffer = new byte[4]; - - static { - PEM_CONVERT_ARRAY = new byte[256]; - - for (int i = 0; i < 255; i++) { - PEM_CONVERT_ARRAY[i] = -1; - } - - for (int i = 0; i < PEM_ARRAY.length; i++) { - PEM_CONVERT_ARRAY[PEM_ARRAY[i]] = (byte) i; - } - } - - protected static int readFully(final InputStream pStream, final byte pBytes[], final int pOffset, final int pLength) - throws IOException - { - for (int i = 0; i < pLength; i++) { - int read = pStream.read(); - - if (read == -1) { - return i != 0 ? i : -1; - } - - pBytes[i + pOffset] = (byte) read; - } - - return pLength; - } - - protected boolean decodeAtom(final InputStream pInput, final ByteBuffer pOutput, final int pLength) - throws IOException { - - byte byte0 = -1; - byte byte1 = -1; - byte byte2 = -1; - byte byte3 = -1; - - if (pLength < 2) { - throw new IOException("BASE64Decoder: Not enough bytes for an atom."); - } - - int read; - - // Skip line feeds - do { - read = pInput.read(); - - if (read == -1) { - return false; - } - } while (read == 10 || read == 13); - - decodeBuffer[0] = (byte) read; - read = readFully(pInput, decodeBuffer, 1, pLength - 1); - - if (read == -1) { - return false; - } - - int length = pLength; - - if (length > 3 && decodeBuffer[3] == 61) { - length = 3; - } - - if (length > 2 && decodeBuffer[2] == 61) { - length = 2; - } - - switch (length) { - case 4: - byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255]; - // fall through - case 3: - byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255]; - // fall through - case 2: - byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255]; - byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255]; - // fall through - default: - switch (length) { - case 2: - pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); - break; - case 3: - pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); - pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); - break; - case 4: - pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); - pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); - pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63)); - break; - } - - break; - } - - return true; - } - - public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { - do { - int k = 72; - int i; - - for (i = 0; i + 4 < k; i += 4) { - if(!decodeAtom(stream, buffer, 4)) { - break; - } - } - - if (!decodeAtom(stream, buffer, k - i)) { - break; - } - } - while (buffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes - - return buffer.position(); - } -} +/* + * 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.io.enc; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * {@code Decoder} implementation for standard base64 encoding. + * + * @see RFC 1421 + * @see RFC 2045 + * + * @see Base64Encoder + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java#2 $ + */ +public final class Base64Decoder implements Decoder { + /** + * This array maps the characters to their 6 bit values + */ + final static byte[] PEM_ARRAY = { + //0 1 2 3 4 5 6 7 + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6 + '4', '5', '6', '7', '8', '9', '+', '/' // 7 + }; + + final static byte[] PEM_CONVERT_ARRAY; + + private byte[] decodeBuffer = new byte[4]; + + static { + PEM_CONVERT_ARRAY = new byte[256]; + + for (int i = 0; i < 255; i++) { + PEM_CONVERT_ARRAY[i] = -1; + } + + for (int i = 0; i < PEM_ARRAY.length; i++) { + PEM_CONVERT_ARRAY[PEM_ARRAY[i]] = (byte) i; + } + } + + protected static int readFully(final InputStream pStream, final byte pBytes[], final int pOffset, final int pLength) + throws IOException + { + for (int i = 0; i < pLength; i++) { + int read = pStream.read(); + + if (read == -1) { + return i != 0 ? i : -1; + } + + pBytes[i + pOffset] = (byte) read; + } + + return pLength; + } + + protected boolean decodeAtom(final InputStream pInput, final ByteBuffer pOutput, final int pLength) + throws IOException { + + byte byte0 = -1; + byte byte1 = -1; + byte byte2 = -1; + byte byte3 = -1; + + if (pLength < 2) { + throw new IOException("BASE64Decoder: Not enough bytes for an atom."); + } + + int read; + + // Skip line feeds + do { + read = pInput.read(); + + if (read == -1) { + return false; + } + } while (read == 10 || read == 13); + + decodeBuffer[0] = (byte) read; + read = readFully(pInput, decodeBuffer, 1, pLength - 1); + + if (read == -1) { + return false; + } + + int length = pLength; + + if (length > 3 && decodeBuffer[3] == 61) { + length = 3; + } + + if (length > 2 && decodeBuffer[2] == 61) { + length = 2; + } + + switch (length) { + case 4: + byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255]; + // fall through + case 3: + byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255]; + // fall through + case 2: + byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255]; + byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255]; + // fall through + default: + switch (length) { + case 2: + pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); + break; + case 3: + pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); + pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); + break; + case 4: + pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); + pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); + pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63)); + break; + } + + break; + } + + return true; + } + + public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { + do { + int k = 72; + int i; + + for (i = 0; i + 4 < k; i += 4) { + if(!decodeAtom(stream, buffer, 4)) { + break; + } + } + + if (!decodeAtom(stream, buffer, k - i)) { + break; + } + } + while (buffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes + + return buffer.position(); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java index d8336498..8a9eaa5f 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java @@ -1,104 +1,106 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.OutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * {@code Encoder} implementation for standard base64 encoding. - *

- * @see RFC 1421 - * @see - * - * @see Base64Decoder - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java#2 $ - */ -public class Base64Encoder implements Encoder { - - public void encode(final OutputStream stream, final ByteBuffer buffer) - throws IOException - { - - // TODO: Implement - // NOTE: This is impossible, given the current spec, as we need to either: - // - buffer all data in the EncoderStream - // - or have flush/end method(s) in the Encoder - // to ensure proper end of stream handling - - int length; - - // TODO: Temp impl, will only work for single writes - while (buffer.hasRemaining()) { - byte a, b, c; - -// if ((buffer.remaining()) > 2) { -// length = 3; -// } -// else { -// length = buffer.remaining(); -// } - length = Math.min(3, buffer.remaining()); - - switch (length) { - case 1: - a = buffer.get(); - b = 0; - stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]); - stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); - stream.write('='); - stream.write('='); - break; - - case 2: - a = buffer.get(); - b = buffer.get(); - c = 0; - stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]); - stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); - stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); - stream.write('='); - break; - - default: - a = buffer.get(); - b = buffer.get(); - c = buffer.get(); - stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]); - stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); - stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); - stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]); - break; - } - } - } -} +/* + * 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.io.enc; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * {@code Encoder} implementation for standard base64 encoding. + * + * @see RFC 1421 + * @see RFC 2045 + * + * @see Base64Decoder + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java#2 $ + */ +public class Base64Encoder implements Encoder { + + public void encode(final OutputStream stream, final ByteBuffer buffer) + throws IOException + { + + // TODO: Implement + // NOTE: This is impossible, given the current spec, as we need to either: + // - buffer all data in the EncoderStream + // - or have flush/end method(s) in the Encoder + // to ensure proper end of stream handling + + int length; + + // TODO: Temp impl, will only work for single writes + while (buffer.hasRemaining()) { + byte a, b, c; + +// if ((buffer.remaining()) > 2) { +// length = 3; +// } +// else { +// length = buffer.remaining(); +// } + length = Math.min(3, buffer.remaining()); + + switch (length) { + case 1: + a = buffer.get(); + b = 0; + stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]); + stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + stream.write('='); + stream.write('='); + break; + + case 2: + a = buffer.get(); + b = buffer.get(); + c = 0; + stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]); + stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + stream.write('='); + break; + + default: + a = buffer.get(); + b = buffer.get(); + c = buffer.get(); + stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]); + stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]); + break; + } + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java index 7acf88af..c377d2d7 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java @@ -1,54 +1,55 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.IOException; - -/** - * Thrown by {@code Decoder}s when encoded data can not be decoded. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java#2 $ - */ -public class DecodeException extends IOException { - - public DecodeException(final String pMessage) { - super(pMessage); - } - - public DecodeException(final String pMessage, final Throwable pCause) { - super(pMessage); - initCause(pCause); - } - - public DecodeException(final Throwable pCause) { - this(pCause.getMessage(), pCause); - } +/* + * 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.io.enc; + +import java.io.IOException; + +/** + * Thrown by {@code Decoder}s when encoded data can not be decoded. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java#2 $ + */ +public class DecodeException extends IOException { + + public DecodeException(final String pMessage) { + super(pMessage); + } + + public DecodeException(final String pMessage, final Throwable pCause) { + super(pMessage); + initCause(pCause); + } + + public DecodeException(final Throwable pCause) { + this(pCause.getMessage(), pCause); + } } \ No newline at end of file diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java index aa7576d0..eae19df0 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java @@ -1,66 +1,69 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * Interface for decoders. - * A {@code Decoder} may be used with a {@code DecoderStream}, to perform - * on-the-fly decoding from an {@code InputStream}. - *

- * Important note: Decoder implementations are typically not synchronized. - *

- * @see Encoder - * @see DecoderStream - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Decoder.java#2 $ - */ -public interface Decoder { - - /** - * Decodes up to {@code buffer.length} bytes from the given input stream, - * into the given buffer. - * - * @param stream the input stream to decode data from - * @param buffer buffer to store the read data - * - * @return the total number of bytes read into the buffer, or {@code 0} - * if there is no more data because the end of the stream has been reached. - * - * @throws DecodeException if encoded data is corrupt. - * @throws IOException if an I/O error occurs. - * @throws java.io.EOFException if a premature end-of-file is encountered. - * @throws java.lang.NullPointerException if either argument is {@code null}. - */ - int decode(InputStream stream, ByteBuffer buffer) throws IOException; -} +/* + * 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.io.enc; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * Interface for decoders. + * A {@code Decoder} may be used with a {@code DecoderStream}, to perform + * on-the-fly decoding from an {@code InputStream}. + *

+ * Important note: Decoder implementations are typically not synchronized. + *

+ * + * @see Encoder + * @see DecoderStream + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Decoder.java#2 $ + */ +public interface Decoder { + + /** + * Decodes up to {@code buffer.length} bytes from the given input stream, + * into the given buffer. + * + * @param stream the input stream to decode data from + * @param buffer buffer to store the read data + * + * @return the total number of bytes read into the buffer, or {@code 0} + * if there is no more data because the end of the stream has been reached. + * + * @throws DecodeException if encoded data is corrupt. + * @throws IOException if an I/O error occurs. + * @throws java.io.EOFException if a premature end-of-file is encountered. + * @throws java.lang.NullPointerException if either argument is {@code null}. + */ + int decode(InputStream stream, ByteBuffer buffer) throws IOException; +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java index 07826694..ec542a46 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java @@ -1,198 +1,199 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * An {@code InputStream} that provides on-the-fly decoding from an underlying - * stream. - *

- * @see EncoderStream - * @see Decoder - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $ - */ -public final class DecoderStream extends FilterInputStream { - protected final ByteBuffer buffer; - protected final Decoder decoder; - - /** - * Creates a new decoder stream and chains it to the - * input stream specified by the {@code pStream} argument. - * The stream will use a default decode buffer size. - * - * @param pStream the underlying input stream. - * @param pDecoder the decoder that will be used to decode the underlying stream - * - * @see java.io.FilterInputStream#in - */ - public DecoderStream(final InputStream pStream, final Decoder pDecoder) { - // TODO: Let the decoder decide preferred buffer size - this(pStream, pDecoder, 1024); - } - - /** - * Creates a new decoder stream and chains it to the - * input stream specified by the {@code pStream} argument. - * - * @param pStream the underlying input stream. - * @param pDecoder the decoder that will be used to decode the underlying stream - * @param pBufferSize the size of the decode buffer - * - * @see java.io.FilterInputStream#in - */ - public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) { - super(pStream); - - decoder = pDecoder; - buffer = ByteBuffer.allocate(pBufferSize); - buffer.flip(); - } - - public int available() throws IOException { - return buffer.remaining(); - } - - public int read() throws IOException { - if (!buffer.hasRemaining()) { - if (fill() < 0) { - return -1; - } - } - - return buffer.get() & 0xff; - } - - public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException { - if (pBytes == null) { - throw new NullPointerException(); - } - else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || - ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { - throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength); - } - else if (pLength == 0) { - return 0; - } - - // End of file? - if (!buffer.hasRemaining()) { - if (fill() < 0) { - return -1; - } - } - - // Read until we have read pLength bytes, or have reached EOF - int count = 0; - int off = pOffset; - - while (pLength > count) { - if (!buffer.hasRemaining()) { - if (fill() < 0) { - break; - } - } - - // Copy as many bytes as possible - int dstLen = Math.min(pLength - count, buffer.remaining()); - buffer.get(pBytes, off, dstLen); - - // Update offset (rest) - off += dstLen; - - // Increase count - count += dstLen; - } - - return count; - } - - public long skip(final long pLength) throws IOException { - // End of file? - if (!buffer.hasRemaining()) { - if (fill() < 0) { - return 0; // Yes, 0, not -1 - } - } - - // Skip until we have skipped pLength bytes, or have reached EOF - long total = 0; - - while (total < pLength) { - if (!buffer.hasRemaining()) { - if (fill() < 0) { - break; - } - } - - // NOTE: Skipped can never be more than avail, which is an int, so the cast is safe - int skipped = (int) Math.min(pLength - total, buffer.remaining()); - buffer.position(buffer.position() + skipped); - total += skipped; - } - - return total; - } - - /** - * Fills the buffer, by decoding data from the underlying input stream. - * - * @return the number of bytes decoded, or {@code -1} if the end of the - * file is reached - * - * @throws IOException if an I/O error occurs - */ - protected int fill() throws IOException { - buffer.clear(); - int read = decoder.decode(in, buffer); - - // TODO: Enforce this in test case, leave here to aid debugging - if (read > buffer.capacity()) { - throw new AssertionError( - String.format( - "Decode beyond buffer (%d): %d (using %s decoder)", - buffer.capacity(), read, decoder.getClass().getName() - ) - ); - } - - buffer.flip(); - - if (read == 0) { - return -1; - } - - return read; - } -} +/* + * 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.io.enc; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * An {@code InputStream} that provides on-the-fly decoding from an underlying stream. + * + * @see EncoderStream + * @see Decoder + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $ + */ +public final class DecoderStream extends FilterInputStream { + protected final ByteBuffer buffer; + protected final Decoder decoder; + + /** + * Creates a new decoder stream and chains it to the + * input stream specified by the {@code pStream} argument. + * The stream will use a default decode buffer size. + * + * @param pStream the underlying input stream. + * @param pDecoder the decoder that will be used to decode the underlying stream + * + * @see java.io.FilterInputStream#in + */ + public DecoderStream(final InputStream pStream, final Decoder pDecoder) { + // TODO: Let the decoder decide preferred buffer size + this(pStream, pDecoder, 1024); + } + + /** + * Creates a new decoder stream and chains it to the + * input stream specified by the {@code pStream} argument. + * + * @param pStream the underlying input stream. + * @param pDecoder the decoder that will be used to decode the underlying stream + * @param pBufferSize the size of the decode buffer + * + * @see java.io.FilterInputStream#in + */ + public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) { + super(pStream); + + decoder = pDecoder; + buffer = ByteBuffer.allocate(pBufferSize); + buffer.flip(); + } + + public int available() throws IOException { + return buffer.remaining(); + } + + public int read() throws IOException { + if (!buffer.hasRemaining()) { + if (fill() < 0) { + return -1; + } + } + + return buffer.get() & 0xff; + } + + public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException { + if (pBytes == null) { + throw new NullPointerException(); + } + else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || + ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { + throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength); + } + else if (pLength == 0) { + return 0; + } + + // End of file? + if (!buffer.hasRemaining()) { + if (fill() < 0) { + return -1; + } + } + + // Read until we have read pLength bytes, or have reached EOF + int count = 0; + int off = pOffset; + + while (pLength > count) { + if (!buffer.hasRemaining()) { + if (fill() < 0) { + break; + } + } + + // Copy as many bytes as possible + int dstLen = Math.min(pLength - count, buffer.remaining()); + buffer.get(pBytes, off, dstLen); + + // Update offset (rest) + off += dstLen; + + // Increase count + count += dstLen; + } + + return count; + } + + public long skip(final long pLength) throws IOException { + // End of file? + if (!buffer.hasRemaining()) { + if (fill() < 0) { + return 0; // Yes, 0, not -1 + } + } + + // Skip until we have skipped pLength bytes, or have reached EOF + long total = 0; + + while (total < pLength) { + if (!buffer.hasRemaining()) { + if (fill() < 0) { + break; + } + } + + // NOTE: Skipped can never be more than avail, which is an int, so the cast is safe + int skipped = (int) Math.min(pLength - total, buffer.remaining()); + buffer.position(buffer.position() + skipped); + total += skipped; + } + + return total; + } + + /** + * Fills the buffer, by decoding data from the underlying input stream. + * + * @return the number of bytes decoded, or {@code -1} if the end of the + * file is reached + * + * @throws IOException if an I/O error occurs + */ + protected int fill() throws IOException { + buffer.clear(); + int read = decoder.decode(in, buffer); + + // TODO: Enforce this in test case, leave here to aid debugging + if (read > buffer.capacity()) { + throw new AssertionError( + String.format( + "Decode beyond buffer (%d): %d (using %s decoder)", + buffer.capacity(), read, decoder.getClass().getName() + ) + ); + } + + buffer.flip(); + + if (read == 0) { + return -1; + } + + return read; + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java index 1864e4dc..c5c8278c 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java @@ -1,63 +1,66 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Interface for encoders. - * An {@code Encoder} may be used with an {@code EncoderStream}, to perform - * on-the-fly encoding to an {@code OutputStream}. - *

- * Important note: Encoder implementations are typically not synchronized. - * - * @see Decoder - * @see EncoderStream - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Encoder.java#2 $ - */ -public interface Encoder { - - /** - * Encodes up to {@code buffer.remaining()} bytes into the given input stream, - * from the given buffer. - * - * @param stream the output stream to encode data to - * @param buffer buffer to read data from - * - * @throws java.io.IOException if an I/O error occurs - */ - void encode(OutputStream stream, ByteBuffer buffer) throws IOException; - - //TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size - // void flush()? -} +/* + * 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.io.enc; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * Interface for encoders. + * An {@code Encoder} may be used with an {@code EncoderStream}, to perform + * on-the-fly encoding to an {@code OutputStream}. + *

+ * Important note: Encoder implementations are typically not synchronized. + *

+ * + * @see Decoder + * @see EncoderStream + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Encoder.java#2 $ + */ +public interface Encoder { + + /** + * Encodes up to {@code buffer.remaining()} bytes into the given input stream, + * from the given buffer. + * + * @param stream the output stream to encode data to + * @param buffer buffer to read data from + * + * @throws java.io.IOException if an I/O error occurs + */ + void encode(OutputStream stream, ByteBuffer buffer) throws IOException; + + //TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size + // void flush()? +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java index f1ba67ef..dafceb2e 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java @@ -1,135 +1,136 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * An {@code OutputStream} that provides on-the-fly encoding to an underlying - * stream. - *

- * @see DecoderStream - * @see Encoder - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $ - */ -public final class EncoderStream extends FilterOutputStream { - // TODO: This class need a test case ASAP!!! - - protected final Encoder encoder; - private final boolean flushOnWrite; - - protected final ByteBuffer buffer; - - /** - * Creates an output stream filter built on top of the specified - * underlying output stream. - * - * @param pStream the underlying output stream - * @param pEncoder the encoder to use - */ - public EncoderStream(final OutputStream pStream, final Encoder pEncoder) { - this(pStream, pEncoder, false); - } - - /** - * Creates an output stream filter built on top of the specified - * underlying output stream. - * - * @param pStream the underlying output stream - * @param pEncoder the encoder to use - * @param pFlushOnWrite if {@code true}, calls to the byte-array - * {@code write} methods will automatically flush the buffer. - */ - public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) { - super(pStream); - - encoder = pEncoder; - flushOnWrite = pFlushOnWrite; - - buffer = ByteBuffer.allocate(1024); - buffer.flip(); - } - - public void close() throws IOException { - flush(); - super.close(); - } - - public void flush() throws IOException { - encodeBuffer(); - super.flush(); - } - - private void encodeBuffer() throws IOException { - if (buffer.position() != 0) { - buffer.flip(); - - // Make sure all remaining data in buffer is written to the stream - encoder.encode(out, buffer); - - // Reset buffer - buffer.clear(); - } - } - - public final void write(final byte[] pBytes) throws IOException { - write(pBytes, 0, pBytes.length); - } - - // TODO: Verify that this works for the general case (it probably won't)... - // TODO: We might need a way to explicitly flush the encoder, or specify - // that the encoder can't buffer. In that case, the encoder should probably - // tell the EncoderStream how large buffer it prefers... - public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (!flushOnWrite && pLength < buffer.remaining()) { - // Buffer data - buffer.put(pBytes, pOffset, pLength); - } - else { - // Encode data already in the buffer - encodeBuffer(); - - // Encode rest without buffering - encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength)); - } - } - - public void write(final int pByte) throws IOException { - if (!buffer.hasRemaining()) { - encodeBuffer(); // Resets bufferPos to 0 - } - - buffer.put((byte) pByte); - } -} +/* + * 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.io.enc; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * An {@code OutputStream} that provides on-the-fly encoding to an underlying stream. + * + * @see DecoderStream + * @see Encoder + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $ + */ +public final class EncoderStream extends FilterOutputStream { + // TODO: This class need a test case ASAP!!! + + protected final Encoder encoder; + private final boolean flushOnWrite; + + protected final ByteBuffer buffer; + + /** + * Creates an output stream filter built on top of the specified + * underlying output stream. + * + * @param pStream the underlying output stream + * @param pEncoder the encoder to use + */ + public EncoderStream(final OutputStream pStream, final Encoder pEncoder) { + this(pStream, pEncoder, false); + } + + /** + * Creates an output stream filter built on top of the specified + * underlying output stream. + * + * @param pStream the underlying output stream + * @param pEncoder the encoder to use + * @param pFlushOnWrite if {@code true}, calls to the byte-array + * {@code write} methods will automatically flush the buffer. + */ + public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) { + super(pStream); + + encoder = pEncoder; + flushOnWrite = pFlushOnWrite; + + buffer = ByteBuffer.allocate(1024); + buffer.flip(); + } + + public void close() throws IOException { + flush(); + super.close(); + } + + public void flush() throws IOException { + encodeBuffer(); + super.flush(); + } + + private void encodeBuffer() throws IOException { + if (buffer.position() != 0) { + buffer.flip(); + + // Make sure all remaining data in buffer is written to the stream + encoder.encode(out, buffer); + + // Reset buffer + buffer.clear(); + } + } + + public final void write(final byte[] pBytes) throws IOException { + write(pBytes, 0, pBytes.length); + } + + // TODO: Verify that this works for the general case (it probably won't)... + // TODO: We might need a way to explicitly flush the encoder, or specify + // that the encoder can't buffer. In that case, the encoder should probably + // tell the EncoderStream how large buffer it prefers... + public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + if (!flushOnWrite && pLength < buffer.remaining()) { + // Buffer data + buffer.put(pBytes, pOffset, pLength); + } + else { + // Encode data already in the buffer + encodeBuffer(); + + // Encode rest without buffering + encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength)); + } + } + + public void write(final int pByte) throws IOException { + if (!buffer.hasRemaining()) { + encodeBuffer(); // Resets bufferPos to 0 + } + + buffer.put((byte) pByte); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java index 86c32a1f..69865e39 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java @@ -1,208 +1,194 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * Decoder implementation for Apple PackBits run-length encoding. - *

- * From Wikipedia, the free encyclopedia
- * PackBits is a fast, simple compression scheme for run-length encoding of - * data. - *

- * Apple introduced the PackBits format with the release of MacPaint on the - * Macintosh computer. This compression scheme is one of the types of - * compression that can be used in TIFF-files. - *

- * A PackBits data stream consists of packets of one byte of header followed by - * data. The header is a signed byte; the data can be signed, unsigned, or - * packed (such as MacPaint pixels). - *

- * - * - * - *
Header byteData
0 to 127 1 + n literal bytes of data
0 to -127 One byte of data, repeated 1 - n times in - * the decompressed output
-128 No operation
- *

- * Note that interpreting 0 as positive or negative makes no difference in the - * output. Runs of two bytes adjacent to non-runs are typically written as - * literal data. - *

- * See Understanding PackBits - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $ - */ -public final class PackBitsDecoder implements Decoder { - // TODO: Look at ICNSImageReader#unpackbits... What is this weirdness? - - private final boolean disableNoOp; - private final byte[] sample; - - private int leftOfRun; - private boolean splitRun; - private boolean reachedEOF; - - /** Creates a {@code PackBitsDecoder}. */ - public PackBitsDecoder() { - this(1, false); - } - - /** - * Creates a {@code PackBitsDecoder}, with optional compatibility mode. - *

- * As some implementations of PackBits-like encoders treat {@code -128} as length of - * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility. - * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams. - * - * @param disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op - */ - public PackBitsDecoder(final boolean disableNoOp) { - this(1, disableNoOp); - } - - /** - * Creates a {@code PackBitsDecoder}, with optional compatibility mode. - *

- * As some implementations of PackBits-like encoders treat {@code -128} as length of - * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility. - * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams. - * - * @param disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op - */ - public PackBitsDecoder(int sampleSize, final boolean disableNoOp) { - this.sample = new byte[sampleSize]; - this.disableNoOp = disableNoOp; - } - - /** - * Decodes bytes from the given input stream, to the given buffer. - * - * @param stream the stream to decode from - * @param buffer a byte array, minimum 128 (or 129 if no-op is disabled) bytes long - * @return The number of bytes decoded - * - * @throws java.io.IOException - */ - public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { - if (reachedEOF) { - return -1; - } - - // TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream... - while (buffer.hasRemaining()) { - int n; - - if (splitRun) { - // Continue run - n = leftOfRun; - splitRun = false; - } - else { - // Start new run - int b = stream.read(); - if (b < 0) { - reachedEOF = true; - break; - } - n = (byte) b; - } - - // Split run at or before max - if (n >= 0 && n + 1 > buffer.remaining()) { - leftOfRun = n; - splitRun = true; - break; - } - else if (n < 0 && -n + 1 > buffer.remaining()) { - leftOfRun = n; - splitRun = true; - break; - } - - try { - if (n >= 0) { - // Copy next n + 1 bytes literally - readFully(stream, buffer, sample.length * (n + 1)); - } - // Allow -128 for compatibility, see above - else if (disableNoOp || n != -128) { - // Replicate the next byte -n + 1 times - for (int s = 0; s < sample.length; s++) { - sample[s] = readByte(stream); - } - - for (int i = -n + 1; i > 0; i--) { - buffer.put(sample); - } - } - // else NOOP (-128) - } - catch (IndexOutOfBoundsException e) { - throw new DecodeException("Error in PackBits decompression, data seems corrupt", e); - } - } - - return buffer.position(); - } - - static byte readByte(final InputStream pStream) throws IOException { - int read = pStream.read(); - - if (read < 0) { - throw new EOFException("Unexpected end of PackBits stream"); - } - - return (byte) read; - } - - static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException { - if (pLength < 0) { - throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength)); - } - - int total = 0; - - while (total < pLength) { - int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total); - - if (count < 0) { - throw new EOFException("Unexpected end of PackBits stream"); - } - - total += count; - } - - pBuffer.position(pBuffer.position() + total); - } -} +/* + * 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.io.enc; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * Decoder implementation for Apple PackBits run-length encoding. + *

+ * From Wikipedia, the free encyclopedia + *
+ * PackBits is a fast, simple compression scheme for run-length encoding of + * data. + *

+ *

+ * Apple introduced the PackBits format with the release of MacPaint on the + * Macintosh computer. This compression scheme is one of the types of + * compression that can be used in TIFF-files. + *

+ *

+ * A PackBits data stream consists of packets of one byte of header followed by + * data. The header is a signed byte; the data can be signed, unsigned, or + * packed (such as MacPaint pixels). + *

+ * + * + * + * + * + * + *
PackBits
Header byteData
0 to 127 1 + n literal bytes of data
0 to -127 One byte of data, repeated 1 - n times in the decompressed output
-128 No operation
+ *

+ * Note that interpreting 0 as positive or negative makes no difference in the + * output. Runs of two bytes adjacent to non-runs are typically written as + * literal data. + *

+ * + * @see Understanding PackBits + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $ + */ +public final class PackBitsDecoder implements Decoder { + // TODO: Look at ICNSImageReader#unpackbits... What is this weirdness? + + private final boolean disableNoOp; + private final byte[] sample; + + private boolean reachedEOF; + + /** Creates a {@code PackBitsDecoder}. */ + public PackBitsDecoder() { + this(1, false); + } + + /** + * Creates a {@code PackBitsDecoder}, with optional compatibility mode. + *

+ * As some implementations of PackBits-like encoders treat {@code -128} as length of + * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility. + * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams. + *

+ * + * @param disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op + */ + public PackBitsDecoder(final boolean disableNoOp) { + this(1, disableNoOp); + } + + /** + * Creates a {@code PackBitsDecoder}, with optional compatibility mode. + *

+ * As some implementations of PackBits-like encoders treat {@code -128} as length of + * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility. + * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams. + *

+ * + * @param disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op + */ + public PackBitsDecoder(int sampleSize, final boolean disableNoOp) { + this.sample = new byte[sampleSize]; + this.disableNoOp = disableNoOp; + } + + /** + * Decodes bytes from the given input stream, to the given buffer. + * + * @param stream the stream to decode from + * @param buffer a byte array, minimum 128 (or 129 if no-op is disabled) bytes long + * @return The number of bytes decoded + * + * @throws java.io.IOException if a problem occurs during decoding. + */ + public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { + if (reachedEOF) { + return -1; + } + + // NOTE: We don't decode more than single runs, because some writers add pad bytes inside the stream... + // Start new run + int b = stream.read(); + if (b < 0) { + reachedEOF = true; + return 0; + } + + int n = (byte) b; + + try { + if (n >= 0) { + // Copy next n + 1 bytes literally + readFully(stream, buffer, sample.length * (n + 1)); + } + // Allow -128 for compatibility, see above + else if (disableNoOp || n != -128) { + // Replicate the next byte -n + 1 times + for (int s = 0; s < sample.length; s++) { + sample[s] = readByte(stream); + } + + for (int i = -n + 1; i > 0; i--) { + buffer.put(sample); + } + } + // else NOOP (-128) + } + catch (IndexOutOfBoundsException e) { + throw new DecodeException("Error in PackBits decompression, data seems corrupt", e); + } + + return buffer.position(); + } + + static byte readByte(final InputStream pStream) throws IOException { + int read = pStream.read(); + + if (read < 0) { + throw new EOFException("Unexpected end of PackBits stream"); + } + + return (byte) read; + } + + static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException { + if (pLength < 0) { + throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength)); + } + + int total = 0; + + while (total < pLength) { + int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total); + + if (count < 0) { + throw new EOFException("Unexpected end of PackBits stream"); + } + + total += count; + } + + pBuffer.position(pBuffer.position() + total); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java index e51a94e8..4bbfc15f 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java @@ -1,130 +1,138 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.enc; - -import java.io.OutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Encoder implementation for Apple PackBits run-length encoding. - *

- * From Wikipedia, the free encyclopedia
- * PackBits is a fast, simple compression scheme for run-length encoding of - * data. - *

- * Apple introduced the PackBits format with the release of MacPaint on the - * Macintosh computer. This compression scheme is one of the types of - * compression that can be used in TIFF-files. - *

- * A PackBits data stream consists of packets of one byte of header followed by - * data. The header is a signed byte; the data can be signed, unsigned, or - * packed (such as MacPaint pixels). - *

- * - * - * - *
Header byteData
0 to 127 1 + n literal bytes of data
0 to -127 One byte of data, repeated 1 - n times in - * the decompressed output
-128 No operation
- *

- * Note that interpreting 0 as positive or negative makes no difference in the - * output. Runs of two bytes adjacent to non-runs are typically written as - * literal data. - *

- * See Understanding PackBits - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java#1 $ - */ -public final class PackBitsEncoder implements Encoder { - - final private byte[] buffer = new byte[128]; - - /** - * Creates a {@code PackBitsEncoder}. - */ - public PackBitsEncoder() { - } - - public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException { - encode(stream, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); - buffer.position(buffer.remaining()); - } - - private void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException { - // NOTE: It's best to encode a 2 byte repeat - // run as a replicate run except when preceded and followed by a - // literal run, in which case it's best to merge the three into one - // literal run. Always encode 3 byte repeats as replicate runs. - // NOTE: Worst case: output = input + (input + 127) / 128 - - int offset = pOffset; - final int max = pOffset + pLength - 1; - final int maxMinus1 = max - 1; - - while (offset <= max) { - // Compressed run - int run = 1; - byte replicate = pBuffer[offset]; - while (run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) { - offset++; - run++; - } - - if (run > 1) { - offset++; - pStream.write(-(run - 1)); - pStream.write(replicate); - } - - // Literal run - run = 0; - while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1]) - || (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) { - buffer[run++] = pBuffer[offset++]; - } - - // If last byte, include it in literal run, if space - if (offset == max && run > 0 && run < 128) { - buffer[run++] = pBuffer[offset++]; - } - - if (run > 0) { - pStream.write(run - 1); - pStream.write(buffer, 0, run); - } - - // If last byte, and not space, start new literal run - if (offset == max && (run <= 0 || run >= 128)) { - pStream.write(0); - pStream.write(pBuffer[offset++]); - } - } - } -} +/* + * 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.io.enc; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * Encoder implementation for Apple PackBits run-length encoding. + *

+ * From Wikipedia, the free encyclopedia + *
+ * PackBits is a fast, simple compression scheme for run-length encoding of + * data. + *

+ *

+ * Apple introduced the PackBits format with the release of MacPaint on the + * Macintosh computer. This compression scheme is one of the types of + * compression that can be used in TIFF-files. + *

+ *

+ * A PackBits data stream consists of packets of one byte of header followed by + * data. The header is a signed byte; the data can be signed, unsigned, or + * packed (such as MacPaint pixels). + *

+ * + * + * + * + * + * + *
PackBits
Header byteData
0 to 127 1 + n literal bytes of data
0 to -127 One byte of data, repeated 1 - n times in the decompressed output
-128 No operation
+ *

+ * Note that interpreting 0 as positive or negative makes no difference in the + * output. Runs of two bytes adjacent to non-runs are typically written as + * literal data. + *

+ * + * @see Understanding PackBits + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java#1 $ + */ +public final class PackBitsEncoder implements Encoder { + + final private byte[] buffer = new byte[128]; + + /** + * Creates a {@code PackBitsEncoder}. + */ + public PackBitsEncoder() { + } + + public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException { + encode(stream, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); + buffer.position(buffer.remaining()); + } + + private void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException { + // NOTE: It's best to encode a 2 byte repeat + // run as a replicate run except when preceded and followed by a + // literal run, in which case it's best to merge the three into one + // literal run. Always encode 3 byte repeats as replicate runs. + // NOTE: Worst case: output = input + (input + 127) / 128 + + int offset = pOffset; + final int max = pOffset + pLength - 1; + final int maxMinus1 = max - 1; + + while (offset <= max) { + // Compressed run + int run = 1; + byte replicate = pBuffer[offset]; + while (run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) { + offset++; + run++; + } + + if (run > 1) { + offset++; + pStream.write(-(run - 1)); + pStream.write(replicate); + } + + // Literal run + run = 0; + while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1]) + || (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) { + buffer[run++] = pBuffer[offset++]; + } + + // If last byte, include it in literal run, if space + if (offset == max && run > 0 && run < 128) { + buffer[run++] = pBuffer[offset++]; + } + + if (run > 0) { + pStream.write(run - 1); + pStream.write(buffer, 0, run); + } + + // If last byte, and not space, start new literal run + if (offset == max && (run <= 0 || run >= 128)) { + pStream.write(0); + pStream.write(pBuffer[offset++]); + } + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/package-info.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/package-info.java index 72d47b16..d7ff80c0 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/package-info.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/package-info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Contains customized stream classes, that can read or write compressed data on the fly, * along with encoders and decoders for popular stream formats, such as Base64, ZIP (deflate), LZW, PackBits etc.. diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java index 8dcffcc9..75aeb477 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java @@ -1,764 +1,801 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.ole2; - -import com.twelvemonkeys.io.*; -import com.twelvemonkeys.lang.StringUtil; - -import javax.imageio.stream.ImageInputStream; -import java.io.*; -import java.util.Arrays; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.UUID; - -/** - * Represents a read-only OLE2 compound document. - *

- * - * NOTE: This class is not synchronized. Accessing the document or its - * entries from different threads, will need synchronization on the document - * instance. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java#4 $ - */ -public final class CompoundDocument { - // TODO: Write support... - // TODO: Properties: http://support.microsoft.com/kb/186898 - - static final byte[] MAGIC = new byte[]{ - (byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0, - (byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1, - }; - - private static final int FREE_SID = -1; - private static final int END_OF_CHAIN_SID = -2; - private static final int SAT_SECTOR_SID = -3; // Sector used by SAT - private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT - - public static final int HEADER_SIZE = 512; - - /** The epoch offset of CompoundDocument time stamps */ - public final static long EPOCH_OFFSET = -11644477200000L; - - private final DataInput input; - - private UUID uUID; - - private int sectorSize; - private int shortSectorSize; - - private int directorySId; - - private int minStreamSize; - - private int shortSATSId; - private int shortSATSize; - - // Master Sector Allocation Table - private int[] masterSAT; - private int[] SAT; - private int[] shortSAT; - - private Entry rootEntry; - private SIdChain shortStreamSIdChain; - private SIdChain directorySIdChain; - - /** - * Creates a (for now) read only {@code CompoundDocument}. - * - * @param pFile the file to read from - * - * @throws IOException if an I/O exception occurs while reading the header - */ - public CompoundDocument(final File pFile) throws IOException { - input = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r"); - - // TODO: Might be better to read header on first read operation?! - // OTOH: It's also good to be fail-fast, so at least we should make - // sure we're reading a valid document - readHeader(); - } - - /** - * Creates a read only {@code CompoundDocument}. - * - * @param pInput the input to read from - * - * @throws IOException if an I/O exception occurs while reading the header - */ - public CompoundDocument(final InputStream pInput) throws IOException { - this(new FileCacheSeekableStream(pInput)); - } - - // For testing only, consider exposing later - CompoundDocument(final SeekableInputStream pInput) throws IOException { - input = new SeekableLittleEndianDataInputStream(pInput); - - // TODO: Might be better to read header on first read operation?! - // OTOH: It's also good to be fail-fast, so at least we should make - // sure we're reading a valid document - readHeader(); - } - - /** - * Creates a read only {@code CompoundDocument}. - * - * @param pInput the input to read from - * - * @throws IOException if an I/O exception occurs while reading the header - */ - public CompoundDocument(final ImageInputStream pInput) throws IOException { - input = pInput; - - // TODO: Might be better to read header on first read operation?! - // OTOH: It's also good to be fail-fast, so at least we should make - // sure we're reading a valid document - readHeader(); - } - - public static boolean canRead(final DataInput pInput) { - return canRead(pInput, true); - } - - // TODO: Refactor.. Figure out what we really need to expose to ImageIO for - // easy reading of the Thumbs.db file - // It's probably safer to create one version for InputStream and one for File - private static boolean canRead(final DataInput pInput, final boolean pReset) { - long pos = FREE_SID; - if (pReset) { - try { - if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) { - ((InputStream) pInput).mark(8); - } - else if (pInput instanceof ImageInputStream) { - ((ImageInputStream) pInput).mark(); - } - else if (pInput instanceof RandomAccessFile) { - pos = ((RandomAccessFile) pInput).getFilePointer(); - } - else if (pInput instanceof LittleEndianRandomAccessFile) { - pos = ((LittleEndianRandomAccessFile) pInput).getFilePointer(); - } - else { - return false; - } - } - catch (IOException ignore) { - return false; - } - } - - try { - byte[] magic = new byte[8]; - pInput.readFully(magic); - return Arrays.equals(magic, MAGIC); - } - catch (IOException ignore) { - // Ignore - } - finally { - if (pReset) { - try { - if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) { - ((InputStream) pInput).reset(); - } - else if (pInput instanceof ImageInputStream) { - ((ImageInputStream) pInput).reset(); - } - else if (pInput instanceof RandomAccessFile) { - ((RandomAccessFile) pInput).seek(pos); - } - else if (pInput instanceof LittleEndianRandomAccessFile) { - ((LittleEndianRandomAccessFile) pInput).seek(pos); - } - } - catch (IOException e) { - // TODO: This isn't actually good enough... - // Means something fucked up, and will fail... - e.printStackTrace(); - } - } - } - - return false; - } - - private void readHeader() throws IOException { - if (masterSAT != null) { - return; - } - - if (!canRead(input, false)) { - throw new CorruptDocumentException("Not an OLE 2 Compound Document"); - } - - // UID (seems to be all 0s) - uUID = new UUID(input.readLong(), input.readLong()); -// System.out.println("uUID: " + uUID); - - // int version = - input.readUnsignedShort(); -// System.out.println("version: " + version); - // int revision = - input.readUnsignedShort(); -// System.out.println("revision: " + revision); - - int byteOrder = input.readUnsignedShort(); -// System.out.printf("byteOrder: 0x%04x\n", byteOrder); - if (byteOrder == 0xffff) { - throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents"); - } - else if (byteOrder != 0xfffe) { - // Reversed, as I'm already reading little-endian - throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder)); - } - - sectorSize = 1 << input.readUnsignedShort(); -// System.out.println("sectorSize: " + sectorSize + " bytes"); - shortSectorSize = 1 << input.readUnsignedShort(); -// System.out.println("shortSectorSize: " + shortSectorSize + " bytes"); - - // Reserved - if (skipBytesFully(10) != 10) { - throw new CorruptDocumentException(); - } - - int SATSize = input.readInt(); -// System.out.println("normalSATSize: " + SATSize); - - directorySId = input.readInt(); -// System.out.println("directorySId: " + directorySId); - - // Reserved - if (skipBytesFully(4) != 4) { - throw new CorruptDocumentException(); - } - - minStreamSize = input.readInt(); -// System.out.println("minStreamSize: " + minStreamSize + " bytes"); - - shortSATSId = input.readInt(); -// System.out.println("shortSATSId: " + shortSATSId); - shortSATSize = input.readInt(); -// System.out.println("shortSATSize: " + shortSATSize); - int masterSATSId = input.readInt(); -// System.out.println("masterSATSId: " + masterSATSId); - int masterSATSize = input.readInt(); -// System.out.println("masterSATSize: " + masterSATSize); - - // Read masterSAT: 436 bytes, containing up to 109 SIDs - //System.out.println("MSAT:"); - masterSAT = new int[SATSize]; - final int headerSIds = Math.min(SATSize, 109); - for (int i = 0; i < headerSIds; i++) { - masterSAT[i] = input.readInt(); - //System.out.println("\tSID(" + i + "): " + masterSAT[i]); - } - - if (masterSATSId == END_OF_CHAIN_SID) { - // End of chain - int freeSIdLength = 436 - (SATSize * 4); - if (skipBytesFully(freeSIdLength) != freeSIdLength) { - throw new CorruptDocumentException(); - } - } - else { - // Parse the SIDs in the extended MasterSAT sectors... - seekToSId(masterSATSId, FREE_SID); - - int index = headerSIds; - for (int i = 0; i < masterSATSize; i++) { - for (int j = 0; j < 127; j++) { - int sid = input.readInt(); - switch (sid) { - case FREE_SID:// Free - break; - default: - masterSAT[index++] = sid; - break; - } - } - - int next = input.readInt(); - if (next == END_OF_CHAIN_SID) {// End of chain - break; - } - - seekToSId(next, FREE_SID); - } - } - } - - private int skipBytesFully(final int n) throws IOException { - int toSkip = n; - - while (toSkip > 0) { - int skipped = input.skipBytes(n); - if (skipped <= 0) { - break; - } - - toSkip -= skipped; - } - - return n - toSkip; - } - - private void readSAT() throws IOException { - if (SAT != null) { - return; - } - - final int intsPerSector = sectorSize / 4; - - // Read the Sector Allocation Table - SAT = new int[masterSAT.length * intsPerSector]; - - for (int i = 0; i < masterSAT.length; i++) { - seekToSId(masterSAT[i], FREE_SID); - - for (int j = 0; j < intsPerSector; j++) { - int nextSID = input.readInt(); - int index = (j + (i * intsPerSector)); - - SAT[index] = nextSID; - } - } - - // Read the short-stream Sector Allocation Table - SIdChain chain = getSIdChain(shortSATSId, FREE_SID); - shortSAT = new int[shortSATSize * intsPerSector]; - for (int i = 0; i < shortSATSize; i++) { - seekToSId(chain.get(i), FREE_SID); - - for (int j = 0; j < intsPerSector; j++) { - int nextSID = input.readInt(); - int index = (j + (i * intsPerSector)); - - shortSAT[index] = nextSID; - } - } - } - - /** - * Gets the SIdChain for the given stream Id - * - * @param pSId the stream Id - * @param pStreamSize the size of the stream, or -1 for system control streams - * @return the SIdChain for the given stream Id - * @throws IOException if an I/O exception occurs - */ - private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException { - SIdChain chain = new SIdChain(); - - int[] sat = isShortStream(pStreamSize) ? shortSAT : SAT; - - int sid = pSId; - while (sid != END_OF_CHAIN_SID && sid != FREE_SID) { - chain.addSID(sid); - sid = sat[sid]; - } - - return chain; - } - - private boolean isShortStream(final long pStreamSize) { - return pStreamSize != FREE_SID && pStreamSize < minStreamSize; - } - - /** - * Seeks to the start pos for the given stream Id - * - * @param pSId the stream Id - * @param pStreamSize the size of the stream, or -1 for system control streams - * @throws IOException if an I/O exception occurs - */ - private void seekToSId(final int pSId, final long pStreamSize) throws IOException { - long pos; - - if (isShortStream(pStreamSize)) { - // The short stream is not continuous... - Entry root = getRootEntry(); - if (shortStreamSIdChain == null) { - shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize); - } - -// System.err.println("pSId: " + pSId); - int shortPerSId = sectorSize / shortSectorSize; -// System.err.println("shortPerSId: " + shortPerSId); - int offset = pSId / shortPerSId; -// System.err.println("offset: " + offset); - int shortOffset = pSId - (offset * shortPerSId); -// System.err.println("shortOffset: " + shortOffset); -// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset)); - - pos = HEADER_SIZE - + (shortStreamSIdChain.get(offset) * (long) sectorSize) - + (shortOffset * (long) shortSectorSize); -// System.err.println("pos: " + pos); - } - else { - pos = HEADER_SIZE + pSId * (long) sectorSize; - } - - if (input instanceof LittleEndianRandomAccessFile) { - ((LittleEndianRandomAccessFile) input).seek(pos); - } - else if (input instanceof ImageInputStream) { - ((ImageInputStream) input).seek(pos); - } - else { - ((SeekableLittleEndianDataInputStream) input).seek(pos); - } - } - - private void seekToDId(final int pDId) throws IOException { - if (directorySIdChain == null) { - directorySIdChain = getSIdChain(directorySId, FREE_SID); - } - - int dIdsPerSId = sectorSize / Entry.LENGTH; - - int sIdOffset = pDId / dIdsPerSId; - int dIdOffset = pDId - (sIdOffset * dIdsPerSId); - - int sId = directorySIdChain.get(sIdOffset); - - seekToSId(sId, FREE_SID); - if (input instanceof LittleEndianRandomAccessFile) { - LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input; - input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH); - } - else if (input instanceof ImageInputStream) { - ImageInputStream input = (ImageInputStream) this.input; - input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH); - } - else { - SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input; - input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH); - } - } - - SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException { - SIdChain chain = getSIdChain(pStreamId, pStreamSize); - - // TODO: Detach? Means, we have to copy to a byte buffer, or keep track of - // positions, and seek back and forth (would be cool, but difficult).. - int sectorSize = pStreamSize < minStreamSize ? shortSectorSize : this.sectorSize; - - return new MemoryCacheSeekableStream(new Stream(chain, pStreamSize, sectorSize, this)); - } - - private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException { - // This is always exactly 128 bytes, so we'll just read it all, - // and buffer (we might want to optimize this later). - byte[] bytes = new byte[Entry.LENGTH]; - - seekToDId(pDirectoryId); - input.readFully(bytes); - - return new ByteArrayInputStream(bytes); - } - - Entry getEntry(final int pDirectoryId, Entry pParent) throws IOException { - Entry entry = Entry.readEntry(new LittleEndianDataInputStream( - getDirectoryStreamForDId(pDirectoryId) - )); - entry.parent = pParent; - entry.document = this; - return entry; - } - - SortedSet getEntries(final int pDirectoryId, final Entry pParent) - throws IOException { - return getEntriesRecursive(pDirectoryId, pParent, new TreeSet()); - } - - private SortedSet getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet pEntries) - throws IOException { - - //System.out.println("pDirectoryId: " + pDirectoryId); - - Entry entry = getEntry(pDirectoryId, pParent); - - //System.out.println("entry: " + entry); - - if (!pEntries.add(entry)) { - // TODO: This occurs in some Thumbs.db files, and Windows will - // still parse the file gracefully somehow... - // Deleting and regenerating the file will remove the cyclic - // references, but... How can Windows parse this file? - throw new CorruptDocumentException("Cyclic chain reference for entry: " + pDirectoryId); - } - - if (entry.prevDId != FREE_SID) { - //System.out.println("prevDId: " + entry.prevDId); - getEntriesRecursive(entry.prevDId, pParent, pEntries); - } - if (entry.nextDId != FREE_SID) { - //System.out.println("nextDId: " + entry.nextDId); - getEntriesRecursive(entry.nextDId, pParent, pEntries); - } - - return pEntries; - } - - /*public*/ Entry getEntry(String pPath) throws IOException { - if (StringUtil.isEmpty(pPath) || !pPath.startsWith("/")) { - throw new IllegalArgumentException("Path must be absolute, and contain a valid path: " + pPath); - } - - Entry entry = getRootEntry(); - if (pPath.equals("/")) { - // '/' means root entry - return entry; - } - else { - // Otherwise get children recursively: - String[] pathElements = StringUtil.toStringArray(pPath, "/"); - for (String pathElement : pathElements) { - entry = entry.getChildEntry(pathElement); - - // No such child... - if (entry == null) { - break;// TODO: FileNotFoundException? Should behave like Entry.getChildEntry!! - } - } - return entry; - } - } - - public Entry getRootEntry() throws IOException { - if (rootEntry == null) { - readSAT(); - - rootEntry = getEntry(0, null); - - if (rootEntry.type != Entry.ROOT_STORAGE) { - throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type); - } - } - - return rootEntry; - } - - // This is useless, as most documents on file have all-zero UUIDs... -// @Override -// public int hashCode() { -// return uUID.hashCode(); -// } -// -// @Override -// public boolean equals(final Object pOther) { -// if (pOther == this) { -// return true; -// } -// -// if (pOther == null) { -// return true; -// } -// -// if (pOther.getClass() == getClass()) { -// return uUID.equals(((CompoundDocument) pOther).uUID); -// } -// -// return false; -// } - - @Override - public String toString() { - return String.format( - "%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]", - getClass().getSimpleName(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.length - ); - } - - /** - * Converts the given time stamp to standard Java time representation, - * milliseconds since January 1, 1970. - * The time stamp parameter is assumed to be in units of - * 100 nano seconds since January 1, 1601. - *

- * If the timestamp is {@code 0L} (meaning not specified), no conversion - * is done, to behave like {@code java.io.File}. - * - * @param pMSTime an unsigned long value representing the time stamp (in - * units of 100 nano seconds since January 1, 1601). - * - * @return the time stamp converted to Java time stamp in milliseconds, - * or {@code 0L} if {@code pMSTime == 0L} - */ - public static long toJavaTimeInMillis(final long pMSTime) { - // NOTE: The time stamp field is an unsigned 64-bit integer value that - // contains the time elapsed since 1601-Jan-01 00:00:00 (Gregorian - // calendar). - // One unit of this value is equal to 100 nanoseconds). - // That means, each second the time stamp value will be increased by - // 10 million units. - - if (pMSTime == 0L) { - return 0L; // This is just less confusing... - } - - // Convert to milliseconds (signed), - // then convert to Java std epoch (1970-Jan-01 00:00:00) - return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET; - } - - static class Stream extends InputStream { - private final SIdChain chain; - private final CompoundDocument document; - private final long length; - - private long streamPos; - private int nextSectorPos; - private byte[] buffer; - private int bufferPos; - - public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) { - this.chain = chain; - this.length = streamSize; - - this.buffer = new byte[sectorSize]; - this.bufferPos = buffer.length; - - this.document = document; - } - - @Override - public int available() throws IOException { - return (int) Math.min(buffer.length - bufferPos, length - streamPos); - } - - public int read() throws IOException { - if (available() <= 0) { - if (!fillBuffer()) { - return -1; - } - } - - streamPos++; - - return buffer[bufferPos++] & 0xff; - } - - private boolean fillBuffer() throws IOException { - if (streamPos < length && nextSectorPos < chain.length()) { - // TODO: Sync on document.input here, and we are completely detached... :-) - // TODO: Update: We also need to sync other places... :-P - synchronized (document) { - document.seekToSId(chain.get(nextSectorPos), length); - document.input.readFully(buffer); - } - - nextSectorPos++; - bufferPos = 0; - - return true; - } - - return false; - } - - @Override - public int read(byte b[], int off, int len) throws IOException { - if (available() <= 0) { - if (!fillBuffer()) { - return -1; - } - } - - int toRead = Math.min(len, available()); - - System.arraycopy(buffer, bufferPos, b, off, toRead); - bufferPos += toRead; - streamPos += toRead; - - return toRead; - } - - @Override - public void close() throws IOException { - buffer = null; - } - } - - static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable { - private final SeekableInputStream seekable; - - public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) { - super(pInput); - seekable = pInput; - } - - public void seek(final long pPosition) throws IOException { - seekable.seek(pPosition); - } - - public boolean isCachedFile() { - return seekable.isCachedFile(); - } - - public boolean isCachedMemory() { - return seekable.isCachedMemory(); - } - - public boolean isCached() { - return seekable.isCached(); - } - - public long getStreamPosition() throws IOException { - return seekable.getStreamPosition(); - } - - public long getFlushedPosition() throws IOException { - return seekable.getFlushedPosition(); - } - - public void flushBefore(final long pPosition) throws IOException { - seekable.flushBefore(pPosition); - } - - public void flush() throws IOException { - seekable.flush(); - } - - @Override - public void reset() throws IOException { - seekable.reset(); - } - - public void mark() { - seekable.mark(); - } - } -} +/* + * 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.io.ole2; + +import com.twelvemonkeys.io.*; +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.stream.ImageInputStream; +import java.io.*; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * Represents a read-only OLE2 compound document. + *

+ * + * NOTE: This class is not synchronized. Accessing the document or its + * entries from different threads, will need synchronization on the document + * instance. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java#4 $ + */ +public final class CompoundDocument implements AutoCloseable { + // TODO: Write support... + // TODO: Properties: http://support.microsoft.com/kb/186898 + + static final byte[] MAGIC = new byte[]{ + (byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0, + (byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1, + }; + + private static final int FREE_SID = -1; + private static final int END_OF_CHAIN_SID = -2; + private static final int SAT_SECTOR_SID = -3; // Sector used by SAT + private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT + + public static final int HEADER_SIZE = 512; + + /** The epoch offset of CompoundDocument time stamps */ + public final static long EPOCH_OFFSET = -11644477200000L; + + private final DataInput input; + + private UUID uUID; + + private int sectorSize; + private int shortSectorSize; + + private int directorySId; + + private int minStreamSize; + + private int shortSATSId; + private int shortSATSize; + + // Master Sector Allocation Table + private int[] masterSAT; + private int[] SAT; + private int[] shortSAT; + + private Entry rootEntry; + private SIdChain shortStreamSIdChain; + private SIdChain directorySIdChain; + + /** + * Creates a (for now) read only {@code CompoundDocument}. + *

+ * Warning! You must invoke {@link #close()} on the compound document + * created from this constructor when done, to avoid leaking file + * descriptors. + *

+ * + * @param file the file to read from + * + * @throws IOException if an I/O exception occurs while reading the header + */ + public CompoundDocument(final File file) throws IOException { + // TODO: We need to close this (or it's underlying RAF)! Otherwise we're leaking file descriptors! + input = new LittleEndianRandomAccessFile(FileUtil.resolve(file), "r"); + + // TODO: Might be better to read header on first read operation?! + // OTOH: It's also good to be fail-fast, so at least we should make + // sure we're reading a valid document + readHeader(); + } + + /** + * Creates a read only {@code CompoundDocument}. + * + * @param pInput the input to read from. + * + * @throws IOException if an I/O exception occurs while reading the header + */ + public CompoundDocument(final InputStream pInput) throws IOException { + this(new MemoryCacheSeekableStream(pInput)); + } + + // For testing only, consider exposing later + CompoundDocument(final SeekableInputStream stream) throws IOException { + input = new SeekableLittleEndianDataInputStream(stream); + + // TODO: Might be better to read header on first read operation?! + // OTOH: It's also good to be fail-fast, so at least we should make + // sure we're reading a valid document + readHeader(); + } + + /** + * Creates a read only {@code CompoundDocument}. + * + * @param input the input to read from + * + * @throws IOException if an I/O exception occurs while reading the header + */ + public CompoundDocument(final ImageInputStream input) throws IOException { + this.input = notNull(input, "input"); + + // This implementation only supports little endian (Intel) CompoundDocuments + input.setByteOrder(ByteOrder.LITTLE_ENDIAN); + + // TODO: Might be better to read header on first read operation?! + // OTOH: It's also good to be fail-fast, so at least we should make + // sure we're reading a valid document + readHeader(); + } + + /** + * This method will close the underlying {@link RandomAccessFile} if any, + * but will leave any stream created outside the document open. + * + * @see #CompoundDocument(File) + * @see RandomAccessFile#close() + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + if (input instanceof RandomAccessFile) { + ((RandomAccessFile) input).close(); + } + else if (input instanceof LittleEndianRandomAccessFile) { + ((LittleEndianRandomAccessFile) input).close(); + } + + // Other streams are left open + } + + public static boolean canRead(final DataInput pInput) { + return canRead(pInput, true); + } + + // TODO: Refactor.. Figure out what we really need to expose to ImageIO for + // easy reading of the Thumbs.db file + // It's probably safer to create one version for InputStream and one for File + private static boolean canRead(final DataInput pInput, final boolean pReset) { + long pos = FREE_SID; + if (pReset) { + try { + if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) { + ((InputStream) pInput).mark(8); + } + else if (pInput instanceof ImageInputStream) { + ((ImageInputStream) pInput).mark(); + } + else if (pInput instanceof RandomAccessFile) { + pos = ((RandomAccessFile) pInput).getFilePointer(); + } + else if (pInput instanceof LittleEndianRandomAccessFile) { + pos = ((LittleEndianRandomAccessFile) pInput).getFilePointer(); + } + else { + return false; + } + } + catch (IOException ignore) { + return false; + } + } + + try { + byte[] magic = new byte[8]; + pInput.readFully(magic); + return Arrays.equals(magic, MAGIC); + } + catch (IOException ignore) { + // Ignore + } + finally { + if (pReset) { + try { + if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) { + ((InputStream) pInput).reset(); + } + else if (pInput instanceof ImageInputStream) { + ((ImageInputStream) pInput).reset(); + } + else if (pInput instanceof RandomAccessFile) { + ((RandomAccessFile) pInput).seek(pos); + } + else if (pInput instanceof LittleEndianRandomAccessFile) { + ((LittleEndianRandomAccessFile) pInput).seek(pos); + } + } + catch (IOException e) { + // TODO: This isn't actually good enough... + // Means something fucked up, and will fail... + e.printStackTrace(); + } + } + } + + return false; + } + + private void readHeader() throws IOException { + if (masterSAT != null) { + return; + } + + if (!canRead(input, false)) { + throw new CorruptDocumentException("Not an OLE 2 Compound Document"); + } + + // UID (seems to be all 0s) + uUID = new UUID(input.readLong(), input.readLong()); +// System.out.println("uUID: " + uUID); + + // int version = + input.readUnsignedShort(); +// System.out.println("version: " + version); + // int revision = + input.readUnsignedShort(); +// System.out.println("revision: " + revision); + + int byteOrder = input.readUnsignedShort(); +// System.out.printf("byteOrder: 0x%04x\n", byteOrder); + if (byteOrder == 0xffff) { + throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents"); + } + else if (byteOrder != 0xfffe) { + // Reversed, as I'm already reading little-endian + throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder)); + } + + sectorSize = 1 << input.readUnsignedShort(); +// System.out.println("sectorSize: " + sectorSize + " bytes"); + shortSectorSize = 1 << input.readUnsignedShort(); +// System.out.println("shortSectorSize: " + shortSectorSize + " bytes"); + + // Reserved + if (skipBytesFully(10) != 10) { + throw new CorruptDocumentException(); + } + + int SATSize = input.readInt(); +// System.out.println("normalSATSize: " + SATSize); + + directorySId = input.readInt(); +// System.out.println("directorySId: " + directorySId); + + // Reserved + if (skipBytesFully(4) != 4) { + throw new CorruptDocumentException(); + } + + minStreamSize = input.readInt(); +// System.out.println("minStreamSize: " + minStreamSize + " bytes"); + + shortSATSId = input.readInt(); +// System.out.println("shortSATSId: " + shortSATSId); + shortSATSize = input.readInt(); +// System.out.println("shortSATSize: " + shortSATSize); + int masterSATSId = input.readInt(); +// System.out.println("masterSATSId: " + masterSATSId); + int masterSATSize = input.readInt(); +// System.out.println("masterSATSize: " + masterSATSize); + + // Read masterSAT: 436 bytes, containing up to 109 SIDs + //System.out.println("MSAT:"); + masterSAT = new int[SATSize]; + final int headerSIds = Math.min(SATSize, 109); + for (int i = 0; i < headerSIds; i++) { + masterSAT[i] = input.readInt(); + //System.out.println("\tSID(" + i + "): " + masterSAT[i]); + } + + if (masterSATSId == END_OF_CHAIN_SID) { + // End of chain + int freeSIdLength = 436 - (SATSize * 4); + if (skipBytesFully(freeSIdLength) != freeSIdLength) { + throw new CorruptDocumentException(); + } + } + else { + // Parse the SIDs in the extended MasterSAT sectors... + seekToSId(masterSATSId, FREE_SID); + + int index = headerSIds; + for (int i = 0; i < masterSATSize; i++) { + for (int j = 0; j < 127; j++) { + int sid = input.readInt(); + switch (sid) { + case FREE_SID:// Free + break; + default: + masterSAT[index++] = sid; + break; + } + } + + int next = input.readInt(); + if (next == END_OF_CHAIN_SID) {// End of chain + break; + } + + seekToSId(next, FREE_SID); + } + } + } + + private int skipBytesFully(final int n) throws IOException { + int toSkip = n; + + while (toSkip > 0) { + int skipped = input.skipBytes(n); + if (skipped <= 0) { + break; + } + + toSkip -= skipped; + } + + return n - toSkip; + } + + private void readSAT() throws IOException { + if (SAT != null) { + return; + } + + final int intsPerSector = sectorSize / 4; + + // Read the Sector Allocation Table + SAT = new int[masterSAT.length * intsPerSector]; + + for (int i = 0; i < masterSAT.length; i++) { + seekToSId(masterSAT[i], FREE_SID); + + for (int j = 0; j < intsPerSector; j++) { + int nextSID = input.readInt(); + int index = (j + (i * intsPerSector)); + + SAT[index] = nextSID; + } + } + + // Read the short-stream Sector Allocation Table + SIdChain chain = getSIdChain(shortSATSId, FREE_SID); + shortSAT = new int[shortSATSize * intsPerSector]; + for (int i = 0; i < shortSATSize; i++) { + seekToSId(chain.get(i), FREE_SID); + + for (int j = 0; j < intsPerSector; j++) { + int nextSID = input.readInt(); + int index = (j + (i * intsPerSector)); + + shortSAT[index] = nextSID; + } + } + } + + /** + * Gets the SIdChain for the given stream Id + * + * @param pSId the stream Id + * @param pStreamSize the size of the stream, or -1 for system control streams + * @return the SIdChain for the given stream Id + * @throws IOException if an I/O exception occurs + */ + private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException { + SIdChain chain = new SIdChain(); + + int[] sat = isShortStream(pStreamSize) ? shortSAT : SAT; + + int sid = pSId; + while (sid != END_OF_CHAIN_SID && sid != FREE_SID) { + chain.addSID(sid); + sid = sat[sid]; + } + + return chain; + } + + private boolean isShortStream(final long pStreamSize) { + return pStreamSize != FREE_SID && pStreamSize < minStreamSize; + } + + /** + * Seeks to the start pos for the given stream Id + * + * @param pSId the stream Id + * @param pStreamSize the size of the stream, or -1 for system control streams + * @throws IOException if an I/O exception occurs + */ + private void seekToSId(final int pSId, final long pStreamSize) throws IOException { + long pos; + + if (isShortStream(pStreamSize)) { + // The short stream is not continuous... + Entry root = getRootEntry(); + if (shortStreamSIdChain == null) { + shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize); + } + +// System.err.println("pSId: " + pSId); + int shortPerSId = sectorSize / shortSectorSize; +// System.err.println("shortPerSId: " + shortPerSId); + int offset = pSId / shortPerSId; +// System.err.println("offset: " + offset); + int shortOffset = pSId - (offset * shortPerSId); +// System.err.println("shortOffset: " + shortOffset); +// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset)); + + pos = HEADER_SIZE + + (shortStreamSIdChain.get(offset) * (long) sectorSize) + + (shortOffset * (long) shortSectorSize); +// System.err.println("pos: " + pos); + } + else { + pos = HEADER_SIZE + pSId * (long) sectorSize; + } + + if (input instanceof LittleEndianRandomAccessFile) { + ((LittleEndianRandomAccessFile) input).seek(pos); + } + else if (input instanceof ImageInputStream) { + ((ImageInputStream) input).seek(pos); + } + else { + ((SeekableLittleEndianDataInputStream) input).seek(pos); + } + } + + private void seekToDId(final int pDId) throws IOException { + if (directorySIdChain == null) { + directorySIdChain = getSIdChain(directorySId, FREE_SID); + } + + int dIdsPerSId = sectorSize / Entry.LENGTH; + + int sIdOffset = pDId / dIdsPerSId; + int dIdOffset = pDId - (sIdOffset * dIdsPerSId); + + int sId = directorySIdChain.get(sIdOffset); + + seekToSId(sId, FREE_SID); + if (input instanceof LittleEndianRandomAccessFile) { + LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input; + input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH); + } + else if (input instanceof ImageInputStream) { + ImageInputStream input = (ImageInputStream) this.input; + input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH); + } + else { + SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input; + input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH); + } + } + + SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException { + SIdChain chain = getSIdChain(pStreamId, pStreamSize); + + // TODO: Detach? Means, we have to copy to a byte buffer, or keep track of + // positions, and seek back and forth (would be cool, but difficult).. + int sectorSize = pStreamSize < minStreamSize ? shortSectorSize : this.sectorSize; + + return new MemoryCacheSeekableStream(new Stream(chain, pStreamSize, sectorSize, this)); + } + + private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException { + // This is always exactly 128 bytes, so we'll just read it all, + // and buffer (we might want to optimize this later). + byte[] bytes = new byte[Entry.LENGTH]; + + seekToDId(pDirectoryId); + input.readFully(bytes); + + return new ByteArrayInputStream(bytes); + } + + Entry getEntry(final int pDirectoryId, Entry pParent) throws IOException { + Entry entry = Entry.readEntry(new LittleEndianDataInputStream( + getDirectoryStreamForDId(pDirectoryId) + )); + entry.parent = pParent; + entry.document = this; + return entry; + } + + SortedSet getEntries(final int pDirectoryId, final Entry pParent) + throws IOException { + return getEntriesRecursive(pDirectoryId, pParent, new TreeSet()); + } + + private SortedSet getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet pEntries) + throws IOException { + + //System.out.println("pDirectoryId: " + pDirectoryId); + + Entry entry = getEntry(pDirectoryId, pParent); + + //System.out.println("entry: " + entry); + + if (!pEntries.add(entry)) { + // TODO: This occurs in some Thumbs.db files, and Windows will + // still parse the file gracefully somehow... + // Deleting and regenerating the file will remove the cyclic + // references, but... How can Windows parse this file? + throw new CorruptDocumentException("Cyclic chain reference for entry: " + pDirectoryId); + } + + if (entry.prevDId != FREE_SID) { + //System.out.println("prevDId: " + entry.prevDId); + getEntriesRecursive(entry.prevDId, pParent, pEntries); + } + if (entry.nextDId != FREE_SID) { + //System.out.println("nextDId: " + entry.nextDId); + getEntriesRecursive(entry.nextDId, pParent, pEntries); + } + + return pEntries; + } + + /*public*/ Entry getEntry(String pPath) throws IOException { + if (StringUtil.isEmpty(pPath) || !pPath.startsWith("/")) { + throw new IllegalArgumentException("Path must be absolute, and contain a valid path: " + pPath); + } + + Entry entry = getRootEntry(); + if (pPath.equals("/")) { + // '/' means root entry + return entry; + } + else { + // Otherwise get children recursively: + String[] pathElements = StringUtil.toStringArray(pPath, "/"); + for (String pathElement : pathElements) { + entry = entry.getChildEntry(pathElement); + + // No such child... + if (entry == null) { + break;// TODO: FileNotFoundException? Should behave like Entry.getChildEntry!! + } + } + return entry; + } + } + + public Entry getRootEntry() throws IOException { + if (rootEntry == null) { + readSAT(); + + rootEntry = getEntry(0, null); + + if (rootEntry.type != Entry.ROOT_STORAGE) { + throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type); + } + } + + return rootEntry; + } + + // This is useless, as most documents on file have all-zero UUIDs... +// @Override +// public int hashCode() { +// return uUID.hashCode(); +// } +// +// @Override +// public boolean equals(final Object pOther) { +// if (pOther == this) { +// return true; +// } +// +// if (pOther == null) { +// return true; +// } +// +// if (pOther.getClass() == getClass()) { +// return uUID.equals(((CompoundDocument) pOther).uUID); +// } +// +// return false; +// } + + @Override + public String toString() { + return String.format( + "%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]", + getClass().getSimpleName(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.length + ); + } + + /** + * Converts the given time stamp to standard Java time representation, + * milliseconds since January 1, 1970. + * The time stamp parameter is assumed to be in units of + * 100 nano seconds since January 1, 1601. + *

+ * If the timestamp is {@code 0L} (meaning not specified), no conversion + * is done, to behave like {@code java.io.File}. + *

+ * + * @param pMSTime an unsigned long value representing the time stamp (in + * units of 100 nano seconds since January 1, 1601). + * + * @return the time stamp converted to Java time stamp in milliseconds, + * or {@code 0L} if {@code pMSTime == 0L} + */ + public static long toJavaTimeInMillis(final long pMSTime) { + // NOTE: The time stamp field is an unsigned 64-bit integer value that + // contains the time elapsed since 1601-Jan-01 00:00:00 (Gregorian + // calendar). + // One unit of this value is equal to 100 nanoseconds). + // That means, each second the time stamp value will be increased by + // 10 million units. + + if (pMSTime == 0L) { + return 0L; // This is just less confusing... + } + + // Convert to milliseconds (signed), + // then convert to Java std epoch (1970-Jan-01 00:00:00) + return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET; + } + + static class Stream extends InputStream { + private final SIdChain chain; + private final CompoundDocument document; + private final long length; + + private long streamPos; + private int nextSectorPos; + private byte[] buffer; + private int bufferPos; + + public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) { + this.chain = chain; + this.length = streamSize; + + this.buffer = new byte[sectorSize]; + this.bufferPos = buffer.length; + + this.document = document; + } + + @Override + public int available() throws IOException { + return (int) Math.min(buffer.length - bufferPos, length - streamPos); + } + + public int read() throws IOException { + if (available() <= 0) { + if (!fillBuffer()) { + return -1; + } + } + + streamPos++; + + return buffer[bufferPos++] & 0xff; + } + + private boolean fillBuffer() throws IOException { + if (streamPos < length && nextSectorPos < chain.length()) { + // TODO: Sync on document.input here, and we are completely detached... :-) + // TODO: Update: We also need to sync other places... :-P + synchronized (document) { + document.seekToSId(chain.get(nextSectorPos), length); + document.input.readFully(buffer); + } + + nextSectorPos++; + bufferPos = 0; + + return true; + } + + return false; + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (available() <= 0) { + if (!fillBuffer()) { + return -1; + } + } + + int toRead = Math.min(len, available()); + + System.arraycopy(buffer, bufferPos, b, off, toRead); + bufferPos += toRead; + streamPos += toRead; + + return toRead; + } + + @Override + public void close() throws IOException { + buffer = null; + } + } + + static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable { + private final SeekableInputStream seekable; + + public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) { + super(pInput); + seekable = pInput; + } + + public void seek(final long pPosition) throws IOException { + seekable.seek(pPosition); + } + + public boolean isCachedFile() { + return seekable.isCachedFile(); + } + + public boolean isCachedMemory() { + return seekable.isCachedMemory(); + } + + public boolean isCached() { + return seekable.isCached(); + } + + public long getStreamPosition() throws IOException { + return seekable.getStreamPosition(); + } + + public long getFlushedPosition() throws IOException { + return seekable.getFlushedPosition(); + } + + public void flushBefore(final long pPosition) throws IOException { + seekable.flushBefore(pPosition); + } + + public void flush() throws IOException { + seekable.flush(); + } + + @Override + public void reset() throws IOException { + seekable.reset(); + } + + public void mark() { + seekable.mark(); + } + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CorruptDocumentException.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CorruptDocumentException.java index e7340407..9a6f020c 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CorruptDocumentException.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CorruptDocumentException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.io.ole2; diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java index d4ac6b60..1364c7cc 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java @@ -1,340 +1,344 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.ole2; - -import com.twelvemonkeys.io.SeekableInputStream; - -import java.io.DataInput; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.SortedSet; -import java.util.TreeSet; - -/** - * Represents an OLE 2 compound document entry. - * This is similar to a file in a file system, or an entry in a ZIP or JAR file. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/Entry.java#4 $ - * @see com.twelvemonkeys.io.ole2.CompoundDocument - */ -// TODO: Consider extending java.io.File... -public final class Entry implements Comparable { - String name; - byte type; - byte nodeColor; - - int prevDId; - int nextDId; - int rootNodeDId; - - long createdTimestamp; - long modifiedTimestamp; - - int startSId; - int streamSize; - - CompoundDocument document; - Entry parent; - SortedSet children; - - public final static int LENGTH = 128; - - static final int EMPTY = 0; - static final int USER_STORAGE = 1; - static final int USER_STREAM = 2; - static final int LOCK_BYTES = 3; - static final int PROPERTY = 4; - static final int ROOT_STORAGE = 5; - - private static final SortedSet NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet()); - - private Entry() { - } - - /** - * Reads an entry from the input. - * - * @param pInput the input data - * @return the {@code Entry} read from the input data - * @throws IOException if an i/o exception occurs during reading - */ - static Entry readEntry(final DataInput pInput) throws IOException { - Entry p = new Entry(); - p.read(pInput); - return p; - } - - /** - * Reads this entry - * - * @param pInput the input data - * @throws IOException if an i/o exception occurs during reading - */ - private void read(final DataInput pInput) throws IOException { - byte[] bytes = new byte[64]; - pInput.readFully(bytes); - - // NOTE: Length is in bytes, including the null-terminator... - int nameLength = pInput.readShort(); - name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE")); -// System.out.println("name: " + name); - - type = pInput.readByte(); -// System.out.println("type: " + type); - - nodeColor = pInput.readByte(); -// System.out.println("nodeColor: " + nodeColor); - - prevDId = pInput.readInt(); -// System.out.println("prevDId: " + prevDId); - nextDId = pInput.readInt(); -// System.out.println("nextDId: " + nextDId); - rootNodeDId = pInput.readInt(); -// System.out.println("rootNodeDId: " + rootNodeDId); - - // UID (16) + user flags (4), ignored - if (pInput.skipBytes(20) != 20) { - throw new CorruptDocumentException(); - } - - createdTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong()); - modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong()); - - startSId = pInput.readInt(); -// System.out.println("startSId: " + startSId); - streamSize = pInput.readInt(); -// System.out.println("streamSize: " + streamSize); - - // Reserved - pInput.readInt(); - } - - /** - * If {@code true} this {@code Entry} is the root {@code Entry}. - * - * @return {@code true} if this is the root {@code Entry} - */ - public boolean isRoot() { - return type == ROOT_STORAGE; - } - - /** - * If {@code true} this {@code Entry} is a directory - * {@code Entry}. - * - * @return {@code true} if this is a directory {@code Entry} - */ - public boolean isDirectory() { - return type == USER_STORAGE; - } - - /** - * If {@code true} this {@code Entry} is a file (document) - * {@code Entry}. - * - * @return {@code true} if this is a document {@code Entry} - */ - public boolean isFile() { - return type == USER_STREAM; - } - - /** - * Returns the name of this {@code Entry} - * - * @return the name of this {@code Entry} - */ - public String getName() { - return name; - } - - /** - * Returns the {@code InputStream} for this {@code Entry} - * - * @return an {@code InputStream} containing the data for this - * {@code Entry} or {@code null} if this is a directory {@code Entry} - * @throws java.io.IOException if an I/O exception occurs - * @see #length() - */ - public SeekableInputStream getInputStream() throws IOException { - if (!isFile()) { - return null; - } - - return document.getInputStreamForSId(startSId, streamSize); - } - - /** - * Returns the length of this entry - * - * @return the length of the stream for this entry, or {@code 0} if this is - * a directory {@code Entry} - * @see #getInputStream() - */ - public long length() { - if (!isFile()) { - return 0L; - } - - return streamSize; - } - - /** - * Returns the time that this entry was created. - * The time is converted from its internal representation to standard Java - * representation, milliseconds since the epoch - * (00:00:00 GMT, January 1, 1970). - *

- * Note that most applications leaves this value empty ({@code 0L}). - * - * @return A {@code long} value representing the time this entry was - * created, measured in milliseconds since the epoch - * (00:00:00 GMT, January 1, 1970), or {@code 0L} if no - * creation time stamp exists for this entry. - */ - public long created() { - return createdTimestamp; - } - - /** - * Returns the time that this entry was last modified. - * The time is converted from its internal representation to standard Java - * representation, milliseconds since the epoch - * (00:00:00 GMT, January 1, 1970). - *

- * Note that many applications leaves this value empty ({@code 0L}). - * - * @return A {@code long} value representing the time this entry was - * last modified, measured in milliseconds since the epoch - * (00:00:00 GMT, January 1, 1970), or {@code 0L} if no - * modification time stamp exists for this entry. - */ - public long lastModified() { - return modifiedTimestamp; - } - - /** - * Return the parent of this {@code Entry} - * - * @return the parent of this {@code Entry}, or {@code null} if this is - * the root {@code Entry} - */ - public Entry getParentEntry() { - return parent; - } - - /** - * Returns the child of this {@code Entry} with the given name. - * - * @param pName the name of the child {@code Entry} - * @return the child {@code Entry} or {@code null} if thee is no such - * child - * @throws java.io.IOException if an I/O exception occurs - */ - public Entry getChildEntry(final String pName) throws IOException { - if (isFile() || rootNodeDId == -1) { - return null; - } - - Entry dummy = new Entry(); - dummy.name = pName; - dummy.parent = this; - - SortedSet child = getChildEntries().tailSet(dummy); - return (Entry) child.first(); - } - - /** - * Returns the children of this {@code Entry}. - * - * @return a {@code SortedSet} of {@code Entry} objects - * @throws java.io.IOException if an I/O exception occurs - */ - public SortedSet getChildEntries() throws IOException { - if (children == null) { - if (isFile() || rootNodeDId == -1) { - children = NO_CHILDREN; - } - else { - // Start at root node in R/B tree, and read to the left and right, - // re-build tree, according to the docs - children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this)); - } - } - - return children; - } - - @Override - public String toString() { - return "\"" + name + "\"" - + " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root")) - + (parent != null ? ", parent: \"" + parent.getName() + "\"" : "") - + (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.size()) : "(unknown)")) - + ", SId=" + startSId + ", length=" + streamSize + ")"; - } - - @Override - public boolean equals(final Object pOther) { - if (pOther == this) { - return true; - } - if (!(pOther instanceof Entry)) { - return false; - } - - Entry other = (Entry) pOther; - return name.equals(other.name) && (parent == other.parent - || (parent != null && parent.equals(other.parent))); - } - - @Override - public int hashCode() { - return name.hashCode() ^ startSId; - } - - public int compareTo(final Entry pOther) { - if (this == pOther) { - return 0; - } - - // NOTE: This is the sorting algorthm defined by the Compound Document: - // - first sort by name length - // - if lengths are equal, sort by comparing strings, case sensitive - - int diff = name.length() - pOther.name.length(); - if (diff != 0) { - return diff; - } - - return name.compareTo(pOther.name); - } -} +/* + * 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.io.ole2; + +import com.twelvemonkeys.io.SeekableInputStream; + +import java.io.DataInput; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Represents an OLE 2 compound document entry. + * This is similar to a file in a file system, or an entry in a ZIP or JAR file. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/Entry.java#4 $ + * @see com.twelvemonkeys.io.ole2.CompoundDocument + */ +// TODO: Consider extending java.io.File... +public final class Entry implements Comparable { + String name; + byte type; + byte nodeColor; + + int prevDId; + int nextDId; + int rootNodeDId; + + long createdTimestamp; + long modifiedTimestamp; + + int startSId; + int streamSize; + + CompoundDocument document; + Entry parent; + SortedSet children; + + public final static int LENGTH = 128; + + static final int EMPTY = 0; + static final int USER_STORAGE = 1; + static final int USER_STREAM = 2; + static final int LOCK_BYTES = 3; + static final int PROPERTY = 4; + static final int ROOT_STORAGE = 5; + + private static final SortedSet NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet()); + + private Entry() { + } + + /** + * Reads an entry from the input. + * + * @param pInput the input data + * @return the {@code Entry} read from the input data + * @throws IOException if an i/o exception occurs during reading + */ + static Entry readEntry(final DataInput pInput) throws IOException { + Entry p = new Entry(); + p.read(pInput); + return p; + } + + /** + * Reads this entry + * + * @param pInput the input data + * @throws IOException if an i/o exception occurs during reading + */ + private void read(final DataInput pInput) throws IOException { + byte[] bytes = new byte[64]; + pInput.readFully(bytes); + + // NOTE: Length is in bytes, including the null-terminator... + int nameLength = pInput.readShort(); + name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE")); +// System.out.println("name: " + name); + + type = pInput.readByte(); +// System.out.println("type: " + type); + + nodeColor = pInput.readByte(); +// System.out.println("nodeColor: " + nodeColor); + + prevDId = pInput.readInt(); +// System.out.println("prevDId: " + prevDId); + nextDId = pInput.readInt(); +// System.out.println("nextDId: " + nextDId); + rootNodeDId = pInput.readInt(); +// System.out.println("rootNodeDId: " + rootNodeDId); + + // UID (16) + user flags (4), ignored + if (pInput.skipBytes(20) != 20) { + throw new CorruptDocumentException(); + } + + createdTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong()); + modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong()); + + startSId = pInput.readInt(); +// System.out.println("startSId: " + startSId); + streamSize = pInput.readInt(); +// System.out.println("streamSize: " + streamSize); + + // Reserved + pInput.readInt(); + } + + /** + * If {@code true} this {@code Entry} is the root {@code Entry}. + * + * @return {@code true} if this is the root {@code Entry} + */ + public boolean isRoot() { + return type == ROOT_STORAGE; + } + + /** + * If {@code true} this {@code Entry} is a directory + * {@code Entry}. + * + * @return {@code true} if this is a directory {@code Entry} + */ + public boolean isDirectory() { + return type == USER_STORAGE; + } + + /** + * If {@code true} this {@code Entry} is a file (document) + * {@code Entry}. + * + * @return {@code true} if this is a document {@code Entry} + */ + public boolean isFile() { + return type == USER_STREAM; + } + + /** + * Returns the name of this {@code Entry} + * + * @return the name of this {@code Entry} + */ + public String getName() { + return name; + } + + /** + * Returns the {@code InputStream} for this {@code Entry} + * + * @return an {@code InputStream} containing the data for this + * {@code Entry} or {@code null} if this is a directory {@code Entry} + * @throws java.io.IOException if an I/O exception occurs + * @see #length() + */ + public SeekableInputStream getInputStream() throws IOException { + if (!isFile()) { + return null; + } + + return document.getInputStreamForSId(startSId, streamSize); + } + + /** + * Returns the length of this entry + * + * @return the length of the stream for this entry, or {@code 0} if this is + * a directory {@code Entry} + * @see #getInputStream() + */ + public long length() { + if (!isFile()) { + return 0L; + } + + return streamSize; + } + + /** + * Returns the time that this entry was created. + * The time is converted from its internal representation to standard Java + * representation, milliseconds since the epoch + * (00:00:00 GMT, January 1, 1970). + *

+ * Note that most applications leaves this value empty ({@code 0L}). + *

+ * + * @return A {@code long} value representing the time this entry was + * created, measured in milliseconds since the epoch + * (00:00:00 GMT, January 1, 1970), or {@code 0L} if no + * creation time stamp exists for this entry. + */ + public long created() { + return createdTimestamp; + } + + /** + * Returns the time that this entry was last modified. + * The time is converted from its internal representation to standard Java + * representation, milliseconds since the epoch + * (00:00:00 GMT, January 1, 1970). + *

+ * Note that many applications leaves this value empty ({@code 0L}). + *

+ * + * @return A {@code long} value representing the time this entry was + * last modified, measured in milliseconds since the epoch + * (00:00:00 GMT, January 1, 1970), or {@code 0L} if no + * modification time stamp exists for this entry. + */ + public long lastModified() { + return modifiedTimestamp; + } + + /** + * Return the parent of this {@code Entry} + * + * @return the parent of this {@code Entry}, or {@code null} if this is + * the root {@code Entry} + */ + public Entry getParentEntry() { + return parent; + } + + /** + * Returns the child of this {@code Entry} with the given name. + * + * @param pName the name of the child {@code Entry} + * @return the child {@code Entry} or {@code null} if thee is no such + * child + * @throws java.io.IOException if an I/O exception occurs + */ + public Entry getChildEntry(final String pName) throws IOException { + if (isFile() || rootNodeDId == -1) { + return null; + } + + Entry dummy = new Entry(); + dummy.name = pName; + dummy.parent = this; + + SortedSet child = getChildEntries().tailSet(dummy); + return (Entry) child.first(); + } + + /** + * Returns the children of this {@code Entry}. + * + * @return a {@code SortedSet} of {@code Entry} objects + * @throws java.io.IOException if an I/O exception occurs + */ + public SortedSet getChildEntries() throws IOException { + if (children == null) { + if (isFile() || rootNodeDId == -1) { + children = NO_CHILDREN; + } + else { + // Start at root node in R/B tree, and read to the left and right, + // re-build tree, according to the docs + children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this)); + } + } + + return children; + } + + @Override + public String toString() { + return "\"" + name + "\"" + + " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root")) + + (parent != null ? ", parent: \"" + parent.getName() + "\"" : "") + + (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.size()) : "(unknown)")) + + ", SId=" + startSId + ", length=" + streamSize + ")"; + } + + @Override + public boolean equals(final Object pOther) { + if (pOther == this) { + return true; + } + if (!(pOther instanceof Entry)) { + return false; + } + + Entry other = (Entry) pOther; + return name.equals(other.name) && (parent == other.parent + || (parent != null && parent.equals(other.parent))); + } + + @Override + public int hashCode() { + return name.hashCode() ^ startSId; + } + + public int compareTo(final Entry pOther) { + if (this == pOther) { + return 0; + } + + // NOTE: This is the sorting algorthm defined by the Compound Document: + // - first sort by name length + // - if lengths are equal, sort by comparing strings, case sensitive + + int diff = name.length() - pOther.name.length(); + if (diff != 0) { + return diff; + } + + return name.compareTo(pOther.name); + } +} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java index b1bb7b73..ac48c99b 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.io.ole2; diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/package-info.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/package-info.java index 0220f950..35412b61 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/package-info.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/package-info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Contains classes for reading the contents of the * Microsoft OLE 2 compound document format. diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/package_info.java b/common/common-io/src/main/java/com/twelvemonkeys/io/package_info.java index 87a4749e..07897ee8 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/package_info.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/package_info.java @@ -1,3 +1,32 @@ +/* + * 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. + */ /** * Provides for system input and output through data streams, serialization and the file system. */ diff --git a/common/common-io/src/main/java/com/twelvemonkeys/net/HTTPUtil.java b/common/common-io/src/main/java/com/twelvemonkeys/net/HTTPUtil.java index 399adf29..d50e203e 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/net/HTTPUtil.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/net/HTTPUtil.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.net; diff --git a/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java b/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java index a576a5e4..3b787b3b 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java @@ -1,312 +1,313 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.net; - -import com.twelvemonkeys.lang.StringUtil; -import com.twelvemonkeys.lang.SystemUtil; - -import java.io.IOException; -import java.util.*; - -/** - * Contains mappings from file extension to mime-types and from mime-type to file-types. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/net/MIMEUtil.java#5 $ - * - * @see MIME Media Types - */ -public final class MIMEUtil { - // TODO: Piggy-back on the mappings form the JRE? (1.6 comes with javax.activation) - // TODO: Piggy-back on mappings from javax.activation? - // See: http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/activation/MimetypesFileTypeMap.html - // See: http://java.sun.com/javase/6/docs/api/javax/activation/MimetypesFileTypeMap.html - // TODO: Use the format (and lookup) specified by the above URLs - // TODO: Allow 3rd party to add mappings? Will need application context support to do it safe.. :-P - - private static Map> sExtToMIME = new HashMap>(); - private static Map> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME); - - private static Map> sMIMEToExt = new HashMap>(); - private static Map> sUnmodifiableMIMEToExt = Collections.unmodifiableMap(sMIMEToExt); - - static { - // Load mapping for MIMEUtil - try { - Properties mappings = SystemUtil.loadProperties(MIMEUtil.class); - - for (Map.Entry entry : mappings.entrySet()) { - // Convert and break up extensions and mimeTypes - String extStr = StringUtil.toLowerCase((String) entry.getKey()); - List extensions = - Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, "))); - - String typeStr = StringUtil.toLowerCase((String) entry.getValue()); - List mimeTypes = - Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(typeStr, ";, "))); - - // TODO: Handle duplicates in MIME to extension mapping, like - // xhtml=application/xhtml+xml;application/xml - // xml=text/xml;application/xml - - // Populate normal and reverse MIME-mappings - for (String extension : extensions) { - sExtToMIME.put(extension, mimeTypes); - } - - for (String mimeType : mimeTypes) { - sMIMEToExt.put(mimeType, extensions); - } - } - } - catch (IOException e) { - System.err.println("Could not read properties for MIMEUtil: " + e.getMessage()); - e.printStackTrace(); - } - } - - // Disallow construction - private MIMEUtil() { - } - - /** - * Returns the default MIME type for the given file extension. - * - * @param pFileExt the file extension - * - * @return a {@code String} containing the MIME type, or {@code null} if - * there are no known MIME types for the given file extension. - */ - public static String getMIMEType(final String pFileExt) { - List types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt)); - return (types == null || types.isEmpty()) ? null : types.get(0); - } - - /** - * Returns all MIME types for the given file extension. - * - * @param pFileExt the file extension - * - * @return a {@link List} of {@code String}s containing the MIME types, or an empty - * list, if there are no known MIME types for the given file extension. - */ - public static List getMIMETypes(final String pFileExt) { - List types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt)); - return maskNull(types); - } - - /** - * Returns an unmodifiabale {@link Map} view of the extension to - * MIME mapping, to use as the default mapping in client applications. - * - * @return an unmodifiabale {@code Map} view of the extension to - * MIME mapping. - */ - public static Map> getMIMETypeMappings() { - return sUnmodifiableExtToMIME; - } - - /** - * Returns the default file extension for the given MIME type. - * Specifying a wildcard type will return {@code null}. - * - * @param pMIME the MIME type - * - * @return a {@code String} containing the file extension, or {@code null} - * if there are no known file extensions for the given MIME type. - */ - public static String getExtension(final String pMIME) { - String mime = bareMIME(StringUtil.toLowerCase(pMIME)); - List extensions = sMIMEToExt.get(mime); - return (extensions == null || extensions.isEmpty()) ? null : extensions.get(0); - } - - /** - * Returns all file extension for the given MIME type. - * The default extension will be the first in the list. - * Note that no specific order is given for wildcard types (image/*, */* etc). - * - * @param pMIME the MIME type - * - * @return a {@link List} of {@code String}s containing the MIME types, or an empty - * list, if there are no known file extensions for the given MIME type. - */ - public static List getExtensions(final String pMIME) { - String mime = bareMIME(StringUtil.toLowerCase(pMIME)); - if (mime.endsWith("/*")) { - return getExtensionForWildcard(mime); - } - List extensions = sMIMEToExt.get(mime); - return maskNull(extensions); - } - - // Gets all extensions for a wildcard MIME type - private static List getExtensionForWildcard(final String pMIME) { - final String family = pMIME.substring(0, pMIME.length() - 1); - Set extensions = new LinkedHashSet(); - for (Map.Entry> mimeToExt : sMIMEToExt.entrySet()) { - if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) { - extensions.addAll(mimeToExt.getValue()); - } - } - return Collections.unmodifiableList(new ArrayList(extensions)); - } - - /** - * Returns an unmodifiabale {@link Map} view of the MIME to - * extension mapping, to use as the default mapping in client applications. - * - * @return an unmodifiabale {@code Map} view of the MIME to - * extension mapping. - */ - public static Map> getExtensionMappings() { - return sUnmodifiableMIMEToExt; - } - - /** - * Tests wehter the type is a subtype of the type family. - * - * @param pTypeFamily the MIME type family ({@code image/*, */*}, etc) - * @param pType the MIME type - * @return {@code true} if {@code pType} is a subtype of {@code pTypeFamily}, otherwise {@code false} - */ - // TODO: Rename? isSubtype? - // TODO: Make public - static boolean includes(final String pTypeFamily, final String pType) { - // TODO: Handle null in a well-defined way - // - Is null family same as */*? - // - Is null subtype of any family? Subtype of no family? - - String type = bareMIME(pType); - return type.equals(pTypeFamily) - || "*/*".equals(pTypeFamily) - || pTypeFamily.endsWith("/*") && pTypeFamily.startsWith(type.substring(0, type.indexOf('/'))); - } - - /** - * Removes any charset or extra info from the mime-type string (anything after a semicolon, {@code ;}, inclusive). - * - * @param pMIME the mime-type string - * @return the bare mime-type - */ - public static String bareMIME(final String pMIME) { - int idx; - if (pMIME != null && (idx = pMIME.indexOf(';')) >= 0) { - return pMIME.substring(0, idx); - } - return pMIME; - } - - // Returns the list or empty list if list is null - private static List maskNull(List pTypes) { - return (pTypes == null) ? Collections.emptyList() : pTypes; - } - - /** - * For debugging. Prints all known MIME types and file extensions. - * - * @param pArgs command line arguments - */ - public static void main(String[] pArgs) { - if (pArgs.length > 1) { - String type = pArgs[0]; - String family = pArgs[1]; - boolean incuded = includes(family, type); - System.out.println( - "Mime type family " + family - + (incuded ? " includes " : " does not include ") - + "type " + type - ); - } - if (pArgs.length > 0) { - String str = pArgs[0]; - - if (str.indexOf('/') >= 0) { - // MIME - String extension = getExtension(str); - System.out.println("Default extension for MIME type '" + str + "' is " - + (extension != null ? ": '" + extension + "'" : "unknown") + "."); - System.out.println("All possible: " + getExtensions(str)); - } - else { - // EXT - String mimeType = getMIMEType(str); - System.out.println("Default MIME type for extension '" + str + "' is " - + (mimeType != null ? ": '" + mimeType + "'" : "unknown") + "."); - System.out.println("All possible: " + getMIMETypes(str)); - } - - return; - } - - Set set = sMIMEToExt.keySet(); - String[] mimeTypes = new String[set.size()]; - int i = 0; - for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) { - String mime = (String) iterator.next(); - mimeTypes[i] = mime; - } - Arrays.sort(mimeTypes); - - System.out.println("Known MIME types (" + mimeTypes.length + "):"); - for (int j = 0; j < mimeTypes.length; j++) { - String mimeType = mimeTypes[j]; - - if (j != 0) { - System.out.print(", "); - } - - System.out.print(mimeType); - } - - System.out.println("\n"); - - set = sExtToMIME.keySet(); - String[] extensions = new String[set.size()]; - i = 0; - for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) { - String ext = (String) iterator.next(); - extensions[i] = ext; - } - Arrays.sort(extensions); - - System.out.println("Known file types (" + extensions.length + "):"); - for (int j = 0; j < extensions.length; j++) { - String extension = extensions[j]; - - if (j != 0) { - System.out.print(", "); - } - - System.out.print(extension); - } - System.out.println(); - } +/* + * 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.net; + +import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.lang.SystemUtil; + +import java.io.IOException; +import java.util.*; + +/** + * Contains mappings from file extension to mime-types and from mime-type to file-types. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/net/MIMEUtil.java#5 $ + * + * @see MIME Media Types + */ +public final class MIMEUtil { + // TODO: Piggy-back on the mappings form the JRE? (1.6 comes with javax.activation) + // TODO: Piggy-back on mappings from javax.activation? + // See: http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/activation/MimetypesFileTypeMap.html + // See: http://java.sun.com/javase/6/docs/api/javax/activation/MimetypesFileTypeMap.html + // TODO: Use the format (and lookup) specified by the above URLs + // TODO: Allow 3rd party to add mappings? Will need application context support to do it safe.. :-P + + private static Map> sExtToMIME = new HashMap>(); + private static Map> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME); + + private static Map> sMIMEToExt = new HashMap>(); + private static Map> sUnmodifiableMIMEToExt = Collections.unmodifiableMap(sMIMEToExt); + + static { + // Load mapping for MIMEUtil + try { + Properties mappings = SystemUtil.loadProperties(MIMEUtil.class); + + for (Map.Entry entry : mappings.entrySet()) { + // Convert and break up extensions and mimeTypes + String extStr = StringUtil.toLowerCase((String) entry.getKey()); + List extensions = + Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, "))); + + String typeStr = StringUtil.toLowerCase((String) entry.getValue()); + List mimeTypes = + Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(typeStr, ";, "))); + + // TODO: Handle duplicates in MIME to extension mapping, like + // xhtml=application/xhtml+xml;application/xml + // xml=text/xml;application/xml + + // Populate normal and reverse MIME-mappings + for (String extension : extensions) { + sExtToMIME.put(extension, mimeTypes); + } + + for (String mimeType : mimeTypes) { + sMIMEToExt.put(mimeType, extensions); + } + } + } + catch (IOException e) { + System.err.println("Could not read properties for MIMEUtil: " + e.getMessage()); + e.printStackTrace(); + } + } + + // Disallow construction + private MIMEUtil() { + } + + /** + * Returns the default MIME type for the given file extension. + * + * @param pFileExt the file extension + * + * @return a {@code String} containing the MIME type, or {@code null} if + * there are no known MIME types for the given file extension. + */ + public static String getMIMEType(final String pFileExt) { + List types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt)); + return (types == null || types.isEmpty()) ? null : types.get(0); + } + + /** + * Returns all MIME types for the given file extension. + * + * @param pFileExt the file extension + * + * @return a {@link List} of {@code String}s containing the MIME types, or an empty + * list, if there are no known MIME types for the given file extension. + */ + public static List getMIMETypes(final String pFileExt) { + List types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt)); + return maskNull(types); + } + + /** + * Returns an unmodifiabale {@link Map} view of the extension to + * MIME mapping, to use as the default mapping in client applications. + * + * @return an unmodifiabale {@code Map} view of the extension to + * MIME mapping. + */ + public static Map> getMIMETypeMappings() { + return sUnmodifiableExtToMIME; + } + + /** + * Returns the default file extension for the given MIME type. + * Specifying a wildcard type will return {@code null}. + * + * @param pMIME the MIME type + * + * @return a {@code String} containing the file extension, or {@code null} + * if there are no known file extensions for the given MIME type. + */ + public static String getExtension(final String pMIME) { + String mime = bareMIME(StringUtil.toLowerCase(pMIME)); + List extensions = sMIMEToExt.get(mime); + return (extensions == null || extensions.isEmpty()) ? null : extensions.get(0); + } + + /** + * Returns all file extension for the given MIME type. + * The default extension will be the first in the list. + * Note that no specific order is given for wildcard types (image/*, */* etc). + * + * @param pMIME the MIME type + * + * @return a {@link List} of {@code String}s containing the MIME types, or an empty + * list, if there are no known file extensions for the given MIME type. + */ + public static List getExtensions(final String pMIME) { + String mime = bareMIME(StringUtil.toLowerCase(pMIME)); + if (mime.endsWith("/*")) { + return getExtensionForWildcard(mime); + } + List extensions = sMIMEToExt.get(mime); + return maskNull(extensions); + } + + // Gets all extensions for a wildcard MIME type + private static List getExtensionForWildcard(final String pMIME) { + final String family = pMIME.substring(0, pMIME.length() - 1); + Set extensions = new LinkedHashSet(); + for (Map.Entry> mimeToExt : sMIMEToExt.entrySet()) { + if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) { + extensions.addAll(mimeToExt.getValue()); + } + } + return Collections.unmodifiableList(new ArrayList(extensions)); + } + + /** + * Returns an unmodifiabale {@link Map} view of the MIME to + * extension mapping, to use as the default mapping in client applications. + * + * @return an unmodifiabale {@code Map} view of the MIME to + * extension mapping. + */ + public static Map> getExtensionMappings() { + return sUnmodifiableMIMEToExt; + } + + /** + * Tests wehter the type is a subtype of the type family. + * + * @param pTypeFamily the MIME type family ({@code image/*, */*}, etc) + * @param pType the MIME type + * @return {@code true} if {@code pType} is a subtype of {@code pTypeFamily}, otherwise {@code false} + */ + // TODO: Rename? isSubtype? + // TODO: Make public + static boolean includes(final String pTypeFamily, final String pType) { + // TODO: Handle null in a well-defined way + // - Is null family same as */*? + // - Is null subtype of any family? Subtype of no family? + + String type = bareMIME(pType); + return type.equals(pTypeFamily) + || "*/*".equals(pTypeFamily) + || pTypeFamily.endsWith("/*") && pTypeFamily.startsWith(type.substring(0, type.indexOf('/'))); + } + + /** + * Removes any charset or extra info from the mime-type string (anything after a semicolon, {@code ;}, inclusive). + * + * @param pMIME the mime-type string + * @return the bare mime-type + */ + public static String bareMIME(final String pMIME) { + int idx; + if (pMIME != null && (idx = pMIME.indexOf(';')) >= 0) { + return pMIME.substring(0, idx); + } + return pMIME; + } + + // Returns the list or empty list if list is null + private static List maskNull(List pTypes) { + return (pTypes == null) ? Collections.emptyList() : pTypes; + } + + /** + * For debugging. Prints all known MIME types and file extensions. + * + * @param pArgs command line arguments + */ + public static void main(String[] pArgs) { + if (pArgs.length > 1) { + String type = pArgs[0]; + String family = pArgs[1]; + boolean incuded = includes(family, type); + System.out.println( + "Mime type family " + family + + (incuded ? " includes " : " does not include ") + + "type " + type + ); + } + if (pArgs.length > 0) { + String str = pArgs[0]; + + if (str.indexOf('/') >= 0) { + // MIME + String extension = getExtension(str); + System.out.println("Default extension for MIME type '" + str + "' is " + + (extension != null ? ": '" + extension + "'" : "unknown") + "."); + System.out.println("All possible: " + getExtensions(str)); + } + else { + // EXT + String mimeType = getMIMEType(str); + System.out.println("Default MIME type for extension '" + str + "' is " + + (mimeType != null ? ": '" + mimeType + "'" : "unknown") + "."); + System.out.println("All possible: " + getMIMETypes(str)); + } + + return; + } + + Set set = sMIMEToExt.keySet(); + String[] mimeTypes = new String[set.size()]; + int i = 0; + for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) { + String mime = (String) iterator.next(); + mimeTypes[i] = mime; + } + Arrays.sort(mimeTypes); + + System.out.println("Known MIME types (" + mimeTypes.length + "):"); + for (int j = 0; j < mimeTypes.length; j++) { + String mimeType = mimeTypes[j]; + + if (j != 0) { + System.out.print(", "); + } + + System.out.print(mimeType); + } + + System.out.println("\n"); + + set = sExtToMIME.keySet(); + String[] extensions = new String[set.size()]; + i = 0; + for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) { + String ext = (String) iterator.next(); + extensions[i] = ext; + } + Arrays.sort(extensions); + + System.out.println("Known file types (" + extensions.length + "):"); + for (int j = 0; j < extensions.length; j++) { + String extension = extensions[j]; + + if (j != 0) { + System.out.print(", "); + } + + System.out.print(extension); + } + System.out.println(); + } } \ No newline at end of file diff --git a/common/common-io/src/main/java/com/twelvemonkeys/net/package_info.java b/common/common-io/src/main/java/com/twelvemonkeys/net/package_info.java index 0a0322ca..8fae1caa 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/net/package_info.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/net/package_info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides classes for net access. */ diff --git a/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java b/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java index 4dd47830..e2975901 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java @@ -4,34 +4,35 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. - */ + * * 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.xml; -import org.w3c.dom.Document; -import org.w3c.dom.Node; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.DOMImplementationList; +import org.w3c.dom.Document; +import org.w3c.dom.Node; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; @@ -101,8 +102,9 @@ public final class DOMSerializer { /** * Specifies wether the serializer should use indentation and optimize for * readability. - *

- * Note: This is a hint, and may be ignored by DOM implemenations. + *

+ * Note: This is a hint, and may be ignored by DOM implementations. + *

* * @param pPrettyPrint {@code true} to enable pretty printing */ diff --git a/common/common-io/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java b/common/common-io/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java old mode 100755 new mode 100644 index c1945f11..50ef4673 --- a/common/common-io/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.xml; @@ -387,9 +389,10 @@ public class XMLSerializer { private void writeDocument(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) { // Document fragments might not have child nodes... if (pNode.hasChildNodes()) { - NodeList nodes = pNode.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - writeNodeRecursive(pOut, nodes.item(i), pContext); + Node child = pNode.getFirstChild(); + while (child != null) { + writeNodeRecursive(pOut, child, pContext); + child = child.getNextSibling(); } } } @@ -446,9 +449,10 @@ public class XMLSerializer { pOut.println(); } - NodeList children = pNode.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - writeNodeRecursive(pOut, children.item(i), pContext.push()); + Node child = pNode.getFirstChild(); + while (child != null) { + writeNodeRecursive(pOut, child, pContext.push()); + child = child.getNextSibling(); } if (!pContext.preserveSpace) { diff --git a/common/common-io/src/main/java/com/twelvemonkeys/xml/package_info.java b/common/common-io/src/main/java/com/twelvemonkeys/xml/package_info.java index 5aa03c91..ee8b3793 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/xml/package_info.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/xml/package_info.java @@ -1,4 +1,33 @@ +/* + * 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. + */ /** * Provides XML support classes. */ - package com.twelvemonkeys.xml; \ No newline at end of file +package com.twelvemonkeys.xml; \ No newline at end of file diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/CompoundReaderTest.java similarity index 54% rename from common/common-io/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/CompoundReaderTest.java index a3a4ed2f..6e1fb796 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/CompoundReaderTest.java @@ -1,68 +1,98 @@ -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.StringUtil; -import com.twelvemonkeys.util.CollectionUtil; -import org.junit.Test; - -import java.io.Reader; -import java.io.IOException; -import java.io.StringReader; -import java.util.List; -import java.util.ArrayList; - -import static org.junit.Assert.*; - -/** - * CompoundReaderTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java#2 $ - */ -public class CompoundReaderTestCase extends ReaderAbstractTestCase { - protected Reader makeReader(String pInput) { - // Split - String[] input = StringUtil.toStringArray(pInput, " "); - List readers = new ArrayList(input.length); - - // Reappend spaces... - // TODO: Add other readers - for (int i = 0; i < input.length; i++) { - if (i != 0) { - input[i] = " " + input[i]; - } - readers.add(new StringReader(input[i])); - } - - return new CompoundReader(readers.iterator()); - } - - @Test - public void testNullConstructor() { - try { - new CompoundReader(null); - fail("Should not allow null argument"); - } - catch (RuntimeException e) { - assertNotNull(e.getMessage()); - } - } - - @Test - public void testEmptyIteratorConstructor() throws IOException { - Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0])); - assertEquals(-1, reader.read()); - } - - @Test - public void testIteratorWithNullConstructor() throws IOException { - try { - new CompoundReader(CollectionUtil.iterator(new Reader[] {null})); - fail("Should not allow null in iterator argument"); - } - catch (RuntimeException e) { - assertNotNull(e.getMessage()); - } - } -} +/* + * 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.io; + +import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.util.CollectionUtil; +import org.junit.Test; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * CompoundReaderTestCase + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java#2 $ + */ +public class CompoundReaderTest extends ReaderAbstractTest { + protected Reader makeReader(String pInput) { + // Split + String[] input = StringUtil.toStringArray(pInput, " "); + List readers = new ArrayList(input.length); + + // Reappend spaces... + // TODO: Add other readers + for (int i = 0; i < input.length; i++) { + if (i != 0) { + input[i] = " " + input[i]; + } + readers.add(new StringReader(input[i])); + } + + return new CompoundReader(readers.iterator()); + } + + @Test + public void testNullConstructor() { + try { + new CompoundReader(null); + fail("Should not allow null argument"); + } + catch (RuntimeException e) { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testEmptyIteratorConstructor() throws IOException { + Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0])); + assertEquals(-1, reader.read()); + } + + @Test + public void testIteratorWithNullConstructor() throws IOException { + try { + new CompoundReader(CollectionUtil.iterator(new Reader[] {null})); + fail("Should not allow null in iterator argument"); + } + catch (RuntimeException e) { + assertNotNull(e.getMessage()); + } + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTest.java new file mode 100755 index 00000000..c3537431 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTest.java @@ -0,0 +1,66 @@ +/* + * 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.io; + +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +/** + * FastByteArrayOutputStreamTestCase + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTestCase.java#1 $ + */ +public class FastByteArrayOutputStreamTest extends OutputStreamAbstractTest { + protected FastByteArrayOutputStream makeObject() { + return new FastByteArrayOutputStream(256); + } + + @Test + public void testCreateInputStream() throws IOException { + FastByteArrayOutputStream out = makeObject(); + + String hello = "Hello World"; + out.write(hello.getBytes("UTF-8")); + + InputStream in = out.createInputStream(); + + byte[] read = FileUtil.read(in); + + assertEquals(hello, new String(read, "UTF-8")); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTestCase.java deleted file mode 100755 index 95f09ccf..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTestCase.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.twelvemonkeys.io; - -import org.junit.Test; - -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.Assert.assertEquals; - -/** - * FastByteArrayOutputStreamTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FastByteArrayOutputStreamTestCase.java#1 $ - */ -public class FastByteArrayOutputStreamTestCase extends OutputStreamAbstractTestCase { - protected FastByteArrayOutputStream makeObject() { - return new FastByteArrayOutputStream(256); - } - - @Test - public void testCreateInputStream() throws IOException { - FastByteArrayOutputStream out = makeObject(); - - String hello = "Hello World"; - out.write(hello.getBytes("UTF-8")); - - InputStream in = out.createInputStream(); - - byte[] read = FileUtil.read(in); - - assertEquals(hello, new String(read, "UTF-8")); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTest.java new file mode 100755 index 00000000..01d139b1 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTest.java @@ -0,0 +1,52 @@ +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * FileCacheSeekableStreamTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java#3 $ + */ +public class FileCacheSeekableStreamTest extends SeekableInputStreamAbstractTest { + protected SeekableInputStream makeInputStream(final InputStream pStream) { + try { + return new FileCacheSeekableStream(pStream); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java deleted file mode 100755 index d12e9ca5..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.twelvemonkeys.io; - -import java.io.IOException; -import java.io.InputStream; - -/** - * FileCacheSeekableStreamTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java#3 $ - */ -public class FileCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase { - protected SeekableInputStream makeInputStream(final InputStream pStream) { - try { - return new FileCacheSeekableStream(pStream); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTest.java similarity index 55% rename from common/common-io/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTest.java index 65f3c989..d4325af9 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTest.java @@ -1,74 +1,104 @@ -package com.twelvemonkeys.io; - -import org.junit.Test; - -import java.io.*; - -import static org.junit.Assert.*; - -/** - * MemoryCacheSeekableStreamTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java#3 $ - */ -public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase { - protected SeekableInputStream makeInputStream(final InputStream pStream) { - try { - return new FileSeekableStream(createFileWithContent(pStream)); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - private File createFileWithContent(final InputStream pStream) throws IOException { - File temp = File.createTempFile("tm-io-junit", null); - temp.deleteOnExit(); - OutputStream os = new FileOutputStream(temp); - try { - FileUtil.copy(pStream, os); - } - finally { - os.close(); - pStream.close(); - } - return temp; - } - - @Test - @Override - public void testCloseUnderlyingStream() throws IOException { - // There is no underlying stream here... - } - - @Test - public void testCloseUnderlyingFile() throws IOException { - final boolean[] closed = new boolean[1]; - - File file = createFileWithContent(new ByteArrayInputStream(makeRandomArray(256))); - - RandomAccessFile raf = new RandomAccessFile(file, "r") { - @Override - public void close() throws IOException { - closed[0] = true; - super.close(); - } - }; - - FileSeekableStream stream = new FileSeekableStream(raf); - - try { - FileUtil.read(stream); // Read until EOF - - assertEquals("EOF not reached (test case broken)", -1, stream.read()); - assertFalse("Underlying stream closed before close", closed[0]); - } - finally { - stream.close(); - } - - assertTrue("Underlying stream not closed", closed[0]); - } -} +/* + * 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.io; + +import org.junit.Test; + +import java.io.*; + +import static org.junit.Assert.*; + +/** + * MemoryCacheSeekableStreamTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java#3 $ + */ +public class FileSeekableStreamTest extends SeekableInputStreamAbstractTest { + protected SeekableInputStream makeInputStream(final InputStream pStream) { + try { + return new FileSeekableStream(createFileWithContent(pStream)); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private File createFileWithContent(final InputStream pStream) throws IOException { + File temp = File.createTempFile("tm-io-junit", null); + temp.deleteOnExit(); + OutputStream os = new FileOutputStream(temp); + try { + FileUtil.copy(pStream, os); + } + finally { + os.close(); + pStream.close(); + } + return temp; + } + + @Test + @Override + public void testCloseUnderlyingStream() throws IOException { + // There is no underlying stream here... + } + + @Test + public void testCloseUnderlyingFile() throws IOException { + final boolean[] closed = new boolean[1]; + + File file = createFileWithContent(new ByteArrayInputStream(makeRandomArray(256))); + + RandomAccessFile raf = new RandomAccessFile(file, "r") { + @Override + public void close() throws IOException { + closed[0] = true; + super.close(); + } + }; + + FileSeekableStream stream = new FileSeekableStream(raf); + + try { + FileUtil.read(stream); // Read until EOF + + assertEquals("EOF not reached (test case broken)", -1, stream.read()); + assertFalse("Underlying stream closed before close", closed[0]); + } + finally { + stream.close(); + } + + assertTrue("Underlying stream not closed", closed[0]); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTest.java similarity index 89% rename from common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTest.java index 9f21a06d..59e1ac38 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTest.java @@ -1,419 +1,448 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.ObjectAbstractTestCase; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; - -import static org.junit.Assert.*; - -/** - * InputStreamAbstractTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java#1 $ - */ -public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase { - // TODO: FixMe! THIS TEST IS (WAS) COMPLETELY BROKEN... - // It relies on the contents of the stream being a certain order byte0 == 0, byte1 == 1 etc.. - // But the subclasses don't implement this.. Need to fix. - - final static private long SEED = 29487982745l; - final static Random sRandom = new Random(SEED); - - protected final Object makeObject() { - return makeInputStream(); - } - - protected InputStream makeInputStream() { - return makeInputStream(16); - } - - protected InputStream makeInputStream(int pSize) { - byte[] bytes = makeRandomArray(pSize); - return makeInputStream(bytes); - } - - protected abstract InputStream makeInputStream(byte[] pBytes); - - protected final byte[] makeRandomArray(final int pSize) { - byte[] bytes = new byte[pSize]; - sRandom.nextBytes(bytes); - return bytes; - } - - protected final byte[] makeOrderedArray(final int pSize) { - byte[] bytes = new byte[pSize]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = (byte) i; - } - return bytes; - } - - @Test - public void testRead() throws Exception { - int size = 5; - InputStream input = makeInputStream(makeOrderedArray(size)); - for (int i = 0; i < size; i++) { - assertTrue("Check Size [" + i + "]", (size - i) >= input.available()); - assertEquals("Check Value [" + i + "]", i, input.read()); - } - assertEquals("Available after contents all read", 0, input.available()); - - // Test reading after the end of file - try { - int result = input.read(); - assertEquals("Wrong value read after end of file", -1, result); - } - catch (IOException e) { - fail("Should not have thrown an IOException: " + e.getMessage()); - } - } - - @Test - public void testAvailable() throws Exception { - InputStream input = makeInputStream(1); - assertFalse("Unexpected EOF", input.read() < 0); - assertEquals("Available after contents all read", 0, input.available()); - - // Check availbale is zero after End of file - assertEquals("End of File", -1, input.read()); - assertEquals("Available after End of File", 0, input.available()); - } - - @Test - public void testReadByteArray() throws Exception { - byte[] bytes = new byte[10]; - byte[] data = makeOrderedArray(15); - InputStream input = makeInputStream(data); - - // Read into array - int count1 = input.read(bytes); - assertEquals("Read 1", bytes.length, count1); - for (int i = 0; i < count1; i++) { - assertEquals("Check Bytes 1", i, bytes[i]); - } - - // Read into array - int count2 = input.read(bytes); - assertEquals("Read 2", 5, count2); - for (int i = 0; i < count2; i++) { - assertEquals("Check Bytes 2", count1 + i, bytes[i]); - } - - // End of File - int count3 = input.read(bytes); - assertEquals("Read 3 (EOF)", -1, count3); - - // Test reading after the end of file - try { - int result = input.read(bytes); - assertEquals("Wrong value read after end of file", -1, result); - } - catch (IOException e) { - fail("Should not have thrown an IOException: " + e.getMessage()); - } - - // Reset - input = makeInputStream(data); - - // Read into array using offset & length - int offset = 2; - int lth = 4; - int count5 = input.read(bytes, offset, lth); - assertEquals("Read 5", lth, count5); - for (int i = offset; i < lth; i++) { - assertEquals("Check Bytes 2", i - offset, bytes[i]); - } - } - - @Test - public void testEOF() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(2)); - assertEquals("Read 1", 0, input.read()); - assertEquals("Read 2", 1, input.read()); - assertEquals("Read 3", -1, input.read()); - assertEquals("Read 4", -1, input.read()); - assertEquals("Read 5", -1, input.read()); - } - - @Test - public void testMarkResetUnsupported() throws IOException { - InputStream input = makeInputStream(10); - if (input.markSupported()) { - return; - } - - input.mark(100); // Should be a no-op - - int read = input.read(); - assertTrue(read >= 0); - - // TODO: According to InputStream#reset, it is allowed to do some - // implementation specific reset, and still be correct... - try { - input.reset(); - fail("Should throw IOException"); - } - catch (IOException e) { - assertTrue("Wrong messge: " + e.getMessage(), e.getMessage().contains("reset")); - } - } - - @Test - public void testResetNoMark() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(10)); - - if (!input.markSupported()) { - return; // Not supported, skip test - } - - int read = input.read(); - assertEquals(0, read); - - // No mark may either throw exception, or reset to beginning of stream. - try { - input.reset(); - assertEquals("Re-read of reset data should be same", 0, input.read()); - } - catch (Exception e) { - assertTrue("Wrong no mark IOException message", e.getMessage().contains("mark")); - } - } - - @Test - public void testMarkReset() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(25)); - - if (!input.markSupported()) { - return; // Not supported, skip test - } - - int read = input.read(); - assertEquals(0, read); - - int position = 1; - int readlimit = 10; - - // Mark - input.mark(readlimit); - - // Read further - for (int i = 0; i < 3; i++) { - assertEquals("Read After Mark [" + i + "]", (position + i), input.read()); - } - - // Reset - input.reset(); - - // Read from marked position - for (int i = 0; i < readlimit + 1; i++) { - assertEquals("Read After Reset [" + i + "]", (position + i), input.read()); - } - } - - @Test - public void testResetAfterReadLimit() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(25)); - - if (!input.markSupported()) { - return; // Not supported, skip test - } - - int read = input.read(); - assertEquals(0, read); - - int position = 1; - int readlimit = 5; - - // Mark - input.mark(readlimit); - - // Read past marked position - for (int i = 0; i < readlimit + 1; i++) { - assertEquals("Read After Reset [" + i + "]", (position + i), input.read()); - } - - // Reset after read limit passed, may either throw exception, or reset to last mark - try { - input.reset(); - assertEquals("Re-read of reset data should be same", 1, input.read()); - } - catch (Exception e) { - assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); - } - } - - @Test - public void testResetAfterReset() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(25)); - - if (!input.markSupported()) { - return; // Not supported, skip test - } - - int first = input.read(); - assertTrue("Expected to read positive value", first >= 0); - - int readlimit = 5; - - // Mark - input.mark(readlimit); - int read = input.read(); - assertTrue("Expected to read positive value", read >= 0); - - assertTrue(input.read() >= 0); - assertTrue(input.read() >= 0); - - input.reset(); - assertEquals("Expected value read differs from actual", read, input.read()); - - // Reset after read limit passed, may either throw exception, or reset to last good mark - try { - input.reset(); - int reRead = input.read(); - assertTrue("Re-read of reset data should be same as initially marked or first", reRead == read || reRead == first); - } - catch (Exception e) { - assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); - } - } - - @Test - public void testSkip() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(10)); - - assertEquals("Unexpected value read", 0, input.read()); - assertEquals("Unexpected value read", 1, input.read()); - assertEquals("Unexpected number of bytes skipped", 5, input.skip(5)); - assertEquals("Unexpected value read", 7, input.read()); - - assertEquals("Unexpected number of bytes skipped", 2, input.skip(5)); // only 2 left to skip - assertEquals("Unexpected value read after EOF", -1, input.read()); - - // Spec says skip might return 0 or negative after EOF... - assertTrue("Positive value skipped after EOF", input.skip(5) <= 0); // End of file - assertEquals("Unexpected value read after EOF", -1, input.read()); - } - - @Test - public void testSanityOrdered() throws IOException { - // This is to sanity check that the test itself is correct... - byte[] bytes = makeOrderedArray(25); - InputStream expected = new ByteArrayInputStream(bytes); - InputStream actual = makeInputStream(bytes); - - for (byte b : bytes) { - assertEquals((int) b, expected.read()); - assertEquals((int) b, actual.read()); - } - } - - @Test - public void testSanityOrdered2() throws IOException { - // This is to sanity check that the test itself is correct... - byte[] bytes = makeOrderedArray(25); - InputStream expected = new ByteArrayInputStream(bytes); - InputStream actual = makeInputStream(bytes); - - byte[] e = new byte[bytes.length]; - byte[] a = new byte[bytes.length]; - - assertEquals(e.length, expected.read(e, 0, e.length)); - assertEquals(a.length, actual.read(a, 0, a.length)); - - for (int i = 0; i < bytes.length; i++) { - assertEquals(bytes[i], e[i]); - assertEquals(bytes[i], a[i]); - } - } - - @Test - public void testSanityNegative() throws IOException { - // This is to sanity check that the test itself is correct... - byte[] bytes = new byte[25]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = (byte) (255 - i); - } - InputStream expected = new ByteArrayInputStream(bytes); - InputStream actual = makeInputStream(bytes); - - for (byte b : bytes) { - assertEquals(b & 0xff, expected.read()); - assertEquals(b & 0xff, actual.read()); - } - } - - @Test - public void testSanityNegative2() throws IOException { - // This is to sanity check that the test itself is correct... - byte[] bytes = new byte[25]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = (byte) (255 - i); - } - InputStream expected = new ByteArrayInputStream(bytes); - InputStream actual = makeInputStream(bytes); - - byte[] e = new byte[bytes.length]; - byte[] a = new byte[bytes.length]; - - assertEquals(e.length, expected.read(e, 0, e.length)); - assertEquals(a.length, actual.read(a, 0, a.length)); - - for (int i = 0; i < bytes.length; i++) { - assertEquals(bytes[i], e[i]); - assertEquals(bytes[i], a[i]); - } - } - - @Test - public void testSanityRandom() throws IOException { - // This is to sanity check that the test itself is correct... - byte[] bytes = makeRandomArray(25); - InputStream expected = new ByteArrayInputStream(bytes); - InputStream actual = makeInputStream(bytes); - - for (byte b : bytes) { - assertEquals(b & 0xff, expected.read()); - assertEquals(b & 0xff, actual.read()); - } - } - - @Test - public void testSanityRandom2() throws IOException { - // This is to sanity check that the test itself is correct... - byte[] bytes = makeRandomArray(25); - InputStream expected = new ByteArrayInputStream(bytes); - InputStream actual = makeInputStream(bytes); - - byte[] e = new byte[bytes.length]; - byte[] a = new byte[bytes.length]; - - assertEquals(e.length, expected.read(e, 0, e.length)); - assertEquals(a.length, actual.read(a, 0, a.length)); - - for (int i = 0; i < bytes.length; i++) { - assertEquals(bytes[i], e[i]); - assertEquals(bytes[i], a[i]); - } - }} +/* + * 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. + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.twelvemonkeys.io; + +import com.twelvemonkeys.lang.ObjectAbstractTest; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Random; + +import static org.junit.Assert.*; + +/** + * InputStreamAbstractTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java#1 $ + */ +public abstract class InputStreamAbstractTest extends ObjectAbstractTest { + // TODO: FixMe! THIS TEST IS (WAS) COMPLETELY BROKEN... + // It relies on the contents of the stream being a certain order byte0 == 0, byte1 == 1 etc.. + // But the subclasses don't implement this.. Need to fix. + + final static private long SEED = 29487982745l; + final static Random sRandom = new Random(SEED); + + protected final Object makeObject() { + return makeInputStream(); + } + + protected InputStream makeInputStream() { + return makeInputStream(16); + } + + protected InputStream makeInputStream(int pSize) { + byte[] bytes = makeRandomArray(pSize); + return makeInputStream(bytes); + } + + protected abstract InputStream makeInputStream(byte[] pBytes); + + protected final byte[] makeRandomArray(final int pSize) { + byte[] bytes = new byte[pSize]; + sRandom.nextBytes(bytes); + return bytes; + } + + protected final byte[] makeOrderedArray(final int pSize) { + byte[] bytes = new byte[pSize]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) i; + } + return bytes; + } + + @Test + public void testRead() throws Exception { + int size = 5; + InputStream input = makeInputStream(makeOrderedArray(size)); + for (int i = 0; i < size; i++) { + assertTrue("Check Size [" + i + "]", (size - i) >= input.available()); + assertEquals("Check Value [" + i + "]", i, input.read()); + } + assertEquals("Available after contents all read", 0, input.available()); + + // Test reading after the end of file + try { + int result = input.read(); + assertEquals("Wrong value read after end of file", -1, result); + } + catch (IOException e) { + fail("Should not have thrown an IOException: " + e.getMessage()); + } + } + + @Test + public void testAvailable() throws Exception { + InputStream input = makeInputStream(1); + assertFalse("Unexpected EOF", input.read() < 0); + assertEquals("Available after contents all read", 0, input.available()); + + // Check availbale is zero after End of file + assertEquals("End of File", -1, input.read()); + assertEquals("Available after End of File", 0, input.available()); + } + + @Test + public void testReadByteArray() throws Exception { + byte[] bytes = new byte[10]; + byte[] data = makeOrderedArray(15); + InputStream input = makeInputStream(data); + + // Read into array + int count1 = input.read(bytes); + assertEquals("Read 1", bytes.length, count1); + for (int i = 0; i < count1; i++) { + assertEquals("Check Bytes 1", i, bytes[i]); + } + + // Read into array + int count2 = input.read(bytes); + assertEquals("Read 2", 5, count2); + for (int i = 0; i < count2; i++) { + assertEquals("Check Bytes 2", count1 + i, bytes[i]); + } + + // End of File + int count3 = input.read(bytes); + assertEquals("Read 3 (EOF)", -1, count3); + + // Test reading after the end of file + try { + int result = input.read(bytes); + assertEquals("Wrong value read after end of file", -1, result); + } + catch (IOException e) { + fail("Should not have thrown an IOException: " + e.getMessage()); + } + + // Reset + input = makeInputStream(data); + + // Read into array using offset & length + int offset = 2; + int lth = 4; + int count5 = input.read(bytes, offset, lth); + assertEquals("Read 5", lth, count5); + for (int i = offset; i < lth; i++) { + assertEquals("Check Bytes 2", i - offset, bytes[i]); + } + } + + @Test + public void testEOF() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(2)); + assertEquals("Read 1", 0, input.read()); + assertEquals("Read 2", 1, input.read()); + assertEquals("Read 3", -1, input.read()); + assertEquals("Read 4", -1, input.read()); + assertEquals("Read 5", -1, input.read()); + } + + @Test + public void testMarkResetUnsupported() throws IOException { + InputStream input = makeInputStream(10); + if (input.markSupported()) { + return; + } + + input.mark(100); // Should be a no-op + + int read = input.read(); + assertTrue(read >= 0); + + // TODO: According to InputStream#reset, it is allowed to do some + // implementation specific reset, and still be correct... + try { + input.reset(); + fail("Should throw IOException"); + } + catch (IOException e) { + assertTrue("Wrong messge: " + e.getMessage(), e.getMessage().contains("reset")); + } + } + + @Test + public void testResetNoMark() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(10)); + + if (!input.markSupported()) { + return; // Not supported, skip test + } + + int read = input.read(); + assertEquals(0, read); + + // No mark may either throw exception, or reset to beginning of stream. + try { + input.reset(); + assertEquals("Re-read of reset data should be same", 0, input.read()); + } + catch (Exception e) { + assertTrue("Wrong no mark IOException message", e.getMessage().contains("mark")); + } + } + + @Test + public void testMarkReset() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(25)); + + if (!input.markSupported()) { + return; // Not supported, skip test + } + + int read = input.read(); + assertEquals(0, read); + + int position = 1; + int readlimit = 10; + + // Mark + input.mark(readlimit); + + // Read further + for (int i = 0; i < 3; i++) { + assertEquals("Read After Mark [" + i + "]", (position + i), input.read()); + } + + // Reset + input.reset(); + + // Read from marked position + for (int i = 0; i < readlimit + 1; i++) { + assertEquals("Read After Reset [" + i + "]", (position + i), input.read()); + } + } + + @Test + public void testResetAfterReadLimit() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(25)); + + if (!input.markSupported()) { + return; // Not supported, skip test + } + + int read = input.read(); + assertEquals(0, read); + + int position = 1; + int readlimit = 5; + + // Mark + input.mark(readlimit); + + // Read past marked position + for (int i = 0; i < readlimit + 1; i++) { + assertEquals("Read After Reset [" + i + "]", (position + i), input.read()); + } + + // Reset after read limit passed, may either throw exception, or reset to last mark + try { + input.reset(); + assertEquals("Re-read of reset data should be same", 1, input.read()); + } + catch (Exception e) { + assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); + } + } + + @Test + public void testResetAfterReset() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(25)); + + if (!input.markSupported()) { + return; // Not supported, skip test + } + + int first = input.read(); + assertTrue("Expected to read positive value", first >= 0); + + int readlimit = 5; + + // Mark + input.mark(readlimit); + int read = input.read(); + assertTrue("Expected to read positive value", read >= 0); + + assertTrue(input.read() >= 0); + assertTrue(input.read() >= 0); + + input.reset(); + assertEquals("Expected value read differs from actual", read, input.read()); + + // Reset after read limit passed, may either throw exception, or reset to last good mark + try { + input.reset(); + int reRead = input.read(); + assertTrue("Re-read of reset data should be same as initially marked or first", reRead == read || reRead == first); + } + catch (Exception e) { + assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); + } + } + + @Test + public void testSkip() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(10)); + + assertEquals("Unexpected value read", 0, input.read()); + assertEquals("Unexpected value read", 1, input.read()); + assertEquals("Unexpected number of bytes skipped", 5, input.skip(5)); + assertEquals("Unexpected value read", 7, input.read()); + + assertEquals("Unexpected number of bytes skipped", 2, input.skip(5)); // only 2 left to skip + assertEquals("Unexpected value read after EOF", -1, input.read()); + + // Spec says skip might return 0 or negative after EOF... + assertTrue("Positive value skipped after EOF", input.skip(5) <= 0); // End of file + assertEquals("Unexpected value read after EOF", -1, input.read()); + } + + @Test + public void testSanityOrdered() throws IOException { + // This is to sanity check that the test itself is correct... + byte[] bytes = makeOrderedArray(25); + InputStream expected = new ByteArrayInputStream(bytes); + InputStream actual = makeInputStream(bytes); + + for (byte b : bytes) { + assertEquals((int) b, expected.read()); + assertEquals((int) b, actual.read()); + } + } + + @Test + public void testSanityOrdered2() throws IOException { + // This is to sanity check that the test itself is correct... + byte[] bytes = makeOrderedArray(25); + InputStream expected = new ByteArrayInputStream(bytes); + InputStream actual = makeInputStream(bytes); + + byte[] e = new byte[bytes.length]; + byte[] a = new byte[bytes.length]; + + assertEquals(e.length, expected.read(e, 0, e.length)); + assertEquals(a.length, actual.read(a, 0, a.length)); + + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], e[i]); + assertEquals(bytes[i], a[i]); + } + } + + @Test + public void testSanityNegative() throws IOException { + // This is to sanity check that the test itself is correct... + byte[] bytes = new byte[25]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) (255 - i); + } + InputStream expected = new ByteArrayInputStream(bytes); + InputStream actual = makeInputStream(bytes); + + for (byte b : bytes) { + assertEquals(b & 0xff, expected.read()); + assertEquals(b & 0xff, actual.read()); + } + } + + @Test + public void testSanityNegative2() throws IOException { + // This is to sanity check that the test itself is correct... + byte[] bytes = new byte[25]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) (255 - i); + } + InputStream expected = new ByteArrayInputStream(bytes); + InputStream actual = makeInputStream(bytes); + + byte[] e = new byte[bytes.length]; + byte[] a = new byte[bytes.length]; + + assertEquals(e.length, expected.read(e, 0, e.length)); + assertEquals(a.length, actual.read(a, 0, a.length)); + + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], e[i]); + assertEquals(bytes[i], a[i]); + } + } + + @Test + public void testSanityRandom() throws IOException { + // This is to sanity check that the test itself is correct... + byte[] bytes = makeRandomArray(25); + InputStream expected = new ByteArrayInputStream(bytes); + InputStream actual = makeInputStream(bytes); + + for (byte b : bytes) { + assertEquals(b & 0xff, expected.read()); + assertEquals(b & 0xff, actual.read()); + } + } + + @Test + public void testSanityRandom2() throws IOException { + // This is to sanity check that the test itself is correct... + byte[] bytes = makeRandomArray(25); + InputStream expected = new ByteArrayInputStream(bytes); + InputStream actual = makeInputStream(bytes); + + byte[] e = new byte[bytes.length]; + byte[] a = new byte[bytes.length]; + + assertEquals(e.length, expected.read(e, 0, e.length)); + assertEquals(a.length, actual.read(a, 0, a.length)); + + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], e[i]); + assertEquals(bytes[i], a[i]); + } + }} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java index 1f7c551b..9527c85c 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.io; diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTest.java new file mode 100755 index 00000000..f8e5d028 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTest.java @@ -0,0 +1,46 @@ +/* + * 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.io; + +import java.io.InputStream; + +/** + * MemoryCacheSeekableStreamTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java#2 $ + */ +public class MemoryCacheSeekableStreamTest extends SeekableInputStreamAbstractTest { + protected SeekableInputStream makeInputStream(final InputStream pStream) { + return new MemoryCacheSeekableStream(pStream); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java deleted file mode 100755 index 53387cb5..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.twelvemonkeys.io; - -import java.io.InputStream; - -/** - * MemoryCacheSeekableStreamTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java#2 $ - */ -public class MemoryCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase { - protected SeekableInputStream makeInputStream(final InputStream pStream) { - return new MemoryCacheSeekableStream(pStream); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTest.java similarity index 79% rename from common/common-io/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTest.java index d3405dae..c5f5a412 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTest.java @@ -1,254 +1,285 @@ -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.ObjectAbstractTestCase; -import org.junit.Test; - -import java.io.OutputStream; -import java.io.IOException; - -import static org.junit.Assert.*; - -/** - * InputStreamAbstractTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTestCase.java#1 $ - */ -public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase { - protected abstract OutputStream makeObject(); - - @Test - public void testWrite() throws IOException { - OutputStream os = makeObject(); - - for (int i = 0; i < 256; i++) { - os.write((byte) i); - } - } - - @Test - public void testWriteByteArray() throws IOException { - OutputStream os = makeObject(); - - os.write(new byte[256]); - } - - @Test - public void testWriteByteArrayNull() { - OutputStream os = makeObject(); - try { - os.write(null); - fail("Should not accept null-argument"); - } - catch (IOException e) { - fail("Should not throw IOException of null-arguemnt: " + e.getMessage()); - } - catch (NullPointerException e) { - assertNotNull(e); - } - catch (RuntimeException e) { - fail("Should only throw NullPointerException: " + e.getClass() + ": " + e.getMessage()); - } - } - - @Test - public void testWriteByteArrayOffsetLength() throws IOException { - byte[] input = new byte[256]; - - OutputStream os = makeObject(); - - // TODO: How to test that data is actually written!? - for (int i = 0; i < 256; i++) { - input[i] = (byte) i; - } - - for (int i = 0; i < 256; i++) { - os.write(input, i, 256 - i); - } - - for (int i = 0; i < 4; i++) { - os.write(input, i * 64, 64); - } - } - - @Test - public void testWriteByteArrayZeroLength() { - OutputStream os = makeObject(); - try { - os.write(new byte[1], 0, 0); - } - catch (Exception e) { - fail("Should not throw Exception: " + e.getMessage()); - } - } - - @Test - public void testWriteByteArrayOffsetLengthNull() { - OutputStream os = makeObject(); - try { - os.write(null, 5, 10); - fail("Should not accept null-argument"); - } - catch (IOException e) { - fail("Should not throw IOException of null-arguemnt: " + e.getMessage()); - } - catch (NullPointerException e) { - assertNotNull(e); - } - catch (RuntimeException e) { - fail("Should only throw NullPointerException: " + e.getClass() + ": " + e.getMessage()); - } - } - - @Test - public void testWriteByteArrayNegativeOffset() { - OutputStream os = makeObject(); - try { - os.write(new byte[5], -3, 5); - fail("Should not accept negative offset"); - } - catch (IOException e) { - fail("Should not throw IOException negative offset: " + e.getMessage()); - } - catch (IndexOutOfBoundsException e) { - assertNotNull(e); - } - catch (RuntimeException e) { - fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); - } - } - - @Test - public void testWriteByteArrayNegativeLength() { - OutputStream os = makeObject(); - try { - os.write(new byte[5], 2, -5); - fail("Should not accept negative length"); - } - catch (IOException e) { - fail("Should not throw IOException negative length: " + e.getMessage()); - } - catch (IndexOutOfBoundsException e) { - assertNotNull(e); - } - catch (RuntimeException e) { - fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); - } - } - - @Test - public void testWriteByteArrayOffsetOutOfBounds() { - OutputStream os = makeObject(); - try { - os.write(new byte[5], 5, 1); - fail("Should not accept offset out of bounds"); - } - catch (IOException e) { - fail("Should not throw IOException offset out of bounds: " + e.getMessage()); - } - catch (IndexOutOfBoundsException e) { - assertNotNull(e); - } - catch (RuntimeException e) { - fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); - } - } - - @Test - public void testWriteByteArrayLengthOutOfBounds() { - OutputStream os = makeObject(); - try { - os.write(new byte[5], 1, 5); - fail("Should not accept length out of bounds"); - } - catch (IOException e) { - fail("Should not throw IOException length out of bounds: " + e.getMessage()); - } - catch (IndexOutOfBoundsException e) { - assertNotNull(e); - } - catch (RuntimeException e) { - fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); - } - } - - @Test - public void testFlush() { - // TODO: Implement - } - - @Test - public void testClose() { - // TODO: Implement - } - - @Test - public void testWriteAfterClose() throws IOException { - OutputStream os = makeObject(); - - os.close(); - - boolean success = false; - try { - os.write(0); - success = true; - // TODO: Not all streams throw exception! (ByteArrayOutputStream) - //fail("Write after close"); - } - catch (IOException e) { - assertNotNull(e.getMessage()); - } - - try { - os.write(new byte[16]); - // TODO: Not all streams throw exception! (ByteArrayOutputStream) - //fail("Write after close"); - if (!success) { - fail("Inconsistent write(int)/write(byte[]) after close"); - } - } - catch (IOException e) { - assertNotNull(e.getMessage()); - if (success) { - fail("Inconsistent write(int)/write(byte[]) after close"); - } - } - } - - @Test - public void testFlushAfterClose() throws IOException { - OutputStream os = makeObject(); - - os.close(); - - try { - os.flush(); - // TODO: Not all streams throw exception! (ByteArrayOutputStream) - //fail("Flush after close"); - try { - os.write(0); - } - catch (IOException e) { - fail("Inconsistent write/flush after close"); - } - } - catch (IOException e) { - assertNotNull(e.getMessage()); - } - } - - @Test - public void testCloseAfterClose() throws IOException { - OutputStream os = makeObject(); - - os.close(); - - try { - os.close(); - } - catch (IOException e) { - fail("Close after close, failed: " + e.getMessage()); - } - } -} +/* + * 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.io; + +import com.twelvemonkeys.lang.ObjectAbstractTest; +import org.junit.Test; + +import java.io.IOException; +import java.io.OutputStream; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +/** + * InputStreamAbstractTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/OutputStreamAbstractTestCase.java#1 $ + */ +public abstract class OutputStreamAbstractTest extends ObjectAbstractTest { + protected abstract OutputStream makeObject(); + + @Test + public void testWrite() throws IOException { + OutputStream os = makeObject(); + + for (int i = 0; i < 256; i++) { + os.write((byte) i); + } + } + + @Test + public void testWriteByteArray() throws IOException { + OutputStream os = makeObject(); + + os.write(new byte[256]); + } + + @Test + public void testWriteByteArrayNull() { + OutputStream os = makeObject(); + try { + os.write(null); + fail("Should not accept null-argument"); + } + catch (IOException e) { + fail("Should not throw IOException of null-arguemnt: " + e.getMessage()); + } + catch (NullPointerException e) { + assertNotNull(e); + } + catch (RuntimeException e) { + fail("Should only throw NullPointerException: " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testWriteByteArrayOffsetLength() throws IOException { + byte[] input = new byte[256]; + + OutputStream os = makeObject(); + + // TODO: How to test that data is actually written!? + for (int i = 0; i < 256; i++) { + input[i] = (byte) i; + } + + for (int i = 0; i < 256; i++) { + os.write(input, i, 256 - i); + } + + for (int i = 0; i < 4; i++) { + os.write(input, i * 64, 64); + } + } + + @Test + public void testWriteByteArrayZeroLength() { + OutputStream os = makeObject(); + try { + os.write(new byte[1], 0, 0); + } + catch (Exception e) { + fail("Should not throw Exception: " + e.getMessage()); + } + } + + @Test + public void testWriteByteArrayOffsetLengthNull() { + OutputStream os = makeObject(); + try { + os.write(null, 5, 10); + fail("Should not accept null-argument"); + } + catch (IOException e) { + fail("Should not throw IOException of null-arguemnt: " + e.getMessage()); + } + catch (NullPointerException e) { + assertNotNull(e); + } + catch (RuntimeException e) { + fail("Should only throw NullPointerException: " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testWriteByteArrayNegativeOffset() { + OutputStream os = makeObject(); + try { + os.write(new byte[5], -3, 5); + fail("Should not accept negative offset"); + } + catch (IOException e) { + fail("Should not throw IOException negative offset: " + e.getMessage()); + } + catch (IndexOutOfBoundsException e) { + assertNotNull(e); + } + catch (RuntimeException e) { + fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testWriteByteArrayNegativeLength() { + OutputStream os = makeObject(); + try { + os.write(new byte[5], 2, -5); + fail("Should not accept negative length"); + } + catch (IOException e) { + fail("Should not throw IOException negative length: " + e.getMessage()); + } + catch (IndexOutOfBoundsException e) { + assertNotNull(e); + } + catch (RuntimeException e) { + fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testWriteByteArrayOffsetOutOfBounds() { + OutputStream os = makeObject(); + try { + os.write(new byte[5], 5, 1); + fail("Should not accept offset out of bounds"); + } + catch (IOException e) { + fail("Should not throw IOException offset out of bounds: " + e.getMessage()); + } + catch (IndexOutOfBoundsException e) { + assertNotNull(e); + } + catch (RuntimeException e) { + fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testWriteByteArrayLengthOutOfBounds() { + OutputStream os = makeObject(); + try { + os.write(new byte[5], 1, 5); + fail("Should not accept length out of bounds"); + } + catch (IOException e) { + fail("Should not throw IOException length out of bounds: " + e.getMessage()); + } + catch (IndexOutOfBoundsException e) { + assertNotNull(e); + } + catch (RuntimeException e) { + fail("Should only throw IndexOutOfBoundsException: " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testFlush() { + // TODO: Implement + } + + @Test + public void testClose() { + // TODO: Implement + } + + @Test + public void testWriteAfterClose() throws IOException { + OutputStream os = makeObject(); + + os.close(); + + boolean success = false; + try { + os.write(0); + success = true; + // TODO: Not all streams throw exception! (ByteArrayOutputStream) + //fail("Write after close"); + } + catch (IOException e) { + assertNotNull(e.getMessage()); + } + + try { + os.write(new byte[16]); + // TODO: Not all streams throw exception! (ByteArrayOutputStream) + //fail("Write after close"); + if (!success) { + fail("Inconsistent write(int)/write(byte[]) after close"); + } + } + catch (IOException e) { + assertNotNull(e.getMessage()); + if (success) { + fail("Inconsistent write(int)/write(byte[]) after close"); + } + } + } + + @Test + public void testFlushAfterClose() throws IOException { + OutputStream os = makeObject(); + + os.close(); + + try { + os.flush(); + // TODO: Not all streams throw exception! (ByteArrayOutputStream) + //fail("Flush after close"); + try { + os.write(0); + } + catch (IOException e) { + fail("Inconsistent write/flush after close"); + } + } + catch (IOException e) { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testCloseAfterClose() throws IOException { + OutputStream os = makeObject(); + + os.close(); + + try { + os.close(); + } + catch (IOException e) { + fail("Close after close, failed: " + e.getMessage()); + } + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/ReaderAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/ReaderAbstractTest.java similarity index 79% rename from common/common-io/src/test/java/com/twelvemonkeys/io/ReaderAbstractTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/ReaderAbstractTest.java index cf9e63d5..8fc72aa6 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/ReaderAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/ReaderAbstractTest.java @@ -1,224 +1,254 @@ -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.ObjectAbstractTestCase; -import org.junit.Test; - -import java.io.Reader; -import java.io.IOException; - -import static org.junit.Assert.*; - -/** - * ReaderAbstractTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/ReaderAbstractTestCase.java#1 $ - */ -public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase { - - // Kindly provided by lipsum.org :-) - protected final String mInput = - "Cras tincidunt euismod tellus. Aenean a odio. " + - "Aenean metus. Sed tristique est non purus. Class aptent " + - "taciti sociosqu ad litora torquent per conubia nostra, per " + - "inceptos hymenaeos. Fusce vulputate dolor non mauris. " + - "Nullam nunc massa, pretium quis, ultricies a, varius quis, " + - "neque. Nam id nulla eu ante malesuada fermentum. Sed " + - "vulputate purus eget magna. Sed mollis. Curabitur enim " + - "diam, faucibus ac, hendrerit eu, consequat nec, augue."; - - protected final Object makeObject() { - return makeReader(); - } - - protected Reader makeReader() { - return makeReader(mInput); - } - - protected abstract Reader makeReader(String pInput); - - @Test - public void testRead() throws IOException { - Reader reader = makeReader(); - - int count = 0; - int ch; - StringBuilder buffer = new StringBuilder(mInput.length()); - while ((ch = reader.read()) > 0) { - count++; - buffer.append((char) ch); - } - - assertEquals(mInput.length(), count); - assertEquals(mInput, buffer.toString()); - } - - @Test - public void testReadBuffer() throws IOException { - Reader reader = makeReader(); - - char[] chars = new char[mInput.length()]; - StringBuilder buffer = new StringBuilder(mInput.length()); - - int count; - int offset = 0; - int lenght = chars.length; - while ((count = reader.read(chars, offset, lenght)) > 0) { - buffer.append(chars, offset, count); - offset += count; - lenght -= count; - } - - assertEquals(mInput, buffer.toString()); - assertEquals(mInput, new String(chars)); - } - - @Test - public void testSkipToEnd() throws IOException { - Reader reader = makeReader(); - - int toSkip = mInput.length(); - while (toSkip > 0) { - long skipped = reader.skip(toSkip); - assertFalse("Skipped < 0", skipped < 0); - toSkip -= skipped; - } - - assertEquals(0, toSkip); - } - - @Test - public void testSkipToEndAndRead() throws IOException { - Reader reader = makeReader(); - - int toSkip = mInput.length(); - while (toSkip > 0) { - toSkip -= reader.skip(toSkip); - } - - assertEquals(reader.read(), -1); - } - - // TODO: It's possible to support reset and not mark (resets to beginning of stream, for example) - @Test - public void testResetMarkSupported() throws IOException { - Reader reader = makeReader(); - - if (reader.markSupported()) { - // Mark at 0 - reader.mark(mInput.length() / 4); - - // Read one char - char ch = (char) reader.read(); - reader.reset(); - assertEquals(ch, (char) reader.read()); - reader.reset(); - - // Read from start - StringBuilder first = new StringBuilder(mInput.length() / 4); - for (int i = 0; i < mInput.length() / 4; i++) { - first.append((char) reader.read()); - } - - reader.reset(); // 0 - - StringBuilder second = new StringBuilder(mInput.length() / 4); - for (int i = 0; i < mInput.length() / 4; i++) { - second.append((char) reader.read()); - } - - assertEquals(first.toString(), second.toString()); - - // Mark at 1/4 - reader.mark(mInput.length() / 4); - - // Read from 1/4 - first = new StringBuilder(mInput.length() / 4); - for (int i = 0; i < mInput.length() / 4; i++) { - first.append((char) reader.read()); - } - - reader.reset(); // 1/4 - - second = new StringBuilder(mInput.length() / 4); - for (int i = 0; i < mInput.length() / 4; i++) { - second.append((char) reader.read()); - } - - assertEquals(first.toString(), second.toString()); - - // Read past limit - reader.read(); - - // This may or may not fail, depending on the stream - try { - reader.reset(); - } - catch (IOException ioe) { - assertNotNull(ioe.getMessage()); - } - } - } - - @Test - public void testResetMarkNotSupported() throws IOException { - Reader reader = makeReader(); - - if (!reader.markSupported()) { - try { - reader.mark(mInput.length()); - fail("Mark set, while markSupprted is false"); - } - catch (IOException e) { - assertNotNull(e.getMessage()); - } - - // Read one char - char ch = (char) reader.read(); - try { - reader.reset(); - assertEquals(ch, (char) reader.read()); - } - catch (IOException ioe) { - assertNotNull(ioe.getMessage()); - } - - // Read from start - StringBuilder first = new StringBuilder(mInput.length() / 4); - for (int i = 0; i < mInput.length() / 4; i++) { - first.append((char) reader.read()); - } - - try { - reader.reset(); // 0 - - StringBuilder second = new StringBuilder(mInput.length() / 4); - for (int i = 0; i < mInput.length() / 4; i++) { - second.append((char) reader.read()); - } - - assertEquals(first.toString(), second.toString()); - } - catch (IOException ioe) { - assertNotNull(ioe.getMessage()); - } - } - } - - @Test - public void testReadAfterClose() throws IOException { - Reader reader = makeReader("foo bar"); - - reader.close(); - - try { - reader.read(); - fail("Should not allow read after close"); - } - catch (IOException ioe) { - assertNotNull(ioe.getMessage()); - } - } -} +/* + * 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.io; + +import com.twelvemonkeys.lang.ObjectAbstractTest; +import org.junit.Test; + +import java.io.IOException; +import java.io.Reader; + +import static org.junit.Assert.*; + +/** + * ReaderAbstractTestCase + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/ReaderAbstractTestCase.java#1 $ + */ +public abstract class ReaderAbstractTest extends ObjectAbstractTest { + + // Kindly provided by lipsum.org :-) + protected final String mInput = + "Cras tincidunt euismod tellus. Aenean a odio. " + + "Aenean metus. Sed tristique est non purus. Class aptent " + + "taciti sociosqu ad litora torquent per conubia nostra, per " + + "inceptos hymenaeos. Fusce vulputate dolor non mauris. " + + "Nullam nunc massa, pretium quis, ultricies a, varius quis, " + + "neque. Nam id nulla eu ante malesuada fermentum. Sed " + + "vulputate purus eget magna. Sed mollis. Curabitur enim " + + "diam, faucibus ac, hendrerit eu, consequat nec, augue."; + + protected final Object makeObject() { + return makeReader(); + } + + protected Reader makeReader() { + return makeReader(mInput); + } + + protected abstract Reader makeReader(String pInput); + + @Test + public void testRead() throws IOException { + Reader reader = makeReader(); + + int count = 0; + int ch; + StringBuilder buffer = new StringBuilder(mInput.length()); + while ((ch = reader.read()) > 0) { + count++; + buffer.append((char) ch); + } + + assertEquals(mInput.length(), count); + assertEquals(mInput, buffer.toString()); + } + + @Test + public void testReadBuffer() throws IOException { + Reader reader = makeReader(); + + char[] chars = new char[mInput.length()]; + StringBuilder buffer = new StringBuilder(mInput.length()); + + int count; + int offset = 0; + int lenght = chars.length; + while ((count = reader.read(chars, offset, lenght)) > 0) { + buffer.append(chars, offset, count); + offset += count; + lenght -= count; + } + + assertEquals(mInput, buffer.toString()); + assertEquals(mInput, new String(chars)); + } + + @Test + public void testSkipToEnd() throws IOException { + Reader reader = makeReader(); + + int toSkip = mInput.length(); + while (toSkip > 0) { + long skipped = reader.skip(toSkip); + assertFalse("Skipped < 0", skipped < 0); + toSkip -= skipped; + } + + assertEquals(0, toSkip); + } + + @Test + public void testSkipToEndAndRead() throws IOException { + Reader reader = makeReader(); + + int toSkip = mInput.length(); + while (toSkip > 0) { + toSkip -= reader.skip(toSkip); + } + + assertEquals(reader.read(), -1); + } + + // TODO: It's possible to support reset and not mark (resets to beginning of stream, for example) + @Test + public void testResetMarkSupported() throws IOException { + Reader reader = makeReader(); + + if (reader.markSupported()) { + // Mark at 0 + reader.mark(mInput.length() / 4); + + // Read one char + char ch = (char) reader.read(); + reader.reset(); + assertEquals(ch, (char) reader.read()); + reader.reset(); + + // Read from start + StringBuilder first = new StringBuilder(mInput.length() / 4); + for (int i = 0; i < mInput.length() / 4; i++) { + first.append((char) reader.read()); + } + + reader.reset(); // 0 + + StringBuilder second = new StringBuilder(mInput.length() / 4); + for (int i = 0; i < mInput.length() / 4; i++) { + second.append((char) reader.read()); + } + + assertEquals(first.toString(), second.toString()); + + // Mark at 1/4 + reader.mark(mInput.length() / 4); + + // Read from 1/4 + first = new StringBuilder(mInput.length() / 4); + for (int i = 0; i < mInput.length() / 4; i++) { + first.append((char) reader.read()); + } + + reader.reset(); // 1/4 + + second = new StringBuilder(mInput.length() / 4); + for (int i = 0; i < mInput.length() / 4; i++) { + second.append((char) reader.read()); + } + + assertEquals(first.toString(), second.toString()); + + // Read past limit + reader.read(); + + // This may or may not fail, depending on the stream + try { + reader.reset(); + } + catch (IOException ioe) { + assertNotNull(ioe.getMessage()); + } + } + } + + @Test + public void testResetMarkNotSupported() throws IOException { + Reader reader = makeReader(); + + if (!reader.markSupported()) { + try { + reader.mark(mInput.length()); + fail("Mark set, while markSupprted is false"); + } + catch (IOException e) { + assertNotNull(e.getMessage()); + } + + // Read one char + char ch = (char) reader.read(); + try { + reader.reset(); + assertEquals(ch, (char) reader.read()); + } + catch (IOException ioe) { + assertNotNull(ioe.getMessage()); + } + + // Read from start + StringBuilder first = new StringBuilder(mInput.length() / 4); + for (int i = 0; i < mInput.length() / 4; i++) { + first.append((char) reader.read()); + } + + try { + reader.reset(); // 0 + + StringBuilder second = new StringBuilder(mInput.length() / 4); + for (int i = 0; i < mInput.length() / 4; i++) { + second.append((char) reader.read()); + } + + assertEquals(first.toString(), second.toString()); + } + catch (IOException ioe) { + assertNotNull(ioe.getMessage()); + } + } + } + + @Test + public void testReadAfterClose() throws IOException { + Reader reader = makeReader("foo bar"); + + reader.close(); + + try { + reader.read(); + fail("Should not allow read after close"); + } + catch (IOException ioe) { + assertNotNull(ioe.getMessage()); + } + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableAbstractTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableAbstractTest.java new file mode 100755 index 00000000..2ae256ec --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableAbstractTest.java @@ -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.io; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * SeekableAbstractTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java#1 $ + */ +public abstract class SeekableAbstractTest implements SeekableInterfaceTest { + + protected abstract Seekable createSeekable(); + + @Test + public void testFail() { + fail("Do not create stand-alone test classes based on this class. Instead, create an inner class and delegate to it."); + } + + @Test + public void testSeekable() { + assertTrue(createSeekable() instanceof Seekable); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java deleted file mode 100755 index 28dfb036..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.twelvemonkeys.io; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * SeekableAbstractTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java#1 $ - */ -public abstract class SeekableAbstractTestCase implements SeekableInterfaceTest { - - protected abstract Seekable createSeekable(); - - @Test - public void testFail() { - fail("Do not create stand-alone test classes based on this class. Instead, create an inner class and delegate to it."); - } - - @Test - public void testSeekable() { - assertTrue(createSeekable() instanceof Seekable); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTest.java similarity index 90% rename from common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTest.java index 7a995e49..4a4b2d25 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTest.java @@ -1,488 +1,518 @@ -package com.twelvemonkeys.io; - -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.Assert.*; - -/** - * SeekableInputStreamAbstractTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java#4 $ - */ -public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest { - //// TODO: Figure out a better way of creating interface tests without duplicating code - final SeekableAbstractTestCase seekableTestCase = new SeekableAbstractTestCase() { - protected Seekable createSeekable() { - return makeInputStream(); - } - }; - - @Override - protected SeekableInputStream makeInputStream() { - return (SeekableInputStream) super.makeInputStream(); - } - - @Override - protected SeekableInputStream makeInputStream(final int pSize) { - return (SeekableInputStream) super.makeInputStream(pSize); - } - - protected SeekableInputStream makeInputStream(byte[] pBytes) { - return makeInputStream(new ByteArrayInputStream(pBytes)); - } - - protected abstract SeekableInputStream makeInputStream(InputStream pStream); - - @Test - @Override - public void testResetAfterReset() throws Exception { - InputStream input = makeInputStream(makeOrderedArray(25)); - - if (!input.markSupported()) { - return; // Not supported, skip test - } - - assertTrue("Expected to read positive value", input.read() >= 0); - - int readlimit = 5; - - // Mark - input.mark(readlimit); - int read = input.read(); - assertTrue("Expected to read positive value", read >= 0); - - input.reset(); - assertEquals("Expected value read differs from actual", read, input.read()); - - // Reset after read limit passed, may either throw exception, or reset to last good mark - try { - input.reset(); - assertEquals("Re-read of reset data should be first", 0, input.read()); - } - catch (Exception e) { - assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); - } - } - - @Test - public void testSeekable() { - seekableTestCase.testSeekable(); - } - - @Test - public void testFlushBeyondCurrentPos() throws Exception { - SeekableInputStream seekable = makeInputStream(20); - - int pos = 10; - try { - seekable.flushBefore(pos); - fail("Flush beyond current position should throw IndexOutOfBoundsException"); - } - catch (IndexOutOfBoundsException e) { - // Ignore - } - } - - @Test - public void testSeek() throws Exception { - SeekableInputStream seekable = makeInputStream(55); - int pos = 37; - - seekable.seek(pos); - long streamPos = seekable.getStreamPosition(); - assertEquals("Stream positon should match seeked position", pos, streamPos); - } - - @Test - public void testSeekFlush() throws Exception { - SeekableInputStream seekable = makeInputStream(133); - int pos = 45; - seekable.seek(pos); - seekable.flushBefore(pos); - long flushedPos = seekable.getFlushedPosition(); - assertEquals("Flushed positon should match position", pos, flushedPos); - - try { - seekable.seek(pos - 1); - fail("Read before flushed position succeeded"); - } - catch (IndexOutOfBoundsException e) { - // Ignore - } - } - - @Test - public void testMarkFlushReset() throws Exception { - SeekableInputStream seekable = makeInputStream(77); - - seekable.mark(); - - int position = 55; - seekable.seek(position); - seekable.flushBefore(position); - - try { - seekable.reset(); - fail("Reset before flushed position succeeded"); - } - catch (IOException e) { - // Ignore - } - - assertEquals(position, seekable.getStreamPosition()); - } - - @Test - public void testSeekSkipRead() throws Exception { - SeekableInputStream seekable = makeInputStream(133); - int pos = 45; - for (int i = 0; i < 10; i++) { - seekable.seek(pos); - //noinspection ResultOfMethodCallIgnored - seekable.skip(i); - byte[] bytes = FileUtil.read(seekable); - assertEquals(133, seekable.getStreamPosition()); - assertEquals(133 - 45- i, bytes.length); - } - } - - protected void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException { - System.out.println(); - pSeekable.seek(pStr.length()); - FileUtil.read(pSeekable); - for (int i = 0; i < 10; i++) { - byte[] bytes = FileUtil.read(pSeekable); - int len = bytes.length; - if (len != 0) { - System.err.println("Error in buffer length after full read..."); - System.err.println("len: " + len); - System.err.println("bytes: \"" + new String(bytes) + "\""); - break; - } - } - - System.out.println(); - - for (int i = 0; i < 10; i++) { - pSeekable.seek(0); - int skip = i * 3; - //noinspection ResultOfMethodCallIgnored - pSeekable.skip(skip); - String str = new String(FileUtil.read(pSeekable)); - System.out.println(str); - if (str.length() != pStr.length() - skip) { - throw new Error("Error in buffer length after skip"); - } - } - - System.out.println(); - System.out.println("seek/skip ok!"); - System.out.println(); - } - - protected static void markReset(SeekableInputStream pSeekable) throws IOException { - for (int i = 0; i < 10; i++) { - pSeekable.mark(); - System.out.println(new String(FileUtil.read(pSeekable))); - pSeekable.reset(); - } - - System.out.println(); - System.out.println("mark/reset ok!"); - } - - protected static void timeRead(SeekableInputStream pSeekable) throws IOException { - for (int i = 0; i < 5000; i++) { - pSeekable.mark(); - FileUtil.read(pSeekable); - pSeekable.reset(); - } - - long start = System.currentTimeMillis(); - final int times = 200000; - for (int i = 0; i < times; i++) { - pSeekable.mark(); - FileUtil.read(pSeekable); - pSeekable.reset(); - } - long time = System.currentTimeMillis() - start; - - System.out.println("Time; " + time + "ms (" + (time / (float) times) + "ms/inv)"); - } - - /* - - // Test code below... - protected final static String STR = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce massa orci, adipiscing vel, dapibus et, vulputate tristique, tortor. Quisque sodales. Mauris varius turpis et pede. Nam ac dolor vel diam condimentum elementum. Pellentesque eget tellus. Praesent magna. Sed fringilla. Proin ullamcorper tincidunt ante. Fusce dapibus nibh nec dolor. Etiam erat. Nullam dignissim laoreet nibh. Maecenas scelerisque. Pellentesque in quam. Maecenas sollicitudin, magna nec imperdiet facilisis, metus quam tristique ipsum, vitae consequat massa purus eget leo. Nulla ipsum. Proin non purus eget tellus lobortis iaculis. In lorem justo, posuere id, vulputate at, adipiscing ut, nisl. Nunc dui erat, tincidunt ac, interdum quis, rutrum et, libero. Etiam lectus dui, viverra sit amet, elementum ut, malesuada sed, massa. Vestibulum mi nulla, sodales vel, vestibulum sed, congue blandit, velit."; - - protected static void flushSeek(SeekableInputStream pSeekable, String pStr) throws IOException { - pSeekable.seek(0); - pSeekable.mark(); - int pos = pStr.length() / 2; - try { - pSeekable.flushBefore(pos); - System.err.println("Error in flush/seek"); - } - catch (IndexOutOfBoundsException e) { - // Ignore - } - pSeekable.seek(pos); - long streamPos = pSeekable.getStreamPosition(); - if (streamPos != pos) { - System.err.println("Streampos not equal seeked pos"); - } - - pSeekable.flushBefore(pos); - long flushedPos = pSeekable.getFlushedPosition(); - if (flushedPos != pos) { - System.err.println("flushedpos not equal set flushed pos"); - } - - for (int i = 0; i < 10; i++) { - pSeekable.seek(pos); - //noinspection ResultOfMethodCallIgnored - pSeekable.skip(i); - System.out.println(new String(FileUtil.read(pSeekable))); - } - - try { - pSeekable.seek(pos - 1); - System.err.println("Error in flush/seek"); - } - catch (IndexOutOfBoundsException e) { - // Ignore - } - try { - pSeekable.reset(); - System.err.println("Error in flush/seek"); - } - catch (IOException e) { - // Ignore - } - - System.out.println(); - System.out.println("flush/seek ok!"); - } - - protected static void seekSkip(SeekableInputStream pSeekable, String pStr) throws IOException { - System.out.println(); - pSeekable.seek(pStr.length()); - FileUtil.read(pSeekable); - for (int i = 0; i < 10; i++) { - byte[] bytes = FileUtil.read(pSeekable); - int len = bytes.length; - if (len != 0) { - System.err.println("Error in buffer length after full read..."); - System.err.println("len: " + len); - System.err.println("bytes: \"" + new String(bytes) + "\""); - break; - } - } - - System.out.println(); - - for (int i = 0; i < 10; i++) { - pSeekable.seek(0); - int skip = i * 3; - //noinspection ResultOfMethodCallIgnored - pSeekable.skip(skip); - String str = new String(FileUtil.read(pSeekable)); - System.out.println(str); - if (str.length() != pStr.length() - skip) { - throw new Error("Error in buffer length after skip"); - } - } - - System.out.println(); - System.out.println("seek/skip ok!"); - System.out.println(); - } - - protected static void markReset(SeekableInputStream pSeekable) throws IOException { - for (int i = 0; i < 10; i++) { - pSeekable.mark(); - System.out.println(new String(FileUtil.read(pSeekable))); - pSeekable.reset(); - } - - System.out.println(); - System.out.println("mark/reset ok!"); - } - - protected static void timeRead(SeekableInputStream pSeekable) throws IOException { - for (int i = 0; i < 5000; i++) { - pSeekable.mark(); - FileUtil.read(pSeekable); - pSeekable.reset(); - } - - long start = System.currentTimeMillis(); - final int times = 200000; - for (int i = 0; i < times; i++) { - pSeekable.mark(); - FileUtil.read(pSeekable); - pSeekable.reset(); - } - long time = System.currentTimeMillis() - start; - - System.out.println("Time; " + time + "ms (" + (time / (float) times) + "ms/inv)"); - } - */ - - @Test - public void testReadResetReadDirectBufferBug() throws IOException { - // Make sure we use the exact size of the buffer - final int size = 1024; - - // Fill bytes - byte[] bytes = new byte[size * 2]; - sRandom.nextBytes(bytes); - - // Create wrapper stream - SeekableInputStream stream = makeInputStream(bytes); - - // Read to fill the buffer, then reset - int val; - - val = stream.read(); - assertFalse("Unexepected EOF", val == -1); - val = stream.read(); - assertFalse("Unexepected EOF", val == -1); - val = stream.read(); - assertFalse("Unexepected EOF", val == -1); - val = stream.read(); - assertFalse("Unexepected EOF", val == -1); - - stream.seek(0); - - // Read fully and compare - byte[] result = new byte[size]; - - readFully(stream, result); - assertTrue(rangeEquals(bytes, 0, result, 0, size)); - - readFully(stream, result); - assertTrue(rangeEquals(bytes, size, result, 0, size)); - } - - @Test - public void testReadAllByteValuesRegression() throws IOException { - final int size = 128; - - // Fill bytes - byte[] bytes = new byte[256]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = (byte) i; - } - - // Create wrapper stream - SeekableInputStream stream = makeInputStream(bytes); - - // Fill buffer - byte[] buffer = new byte[size]; - while (stream.read(buffer) >= 0) { - } - - stream.seek(0); - for (int i = 0; i < bytes.length; i += 2) { - assertEquals("Wrong stream position", i, stream.getStreamPosition()); - int count = stream.read(buffer, 0, 2); - assertEquals(2, count); - assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i], buffer[0]); - assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i + 1], buffer[1]); - } - - stream.seek(0); - for (int i = 0; i < bytes.length; i++) { - assertEquals("Wrong stream position", i, stream.getStreamPosition()); - int actual = stream.read(); - assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i] & 0xff, actual); - assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i], (byte) actual); - } - - } - - @Test - public void testCloseUnderlyingStream() throws IOException { - final boolean[] closed = new boolean[1]; - - ByteArrayInputStream input = new ByteArrayInputStream(makeRandomArray(256)) { - @Override - public void close() throws IOException { - closed[0] = true; - super.close(); - } - }; - - SeekableInputStream stream = makeInputStream(input); - - try { - FileUtil.read(stream); // Read until EOF - - assertEquals("EOF not reached (test case broken)", -1, stream.read()); - assertFalse("Underlying stream closed before close", closed[0]); - } - finally { - stream.close(); - } - - assertTrue("Underlying stream not closed", closed[0]); - - } - - private void readFully(InputStream pStream, byte[] pResult) throws IOException { - int pos = 0; - while (pos < pResult.length) { - int read = pStream.read(pResult, pos, pResult.length - pos); - if (read == -1) { - throw new EOFException(); - } - pos += read; - } - } - - /** - * Test two arrays for range equality. That is, they contain the same elements for some specified range. - * - * @param pFirst one array to test for equality - * @param pFirstOffset the offset into the first array to start testing for equality - * @param pSecond the other array to test for equality - * @param pSecondOffset the offset into the second array to start testing for equality - * @param pLength the length of the range to check for equality - * - * @return {@code true} if both arrays are non-{@code null} - * and have at least {@code offset + pLength} elements - * and all elements in the range from the first array is equal to the elements from the second array, - * or if {@code pFirst == pSecond} (including both arrays being {@code null}) - * and {@code pFirstOffset == pSecondOffset}. - * Otherwise {@code false}. - */ - static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) { - if (pFirst == pSecond && pFirstOffset == pSecondOffset) { - return true; - } - - if (pFirst == null || pSecond == null) { - return false; - } - - if (pFirst.length < pFirstOffset + pLength || pSecond.length < pSecondOffset + pLength) { - return false; - } - - for (int i = 0; i < pLength; i++) { - if (pFirst[pFirstOffset + i] != pSecond[pSecondOffset + i]) { - return false; - } - } - - return true; - } -} +/* + * 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.io; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.*; + +/** + * SeekableInputStreamAbstractTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java#4 $ + */ +public abstract class SeekableInputStreamAbstractTest extends InputStreamAbstractTest implements SeekableInterfaceTest { + //// TODO: Figure out a better way of creating interface tests without duplicating code + final SeekableAbstractTest seekableTestCase = new SeekableAbstractTest() { + protected Seekable createSeekable() { + return makeInputStream(); + } + }; + + @Override + protected SeekableInputStream makeInputStream() { + return (SeekableInputStream) super.makeInputStream(); + } + + @Override + protected SeekableInputStream makeInputStream(final int pSize) { + return (SeekableInputStream) super.makeInputStream(pSize); + } + + protected SeekableInputStream makeInputStream(byte[] pBytes) { + return makeInputStream(new ByteArrayInputStream(pBytes)); + } + + protected abstract SeekableInputStream makeInputStream(InputStream pStream); + + @Test + @Override + public void testResetAfterReset() throws Exception { + InputStream input = makeInputStream(makeOrderedArray(25)); + + if (!input.markSupported()) { + return; // Not supported, skip test + } + + assertTrue("Expected to read positive value", input.read() >= 0); + + int readlimit = 5; + + // Mark + input.mark(readlimit); + int read = input.read(); + assertTrue("Expected to read positive value", read >= 0); + + input.reset(); + assertEquals("Expected value read differs from actual", read, input.read()); + + // Reset after read limit passed, may either throw exception, or reset to last good mark + try { + input.reset(); + assertEquals("Re-read of reset data should be first", 0, input.read()); + } + catch (Exception e) { + assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); + } + } + + @Test + public void testSeekable() { + seekableTestCase.testSeekable(); + } + + @Test + public void testFlushBeyondCurrentPos() throws Exception { + SeekableInputStream seekable = makeInputStream(20); + + int pos = 10; + try { + seekable.flushBefore(pos); + fail("Flush beyond current position should throw IndexOutOfBoundsException"); + } + catch (IndexOutOfBoundsException e) { + // Ignore + } + } + + @Test + public void testSeek() throws Exception { + SeekableInputStream seekable = makeInputStream(55); + int pos = 37; + + seekable.seek(pos); + long streamPos = seekable.getStreamPosition(); + assertEquals("Stream positon should match seeked position", pos, streamPos); + } + + @Test + public void testSeekFlush() throws Exception { + SeekableInputStream seekable = makeInputStream(133); + int pos = 45; + seekable.seek(pos); + seekable.flushBefore(pos); + long flushedPos = seekable.getFlushedPosition(); + assertEquals("Flushed positon should match position", pos, flushedPos); + + try { + seekable.seek(pos - 1); + fail("Read before flushed position succeeded"); + } + catch (IndexOutOfBoundsException e) { + // Ignore + } + } + + @Test + public void testMarkFlushReset() throws Exception { + SeekableInputStream seekable = makeInputStream(77); + + seekable.mark(); + + int position = 55; + seekable.seek(position); + seekable.flushBefore(position); + + try { + seekable.reset(); + fail("Reset before flushed position succeeded"); + } + catch (IOException e) { + // Ignore + } + + assertEquals(position, seekable.getStreamPosition()); + } + + @Test + public void testSeekSkipRead() throws Exception { + SeekableInputStream seekable = makeInputStream(133); + int pos = 45; + for (int i = 0; i < 10; i++) { + seekable.seek(pos); + //noinspection ResultOfMethodCallIgnored + seekable.skip(i); + byte[] bytes = FileUtil.read(seekable); + assertEquals(133, seekable.getStreamPosition()); + assertEquals(133 - 45- i, bytes.length); + } + } + + protected void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException { + System.out.println(); + pSeekable.seek(pStr.length()); + FileUtil.read(pSeekable); + for (int i = 0; i < 10; i++) { + byte[] bytes = FileUtil.read(pSeekable); + int len = bytes.length; + if (len != 0) { + System.err.println("Error in buffer length after full read..."); + System.err.println("len: " + len); + System.err.println("bytes: \"" + new String(bytes) + "\""); + break; + } + } + + System.out.println(); + + for (int i = 0; i < 10; i++) { + pSeekable.seek(0); + int skip = i * 3; + //noinspection ResultOfMethodCallIgnored + pSeekable.skip(skip); + String str = new String(FileUtil.read(pSeekable)); + System.out.println(str); + if (str.length() != pStr.length() - skip) { + throw new Error("Error in buffer length after skip"); + } + } + + System.out.println(); + System.out.println("seek/skip ok!"); + System.out.println(); + } + + protected static void markReset(SeekableInputStream pSeekable) throws IOException { + for (int i = 0; i < 10; i++) { + pSeekable.mark(); + System.out.println(new String(FileUtil.read(pSeekable))); + pSeekable.reset(); + } + + System.out.println(); + System.out.println("mark/reset ok!"); + } + + protected static void timeRead(SeekableInputStream pSeekable) throws IOException { + for (int i = 0; i < 5000; i++) { + pSeekable.mark(); + FileUtil.read(pSeekable); + pSeekable.reset(); + } + + long start = System.currentTimeMillis(); + final int times = 200000; + for (int i = 0; i < times; i++) { + pSeekable.mark(); + FileUtil.read(pSeekable); + pSeekable.reset(); + } + long time = System.currentTimeMillis() - start; + + System.out.println("Time; " + time + "ms (" + (time / (float) times) + "ms/inv)"); + } + + /* + + // Test code below... + protected final static String STR = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce massa orci, adipiscing vel, dapibus et, vulputate tristique, tortor. Quisque sodales. Mauris varius turpis et pede. Nam ac dolor vel diam condimentum elementum. Pellentesque eget tellus. Praesent magna. Sed fringilla. Proin ullamcorper tincidunt ante. Fusce dapibus nibh nec dolor. Etiam erat. Nullam dignissim laoreet nibh. Maecenas scelerisque. Pellentesque in quam. Maecenas sollicitudin, magna nec imperdiet facilisis, metus quam tristique ipsum, vitae consequat massa purus eget leo. Nulla ipsum. Proin non purus eget tellus lobortis iaculis. In lorem justo, posuere id, vulputate at, adipiscing ut, nisl. Nunc dui erat, tincidunt ac, interdum quis, rutrum et, libero. Etiam lectus dui, viverra sit amet, elementum ut, malesuada sed, massa. Vestibulum mi nulla, sodales vel, vestibulum sed, congue blandit, velit."; + + protected static void flushSeek(SeekableInputStream pSeekable, String pStr) throws IOException { + pSeekable.seek(0); + pSeekable.mark(); + int pos = pStr.length() / 2; + try { + pSeekable.flushBefore(pos); + System.err.println("Error in flush/seek"); + } + catch (IndexOutOfBoundsException e) { + // Ignore + } + pSeekable.seek(pos); + long streamPos = pSeekable.getStreamPosition(); + if (streamPos != pos) { + System.err.println("Streampos not equal seeked pos"); + } + + pSeekable.flushBefore(pos); + long flushedPos = pSeekable.getFlushedPosition(); + if (flushedPos != pos) { + System.err.println("flushedpos not equal set flushed pos"); + } + + for (int i = 0; i < 10; i++) { + pSeekable.seek(pos); + //noinspection ResultOfMethodCallIgnored + pSeekable.skip(i); + System.out.println(new String(FileUtil.read(pSeekable))); + } + + try { + pSeekable.seek(pos - 1); + System.err.println("Error in flush/seek"); + } + catch (IndexOutOfBoundsException e) { + // Ignore + } + try { + pSeekable.reset(); + System.err.println("Error in flush/seek"); + } + catch (IOException e) { + // Ignore + } + + System.out.println(); + System.out.println("flush/seek ok!"); + } + + protected static void seekSkip(SeekableInputStream pSeekable, String pStr) throws IOException { + System.out.println(); + pSeekable.seek(pStr.length()); + FileUtil.read(pSeekable); + for (int i = 0; i < 10; i++) { + byte[] bytes = FileUtil.read(pSeekable); + int len = bytes.length; + if (len != 0) { + System.err.println("Error in buffer length after full read..."); + System.err.println("len: " + len); + System.err.println("bytes: \"" + new String(bytes) + "\""); + break; + } + } + + System.out.println(); + + for (int i = 0; i < 10; i++) { + pSeekable.seek(0); + int skip = i * 3; + //noinspection ResultOfMethodCallIgnored + pSeekable.skip(skip); + String str = new String(FileUtil.read(pSeekable)); + System.out.println(str); + if (str.length() != pStr.length() - skip) { + throw new Error("Error in buffer length after skip"); + } + } + + System.out.println(); + System.out.println("seek/skip ok!"); + System.out.println(); + } + + protected static void markReset(SeekableInputStream pSeekable) throws IOException { + for (int i = 0; i < 10; i++) { + pSeekable.mark(); + System.out.println(new String(FileUtil.read(pSeekable))); + pSeekable.reset(); + } + + System.out.println(); + System.out.println("mark/reset ok!"); + } + + protected static void timeRead(SeekableInputStream pSeekable) throws IOException { + for (int i = 0; i < 5000; i++) { + pSeekable.mark(); + FileUtil.read(pSeekable); + pSeekable.reset(); + } + + long start = System.currentTimeMillis(); + final int times = 200000; + for (int i = 0; i < times; i++) { + pSeekable.mark(); + FileUtil.read(pSeekable); + pSeekable.reset(); + } + long time = System.currentTimeMillis() - start; + + System.out.println("Time; " + time + "ms (" + (time / (float) times) + "ms/inv)"); + } + */ + + @Test + public void testReadResetReadDirectBufferBug() throws IOException { + // Make sure we use the exact size of the buffer + final int size = 1024; + + // Fill bytes + byte[] bytes = new byte[size * 2]; + sRandom.nextBytes(bytes); + + // Create wrapper stream + SeekableInputStream stream = makeInputStream(bytes); + + // Read to fill the buffer, then reset + int val; + + val = stream.read(); + assertFalse("Unexepected EOF", val == -1); + val = stream.read(); + assertFalse("Unexepected EOF", val == -1); + val = stream.read(); + assertFalse("Unexepected EOF", val == -1); + val = stream.read(); + assertFalse("Unexepected EOF", val == -1); + + stream.seek(0); + + // Read fully and compare + byte[] result = new byte[size]; + + readFully(stream, result); + assertTrue(rangeEquals(bytes, 0, result, 0, size)); + + readFully(stream, result); + assertTrue(rangeEquals(bytes, size, result, 0, size)); + } + + @Test + public void testReadAllByteValuesRegression() throws IOException { + final int size = 128; + + // Fill bytes + byte[] bytes = new byte[256]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) i; + } + + // Create wrapper stream + SeekableInputStream stream = makeInputStream(bytes); + + // Fill buffer + byte[] buffer = new byte[size]; + while (stream.read(buffer) >= 0) { + } + + stream.seek(0); + for (int i = 0; i < bytes.length; i += 2) { + assertEquals("Wrong stream position", i, stream.getStreamPosition()); + int count = stream.read(buffer, 0, 2); + assertEquals(2, count); + assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i], buffer[0]); + assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i + 1], buffer[1]); + } + + stream.seek(0); + for (int i = 0; i < bytes.length; i++) { + assertEquals("Wrong stream position", i, stream.getStreamPosition()); + int actual = stream.read(); + assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i] & 0xff, actual); + assertEquals(String.format("Wrong value read at pos %d", stream.getStreamPosition()), bytes[i], (byte) actual); + } + + } + + @Test + public void testCloseUnderlyingStream() throws IOException { + final boolean[] closed = new boolean[1]; + + ByteArrayInputStream input = new ByteArrayInputStream(makeRandomArray(256)) { + @Override + public void close() throws IOException { + closed[0] = true; + super.close(); + } + }; + + SeekableInputStream stream = makeInputStream(input); + + try { + FileUtil.read(stream); // Read until EOF + + assertEquals("EOF not reached (test case broken)", -1, stream.read()); + assertFalse("Underlying stream closed before close", closed[0]); + } + finally { + stream.close(); + } + + assertTrue("Underlying stream not closed", closed[0]); + + } + + private void readFully(InputStream pStream, byte[] pResult) throws IOException { + int pos = 0; + while (pos < pResult.length) { + int read = pStream.read(pResult, pos, pResult.length - pos); + if (read == -1) { + throw new EOFException(); + } + pos += read; + } + } + + /** + * Test two arrays for range equality. That is, they contain the same elements for some specified range. + * + * @param pFirst one array to test for equality + * @param pFirstOffset the offset into the first array to start testing for equality + * @param pSecond the other array to test for equality + * @param pSecondOffset the offset into the second array to start testing for equality + * @param pLength the length of the range to check for equality + * + * @return {@code true} if both arrays are non-{@code null} + * and have at least {@code offset + pLength} elements + * and all elements in the range from the first array is equal to the elements from the second array, + * or if {@code pFirst == pSecond} (including both arrays being {@code null}) + * and {@code pFirstOffset == pSecondOffset}. + * Otherwise {@code false}. + */ + static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) { + if (pFirst == pSecond && pFirstOffset == pSecondOffset) { + return true; + } + + if (pFirst == null || pSecond == null) { + return false; + } + + if (pFirst.length < pFirstOffset + pLength || pSecond.length < pSecondOffset + pLength) { + return false; + } + + for (int i = 0; i < pLength; i++) { + if (pFirst[pFirstOffset + i] != pSecond[pSecondOffset + i]) { + return false; + } + } + + return true; + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInterfaceTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInterfaceTest.java index 1c59b52c..2a5e92bc 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInterfaceTest.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/SeekableInterfaceTest.java @@ -1,3 +1,33 @@ +/* + * 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.io; /** diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/StringArrayReaderTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/StringArrayReaderTest.java new file mode 100755 index 00000000..53af2336 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/StringArrayReaderTest.java @@ -0,0 +1,86 @@ +/* + * 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.io; + +import com.twelvemonkeys.lang.StringUtil; +import org.junit.Test; + +import java.io.IOException; +import java.io.Reader; + +import static org.junit.Assert.*; + +/** + * StringArrayReaderTestCase + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/StringArrayReaderTestCase.java#1 $ + */ +public class StringArrayReaderTest extends ReaderAbstractTest { + + protected Reader makeReader(String pInput) { + // Split + String[] input = StringUtil.toStringArray(pInput, " "); + // Reappend spaces... + for (int i = 0; i < input.length; i++) { + if (i != 0) { + input[i] = " " + input[i]; + } + } + + return new StringArrayReader(input); + } + + @Test + public void testNullConstructor() { + try { + new StringArrayReader(null); + fail("Should not allow null argument"); + } + catch (RuntimeException e) { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testEmptyArrayConstructor() throws IOException { + Reader reader = new StringArrayReader(new String[0]); + assertEquals(-1, reader.read()); + } + + @Test + public void testEmptyStringConstructor() throws IOException { + Reader reader = new StringArrayReader(new String[] {""}); + assertEquals(-1, reader.read()); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/StringArrayReaderTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/StringArrayReaderTestCase.java deleted file mode 100755 index cf1594c9..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/StringArrayReaderTestCase.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.twelvemonkeys.io; - -import com.twelvemonkeys.lang.StringUtil; -import org.junit.Test; - -import java.io.Reader; -import java.io.IOException; - -import static org.junit.Assert.*; - -/** - * StringArrayReaderTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/StringArrayReaderTestCase.java#1 $ - */ -public class StringArrayReaderTestCase extends ReaderAbstractTestCase { - - protected Reader makeReader(String pInput) { - // Split - String[] input = StringUtil.toStringArray(pInput, " "); - // Reappend spaces... - for (int i = 0; i < input.length; i++) { - if (i != 0) { - input[i] = " " + input[i]; - } - } - - return new StringArrayReader(input); - } - - @Test - public void testNullConstructor() { - try { - new StringArrayReader(null); - fail("Should not allow null argument"); - } - catch (RuntimeException e) { - assertNotNull(e.getMessage()); - } - } - - @Test - public void testEmptyArrayConstructor() throws IOException { - Reader reader = new StringArrayReader(new String[0]); - assertEquals(-1, reader.read()); - } - - @Test - public void testEmptyStringConstructor() throws IOException { - Reader reader = new StringArrayReader(new String[] {""}); - assertEquals(-1, reader.read()); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTest.java similarity index 56% rename from common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTest.java index 922bf5b5..457fb901 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTest.java @@ -1,71 +1,103 @@ -package com.twelvemonkeys.io.enc; - - -import com.twelvemonkeys.io.FileUtil; -import org.junit.Test; - -import java.io.*; - -import static org.junit.Assert.*; - -/** - * Base64DecoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTestCase.java#1 $ - */ -public class Base64DecoderTestCase extends DecoderAbstractTestCase { - - public Decoder createDecoder() { - return new Base64Decoder(); - } - - public Encoder createCompatibleEncoder() { - return new Base64Encoder(); - } - - @Test - public void testEmptyDecode2() throws IOException { - String data = ""; - - InputStream in = new DecoderStream(new ByteArrayInputStream(data.getBytes()), createDecoder()); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - FileUtil.copy(in, bytes); - - assertEquals("Strings does not match", "", new String(bytes.toByteArray())); - } - - @Test - public void testShortDecode() throws IOException { - String data = "dGVzdA=="; - - InputStream in = new DecoderStream(new ByteArrayInputStream(data.getBytes()), createDecoder()); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - FileUtil.copy(in, bytes); - - assertEquals("Strings does not match", "test", new String(bytes.toByteArray())); - } - - @Test - public void testLongDecode() throws IOException { - String data = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIEZ1" + - "c2NlIGVzdC4gTW9yYmkgbHVjdHVzIGNvbnNlY3RldHVlciBqdXN0by4gVml2YW11cyBkYXBpYnVzIGxh" + - "b3JlZXQgcHVydXMuIE51bmMgdml2ZXJyYSBkaWN0dW0gbmlzbC4gSW50ZWdlciB1bGxhbWNvcnBlciwg" + - "bmlzaSBpbiBkaWN0dW0gYW1ldC4="; - - InputStream in = new DecoderStream(new ByteArrayInputStream(data.getBytes()), createDecoder()); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - FileUtil.copy(in, bytes); - - assertEquals("Strings does not match", - "Lorem ipsum dolor sit amet, consectetuer adipiscing " + - "elit. Fusce est. Morbi luctus consectetuer justo. Vivamus " + - "dapibus laoreet purus. Nunc viverra dictum nisl. Integer " + - "ullamcorper, nisi in dictum amet.", - new String(bytes.toByteArray())); - } +/* + * 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.io.enc; + +import com.twelvemonkeys.io.FileUtil; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +/** + * Base64DecoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/Base64DecoderTestCase.java#1 $ + */ +public class Base64DecoderTest extends DecoderAbstractTest { + + public Decoder createDecoder() { + return new Base64Decoder(); + } + + public Encoder createCompatibleEncoder() { + return new Base64Encoder(); + } + + @Test + public void testEmptyDecode2() throws IOException { + String data = ""; + + InputStream in = new DecoderStream(new ByteArrayInputStream(data.getBytes()), createDecoder()); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + FileUtil.copy(in, bytes); + + assertEquals("Strings does not match", "", new String(bytes.toByteArray())); + } + + @Test + public void testShortDecode() throws IOException { + String data = "dGVzdA=="; + + InputStream in = new DecoderStream(new ByteArrayInputStream(data.getBytes()), createDecoder()); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + FileUtil.copy(in, bytes); + + assertEquals("Strings does not match", "test", new String(bytes.toByteArray())); + } + + @Test + public void testLongDecode() throws IOException { + String data = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIEZ1" + + "c2NlIGVzdC4gTW9yYmkgbHVjdHVzIGNvbnNlY3RldHVlciBqdXN0by4gVml2YW11cyBkYXBpYnVzIGxh" + + "b3JlZXQgcHVydXMuIE51bmMgdml2ZXJyYSBkaWN0dW0gbmlzbC4gSW50ZWdlciB1bGxhbWNvcnBlciwg" + + "bmlzaSBpbiBkaWN0dW0gYW1ldC4="; + + InputStream in = new DecoderStream(new ByteArrayInputStream(data.getBytes()), createDecoder()); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + FileUtil.copy(in, bytes); + + assertEquals("Strings does not match", + "Lorem ipsum dolor sit amet, consectetuer adipiscing " + + "elit. Fusce est. Morbi luctus consectetuer justo. Vivamus " + + "dapibus laoreet purus. Nunc viverra dictum nisl. Integer " + + "ullamcorper, nisi in dictum amet.", + new String(bytes.toByteArray())); + } } \ No newline at end of file diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTest.java similarity index 58% rename from common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTest.java index 0c3e7720..7e77196a 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTest.java @@ -1,68 +1,98 @@ -package com.twelvemonkeys.io.enc; - -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import static org.junit.Assert.*; - -/** - * Base64EncoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTestCase.java#1 $ - */ -public class Base64EncoderTestCase extends EncoderAbstractTestCase { - - protected Encoder createEncoder() { - return new Base64Encoder(); - } - - protected Decoder createCompatibleDecoder() { - return new Base64Decoder(); - } - - @Test - public void testEmptyEncode() throws IOException { - String data = ""; - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - OutputStream out = new EncoderStream(bytes, createEncoder(), true); - out.write(data.getBytes()); - - assertEquals("Strings does not match", "", new String(bytes.toByteArray())); - } - - @Test - public void testShortEncode() throws IOException { - String data = "test"; - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - OutputStream out = new EncoderStream(bytes, createEncoder(), true); - out.write(data.getBytes()); - - assertEquals("Strings does not match", "dGVzdA==", new String(bytes.toByteArray())); - } - - @Test - public void testLongEncode() throws IOException { - String data = "Lorem ipsum dolor sit amet, consectetuer adipiscing " + - "elit. Fusce est. Morbi luctus consectetuer justo. Vivamus " + - "dapibus laoreet purus. Nunc viverra dictum nisl. Integer " + - "ullamcorper, nisi in dictum amet."; - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - OutputStream out = new EncoderStream(bytes, createEncoder(), true); - out.write(data.getBytes()); - - assertEquals("Strings does not match", - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIEZ1" + - "c2NlIGVzdC4gTW9yYmkgbHVjdHVzIGNvbnNlY3RldHVlciBqdXN0by4gVml2YW11cyBkYXBpYnVzIGxh" + - "b3JlZXQgcHVydXMuIE51bmMgdml2ZXJyYSBkaWN0dW0gbmlzbC4gSW50ZWdlciB1bGxhbWNvcnBlciwg" + - "bmlzaSBpbiBkaWN0dW0gYW1ldC4=", - new String(bytes.toByteArray())); - } -} +/* + * 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.io.enc; + +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import static org.junit.Assert.assertEquals; + +/** + * Base64EncoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/Base64EncoderTestCase.java#1 $ + */ +public class Base64EncoderTest extends EncoderAbstractTest { + + protected Encoder createEncoder() { + return new Base64Encoder(); + } + + protected Decoder createCompatibleDecoder() { + return new Base64Decoder(); + } + + @Test + public void testEmptyEncode() throws IOException { + String data = ""; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + OutputStream out = new EncoderStream(bytes, createEncoder(), true); + out.write(data.getBytes()); + + assertEquals("Strings does not match", "", new String(bytes.toByteArray())); + } + + @Test + public void testShortEncode() throws IOException { + String data = "test"; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + OutputStream out = new EncoderStream(bytes, createEncoder(), true); + out.write(data.getBytes()); + + assertEquals("Strings does not match", "dGVzdA==", new String(bytes.toByteArray())); + } + + @Test + public void testLongEncode() throws IOException { + String data = "Lorem ipsum dolor sit amet, consectetuer adipiscing " + + "elit. Fusce est. Morbi luctus consectetuer justo. Vivamus " + + "dapibus laoreet purus. Nunc viverra dictum nisl. Integer " + + "ullamcorper, nisi in dictum amet."; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + OutputStream out = new EncoderStream(bytes, createEncoder(), true); + out.write(data.getBytes()); + + assertEquals("Strings does not match", + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIEZ1" + + "c2NlIGVzdC4gTW9yYmkgbHVjdHVzIGNvbnNlY3RldHVlciBqdXN0by4gVml2YW11cyBkYXBpYnVzIGxh" + + "b3JlZXQgcHVydXMuIE51bmMgdml2ZXJyYSBkaWN0dW0gbmlzbC4gSW50ZWdlciB1bGxhbWNvcnBlciwg" + + "bmlzaSBpbiBkaWN0dW0gYW1ldC4=", + new String(bytes.toByteArray())); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTest.java similarity index 65% rename from common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTest.java index 51788010..6f1e8948 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTest.java @@ -1,115 +1,145 @@ -package com.twelvemonkeys.io.enc; - -import com.twelvemonkeys.io.FileUtil; -import com.twelvemonkeys.lang.ObjectAbstractTestCase; -import org.junit.Test; - -import java.io.*; -import java.nio.ByteBuffer; - -import static org.junit.Assert.*; - -/** - * AbstractDecoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java#1 $ - */ -public abstract class DecoderAbstractTestCase extends ObjectAbstractTestCase { - - public abstract Decoder createDecoder(); - public abstract Encoder createCompatibleEncoder(); - - protected Object makeObject() { - return createDecoder(); - } - - @Test(expected = NullPointerException.class) - public final void testNullDecode() throws IOException { - Decoder decoder = createDecoder(); - ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[20]); - - decoder.decode(bytes, null); - fail("null should throw NullPointerException"); - } - - @Test - public final void testEmptyDecode() throws IOException { - Decoder decoder = createDecoder(); - ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]); - - try { - int count = decoder.decode(bytes, ByteBuffer.allocate(128)); - assertEquals("Should not be able to read any bytes", 0, count); - } - catch (EOFException allowed) { - // Okay - } - } - - private byte[] createData(int pLength) throws Exception { - byte[] bytes = new byte[pLength]; - EncoderAbstractTestCase.RANDOM.nextBytes(bytes); - return bytes; - } - - private void runStreamTest(int pLength) throws Exception { - byte[] data = createData(pLength); - - ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); - OutputStream out = new EncoderStream(outBytes, createCompatibleEncoder(), true); - out.write(data); - out.close(); - byte[] encoded = outBytes.toByteArray(); - - byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createDecoder())); - assertArrayEquals(String.format("Data %d", pLength), data, decoded); - - InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createDecoder()); - outBytes = new ByteArrayOutputStream(); - FileUtil.copy(in, outBytes); - outBytes.close(); - in.close(); - - decoded = outBytes.toByteArray(); - assertArrayEquals(String.format("Data %d", pLength), data, decoded); - } - - @Test - public final void testStreams() throws Exception { - if (createCompatibleEncoder() == null) { - return; - } - - for (int i = 1; i < 100; i++) { - try { - runStreamTest(i); - } - catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - } - - for (int i = 100; i < 2000; i += 250) { - try { - runStreamTest(i); - } - catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - } - - for (int i = 2000; i < 80000; i += 1000) { - try { - runStreamTest(i); - } - catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - } - } -} +/* + * 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.io.enc; + +import com.twelvemonkeys.io.FileUtil; +import com.twelvemonkeys.lang.ObjectAbstractTest; +import org.junit.Test; + +import java.io.*; +import java.nio.ByteBuffer; + +import static org.junit.Assert.*; + +/** + * AbstractDecoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java#1 $ + */ +public abstract class DecoderAbstractTest extends ObjectAbstractTest { + + public abstract Decoder createDecoder(); + public abstract Encoder createCompatibleEncoder(); + + protected Object makeObject() { + return createDecoder(); + } + + @Test(expected = NullPointerException.class) + public final void testNullDecode() throws IOException { + Decoder decoder = createDecoder(); + ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[20]); + + decoder.decode(bytes, null); + fail("null should throw NullPointerException"); + } + + @Test + public final void testEmptyDecode() throws IOException { + Decoder decoder = createDecoder(); + ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]); + + try { + int count = decoder.decode(bytes, ByteBuffer.allocate(128)); + assertEquals("Should not be able to read any bytes", 0, count); + } + catch (EOFException allowed) { + // Okay + } + } + + private byte[] createData(int pLength) throws Exception { + byte[] bytes = new byte[pLength]; + EncoderAbstractTest.RANDOM.nextBytes(bytes); + return bytes; + } + + private void runStreamTest(int pLength) throws Exception { + byte[] data = createData(pLength); + + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + OutputStream out = new EncoderStream(outBytes, createCompatibleEncoder(), true); + out.write(data); + out.close(); + byte[] encoded = outBytes.toByteArray(); + + byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createDecoder())); + assertArrayEquals(String.format("Data %d", pLength), data, decoded); + + InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createDecoder()); + outBytes = new ByteArrayOutputStream(); + FileUtil.copy(in, outBytes); + outBytes.close(); + in.close(); + + decoded = outBytes.toByteArray(); + assertArrayEquals(String.format("Data %d", pLength), data, decoded); + } + + @Test + public final void testStreams() throws Exception { + if (createCompatibleEncoder() == null) { + return; + } + + for (int i = 1; i < 100; i++) { + try { + runStreamTest(i); + } + catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage() + ": " + i); + } + } + + for (int i = 100; i < 2000; i += 250) { + try { + runStreamTest(i); + } + catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage() + ": " + i); + } + } + + for (int i = 2000; i < 80000; i += 1000) { + try { + runStreamTest(i); + } + catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage() + ": " + i); + } + } + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTest.java similarity index 60% rename from common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTest.java index 6376a73d..863f04b7 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTest.java @@ -1,139 +1,152 @@ -package com.twelvemonkeys.io.enc; - -import com.twelvemonkeys.io.FileUtil; -import com.twelvemonkeys.lang.ObjectAbstractTestCase; -import org.junit.Test; - -import java.io.*; -import java.util.Arrays; -import java.util.Random; - -import static org.junit.Assert.*; - -/** - * AbstractEncoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTestCase.java#1 $ - */ -public abstract class EncoderAbstractTestCase extends ObjectAbstractTestCase { - // Use seed to make sure we create same number all the time - static final long SEED = 12345678; - static final Random RANDOM = new Random(SEED); - - protected abstract Encoder createEncoder(); - protected abstract Decoder createCompatibleDecoder(); - - protected Object makeObject() { - return createEncoder(); - } - - @Test - public final void testNullEncode() throws IOException { - Encoder encoder = createEncoder(); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - try { - encoder.encode(bytes, null); - fail("null should throw NullPointerException"); - } - catch (NullPointerException expected) { - } - } - - private byte[] createData(final int pLength) throws Exception { - byte[] bytes = new byte[pLength]; - RANDOM.nextBytes(bytes); - return bytes; - } - - private void runStreamTest(final int pLength) throws Exception { - byte[] data = createData(pLength); - ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); - OutputStream out = new EncoderStream(outBytes, createEncoder(), true); - - try { - // Provoke failure for encoders that doesn't take array offset properly into account - int off = (data.length + 1) / 2; - out.write(data, 0, off); - if (data.length > off) { - out.write(data, off, data.length - off); - } - } - finally { - out.close(); - } - - byte[] encoded = outBytes.toByteArray(); - -// System.err.println("encoded.length: " + encoded.length); -// System.err.println("encoded: " + Arrays.toString(encoded)); - - byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder())); - assertTrue(Arrays.equals(data, decoded)); - - InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder()); - outBytes = new ByteArrayOutputStream(); - - try { - FileUtil.copy(in, outBytes); - } - finally { - outBytes.close(); - in.close(); - } - - decoded = outBytes.toByteArray(); - assertTrue(Arrays.equals(data, decoded)); - } - - @Test - public final void testStreams() throws Exception { - for (int i = 0; i < 100; i++) { - try { - runStreamTest(i); - } - catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - } - - for (int i = 100; i < 2000; i += 250) { - try { - runStreamTest(i); - } - catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - } - - for (int i = 2000; i < 80000; i += 1000) { - try { - runStreamTest(i); - } - catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage() + ": " + i); - } - } - } - - // TODO: Test that the transition from byte[] to ByteBuffer didn't introduce bugs when writing to a wrapped array with offset. - - -} +/* + * 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.io.enc; + +import com.twelvemonkeys.io.FileUtil; +import com.twelvemonkeys.lang.ObjectAbstractTest; + +import org.junit.Test; + +import java.io.*; +import java.util.Random; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; + +/** + * AbstractEncoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/EncoderAbstractTestCase.java#1 $ + */ +public abstract class EncoderAbstractTest extends ObjectAbstractTest { + // Use seed to make sure we create same number all the time + static final long SEED = 12345678; + static final Random RANDOM = new Random(SEED); + + protected abstract Encoder createEncoder(); + protected abstract Decoder createCompatibleDecoder(); + + protected Object makeObject() { + return createEncoder(); + } + + @Test + public final void testNullEncode() throws IOException { + Encoder encoder = createEncoder(); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + try { + encoder.encode(bytes, null); + fail("null should throw NullPointerException"); + } + catch (NullPointerException expected) { + } + } + + private byte[] createData(final int pLength) { + byte[] bytes = new byte[pLength]; + RANDOM.nextBytes(bytes); + return bytes; + } + + private void runStreamTest(final int pLength) throws Exception { + byte[] data = createData(pLength); + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + + try (OutputStream out = new EncoderStream(outBytes, createEncoder(), true)) { + // Provoke failure for encoders that doesn't take array offset properly into account + int off = (data.length + 1) / 2; + out.write(data, 0, off); + if (data.length > off) { + out.write(data, off, data.length - off); + } + } + + byte[] encoded = outBytes.toByteArray(); + +// System.err.println("encoded.length: " + encoded.length); +// System.err.println("encoded: " + Arrays.toString(encoded)); + + byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder())); + assertArrayEquals(data, decoded); + + InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder()); + outBytes = new ByteArrayOutputStream(); + + try { + FileUtil.copy(in, outBytes); + } + finally { + outBytes.close(); + in.close(); + } + + decoded = outBytes.toByteArray(); + assertArrayEquals(data, decoded); + } + + @Test + public final void testStreams() throws Exception { + for (int i = 0; i < 100; i++) { + try { + runStreamTest(i); + } + catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage() + ": " + i); + } + } + + for (int i = 100; i < 2000; i += 250) { + try { + runStreamTest(i); + } + catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage() + ": " + i); + } + } + + for (int i = 2000; i < 80000; i += 1000) { + try { + runStreamTest(i); + } + catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage() + ": " + i); + } + } + } + + // TODO: Test that the transition from byte[] to ByteBuffer didn't introduce bugs when writing to a wrapped array with offset. +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTest.java new file mode 100755 index 00000000..6f974936 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTest.java @@ -0,0 +1,48 @@ +/* + * 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.io.enc; + +/** + * PackBitsDecoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTestCase.java#1 $ + */ +public class PackBitsDecoderTest extends DecoderAbstractTest { + public Decoder createDecoder() { + return new PackBitsDecoder(); + } + + public Encoder createCompatibleEncoder() { + return new PackBitsEncoder(); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTestCase.java deleted file mode 100755 index 584be832..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTestCase.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.twelvemonkeys.io.enc; - -/** - * PackBitsDecoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/PackBitsDecoderTestCase.java#1 $ - */ -public class PackBitsDecoderTestCase extends DecoderAbstractTestCase { - public Decoder createDecoder() { - return new PackBitsDecoder(); - } - - public Encoder createCompatibleEncoder() { - return new PackBitsEncoder(); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTest.java new file mode 100755 index 00000000..b1a13755 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTest.java @@ -0,0 +1,48 @@ +/* + * 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.io.enc; + +/** + * PackBitsEncoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTestCase.java#1 $ + */ +public class PackBitsEncoderTest extends EncoderAbstractTest { + protected Encoder createEncoder() { + return new PackBitsEncoder(); + } + + protected Decoder createCompatibleDecoder() { + return new PackBitsDecoder(); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTestCase.java deleted file mode 100755 index d214fd3f..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTestCase.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.twelvemonkeys.io.enc; - -/** - * PackBitsEncoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/PackBitsEncoderTestCase.java#1 $ - */ -public class PackBitsEncoderTestCase extends EncoderAbstractTestCase { - protected Encoder createEncoder() { - return new PackBitsEncoder(); - } - - protected Decoder createCompatibleDecoder() { - return new PackBitsDecoder(); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTest.java similarity index 52% rename from common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTest.java index ccbf18da..a9efc698 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTest.java @@ -1,3 +1,33 @@ +/* + * 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.io.ole2; import com.twelvemonkeys.io.MemoryCacheSeekableStream; @@ -22,7 +52,7 @@ import static org.junit.Assert.*; * @author last modified by $Author: haku $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTestCase.java#1 $ */ -public class CompoundDocumentTestCase { +public class CompoundDocumentTest { private static final String SAMPLE_DATA = "/Thumbs-camera.db"; @@ -42,64 +72,64 @@ public class CompoundDocumentTestCase { @Test public void testRoot() throws IOException { - CompoundDocument document = createTestDocument(); + try (CompoundDocument document = createTestDocument()) { + Entry root = document.getRootEntry(); - Entry root = document.getRootEntry(); - - assertNotNull(root); - assertEquals("Root Entry", root.getName()); - assertTrue(root.isRoot()); - assertFalse(root.isFile()); - assertFalse(root.isDirectory()); - assertEquals(0, root.length()); - assertNull(root.getInputStream()); + assertNotNull(root); + assertEquals("Root Entry", root.getName()); + assertTrue(root.isRoot()); + assertFalse(root.isFile()); + assertFalse(root.isDirectory()); + assertEquals(0, root.length()); + assertNull(root.getInputStream()); + } } @Test public void testContents() throws IOException { - CompoundDocument document = createTestDocument(); + try (CompoundDocument document = createTestDocument()) { + Entry root = document.getRootEntry(); - Entry root = document.getRootEntry(); + assertNotNull(root); - assertNotNull(root); + SortedSet children = new TreeSet(root.getChildEntries()); + assertEquals(25, children.size()); - SortedSet children = new TreeSet(root.getChildEntries()); - assertEquals(25, children.size()); - - // Weirdness in the file format, name is *written backwards* 1-24 + Catalog - for (String name : "1,2,3,4,5,6,7,8,9,01,02,11,12,21,22,31,32,41,42,51,61,71,81,91,Catalog".split(",")) { - assertEquals(name, children.first().getName()); - children.remove(children.first()); + // Weirdness in the file format, name is *written backwards* 1-24 + Catalog + for (String name : "1,2,3,4,5,6,7,8,9,01,02,11,12,21,22,31,32,41,42,51,61,71,81,91,Catalog".split(",")) { + assertEquals(name, children.first().getName()); + children.remove(children.first()); + } } } @Test(expected = UnsupportedOperationException.class) public void testChildEntriesUnmodifiable() throws IOException { - CompoundDocument document = createTestDocument(); + try (CompoundDocument document = createTestDocument()) { + Entry root = document.getRootEntry(); - Entry root = document.getRootEntry(); + assertNotNull(root); - assertNotNull(root); + SortedSet children = root.getChildEntries(); - SortedSet children = root.getChildEntries(); - - // Should not be allowed, as it modifies the internal structure - children.remove(children.first()); + // Should not be allowed, as it modifies the internal structure + children.remove(children.first()); + } } @Test public void testReadThumbsCatalogFile() throws IOException { - CompoundDocument document = createTestDocument(); + try (CompoundDocument document = createTestDocument()) { + Entry root = document.getRootEntry(); - Entry root = document.getRootEntry(); + assertNotNull(root); + assertEquals(25, root.getChildEntries().size()); - assertNotNull(root); - assertEquals(25, root.getChildEntries().size()); + Entry catalog = root.getChildEntry("Catalog"); - Entry catalog = root.getChildEntry("Catalog"); - - assertNotNull(catalog); - assertNotNull("Input stream may not be null", catalog.getInputStream()); + assertNotNull(catalog); + assertNotNull("Input stream may not be null", catalog.getInputStream()); + } } @Test diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_SeekableLittleEndianDataInputStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_SeekableLittleEndianDataInputStreamTest.java new file mode 100644 index 00000000..c0b80b76 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_SeekableLittleEndianDataInputStreamTest.java @@ -0,0 +1,62 @@ +/* + * 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.io.ole2; + +import com.twelvemonkeys.io.*; +import org.junit.Test; + +import java.io.ByteArrayInputStream; + +/** + * CompoundDocument_SeekableLittleEndianDataInputStreamTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: CompoundDocument_SeekableLittleEndianDataInputStreamTestCase.java,v 1.0 18.10.11 16:35 haraldk Exp$ + */ +public class CompoundDocument_SeekableLittleEndianDataInputStreamTest extends InputStreamAbstractTest implements SeekableInterfaceTest { + private final SeekableInterfaceTest seekableTest = new SeekableAbstractTest() { + @Override + protected Seekable createSeekable() { + return (Seekable) makeInputStream(); + } + }; + + @Override + protected CompoundDocument.SeekableLittleEndianDataInputStream makeInputStream(byte[] pBytes) { + return new CompoundDocument.SeekableLittleEndianDataInputStream(new MemoryCacheSeekableStream(new ByteArrayInputStream(pBytes))); + } + + @Test + public void testSeekable() { + seekableTest.testSeekable(); + } +} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_SeekableLittleEndianDataInputStreamTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_SeekableLittleEndianDataInputStreamTestCase.java deleted file mode 100644 index d4e426d6..00000000 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_SeekableLittleEndianDataInputStreamTestCase.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.io.ole2; - -import com.twelvemonkeys.io.*; -import org.junit.Test; - -import java.io.ByteArrayInputStream; - -/** - * CompoundDocument_SeekableLittleEndianDataInputStreamTestCase - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: CompoundDocument_SeekableLittleEndianDataInputStreamTestCase.java,v 1.0 18.10.11 16:35 haraldk Exp$ - */ -public class CompoundDocument_SeekableLittleEndianDataInputStreamTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest { - private final SeekableInterfaceTest seekableTest = new SeekableAbstractTestCase() { - @Override - protected Seekable createSeekable() { - return (Seekable) makeInputStream(); - } - }; - - @Override - protected CompoundDocument.SeekableLittleEndianDataInputStream makeInputStream(byte[] pBytes) { - return new CompoundDocument.SeekableLittleEndianDataInputStream(new MemoryCacheSeekableStream(new ByteArrayInputStream(pBytes))); - } - - @Test - public void testSeekable() { - seekableTest.testSeekable(); - } -} diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_StreamTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_StreamTest.java similarity index 73% rename from common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_StreamTestCase.java rename to common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_StreamTest.java index 14039eae..6bdd984a 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_StreamTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocument_StreamTest.java @@ -4,39 +4,41 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.io.ole2; -import com.twelvemonkeys.io.InputStreamAbstractTestCase; +import com.twelvemonkeys.io.InputStreamAbstractTest; import com.twelvemonkeys.io.LittleEndianDataOutputStream; import com.twelvemonkeys.io.MemoryCacheSeekableStream; -import com.twelvemonkeys.io.SeekableInputStream; import org.junit.Test; -import java.io.*; -import java.net.URISyntaxException; -import java.net.URL; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.util.Arrays; @@ -49,35 +51,7 @@ import static org.junit.Assert.*; * @author last modified by $Author: haraldk$ * @version $Id: CompoundDocument_StreamTestCase.java,v 1.0 13.10.11 12:01 haraldk Exp$ */ -//@Ignore("Need proper in-memory creation of CompoundDocuments") -public class CompoundDocument_StreamTestCase extends InputStreamAbstractTestCase { - private static final String SAMPLE_DATA = "/Thumbs-camera.db"; - - protected final CompoundDocument createTestDocument() throws IOException { - URL input = getClass().getResource(SAMPLE_DATA); - - assertNotNull("Missing test resource!", input); - assertEquals("Test resource not a file:// resource", "file", input.getProtocol()); - - try { - return new CompoundDocument(new File(input.toURI())); - } - catch (URISyntaxException e) { - throw new AssertionError(e); - } - } - - private SeekableInputStream createRealInputStream() { - try { - Entry first = createTestDocument().getRootEntry().getChildEntries().first(); - assertNotNull(first); - return first.getInputStream(); - } - catch (IOException e) { - throw new AssertionError(e); - } - } - +public class CompoundDocument_StreamTest extends InputStreamAbstractTest { @Override protected InputStream makeInputStream(byte[] data) { try { @@ -180,15 +154,13 @@ public class CompoundDocument_StreamTestCase extends InputStreamAbstractTestCase return pad; } -// @Ignore @Test - public void testDev() throws IOException { + public void testStreamRead() throws IOException { InputStream stream = makeInputStream(makeOrderedArray(32)); int read; int count = 0; while ((read = stream.read()) >= 0) { -// System.out.printf("read %02d: 0x%02x%n", count, read & 0xFF); assertEquals(count, read); count++; } diff --git a/common/common-io/src/test/java/com/twelvemonkeys/net/HTTPUtilTest.java b/common/common-io/src/test/java/com/twelvemonkeys/net/HTTPUtilTest.java index 9bb83f53..f3912e92 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/net/HTTPUtilTest.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/net/HTTPUtilTest.java @@ -4,33 +4,35 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.net; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * HTTPUtilTest diff --git a/common/common-lang/pom.xml b/common/common-lang/pom.xml index 79316441..8fd46cb7 100644 --- a/common/common-lang/pom.xml +++ b/common/common-lang/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.common common - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT common-lang jar @@ -13,4 +13,8 @@ The TwelveMonkeys Common Language support + + com.twelvemonkeys.common.lang + + diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java index 7eae35dd..b6c6a4a1 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java @@ -1,603 +1,607 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.lang; - -import com.twelvemonkeys.util.convert.ConversionException; -import com.twelvemonkeys.util.convert.Converter; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Map; -import java.util.Arrays; - -/** - * A utility class with some useful bean-related functions. - *

- * NOTE: This class is not considered part of the public API and may be changed without notice - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/BeanUtil.java#2 $ - */ -public final class BeanUtil { - - // Disallow creating objects of this type - private BeanUtil() { - } - - /** - * Gets a property value from the given object, using reflection. - * Now supports getting values from properties of properties - * (recursive). - * - * @param pObject The object to get the property from - * @param pProperty The name of the property - * - * @return A string containing the value of the given property, or {@code null} - * if it can not be found. - * @todo Remove System.err's... Create new Exception? Hmm.. - */ - public static Object getPropertyValue(Object pObject, String pProperty) { - // - // TODO: Support get(Object) method of Collections! - // Handle lists and arrays with [] (index) operator - // - - if (pObject == null || pProperty == null || pProperty.length() < 1) { - return null; - } - - Class objClass = pObject.getClass(); - - Object result = pObject; - - // Method for method... - String subProp; - int begIdx = 0; - int endIdx = begIdx; - - while (begIdx < pProperty.length() && begIdx >= 0) { - - endIdx = pProperty.indexOf(".", endIdx + 1); - if (endIdx > 0) { - subProp = pProperty.substring(begIdx, endIdx); - begIdx = endIdx + 1; - } - else { - // The final property! - // If there's just the first-level property, subProp will be - // equal to property - subProp = pProperty.substring(begIdx); - begIdx = -1; - } - - // Check for "[" and "]" - Object[] param = null; - Class[] paramClass = new Class[0]; - - int begBracket; - if ((begBracket = subProp.indexOf("[")) > 0) { - // An error if there is no matching bracket - if (!subProp.endsWith("]")) { - return null; - } - - String between = subProp.substring(begBracket + 1, - subProp.length() - 1); - subProp = subProp.substring(0, begBracket); - - // If brackets exist, check type of argument between brackets - param = new Object[1]; - paramClass = new Class[1]; - - //try { - // TODO: isNumber returns true, even if too big for integer... - if (StringUtil.isNumber(between)) { - // We have a number - // Integer -> array subscript -> getXXX(int i) - try { - // Insert param and it's Class - param[0] = Integer.valueOf(between); - paramClass[0] = Integer.TYPE; // int.class - } - catch (NumberFormatException e) { - // ?? - // Probably too small or too large value.. - } - } - else { - //catch (NumberFormatException e) { - // Not a number... Try String - // String -> Hashtable key -> getXXX(String str) - // Insert param and it's Class - param[0] = between.toLowerCase(); - paramClass[0] = String.class; - } - } - - Method method; - String methodName = "get" + StringUtil.capitalize(subProp); - try { - // Try to get the "get" method for the given property - method = objClass.getMethod(methodName, paramClass); - } - catch (NoSuchMethodException e) { - System.err.print("No method named \"" + methodName + "()\""); - // The array might be of size 0... - if (paramClass.length > 0 && paramClass[0] != null) { - System.err.print(" with the parameter " + paramClass[0].getName()); - } - - System.err.println(" in class " + objClass.getName() + "!"); - return null; - } - - // If method for some reason should be null, give up - if (method == null) { - return null; - } - - try { - // We have a method, try to invoke it - // The resutling object will be either the property we are - // Looking for, or the parent - - // System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")"); - result = method.invoke(result, param); - } - catch (InvocationTargetException e) { - System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param)); - e.getTargetException().printStackTrace(); - e.printStackTrace(); - return null; - } - catch (IllegalAccessException e) { - e.printStackTrace(); - return null; - } - catch (NullPointerException e) { - System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")"); - e.printStackTrace(); - return null; - } - - if (result != null) { - // Get the class of the reulting object - objClass = result.getClass(); - } - else { - return null; - } - } // while - - return result; - } - - /** - * Sets the property value to an object using reflection. - * Supports setting values of properties that are properties of - * properties (recursive). - * - * @param pObject The object to get a property from - * @param pProperty The name of the property - * @param pValue The property value - * - * @throws NoSuchMethodException if there's no write method for the - * given property - * @throws InvocationTargetException if invoking the write method failed - * @throws IllegalAccessException if the caller class has no access to the - * write method - */ - public static void setPropertyValue(Object pObject, String pProperty, Object pValue) - throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - - // - // TODO: Support set(Object, Object)/put(Object, Object) methods - // of Collections! - // Handle lists and arrays with [] (index) operator - - Class paramType = pValue != null ? pValue.getClass() : Object.class; - - // Preserve references - Object obj = pObject; - String property = pProperty; - - // Recurse and find real parent if property contains a '.' - int dotIdx = property.indexOf('.'); - if (dotIdx >= 0) { - // Get real parent - obj = getPropertyValue(obj, property.substring(0, dotIdx)); - // Get the property of the parent - property = property.substring(dotIdx + 1); - } - - // Find method - Object[] params = {pValue}; - Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property), - new Class[] {paramType}, params); - - // Invoke it - method.invoke(obj, params); - } - - private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) - throws NoSuchMethodException { - // NOTE: This method assumes pParams.length == 1 && pValues.length == 1 - - Method method = null; - Class paramType = pParams[0]; - - try { - method = pObject.getClass().getMethod(pName, pParams); - } - catch (NoSuchMethodException e) { - // No direct match - - // 1: If primitive wrapper, try unwrap conversion first - /*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type - params[0] = ReflectUtil.wrapType(paramType); - } - else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) { - pParams[0] = ReflectUtil.unwrapType(paramType); - } - - try { - // If this does not throw an exception, it works - method = pObject.getClass().getMethod(pName, pParams); - } - catch (Throwable t) { - // Ignore - } - - // 2: Try any super-types of paramType, to see if we have a match - if (method == null) { - while ((paramType = paramType.getSuperclass()) != null) { - pParams[0] = paramType; - try { - // If this does not throw an exception, it works - method = pObject.getClass().getMethod(pName, pParams); - } - catch (Throwable t) { - // Ignore/Continue - continue; - } - - break; - } - } - - // 3: Try to find a different method with the same name, that has - // a parameter type we can convert to... - // NOTE: There's no ordering here.. - // TODO: Should we try to do that? What would the ordering be? - if (method == null) { - Method[] methods = pObject.getClass().getMethods(); - for (Method candidate : methods) { - if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName) - && candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) { - // NOTE: Assumes paramTypes.length == 1 - - Class type = candidate.getParameterTypes()[0]; - - try { - pValues[0] = convertValueToType(pValues[0], type); - } - catch (Throwable t) { - continue; - } - - // We were able to convert the parameter, let's try - method = candidate; - break; - } - } - } - - // Give up... - if (method == null) { - throw e; - } - } - return method; - } - - private static Object convertValueToType(Object pValue, Class pType) throws ConversionException { - if (pType.isPrimitive()) { - if (pType == Boolean.TYPE && pValue instanceof Boolean) { - return pValue; - } - else if (pType == Byte.TYPE && pValue instanceof Byte) { - return pValue; - } - else if (pType == Character.TYPE && pValue instanceof Character) { - return pValue; - } - else if (pType == Double.TYPE && pValue instanceof Double) { - return pValue; - } - else if (pType == Float.TYPE && pValue instanceof Float) { - return pValue; - } - else if (pType == Integer.TYPE && pValue instanceof Integer) { - return pValue; - } - else if (pType == Long.TYPE && pValue instanceof Long) { - return pValue; - } - else if (pType == Short.TYPE && pValue instanceof Short) { - return pValue; - } - } - - // TODO: Convert value to single-value array if needed - // TODO: Convert CSV String to string array (or potentially any type of array) - - // TODO: Convert other types - if (pValue instanceof String) { - Converter converter = Converter.getInstance(); - return converter.toObject((String) pValue, pType); - } - else if (pType == String.class) { - Converter converter = Converter.getInstance(); - return converter.toString(pValue); - } - else { - throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName()); - } - } - - /** - * Creates an object from the given class' single argument constructor. - * - * @param pClass The class to create instance from - * @param pParam The parameters to the constructor - * - * @return The object created from the constructor. - * If the constructor could not be invoked for any reason, null is - * returned. - * - * @throws InvocationTargetException if the constructor failed - */ - // TODO: Move to ReflectUtil - public static T createInstance(Class pClass, Object pParam) - throws InvocationTargetException { - return createInstance(pClass, new Object[] {pParam}); - } - - /** - * Creates an object from the given class' constructor that matches - * the given paramaters. - * - * @param pClass The class to create instance from - * @param pParams The parameters to the constructor - * - * @return The object created from the constructor. - * If the constructor could not be invoked for any reason, null is - * returned. - * - * @throws InvocationTargetException if the constructor failed - */ - // TODO: Move to ReflectUtil - public static T createInstance(Class pClass, Object... pParams) - throws InvocationTargetException { - T value; - - try { - // Create param and argument arrays - Class[] paramTypes = null; - if (pParams != null && pParams.length > 0) { - paramTypes = new Class[pParams.length]; - for (int i = 0; i < pParams.length; i++) { - paramTypes[i] = pParams[i].getClass(); - } - } - - // Get constructor - Constructor constructor = pClass.getConstructor(paramTypes); - - // Invoke and create instance - value = constructor.newInstance(pParams); - } - /* All this to let InvocationTargetException pass on */ - catch (NoSuchMethodException nsme) { - return null; - } - catch (IllegalAccessException iae) { - return null; - } - catch (IllegalArgumentException iarge) { - return null; - } - catch (InstantiationException ie) { - return null; - } - catch (ExceptionInInitializerError err) { - return null; - } - - return value; - } - - /** - * Gets an object from any given static method, with the given parameter. - * - * @param pClass The class to invoke method on - * @param pMethod The name of the method to invoke - * @param pParam The parameter to the method - * - * @return The object returned by the static method. - * If the return type of the method is a primitive type, it is wrapped in - * the corresponding wrapper object (int is wrapped in an Integer). - * If the return type of the method is void, null is returned. - * If the method could not be invoked for any reason, null is returned. - * - * @throws InvocationTargetException if the invocation failed - */ - // TODO: Move to ReflectUtil - // TODO: Rename to invokeStatic? - public static Object invokeStaticMethod(Class pClass, String pMethod, Object pParam) - throws InvocationTargetException { - - return invokeStaticMethod(pClass, pMethod, new Object[] {pParam}); - } - - /** - * Gets an object from any given static method, with the given parameter. - * - * @param pClass The class to invoke method on - * @param pMethod The name of the method to invoke - * @param pParams The parameters to the method - * - * @return The object returned by the static method. - * If the return type of the method is a primitive type, it is wrapped in - * the corresponding wrapper object (int is wrapped in an Integer). - * If the return type of the method is void, null is returned. - * If the method could not be invoked for any reason, null is returned. - * - * @throws InvocationTargetException if the invocation failed - */ - // TODO: Move to ReflectUtil - // TODO: Rename to invokeStatic? - public static Object invokeStaticMethod(Class pClass, String pMethod, Object... pParams) - throws InvocationTargetException { - - Object value = null; - - try { - // Create param and argument arrays - Class[] paramTypes = new Class[pParams.length]; - for (int i = 0; i < pParams.length; i++) { - paramTypes[i] = pParams[i].getClass(); - } - - // Get method - // *** If more than one such method is found in the class, and one - // of these methods has a RETURN TYPE that is more specific than - // any of the others, that method is reflected; otherwise one of - // the methods is chosen ARBITRARILY. - // java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[]) - Method method = pClass.getMethod(pMethod, paramTypes); - - // Invoke public static method - if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { - value = method.invoke(null, pParams); - } - - } - /* All this to let InvocationTargetException pass on */ - catch (NoSuchMethodException nsme) { - return null; - } - catch (IllegalAccessException iae) { - return null; - } - catch (IllegalArgumentException iarge) { - return null; - } - - return value; - } - - /** - * Configures the bean according to the given mapping. - * For each {@code Map.Entry} in {@code Map.values()}, - * a method named - * {@code set + capitalize(entry.getKey())} is called on the bean, - * with {@code entry.getValue()} as its argument. - *

- * Properties that has no matching set-method in the bean, are simply - * discarded. - * - * @param pBean The bean to configure - * @param pMapping The mapping for the bean - * - * @throws NullPointerException if any of the parameters are null. - * @throws InvocationTargetException if an error occurs when invoking the - * setter-method. - */ - // TODO: Add a version that takes a ConfigurationErrorListener callback interface - // TODO: ...or a boolean pFailOnError parameter - // TODO: ...or return Exceptions as an array?! - // TODO: ...or something whatsoever that makes clients able to determine something's not right - public static void configure(final Object pBean, final Map pMapping) throws InvocationTargetException { - configure(pBean, pMapping, false); - } - - /** - * Configures the bean according to the given mapping. - * For each {@code Map.Entry} in {@code Map.values()}, - * a method named - * {@code set + capitalize(entry.getKey())} is called on the bean, - * with {@code entry.getValue()} as its argument. - *

- * Optionally, lisp-style names are allowed, and automatically converted - * to Java-style camel-case names. - *

- * Properties that has no matching set-method in the bean, are simply - * discarded. - * - * @see StringUtil#lispToCamel(String) - * - * @param pBean The bean to configure - * @param pMapping The mapping for the bean - * @param pLispToCamel Allow lisp-style names, and automatically convert - * them to Java-style camel-case. - * - * @throws NullPointerException if any of the parameters are null. - * @throws InvocationTargetException if an error occurs when invoking the - * setter-method. - */ - public static void configure(final Object pBean, final Map pMapping, final boolean pLispToCamel) throws InvocationTargetException { - // Loop over properties in mapping - for (final Map.Entry entry : pMapping.entrySet()) { - try { - // Configure each property in turn - final String property = StringUtil.valueOf(entry.getKey()); - try { - setPropertyValue(pBean, property, entry.getValue()); - } - catch (NoSuchMethodException ignore) { - // If invocation failed, convert lisp-style and try again - if (pLispToCamel && property.indexOf('-') > 0) { - setPropertyValue(pBean, StringUtil.lispToCamel(property, false), entry.getValue()); - } - } - } - catch (NoSuchMethodException nsme) { - // This property was not configured - } - catch (IllegalAccessException iae) { - // This property was not configured - } - } - } -} +/* + * 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.lang; + +import com.twelvemonkeys.util.convert.ConversionException; +import com.twelvemonkeys.util.convert.Converter; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Map; + +/** + * A utility class with some useful bean-related functions. + *

+ * NOTE: This class is not considered part of the public API and may be changed without notice + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/BeanUtil.java#2 $ + */ +public final class BeanUtil { + + // Disallow creating objects of this type + private BeanUtil() { + } + + /** + * Gets a property value from the given object, using reflection. + * Now supports getting values from properties of properties + * (recursive). + * + * @param pObject The object to get the property from + * @param pProperty The name of the property + * + * @return A string containing the value of the given property, or {@code null} + * if it can not be found. + */ + public static Object getPropertyValue(Object pObject, String pProperty) { + // TODO: Remove System.err's... Create new Exception? Hmm.. + // TODO: Support get(Object) method of Collections! + // Handle lists and arrays with [] (index) operator + + if (pObject == null || pProperty == null || pProperty.length() < 1) { + return null; + } + + Class objClass = pObject.getClass(); + + Object result = pObject; + + // Method for method... + String subProp; + int begIdx = 0; + int endIdx = begIdx; + + while (begIdx < pProperty.length() && begIdx >= 0) { + + endIdx = pProperty.indexOf('.', endIdx + 1); + if (endIdx > 0) { + subProp = pProperty.substring(begIdx, endIdx); + begIdx = endIdx + 1; + } + else { + // The final property! + // If there's just the first-level property, subProp will be + // equal to property + subProp = pProperty.substring(begIdx); + begIdx = -1; + } + + // Check for "[" and "]" + Object[] param = null; + Class[] paramClass = new Class[0]; + + int begBracket; + if ((begBracket = subProp.indexOf('[')) > 0) { + // An error if there is no matching bracket + if (!subProp.endsWith("]")) { + return null; + } + + String between = subProp.substring(begBracket + 1, + subProp.length() - 1); + subProp = subProp.substring(0, begBracket); + + // If brackets exist, check type of argument between brackets + param = new Object[1]; + paramClass = new Class[1]; + + //try { + // TODO: isNumber returns true, even if too big for integer... + if (StringUtil.isNumber(between)) { + // We have a number + // Integer -> array subscript -> getXXX(int i) + try { + // Insert param and it's Class + param[0] = Integer.valueOf(between); + paramClass[0] = Integer.TYPE; // int.class + } + catch (NumberFormatException e) { + // ?? + // Probably too small or too large value.. + } + } + else { + //catch (NumberFormatException e) { + // Not a number... Try String + // String -> Hashtable key -> getXXX(String str) + // Insert param and it's Class + param[0] = between.toLowerCase(); + paramClass[0] = String.class; + } + } + + Method method; + String methodName = "get" + StringUtil.capitalize(subProp); + try { + // Try to get the "get" method for the given property + method = objClass.getMethod(methodName, paramClass); + } + catch (NoSuchMethodException e) { + System.err.print("No method named \"" + methodName + "()\""); + // The array might be of size 0... + if (paramClass.length > 0 && paramClass[0] != null) { + System.err.print(" with the parameter " + paramClass[0].getName()); + } + + System.err.println(" in class " + objClass.getName() + "!"); + return null; + } + + // If method for some reason should be null, give up + if (method == null) { + return null; + } + + try { + // We have a method, try to invoke it + // The resutling object will be either the property we are + // Looking for, or the parent + + // System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")"); + result = method.invoke(result, param); + } + catch (InvocationTargetException e) { + System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param)); + e.getTargetException().printStackTrace(); + e.printStackTrace(); + return null; + } + catch (IllegalAccessException e) { + e.printStackTrace(); + return null; + } + catch (NullPointerException e) { + System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")"); + e.printStackTrace(); + return null; + } + + if (result != null) { + // Get the class of the reulting object + objClass = result.getClass(); + } + else { + return null; + } + } // while + + return result; + } + + /** + * Sets the property value to an object using reflection. + * Supports setting values of properties that are properties of + * properties (recursive). + * + * @param pObject The object to get a property from + * @param pProperty The name of the property + * @param pValue The property value + * + * @throws NoSuchMethodException if there's no write method for the + * given property + * @throws InvocationTargetException if invoking the write method failed + * @throws IllegalAccessException if the caller class has no access to the + * write method + */ + public static void setPropertyValue(Object pObject, String pProperty, Object pValue) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + + // + // TODO: Support set(Object, Object)/put(Object, Object) methods + // of Collections! + // Handle lists and arrays with [] (index) operator + + Class paramType = pValue != null ? pValue.getClass() : Object.class; + + // Preserve references + Object obj = pObject; + String property = pProperty; + + // Recurse and find real parent if property contains a '.' + int dotIdx = property.indexOf('.'); + if (dotIdx >= 0) { + // Get real parent + obj = getPropertyValue(obj, property.substring(0, dotIdx)); + // Get the property of the parent + property = property.substring(dotIdx + 1); + } + + // Find method + Object[] params = {pValue}; + Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property), + new Class[] {paramType}, params); + + // Invoke it + method.invoke(obj, params); + } + + private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) + throws NoSuchMethodException { + // NOTE: This method assumes pParams.length == 1 && pValues.length == 1 + + Method method = null; + Class paramType = pParams[0]; + + try { + method = pObject.getClass().getMethod(pName, pParams); + } + catch (NoSuchMethodException e) { + // No direct match + + // 1: If primitive wrapper, try unwrap conversion first + /*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type + params[0] = ReflectUtil.wrapType(paramType); + } + else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) { + pParams[0] = ReflectUtil.unwrapType(paramType); + } + + try { + // If this does not throw an exception, it works + method = pObject.getClass().getMethod(pName, pParams); + } + catch (Throwable t) { + // Ignore + } + + // 2: Try any super-types of paramType, to see if we have a match + if (method == null) { + while ((paramType = paramType.getSuperclass()) != null) { + pParams[0] = paramType; + try { + // If this does not throw an exception, it works + method = pObject.getClass().getMethod(pName, pParams); + } + catch (Throwable t) { + // Ignore/Continue + continue; + } + + break; + } + } + + // 3: Try to find a different method with the same name, that has + // a parameter type we can convert to... + // NOTE: There's no ordering here.. + // TODO: Should we try to do that? What would the ordering be? + if (method == null) { + Method[] methods = pObject.getClass().getMethods(); + for (Method candidate : methods) { + if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName) + && candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) { + // NOTE: Assumes paramTypes.length == 1 + + Class type = candidate.getParameterTypes()[0]; + + try { + pValues[0] = convertValueToType(pValues[0], type); + } + catch (Throwable t) { + continue; + } + + // We were able to convert the parameter, let's try + method = candidate; + break; + } + } + } + + // Give up... + if (method == null) { + throw e; + } + } + return method; + } + + private static Object convertValueToType(Object pValue, Class pType) throws ConversionException { + if (pType.isPrimitive()) { + if (pType == Boolean.TYPE && pValue instanceof Boolean) { + return pValue; + } + else if (pType == Byte.TYPE && pValue instanceof Byte) { + return pValue; + } + else if (pType == Character.TYPE && pValue instanceof Character) { + return pValue; + } + else if (pType == Double.TYPE && pValue instanceof Double) { + return pValue; + } + else if (pType == Float.TYPE && pValue instanceof Float) { + return pValue; + } + else if (pType == Integer.TYPE && pValue instanceof Integer) { + return pValue; + } + else if (pType == Long.TYPE && pValue instanceof Long) { + return pValue; + } + else if (pType == Short.TYPE && pValue instanceof Short) { + return pValue; + } + } + + // TODO: Convert value to single-value array if needed + // TODO: Convert CSV String to string array (or potentially any type of array) + + // TODO: Convert other types + if (pValue instanceof String) { + Converter converter = Converter.getInstance(); + return converter.toObject((String) pValue, pType); + } + else if (pType == String.class) { + Converter converter = Converter.getInstance(); + return converter.toString(pValue); + } + else { + throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName()); + } + } + + /** + * Creates an object from the given class' single argument constructor. + * + * @param pClass The class to create instance from + * @param pParam The parameters to the constructor + * + * @return The object created from the constructor. + * If the constructor could not be invoked for any reason, null is + * returned. + * + * @throws InvocationTargetException if the constructor failed + */ + // TODO: Move to ReflectUtil + public static T createInstance(Class pClass, Object pParam) + throws InvocationTargetException { + return createInstance(pClass, new Object[] {pParam}); + } + + /** + * Creates an object from the given class' constructor that matches + * the given paramaters. + * + * @param pClass The class to create instance from + * @param pParams The parameters to the constructor + * + * @return The object created from the constructor. + * If the constructor could not be invoked for any reason, null is + * returned. + * + * @throws InvocationTargetException if the constructor failed + */ + // TODO: Move to ReflectUtil + public static T createInstance(Class pClass, Object... pParams) + throws InvocationTargetException { + T value; + + try { + // Create param and argument arrays + Class[] paramTypes = null; + if (pParams != null && pParams.length > 0) { + paramTypes = new Class[pParams.length]; + for (int i = 0; i < pParams.length; i++) { + paramTypes[i] = pParams[i].getClass(); + } + } + + // Get constructor + Constructor constructor = pClass.getConstructor(paramTypes); + + // Invoke and create instance + value = constructor.newInstance(pParams); + } + /* All this to let InvocationTargetException pass on */ + catch (NoSuchMethodException nsme) { + return null; + } + catch (IllegalAccessException iae) { + return null; + } + catch (IllegalArgumentException iarge) { + return null; + } + catch (InstantiationException ie) { + return null; + } + catch (ExceptionInInitializerError err) { + return null; + } + + return value; + } + + /** + * Gets an object from any given static method, with the given parameter. + * + * @param pClass The class to invoke method on + * @param pMethod The name of the method to invoke + * @param pParam The parameter to the method + * + * @return The object returned by the static method. + * If the return type of the method is a primitive type, it is wrapped in + * the corresponding wrapper object (int is wrapped in an Integer). + * If the return type of the method is void, null is returned. + * If the method could not be invoked for any reason, null is returned. + * + * @throws InvocationTargetException if the invocation failed + */ + // TODO: Move to ReflectUtil + // TODO: Rename to invokeStatic? + public static Object invokeStaticMethod(Class pClass, String pMethod, Object pParam) + throws InvocationTargetException { + + return invokeStaticMethod(pClass, pMethod, new Object[] {pParam}); + } + + /** + * Gets an object from any given static method, with the given parameter. + * + * @param pClass The class to invoke method on + * @param pMethod The name of the method to invoke + * @param pParams The parameters to the method + * + * @return The object returned by the static method. + * If the return type of the method is a primitive type, it is wrapped in + * the corresponding wrapper object (int is wrapped in an Integer). + * If the return type of the method is void, null is returned. + * If the method could not be invoked for any reason, null is returned. + * + * @throws InvocationTargetException if the invocation failed + */ + // TODO: Move to ReflectUtil + // TODO: Rename to invokeStatic? + public static Object invokeStaticMethod(Class pClass, String pMethod, Object... pParams) + throws InvocationTargetException { + + Object value = null; + + try { + // Create param and argument arrays + Class[] paramTypes = new Class[pParams.length]; + for (int i = 0; i < pParams.length; i++) { + paramTypes[i] = pParams[i].getClass(); + } + + // Get method + // *** If more than one such method is found in the class, and one + // of these methods has a RETURN TYPE that is more specific than + // any of the others, that method is reflected; otherwise one of + // the methods is chosen ARBITRARILY. + // java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[]) + Method method = pClass.getMethod(pMethod, paramTypes); + + // Invoke public static method + if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { + value = method.invoke(null, pParams); + } + + } + /* All this to let InvocationTargetException pass on */ + catch (NoSuchMethodException nsme) { + return null; + } + catch (IllegalAccessException iae) { + return null; + } + catch (IllegalArgumentException iarge) { + return null; + } + + return value; + } + + /** + * Configures the bean according to the given mapping. + * For each {@code Map.Entry} in {@code Map.values()}, + * a method named + * {@code set + capitalize(entry.getKey())} is called on the bean, + * with {@code entry.getValue()} as its argument. + *

+ * Properties that has no matching set-method in the bean, are simply + * discarded. + *

+ * + * @param pBean The bean to configure + * @param pMapping The mapping for the bean + * + * @throws NullPointerException if any of the parameters are null. + * @throws InvocationTargetException if an error occurs when invoking the + * setter-method. + */ + // TODO: Add a version that takes a ConfigurationErrorListener callback interface + // TODO: ...or a boolean pFailOnError parameter + // TODO: ...or return Exceptions as an array?! + // TODO: ...or something whatsoever that makes clients able to determine something's not right + public static void configure(final Object pBean, final Map pMapping) throws InvocationTargetException { + configure(pBean, pMapping, false); + } + + /** + * Configures the bean according to the given mapping. + * For each {@code Map.Entry} in {@code Map.values()}, + * a method named + * {@code set + capitalize(entry.getKey())} is called on the bean, + * with {@code entry.getValue()} as its argument. + *

+ * Optionally, lisp-style names are allowed, and automatically converted + * to Java-style camel-case names. + *

+ *

+ * Properties that has no matching set-method in the bean, are simply + * discarded. + *

+ * + * @see StringUtil#lispToCamel(String) + * + * @param pBean The bean to configure + * @param pMapping The mapping for the bean + * @param pLispToCamel Allow lisp-style names, and automatically convert + * them to Java-style camel-case. + * + * @throws NullPointerException if any of the parameters are null. + * @throws InvocationTargetException if an error occurs when invoking the + * setter-method. + */ + public static void configure(final Object pBean, final Map pMapping, final boolean pLispToCamel) throws InvocationTargetException { + // Loop over properties in mapping + for (final Map.Entry entry : pMapping.entrySet()) { + try { + // Configure each property in turn + final String property = StringUtil.valueOf(entry.getKey()); + try { + setPropertyValue(pBean, property, entry.getValue()); + } + catch (NoSuchMethodException ignore) { + // If invocation failed, convert lisp-style and try again + if (pLispToCamel && property.indexOf('-') > 0) { + setPropertyValue(pBean, StringUtil.lispToCamel(property, false), entry.getValue()); + } + } + } + catch (NoSuchMethodException nsme) { + // This property was not configured + } + catch (IllegalAccessException iae) { + // This property was not configured + } + } + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java index 1234eb3c..92e262e3 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java @@ -1,202 +1,203 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.lang; - -import java.util.Date; -import java.util.TimeZone; - -/** - * A utility class with useful date manipulation methods and constants. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/DateUtil.java#1 $ - */ -public final class DateUtil { - - /** One second: 1000 milliseconds. */ - public static final long SECOND = 1000l; - - /** One minute: 60 seconds (60 000 milliseconds). */ - public static final long MINUTE = 60l * SECOND; - - /** - * One hour: 60 minutes (3 600 000 milliseconds). - * 60 minutes = 3 600 seconds = 3 600 000 milliseconds - */ - public static final long HOUR = 60l * MINUTE; - - /** - * One day: 24 hours (86 400 000 milliseconds). - * 24 hours = 1 440 minutes = 86 400 seconds = 86 400 000 milliseconds. - */ - public static final long DAY = 24l * HOUR; - - /** - * One calendar year: 365.2425 days (31556952000 milliseconds). - * 365.2425 days = 8765.82 hours = 525949.2 minutes = 31556952 seconds - * = 31556952000 milliseconds. - */ - public static final long CALENDAR_YEAR = 3652425l * 24l * 60l * 6l; - - private DateUtil() { - } - - /** - * Returns the time between the given start time and now (as defined by - * {@link System#currentTimeMillis()}). - * - * @param pStart the start time - * - * @return the time between the given start time and now. - */ - public static long delta(long pStart) { - return System.currentTimeMillis() - pStart; - } - - /** - * Returns the time between the given start time and now (as defined by - * {@link System#currentTimeMillis()}). - * - * @param pStart the start time - * - * @return the time between the given start time and now. - */ - public static long delta(Date pStart) { - return System.currentTimeMillis() - pStart.getTime(); - } - - /** - * Gets the current time, rounded down to the closest second. - * Equivalent to invoking - * {@code roundToSecond(System.currentTimeMillis())}. - * - * @return the current time, rounded to the closest second. - */ - public static long currentTimeSecond() { - return roundToSecond(System.currentTimeMillis()); - } - - /** - * Gets the current time, rounded down to the closest minute. - * Equivalent to invoking - * {@code roundToMinute(System.currentTimeMillis())}. - * - * @return the current time, rounded to the closest minute. - */ - public static long currentTimeMinute() { - return roundToMinute(System.currentTimeMillis()); - } - - /** - * Gets the current time, rounded down to the closest hour. - * Equivalent to invoking - * {@code roundToHour(System.currentTimeMillis())}. - * - * @return the current time, rounded to the closest hour. - */ - public static long currentTimeHour() { - return roundToHour(System.currentTimeMillis()); - } - - /** - * Gets the current time, rounded down to the closest day. - * Equivalent to invoking - * {@code roundToDay(System.currentTimeMillis())}. - * - * @return the current time, rounded to the closest day. - */ - public static long currentTimeDay() { - return roundToDay(System.currentTimeMillis()); - } - - /** - * Rounds the given time down to the closest second. - * - * @param pTime time - * @return the time rounded to the closest second. - */ - public static long roundToSecond(final long pTime) { - return (pTime / SECOND) * SECOND; - } - - /** - * Rounds the given time down to the closest minute. - * - * @param pTime time - * @return the time rounded to the closest minute. - */ - public static long roundToMinute(final long pTime) { - return (pTime / MINUTE) * MINUTE; - } - - /** - * Rounds the given time down to the closest hour, using the default timezone. - * - * @param pTime time - * @return the time rounded to the closest hour. - */ - public static long roundToHour(final long pTime) { - return roundToHour(pTime, TimeZone.getDefault()); - } - - /** - * Rounds the given time down to the closest hour, using the given timezone. - * - * @param pTime time - * @param pTimeZone the timezone to use when rounding - * @return the time rounded to the closest hour. - */ - public static long roundToHour(final long pTime, final TimeZone pTimeZone) { - int offset = pTimeZone.getOffset(pTime); - return ((pTime / HOUR) * HOUR) - offset; - } - - /** - * Rounds the given time down to the closest day, using the default timezone. - * - * @param pTime time - * @return the time rounded to the closest day. - */ - public static long roundToDay(final long pTime) { - return roundToDay(pTime, TimeZone.getDefault()); - } - - /** - * Rounds the given time down to the closest day, using the given timezone. - * - * @param pTime time - * @param pTimeZone the timezone to use when rounding - * @return the time rounded to the closest day. - */ - public static long roundToDay(final long pTime, final TimeZone pTimeZone) { - int offset = pTimeZone.getOffset(pTime); - return (((pTime + offset) / DAY) * DAY) - offset; - } -} +/* + * 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.lang; + +import java.util.Date; +import java.util.TimeZone; + +/** + * A utility class with useful date manipulation methods and constants. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/DateUtil.java#1 $ + */ +public final class DateUtil { + + /** One second: 1000 milliseconds. */ + public static final long SECOND = 1000l; + + /** One minute: 60 seconds (60 000 milliseconds). */ + public static final long MINUTE = 60l * SECOND; + + /** + * One hour: 60 minutes (3 600 000 milliseconds). + * 60 minutes = 3 600 seconds = 3 600 000 milliseconds + */ + public static final long HOUR = 60l * MINUTE; + + /** + * One day: 24 hours (86 400 000 milliseconds). + * 24 hours = 1 440 minutes = 86 400 seconds = 86 400 000 milliseconds. + */ + public static final long DAY = 24l * HOUR; + + /** + * One calendar year: 365.2425 days (31556952000 milliseconds). + * 365.2425 days = 8765.82 hours = 525949.2 minutes = 31556952 seconds + * = 31556952000 milliseconds. + */ + public static final long CALENDAR_YEAR = 3652425l * 24l * 60l * 6l; + + private DateUtil() { + } + + /** + * Returns the time between the given start time and now (as defined by + * {@link System#currentTimeMillis()}). + * + * @param pStart the start time + * + * @return the time between the given start time and now. + */ + public static long delta(long pStart) { + return System.currentTimeMillis() - pStart; + } + + /** + * Returns the time between the given start time and now (as defined by + * {@link System#currentTimeMillis()}). + * + * @param pStart the start time + * + * @return the time between the given start time and now. + */ + public static long delta(Date pStart) { + return System.currentTimeMillis() - pStart.getTime(); + } + + /** + * Gets the current time, rounded down to the closest second. + * Equivalent to invoking + * {@code roundToSecond(System.currentTimeMillis())}. + * + * @return the current time, rounded to the closest second. + */ + public static long currentTimeSecond() { + return roundToSecond(System.currentTimeMillis()); + } + + /** + * Gets the current time, rounded down to the closest minute. + * Equivalent to invoking + * {@code roundToMinute(System.currentTimeMillis())}. + * + * @return the current time, rounded to the closest minute. + */ + public static long currentTimeMinute() { + return roundToMinute(System.currentTimeMillis()); + } + + /** + * Gets the current time, rounded down to the closest hour. + * Equivalent to invoking + * {@code roundToHour(System.currentTimeMillis())}. + * + * @return the current time, rounded to the closest hour. + */ + public static long currentTimeHour() { + return roundToHour(System.currentTimeMillis()); + } + + /** + * Gets the current time, rounded down to the closest day. + * Equivalent to invoking + * {@code roundToDay(System.currentTimeMillis())}. + * + * @return the current time, rounded to the closest day. + */ + public static long currentTimeDay() { + return roundToDay(System.currentTimeMillis()); + } + + /** + * Rounds the given time down to the closest second. + * + * @param pTime time + * @return the time rounded to the closest second. + */ + public static long roundToSecond(final long pTime) { + return (pTime / SECOND) * SECOND; + } + + /** + * Rounds the given time down to the closest minute. + * + * @param pTime time + * @return the time rounded to the closest minute. + */ + public static long roundToMinute(final long pTime) { + return (pTime / MINUTE) * MINUTE; + } + + /** + * Rounds the given time down to the closest hour, using the default timezone. + * + * @param pTime time + * @return the time rounded to the closest hour. + */ + public static long roundToHour(final long pTime) { + return roundToHour(pTime, TimeZone.getDefault()); + } + + /** + * Rounds the given time down to the closest hour, using the given timezone. + * + * @param pTime time + * @param pTimeZone the timezone to use when rounding + * @return the time rounded to the closest hour. + */ + public static long roundToHour(final long pTime, final TimeZone pTimeZone) { + int offset = pTimeZone.getOffset(pTime); + return ((pTime / HOUR) * HOUR) - offset; + } + + /** + * Rounds the given time down to the closest day, using the default timezone. + * + * @param pTime time + * @return the time rounded to the closest day. + */ + public static long roundToDay(final long pTime) { + return roundToDay(pTime, TimeZone.getDefault()); + } + + /** + * Rounds the given time down to the closest day, using the given timezone. + * + * @param pTime time + * @param pTimeZone the timezone to use when rounding + * @return the time rounded to the closest day. + */ + public static long roundToDay(final long pTime, final TimeZone pTimeZone) { + int offset = pTimeZone.getOffset(pTime); + return (((pTime + offset) / DAY) * DAY) - offset; + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java index d69f37c3..8dadc5d2 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.lang; @@ -197,9 +199,10 @@ public final class Platform { /** * Enumeration of common System {@code Architecture}s. - *

+ *

* For {@link #Unknown unknown architectures}, {@code toString()} will return * the the same value as {@code System.getProperty("os.arch")}. + *

* * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $ @@ -226,9 +229,10 @@ public final class Platform { /** * Enumeration of common {@code OperatingSystem}s. - *

+ *

* For {@link #Unknown unknown operating systems}, {@code getName()} will return * the the same value as {@code System.getProperty("os.name")}. + *

* * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $ diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java index 349be257..aefd92a0 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java @@ -1,137 +1,140 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.lang; - -/** - * Util class for various reflection-based operations. - *

- * NOTE: This class is not considered part of the public API and may be - * changed without notice - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java#1 $ - */ -public final class ReflectUtil { - - /** Don't allow instances */ - private ReflectUtil() {} - - /** - * Returns the primitive type for the given wrapper type. - * - * @param pType the wrapper type - * - * @return the primitive type - * - * @throws IllegalArgumentException if {@code pType} is not a primitive - * wrapper - */ - public static Class unwrapType(Class pType) { - if (pType == Boolean.class) { - return Boolean.TYPE; - } - else if (pType == Byte.class) { - return Byte.TYPE; - } - else if (pType == Character.class) { - return Character.TYPE; - } - else if (pType == Double.class) { - return Double.TYPE; - } - else if (pType == Float.class) { - return Float.TYPE; - } - else if (pType == Integer.class) { - return Integer.TYPE; - } - else if (pType == Long.class) { - return Long.TYPE; - } - else if (pType == Short.class) { - return Short.TYPE; - } - - throw new IllegalArgumentException("Not a primitive wrapper: " + pType); - } - - /** - * Returns the wrapper type for the given primitive type. - * - * @param pType the primitive tpye - * - * @return the wrapper type - * - * @throws IllegalArgumentException if {@code pType} is not a primitive - * type - */ - public static Class wrapType(Class pType) { - if (pType == Boolean.TYPE) { - return Boolean.class; - } - else if (pType == Byte.TYPE) { - return Byte.class; - } - else if (pType == Character.TYPE) { - return Character.class; - } - else if (pType == Double.TYPE) { - return Double.class; - } - else if (pType == Float.TYPE) { - return Float.class; - } - else if (pType == Integer.TYPE) { - return Integer.class; - } - else if (pType == Long.TYPE) { - return Long.class; - } - else if (pType == Short.TYPE) { - return Short.class; - } - - throw new IllegalArgumentException("Not a primitive type: " + pType); - } - - /** - * Returns {@code true} if the given type is a primitive wrapper. - * - * @param pType - * - * @return {@code true} if the given type is a primitive wrapper, otherwise - * {@code false} - */ - public static boolean isPrimitiveWrapper(Class pType) { - return pType == Boolean.class || pType == Byte.class - || pType == Character.class || pType == Double.class - || pType == Float.class || pType == Integer.class - || pType == Long.class || pType == Short.class; - } -} +/* + * 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.lang; + +/** + * Util class for various reflection-based operations. + *

+ * NOTE: This class is not considered part of the public API and may be + * changed without notice + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java#1 $ + */ +public final class ReflectUtil { + + /** Don't allow instances */ + private ReflectUtil() {} + + /** + * Returns the primitive type for the given wrapper type. + * + * @param pType the wrapper type + * + * @return the primitive type + * + * @throws IllegalArgumentException if {@code pType} is not a primitive + * wrapper + */ + public static Class unwrapType(Class pType) { + if (pType == Boolean.class) { + return Boolean.TYPE; + } + else if (pType == Byte.class) { + return Byte.TYPE; + } + else if (pType == Character.class) { + return Character.TYPE; + } + else if (pType == Double.class) { + return Double.TYPE; + } + else if (pType == Float.class) { + return Float.TYPE; + } + else if (pType == Integer.class) { + return Integer.TYPE; + } + else if (pType == Long.class) { + return Long.TYPE; + } + else if (pType == Short.class) { + return Short.TYPE; + } + + throw new IllegalArgumentException("Not a primitive wrapper: " + pType); + } + + /** + * Returns the wrapper type for the given primitive type. + * + * @param pType the primitive tpye + * + * @return the wrapper type + * + * @throws IllegalArgumentException if {@code pType} is not a primitive + * type + */ + public static Class wrapType(Class pType) { + if (pType == Boolean.TYPE) { + return Boolean.class; + } + else if (pType == Byte.TYPE) { + return Byte.class; + } + else if (pType == Character.TYPE) { + return Character.class; + } + else if (pType == Double.TYPE) { + return Double.class; + } + else if (pType == Float.TYPE) { + return Float.class; + } + else if (pType == Integer.TYPE) { + return Integer.class; + } + else if (pType == Long.TYPE) { + return Long.class; + } + else if (pType == Short.TYPE) { + return Short.class; + } + + throw new IllegalArgumentException("Not a primitive type: " + pType); + } + + /** + * Returns {@code true} if the given type is a primitive wrapper. + * + * @param pType + * + * @return {@code true} if the given type is a primitive wrapper, otherwise + * {@code false} + */ + public static boolean isPrimitiveWrapper(Class pType) { + return pType == Boolean.class || pType == Byte.class + || pType == Character.class || pType == Double.class + || pType == Float.class || pType == Integer.class + || pType == Long.class || pType == Short.class; + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java index 1d244b72..69979657 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java @@ -1,2150 +1,2161 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.lang; - -import com.twelvemonkeys.util.StringTokenIterator; - -import java.awt.*; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.sql.Timestamp; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.ArrayList; -import java.util.regex.PatternSyntaxException; -import java.util.regex.Pattern; -import java.io.UnsupportedEncodingException; -import java.nio.charset.UnsupportedCharsetException; - -/** - * A utility class with some useful string manipulation methods. - * - * @author Harald Kuhr - * @author Eirik Torske - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java#2 $ - * @todo Consistency check: Method names, parameter sequence, Exceptions, - * return values, null-value handling and parameter names (cosmetics). - */ -public final class StringUtil { - - /** - * The default delimiter string, used by the {@code toXXXArray()} - * methods. - * Its value is {@code ", \t\n\r\f"}. - * - * - * @see #toStringArray(String) - * @see #toIntArray(String) - * @see #toLongArray(String) - * @see #toDoubleArray(String) - */ - public final static String DELIMITER_STRING = ", \t\n\r\f"; - - // Avoid constructor showing up in API doc - private StringUtil() { - } - - /** - * Constructs a new {@link String} by decoding the specified sub array of bytes using the specified charset. - * Replacement for {@link String#String(byte[], int, int, String) new String(byte[], int, int, String)}, that does - * not throw the checked {@link UnsupportedEncodingException}, - * but instead the unchecked {@link UnsupportedCharsetException} if the character set is not supported. - * - * @param pData the bytes to be decoded to characters - * @param pOffset the index of the first byte to decode - * @param pLength the number of bytes to decode - * @param pCharset the name of a supported character set - * @return a newly created string. - * @throws UnsupportedCharsetException - * - * @see String#String(byte[], int, int, String) - */ - public static String decode(final byte[] pData, final int pOffset, final int pLength, final String pCharset) { - try { - return new String(pData, pOffset, pLength, pCharset); - } - catch (UnsupportedEncodingException e) { - throw new UnsupportedCharsetException(pCharset); - } - } - - /** - * Returns the value of the given {@code Object}, as a {@code String}. - * Unlike String.valueOf, this method returns {@code null} - * instead of the {@code String} "null", if {@code null} is given as - * the argument. - * - * @param pObj the Object to find the {@code String} value of. - * @return the String value of the given object, or {@code null} if the - * {@code pObj} == {@code null}. - * @see String#valueOf(Object) - * @see String#toString() - */ - public static String valueOf(Object pObj) { - return ((pObj != null) ? pObj.toString() : null); - } - - /** - * Converts a string to uppercase. - * - * @param pString the string to convert - * @return the string converted to uppercase, or null if the argument was - * null. - */ - public static String toUpperCase(String pString) { - if (pString != null) { - return pString.toUpperCase(); - } - return null; - } - - /** - * Converts a string to lowercase. - * - * @param pString the string to convert - * @return the string converted to lowercase, or null if the argument was - * null. - */ - public static String toLowerCase(String pString) { - if (pString != null) { - return pString.toLowerCase(); - } - return null; - } - - /** - * Tests if a String is null, or contains nothing but white-space. - * - * @param pString The string to test - * @return true if the string is null or contains only whitespace, - * otherwise false. - */ - public static boolean isEmpty(String pString) { - return ((pString == null) || (pString.trim().length() == 0)); - } - - /** - * Tests a string array, to see if all items are null or an empty string. - * - * @param pStringArray The string array to check. - * @return true if the string array is null or only contains string items - * that are null or contain only whitespace, otherwise false. - */ - public static boolean isEmpty(String[] pStringArray) { - // No elements to test - if (pStringArray == null) { - return true; - } - - // Test all the elements - for (String string : pStringArray) { - if (!isEmpty(string)) { - return false; - } - } - - // All elements are empty - return true; - } - - /** - * Tests if a string contains another string. - * - * @param pContainer The string to test - * @param pLookFor The string to look for - * @return {@code true} if the container string is contains the string, and - * both parameters are non-{@code null}, otherwise {@code false}. - */ - public static boolean contains(String pContainer, String pLookFor) { - return ((pContainer != null) && (pLookFor != null) && (pContainer.indexOf(pLookFor) >= 0)); - } - - /** - * Tests if a string contains another string, ignoring case. - * - * @param pContainer The string to test - * @param pLookFor The string to look for - * @return {@code true} if the container string is contains the string, and - * both parameters are non-{@code null}, otherwise {@code false}. - * @see #contains(String,String) - */ - public static boolean containsIgnoreCase(String pContainer, String pLookFor) { - return indexOfIgnoreCase(pContainer, pLookFor, 0) >= 0; - } - - /** - * Tests if a string contains a specific character. - * - * @param pString The string to check. - * @param pChar The character to search for. - * @return true if the string contains the specific character. - */ - public static boolean contains(final String pString, final int pChar) { - return ((pString != null) && (pString.indexOf(pChar) >= 0)); - } - - /** - * Tests if a string contains a specific character, ignoring case. - * - * @param pString The string to check. - * @param pChar The character to search for. - * @return true if the string contains the specific character. - */ - public static boolean containsIgnoreCase(String pString, int pChar) { - return ((pString != null) - && ((pString.indexOf(Character.toLowerCase((char) pChar)) >= 0) - || (pString.indexOf(Character.toUpperCase((char) pChar)) >= 0))); - - // NOTE: I don't convert the string to uppercase, but instead test - // the string (potentially) two times, as this is more efficient for - // long strings (in most cases). - } - - /** - * Returns the index within this string of the first occurrence of the - * specified substring. - * - * @param pString The string to test - * @param pLookFor The string to look for - * @return if the string argument occurs as a substring within this object, - * then the index of the first character of the first such substring is - * returned; if it does not occur as a substring, -1 is returned. - * @see String#indexOf(String) - */ - public static int indexOfIgnoreCase(String pString, String pLookFor) { - return indexOfIgnoreCase(pString, pLookFor, 0); - } - - /** - * Returns the index within this string of the first occurrence of the - * specified substring, starting at the specified index. - * - * @param pString The string to test - * @param pLookFor The string to look for - * @param pPos The first index to test - * @return if the string argument occurs as a substring within this object, - * then the index of the first character of the first such substring is - * returned; if it does not occur as a substring, -1 is returned. - * @see String#indexOf(String,int) - */ - public static int indexOfIgnoreCase(String pString, String pLookFor, int pPos) { - if ((pString == null) || (pLookFor == null)) { - return -1; - } - if (pLookFor.length() == 0) { - return pPos;// All strings "contains" the empty string - } - if (pLookFor.length() > pString.length()) { - return -1;// Cannot contain string longer than itself - } - - // Get first char - char firstL = Character.toLowerCase(pLookFor.charAt(0)); - char firstU = Character.toUpperCase(pLookFor.charAt(0)); - int indexLower = 0; - int indexUpper = 0; - - for (int i = pPos; i <= (pString.length() - pLookFor.length()); i++) { - - // Peek for first char - indexLower = ((indexLower >= 0) && (indexLower <= i)) - ? pString.indexOf(firstL, i) - : indexLower; - indexUpper = ((indexUpper >= 0) && (indexUpper <= i)) - ? pString.indexOf(firstU, i) - : indexUpper; - if (indexLower < 0) { - if (indexUpper < 0) { - return -1;// First char not found - } - else { - i = indexUpper;// Only upper - } - } - else if (indexUpper < 0) { - i = indexLower;// Only lower - } - else { - - // Both found, select first occurence - i = (indexLower < indexUpper) - ? indexLower - : indexUpper; - } - - // Only one? - if (pLookFor.length() == 1) { - return i;// The only char found! - } - - // Test if we still have enough chars - else if (i > (pString.length() - pLookFor.length())) { - return -1; - } - - // Test if last char equals! (regionMatches is expensive) - else if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1))) - && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) { - continue;// Nope, try next - } - - // Test from second char, until second-last char - else if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) { - return i; - } - } - return -1; - } - - /** - * Returns the index within this string of the rightmost occurrence of the - * specified substring. The rightmost empty string "" is considered to - * occur at the index value {@code pString.length() - 1}. - * - * @param pString The string to test - * @param pLookFor The string to look for - * @return If the string argument occurs one or more times as a substring - * within this object at a starting index no greater than fromIndex, then - * the index of the first character of the last such substring is returned. - * If it does not occur as a substring starting at fromIndex or earlier, -1 - * is returned. - * @see String#lastIndexOf(String) - */ - public static int lastIndexOfIgnoreCase(String pString, String pLookFor) { - return lastIndexOfIgnoreCase(pString, pLookFor, pString != null ? pString.length() - 1 : -1); - } - - /** - * Returns the index within this string of the rightmost occurrence of the - * specified substring. The rightmost empty string "" is considered to - * occur at the index value {@code pPos} - * - * @param pString The string to test - * @param pLookFor The string to look for - * @param pPos The last index to test - * @return If the string argument occurs one or more times as a substring - * within this object at a starting index no greater than fromIndex, then - * the index of the first character of the last such substring is returned. - * If it does not occur as a substring starting at fromIndex or earlier, -1 - * is returned. - * @see String#lastIndexOf(String,int) - */ - public static int lastIndexOfIgnoreCase(String pString, String pLookFor, int pPos) { - if ((pString == null) || (pLookFor == null)) { - return -1; - } - if (pLookFor.length() == 0) { - return pPos;// All strings "contains" the empty string - } - if (pLookFor.length() > pString.length()) { - return -1;// Cannot contain string longer than itself - } - - // Get first char - char firstL = Character.toLowerCase(pLookFor.charAt(0)); - char firstU = Character.toUpperCase(pLookFor.charAt(0)); - int indexLower = pPos; - int indexUpper = pPos; - - for (int i = pPos; i >= 0; i--) { - - // Peek for first char - indexLower = ((indexLower >= 0) && (indexLower >= i)) - ? pString.lastIndexOf(firstL, i) - : indexLower; - indexUpper = ((indexUpper >= 0) && (indexUpper >= i)) - ? pString.lastIndexOf(firstU, i) - : indexUpper; - if (indexLower < 0) { - if (indexUpper < 0) { - return -1;// First char not found - } - else { - i = indexUpper;// Only upper - } - } - else if (indexUpper < 0) { - i = indexLower;// Only lower - } - else { - - // Both found, select last occurence - i = (indexLower > indexUpper) - ? indexLower - : indexUpper; - } - - // Only one? - if (pLookFor.length() == 1) { - return i;// The only char found! - } - - // Test if we still have enough chars - else if (i > (pString.length() - pLookFor.length())) { - //return -1; - continue; - } - - // Test if last char equals! (regionMatches is expensive) - else - if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1))) - && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) { - continue;// Nope, try next - } - - // Test from second char, until second-last char - else - if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) { - return i; - } - } - return -1; - } - - /** - * Returns the index within this string of the first occurrence of the - * specified character. - * - * @param pString The string to test - * @param pChar The character to look for - * @return if the string argument occurs as a substring within this object, - * then the index of the first character of the first such substring is - * returned; if it does not occur as a substring, -1 is returned. - * @see String#indexOf(int) - */ - public static int indexOfIgnoreCase(String pString, int pChar) { - return indexOfIgnoreCase(pString, pChar, 0); - } - - /** - * Returns the index within this string of the first occurrence of the - * specified character, starting at the specified index. - * - * @param pString The string to test - * @param pChar The character to look for - * @param pPos The first index to test - * @return if the string argument occurs as a substring within this object, - * then the index of the first character of the first such substring is - * returned; if it does not occur as a substring, -1 is returned. - * @see String#indexOf(int,int) - */ - public static int indexOfIgnoreCase(String pString, int pChar, int pPos) { - if ((pString == null)) { - return -1; - } - - // Get first char - char lower = Character.toLowerCase((char) pChar); - char upper = Character.toUpperCase((char) pChar); - int indexLower; - int indexUpper; - - // Test for char - indexLower = pString.indexOf(lower, pPos); - indexUpper = pString.indexOf(upper, pPos); - if (indexLower < 0) { - - /* if (indexUpper < 0) - return -1; // First char not found - else */ - return indexUpper;// Only upper - } - else if (indexUpper < 0) { - return indexLower;// Only lower - } - else { - - // Both found, select first occurence - return (indexLower < indexUpper) - ? indexLower - : indexUpper; - } - } - - /** - * Returns the index within this string of the last occurrence of the - * specified character. - * - * @param pString The string to test - * @param pChar The character to look for - * @return if the string argument occurs as a substring within this object, - * then the index of the first character of the first such substring is - * returned; if it does not occur as a substring, -1 is returned. - * @see String#lastIndexOf(int) - */ - public static int lastIndexOfIgnoreCase(String pString, int pChar) { - return lastIndexOfIgnoreCase(pString, pChar, pString != null ? pString.length() : -1); - } - - /** - * Returns the index within this string of the last occurrence of the - * specified character, searching backward starting at the specified index. - * - * @param pString The string to test - * @param pChar The character to look for - * @param pPos The last index to test - * @return if the string argument occurs as a substring within this object, - * then the index of the first character of the first such substring is - * returned; if it does not occur as a substring, -1 is returned. - * @see String#lastIndexOf(int,int) - */ - public static int lastIndexOfIgnoreCase(String pString, int pChar, int pPos) { - if ((pString == null)) { - return -1; - } - - // Get first char - char lower = Character.toLowerCase((char) pChar); - char upper = Character.toUpperCase((char) pChar); - int indexLower; - int indexUpper; - - // Test for char - indexLower = pString.lastIndexOf(lower, pPos); - indexUpper = pString.lastIndexOf(upper, pPos); - if (indexLower < 0) { - - /* if (indexUpper < 0) - return -1; // First char not found - else */ - return indexUpper;// Only upper - } - else if (indexUpper < 0) { - return indexLower;// Only lower - } - else { - - // Both found, select last occurence - return (indexLower > indexUpper) - ? indexLower - : indexUpper; - } - } - - /** - * Trims the argument string for whitespace on the left side only. - * - * @param pString the string to trim - * @return the string with no whitespace on the left, or {@code null} if - * the string argument is {@code null}. - * @see #rtrim - * @see String#trim() - */ - public static String ltrim(String pString) { - if ((pString == null) || (pString.length() == 0)) { - return pString;// Null or empty string - } - for (int i = 0; i < pString.length(); i++) { - if (!Character.isWhitespace(pString.charAt(i))) { - if (i == 0) { - return pString;// First char is not whitespace - } - else { - return pString.substring(i);// Return rest after whitespace - } - } - } - - // If all whitespace, return empty string - return ""; - } - - /** - * Trims the argument string for whitespace on the right side only. - * - * @param pString the string to trim - * @return the string with no whitespace on the right, or {@code null} if - * the string argument is {@code null}. - * @see #ltrim - * @see String#trim() - */ - public static String rtrim(String pString) { - if ((pString == null) || (pString.length() == 0)) { - return pString;// Null or empty string - } - for (int i = pString.length(); i > 0; i--) { - if (!Character.isWhitespace(pString.charAt(i - 1))) { - if (i == pString.length()) { - return pString;// First char is not whitespace - } - else { - return pString.substring(0, i);// Return before whitespace - } - } - } - - // If all whitespace, return empty string - return ""; - } - - /** - * Replaces a substring of a string with another string. All matches are - * replaced. - * - * @param pSource The source String - * @param pPattern The pattern to replace - * @param pReplace The new String to be inserted instead of the - * replace String - * @return The new String with the pattern replaced - */ - public static String replace(String pSource, String pPattern, String pReplace) { - if (pPattern.length() == 0) { - return pSource;// Special case: No pattern to replace - } - - int match; - int offset = 0; - StringBuilder result = new StringBuilder(); - - // Loop string, until last occurence of pattern, and replace - while ((match = pSource.indexOf(pPattern, offset)) != -1) { - // Append everything until pattern - result.append(pSource.substring(offset, match)); - // Append the replace string - result.append(pReplace); - offset = match + pPattern.length(); - } - - // Append rest of string and return - result.append(pSource.substring(offset)); - - return result.toString(); - } - - /** - * Replaces a substring of a string with another string, ignoring case. - * All matches are replaced. - * - * @param pSource The source String - * @param pPattern The pattern to replace - * @param pReplace The new String to be inserted instead of the - * replace String - * @return The new String with the pattern replaced - * @see #replace(String,String,String) - */ - public static String replaceIgnoreCase(String pSource, String pPattern, String pReplace) { - if (pPattern.length() == 0) { - return pSource;// Special case: No pattern to replace - } - int match; - int offset = 0; - StringBuilder result = new StringBuilder(); - - while ((match = indexOfIgnoreCase(pSource, pPattern, offset)) != -1) { - result.append(pSource.substring(offset, match)); - result.append(pReplace); - offset = match + pPattern.length(); - } - result.append(pSource.substring(offset)); - return result.toString(); - } - - /** - * Cuts a string between two words, before a sepcified length, if the - * string is longer than the maxium lenght. The string is optionally padded - * with the pad argument. The method assumes words to be separated by the - * space character (" "). - * Note that the maximum length argument is absolute, and will also include - * the length of the padding. - * - * @param pString The string to cut - * @param pMaxLen The maximum length before cutting - * @param pPad The string to append at the end, aftrer cutting - * @return The cutted string with padding, or the original string, if it - * was shorter than the max length. - * @see #pad(String,int,String,boolean) - */ - public static String cut(String pString, int pMaxLen, String pPad) { - if (pString == null) { - return null; - } - if (pPad == null) { - pPad = ""; - } - int len = pString.length(); - - if (len > pMaxLen) { - len = pString.lastIndexOf(' ', pMaxLen - pPad.length()); - } - else { - return pString; - } - return pString.substring(0, len) + pPad; - } - - /** - * Makes the Nth letter of a String uppercase. If the index is outside the - * the length of the argument string, the argument is simply returned. - * - * @param pString The string to capitalize - * @param pIndex The base-0 index of the char to capitalize. - * @return The capitalized string, or null, if a null argument was given. - */ - public static String capitalize(String pString, int pIndex) { - if (pIndex < 0) { - throw new IndexOutOfBoundsException("Negative index not allowed: " + pIndex); - } - if (pString == null || pString.length() <= pIndex) { - return pString; - } - - // This is the fastest method, according to my tests - - // Skip array duplication if allready capitalized - if (Character.isUpperCase(pString.charAt(pIndex))) { - return pString; - } - - // Convert to char array, capitalize and create new String - char[] charArray = pString.toCharArray(); - charArray[pIndex] = Character.toUpperCase(charArray[pIndex]); - return new String(charArray); - - /** - StringBuilder buf = new StringBuilder(pString); - buf.setCharAt(pIndex, Character.toUpperCase(buf.charAt(pIndex))); - return buf.toString(); - //*/ - - /** - return pString.substring(0, pIndex) - + Character.toUpperCase(pString.charAt(pIndex)) - + pString.substring(pIndex + 1); - //*/ - } - - /** - * Makes the first letter of a String uppercase. - * - * @param pString The string to capitalize - * @return The capitalized string, or null, if a null argument was given. - */ - public static String capitalize(String pString) { - return capitalize(pString, 0); - } - - /** - * Formats a number with leading zeroes, to a specified length. - * - * @param pNum The number to format - * @param pLen The number of digits - * @return A string containing the formatted number - * @throws IllegalArgumentException Thrown, if the number contains - * more digits than allowed by the length argument. - * @see #pad(String,int,String,boolean) - * @deprecated Use StringUtil.pad instead! - */ - - /*public*/ - static String formatNumber(long pNum, int pLen) throws IllegalArgumentException { - StringBuilder result = new StringBuilder(); - - if (pNum >= Math.pow(10, pLen)) { - throw new IllegalArgumentException("The number to format cannot contain more digits than the length argument specifies!"); - } - for (int i = pLen; i > 1; i--) { - if (pNum < Math.pow(10, i - 1)) { - result.append('0'); - } - else { - break; - } - } - result.append(pNum); - return result.toString(); - } - - /** - * String length check with simple concatenation of selected pad-string. - * E.g. a zip number from 123 to the correct 0123. - * - * @param pSource The source string. - * @param pRequiredLength The accurate length of the resulting string. - * @param pPadString The string for concatenation. - * @param pPrepend The location of fill-ins, prepend (true), - * or append (false) - * @return a concatenated string. - * @todo What if source is allready longer than required length? - * @todo Consistency with cut - * @see #cut(String,int,String) - */ - public static String pad(String pSource, int pRequiredLength, String pPadString, boolean pPrepend) { - if (pPadString == null || pPadString.length() == 0) { - throw new IllegalArgumentException("Pad string: \"" + pPadString + "\""); - } - - if (pSource.length() >= pRequiredLength) { - return pSource; - } - - // TODO: Benchmark the new version against the old one, to see if it's really faster - // Rewrite to first create pad - // - pad += pad; - until length is >= gap - // then append the pad and cut if too long - int gap = pRequiredLength - pSource.length(); - StringBuilder result = new StringBuilder(pPadString); - while (result.length() < gap) { - result.append(result); - } - - if (result.length() > gap) { - result.delete(gap, result.length()); - } - - return pPrepend ? result.append(pSource).toString() : result.insert(0, pSource).toString(); - - /* - StringBuilder result = new StringBuilder(pSource); - - // Concatenation until proper string length - while (result.length() < pRequiredLength) { - // Prepend or append - if (pPrepend) { // Front - result.insert(0, pPadString); - } - else { // Back - result.append(pPadString); - } - } - - // Truncate - if (result.length() > pRequiredLength) { - if (pPrepend) { - result.delete(0, result.length() - pRequiredLength); - } - else { - result.delete(pRequiredLength, result.length()); - } - } - return result.toString(); - */ - } - - /** - * Converts the string to a date, using the default date format. - * - * @param pString the string to convert - * @return the date - * @see DateFormat - * @see DateFormat#getInstance() - */ - public static Date toDate(String pString) { - // Default - return toDate(pString, DateFormat.getInstance()); - } - - /** - * Converts the string to a date, using the given format. - * - * @param pString the string to convert - * @param pFormat the date format - * @return the date - * @todo cache formats? - * @see java.text.SimpleDateFormat - * @see java.text.SimpleDateFormat#SimpleDateFormat(String) - */ - public static Date toDate(String pString, String pFormat) { - // Get the format from cache, or create new and insert - // Return new date - return toDate(pString, new SimpleDateFormat(pFormat)); - } - - /** - * Converts the string to a date, using the given format. - * - * @param pString the string to convert - * @param pFormat the date format - * @return the date - * @see SimpleDateFormat - * @see SimpleDateFormat#SimpleDateFormat(String) - * @see DateFormat - */ - public static Date toDate(final String pString, final DateFormat pFormat) { - try { - synchronized (pFormat) { - // Parse date using given format - return pFormat.parse(pString); - } - } - catch (ParseException pe) { - // Wrap in RuntimeException - throw new IllegalArgumentException(pe.getMessage()); - } - } - - /** - * Converts the string to a jdbc Timestamp, using the standard Timestamp - * escape format. - * - * @param pValue the value - * @return a new {@code Timestamp} - * @see java.sql.Timestamp - * @see java.sql.Timestamp#valueOf(String) - */ - public static Timestamp toTimestamp(final String pValue) { - // Parse date using default format - return Timestamp.valueOf(pValue); - } - - /** - * Converts a delimiter separated String to an array of Strings. - * - * @param pString The comma-separated string - * @param pDelimiters The delimiter string - * @return a {@code String} array containing the delimiter separated elements - */ - public static String[] toStringArray(String pString, String pDelimiters) { - if (isEmpty(pString)) { - return new String[0]; - } - - StringTokenIterator st = new StringTokenIterator(pString, pDelimiters); - List v = new ArrayList(); - - while (st.hasMoreElements()) { - v.add(st.nextToken()); - } - - return v.toArray(new String[v.size()]); - } - - /** - * Converts a comma-separated String to an array of Strings. - * - * @param pString The comma-separated string - * @return a {@code String} array containing the comma-separated elements - * @see #toStringArray(String,String) - */ - public static String[] toStringArray(String pString) { - return toStringArray(pString, DELIMITER_STRING); - } - - /** - * Converts a comma-separated String to an array of ints. - * - * @param pString The comma-separated string - * @param pDelimiters The delimiter string - * @param pBase The radix - * @return an {@code int} array - * @throws NumberFormatException if any of the elements are not parseable - * as an int - */ - public static int[] toIntArray(String pString, String pDelimiters, int pBase) { - if (isEmpty(pString)) { - return new int[0]; - } - - // Some room for improvement here... - String[] temp = toStringArray(pString, pDelimiters); - int[] array = new int[temp.length]; - - for (int i = 0; i < array.length; i++) { - array[i] = Integer.parseInt(temp[i], pBase); - } - return array; - } - - /** - * Converts a comma-separated String to an array of ints. - * - * @param pString The comma-separated string - * @return an {@code int} array - * @throws NumberFormatException if any of the elements are not parseable - * as an int - * @see #toStringArray(String,String) - * @see #DELIMITER_STRING - */ - public static int[] toIntArray(String pString) { - return toIntArray(pString, DELIMITER_STRING, 10); - } - - /** - * Converts a comma-separated String to an array of ints. - * - * @param pString The comma-separated string - * @param pDelimiters The delimiter string - * @return an {@code int} array - * @throws NumberFormatException if any of the elements are not parseable - * as an int - * @see #toIntArray(String,String) - */ - public static int[] toIntArray(String pString, String pDelimiters) { - return toIntArray(pString, pDelimiters, 10); - } - - /** - * Converts a comma-separated String to an array of longs. - * - * @param pString The comma-separated string - * @param pDelimiters The delimiter string - * @return a {@code long} array - * @throws NumberFormatException if any of the elements are not parseable - * as a long - */ - public static long[] toLongArray(String pString, String pDelimiters) { - if (isEmpty(pString)) { - return new long[0]; - } - - // Some room for improvement here... - String[] temp = toStringArray(pString, pDelimiters); - long[] array = new long[temp.length]; - - for (int i = 0; i < array.length; i++) { - array[i] = Long.parseLong(temp[i]); - } - return array; - } - - /** - * Converts a comma-separated String to an array of longs. - * - * @param pString The comma-separated string - * @return a {@code long} array - * @throws NumberFormatException if any of the elements are not parseable - * as a long - * @see #toStringArray(String,String) - * @see #DELIMITER_STRING - */ - public static long[] toLongArray(String pString) { - return toLongArray(pString, DELIMITER_STRING); - } - - /** - * Converts a comma-separated String to an array of doubles. - * - * @param pString The comma-separated string - * @param pDelimiters The delimiter string - * @return a {@code double} array - * @throws NumberFormatException if any of the elements are not parseable - * as a double - */ - public static double[] toDoubleArray(String pString, String pDelimiters) { - if (isEmpty(pString)) { - return new double[0]; - } - - // Some room for improvement here... - String[] temp = toStringArray(pString, pDelimiters); - double[] array = new double[temp.length]; - - for (int i = 0; i < array.length; i++) { - array[i] = Double.valueOf(temp[i]); - - // Double.parseDouble() is 1.2... - } - return array; - } - - /** - * Converts a comma-separated String to an array of doubles. - * - * @param pString The comma-separated string - * @return a {@code double} array - * @throws NumberFormatException if any of the elements are not parseable - * as a double - * @see #toDoubleArray(String,String) - * @see #DELIMITER_STRING - */ - public static double[] toDoubleArray(String pString) { - return toDoubleArray(pString, DELIMITER_STRING); - } - - /** - * Parses a string to a Color. - * The argument can be a color constant (static constant from - * {@link java.awt.Color java.awt.Color}), like {@code black} or - * {@code red}, or it can be HTML/CSS-style, on the format: - *
    - *
  • {@code #RRGGBB}, where RR, GG and BB means two digit - * hexadecimal for red, green and blue values respectively.
  • - *
  • {@code #AARRGGBB}, as above, with AA as alpha component.
  • - *
  • {@code #RGB}, where R, G and B means one digit - * hexadecimal for red, green and blue values respectively.
  • - *
  • {@code #ARGB}, as above, with A as alpha component.
  • - *
- * - * @param pString the string representation of the color - * @return the {@code Color} object, or {@code null} if the argument - * is {@code null} - * @throws IllegalArgumentException if the string does not map to a color. - * @see java.awt.Color - */ - public static Color toColor(String pString) { - // No string, no color - if (pString == null) { - return null; - } - - // #RRGGBB format - if (pString.charAt(0) == '#') { - int r = 0; - int g = 0; - int b = 0; - int a = -1;// alpha - - if (pString.length() >= 7) { - int idx = 1; - - // AA - if (pString.length() >= 9) { - a = Integer.parseInt(pString.substring(idx, idx + 2), 0x10); - idx += 2; - } - // RR GG BB - r = Integer.parseInt(pString.substring(idx, idx + 2), 0x10); - g = Integer.parseInt(pString.substring(idx + 2, idx + 4), 0x10); - b = Integer.parseInt(pString.substring(idx + 4, idx + 6), 0x10); - - } - else if (pString.length() >= 4) { - int idx = 1; - - // A - if (pString.length() >= 5) { - a = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; - } - // R G B - r = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; - g = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; - b = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; - } - if (a != -1) { - // With alpha - return new Color(r, g, b, a); - } - - // No alpha - return new Color(r, g, b); - } - - // Get color by name - try { - Class colorClass = Color.class; - Field field = null; - - // Workaround for stupidity in Color class constant field names - try { - field = colorClass.getField(pString); - } - catch (Exception e) { - // Don't care, this is just a workaround... - } - if (field == null) { - // NOTE: The toLowerCase() on the next line will lose darkGray - // and lightGray... - field = colorClass.getField(pString.toLowerCase()); - } - - // Only try to get public final fields - int mod = field.getModifiers(); - - if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) { - return (Color) field.get(null); - } - } - catch (NoSuchFieldException nsfe) { - // No such color, throw illegal argument? - throw new IllegalArgumentException("No such color: " + pString); - } - catch (SecurityException se) { - // Can't access field, return null - } - catch (IllegalAccessException iae) { - // Can't happen, as the field must be public static - } - catch (IllegalArgumentException iar) { - // Can't happen, as the field must be static - } - - // This should never be reached, but you never know... ;-) - return null; - } - - /** - * Creates a HTML/CSS String representation of the given color. - * The HTML/CSS color format is defined as: - *
    - *
  • {@code #RRGGBB}, where RR, GG and BB means two digit - * hexadecimal for red, green and blue values respectively.
  • - *
  • {@code #AARRGGBB}, as above, with AA as alpha component.
  • - *
- *

- * Examlples: {@code toColorString(Color.red) == "#ff0000"}, - * {@code toColorString(new Color(0xcc, 0xcc, 0xcc)) == "#cccccc"}. - * - * @param pColor the color - * @return A String representation of the color on HTML/CSS form - * @todo Consider moving to ImageUtil? - */ - public static String toColorString(Color pColor) { - // Not a color... - if (pColor == null) { - return null; - } - - StringBuilder str = new StringBuilder(Integer.toHexString(pColor.getRGB())); - - // Make sure string is 8 chars - for (int i = str.length(); i < 8; i++) { - str.insert(0, '0'); - } - - // All opaque is default - if (str.charAt(0) == 'f' && str.charAt(1) == 'f') { - str.delete(0, 2); - } - - // Prepend hash - return str.insert(0, '#').toString(); - } - - /** - * Tests a string, to see if it is an number (element of Z). - * Valid integers are positive natural numbers (1, 2, 3, ...), - * their negatives (?1, ?2, ?3, ...) and the number zero. - *

- * Note that there is no guarantees made, that this number can be - * represented as either an int or a long. - * - * @param pString The string to check. - * @return true if the String is a natural number. - */ - public static boolean isNumber(String pString) { - if (isEmpty(pString)) { - return false; - } - - // Special case for first char, may be minus sign ('-') - char ch = pString.charAt(0); - if (!(ch == '-' || Character.isDigit(ch))) { - return false; - } - - // Test every char - for (int i = 1; i < pString.length(); i++) { - if (!Character.isDigit(pString.charAt(i))) { - return false; - } - } - - // All digits must be a natural number - return true; - } - - /* - * This version is benchmarked against toStringArray and found to be - * increasingly slower, the more elements the string contains. - * Kept here - */ - - /** - * Removes all occurences of a specific character in a string. - * This method is not design for efficiency! - *

- * - * @param pSource - * @param pSubstring - * @param pPosition - * @return the modified string. - */ - - /* - public static String removeChar(String pSourceString, final char pBadChar) { - - char[] sourceCharArray = pSourceString.toCharArray(); - List modifiedCharList = new Vector(sourceCharArray.length, 1); - - // Filter the string - for (int i = 0; i < sourceCharArray.length; i++) { - if (sourceCharArray[i] != pBadChar) { - modifiedCharList.add(new Character(sourceCharArray[i])); - } - } - - // Clean the character list - modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList); - - // Create new modified String - char[] modifiedCharArray = new char[modifiedCharList.size()]; - for (int i = 0; i < modifiedCharArray.length; i++) { - modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue(); - } - - return new String(modifiedCharArray); - } - */ - - /** - * - * This method is not design for efficiency! - *

- * @param pSourceString The String for modification. - * @param pBadChars The char array containing the characters to remove from the source string. - * @return the modified string. - * @-deprecated Not tested yet! - * - */ - - /* - public static String removeChars(String pSourceString, final char[] pBadChars) { - - char[] sourceCharArray = pSourceString.toCharArray(); - List modifiedCharList = new Vector(sourceCharArray.length, 1); - - Map badCharMap = new Hashtable(); - Character dummyChar = new Character('*'); - for (int i = 0; i < pBadChars.length; i++) { - badCharMap.put(new Character(pBadChars[i]), dummyChar); - } - - // Filter the string - for (int i = 0; i < sourceCharArray.length; i++) { - Character arrayChar = new Character(sourceCharArray[i]); - if (!badCharMap.containsKey(arrayChar)) { - modifiedCharList.add(new Character(sourceCharArray[i])); - } - } - - // Clean the character list - modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList); - - // Create new modified String - char[] modifiedCharArray = new char[modifiedCharList.size()]; - for (int i = 0; i < modifiedCharArray.length; i++) { - modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue(); - } - - return new String(modifiedCharArray); - - } - */ - - /** - * Ensures that a string includes a given substring at a given position. - *

- * Extends the string with a given string if it is not already there. - * E.g an URL "www.vg.no", to "http://www.vg.no". - * - * @param pSource The source string. - * @param pSubstring The substring to include. - * @param pPosition The location of the fill-in, the index starts with 0. - * @return the string, with the substring at the given location. - */ - static String ensureIncludesAt(String pSource, String pSubstring, int pPosition) { - StringBuilder newString = new StringBuilder(pSource); - - try { - String existingSubstring = pSource.substring(pPosition, pPosition + pSubstring.length()); - - if (!existingSubstring.equalsIgnoreCase(pSubstring)) { - newString.insert(pPosition, pSubstring); - } - } - catch (Exception e) { - // Do something!? - } - return newString.toString(); - } - - /** - * Ensures that a string does not include a given substring at a given - * position. - *

- * Removes a given substring from a string if it is there. - * E.g an URL "http://www.vg.no", to "www.vg.no". - * - * @param pSource The source string. - * @param pSubstring The substring to check and possibly remove. - * @param pPosition The location of possible substring removal, the index starts with 0. - * @return the string, without the substring at the given location. - */ - static String ensureExcludesAt(String pSource, String pSubstring, int pPosition) { - StringBuilder newString = new StringBuilder(pSource); - - try { - String existingString = pSource.substring(pPosition + 1, pPosition + pSubstring.length() + 1); - - if (!existingString.equalsIgnoreCase(pSubstring)) { - newString.delete(pPosition, pPosition + pSubstring.length()); - } - } - catch (Exception e) { - // Do something!? - } - return newString.toString(); - } - - /** - * Gets the first substring between the given string boundaries. - *

- * - * @param pSource The source string. - * @param pBeginBoundaryString The string that marks the beginning. - * @param pEndBoundaryString The string that marks the end. - * @param pOffset The index to start searching in the source - * string. If it is less than 0, the index will be set to 0. - * @return the substring demarcated by the given string boundaries or null - * if not both string boundaries are found. - */ - public static String substring(final String pSource, final String pBeginBoundaryString, final String pEndBoundaryString, - final int pOffset) { - // Check offset - int offset = (pOffset < 0) - ? 0 - : pOffset; - - // Find the start index - int startIndex = pSource.indexOf(pBeginBoundaryString, offset) + pBeginBoundaryString.length(); - - if (startIndex < 0) { - return null; - } - - // Find the end index - int endIndex = pSource.indexOf(pEndBoundaryString, startIndex); - - if (endIndex < 0) { - return null; - } - return pSource.substring(startIndex, endIndex); - } - - /** - * Removes the first substring demarcated by the given string boundaries. - *

- * - * @param pSource The source string. - * @param pBeginBoundaryChar The character that marks the beginning of the - * unwanted substring. - * @param pEndBoundaryChar The character that marks the end of the - * unwanted substring. - * @param pOffset The index to start searching in the source - * string. If it is less than 0, the index will be set to 0. - * @return the source string with all the demarcated substrings removed, - * included the demarcation characters. - * @deprecated this method actually removes all demarcated substring.. doesn't it? - */ - - /*public*/ - static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) { - StringBuilder filteredString = new StringBuilder(); - boolean insideDemarcatedArea = false; - char[] charArray = pSource.toCharArray(); - - for (char c : charArray) { - if (!insideDemarcatedArea) { - if (c == pBeginBoundaryChar) { - insideDemarcatedArea = true; - } - else { - filteredString.append(c); - } - } - else { - if (c == pEndBoundaryChar) { - insideDemarcatedArea = false; - } - } - } - return filteredString.toString(); - } - - /** - * Removes all substrings demarcated by the given string boundaries. - *

- * - * @param pSource The source string. - * @param pBeginBoundaryChar The character that marks the beginning of the unwanted substring. - * @param pEndBoundaryChar The character that marks the end of the unwanted substring. - * @return the source string with all the demarcated substrings removed, included the demarcation characters. - */ - /*public*/ - static String removeSubstrings(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar) { - StringBuilder filteredString = new StringBuilder(); - boolean insideDemarcatedArea = false; - char[] charArray = pSource.toCharArray(); - - for (char c : charArray) { - if (!insideDemarcatedArea) { - if (c == pBeginBoundaryChar) { - insideDemarcatedArea = true; - } - else { - filteredString.append(c); - } - } - else { - if (c == pEndBoundaryChar) { - insideDemarcatedArea = false; - } - } - } - return filteredString.toString(); - } - - /** - * Gets the first element of a {@code String} containing string elements delimited by a given delimiter. - * NB - Straightforward implementation! - *

- * - * @param pSource The source string. - * @param pDelimiter The delimiter used in the source string. - * @return The last string element. - * @todo This method should be re-implemented for more efficient execution. - */ - public static String getFirstElement(final String pSource, final String pDelimiter) { - if (pDelimiter == null) { - throw new IllegalArgumentException("delimiter == null"); - } - - if (StringUtil.isEmpty(pSource)) { - return pSource; - } - - int idx = pSource.indexOf(pDelimiter); - if (idx >= 0) { - return pSource.substring(0, idx); - } - return pSource; - } - - /** - * Gets the last element of a {@code String} containing string elements - * delimited by a given delimiter. - * NB - Straightforward implementation! - *

- * - * @param pSource The source string. - * @param pDelimiter The delimiter used in the source string. - * @return The last string element. - */ - public static String getLastElement(final String pSource, final String pDelimiter) { - if (pDelimiter == null) { - throw new IllegalArgumentException("delimiter == null"); - } - - if (StringUtil.isEmpty(pSource)) { - return pSource; - } - int idx = pSource.lastIndexOf(pDelimiter); - if (idx >= 0) { - return pSource.substring(idx + 1); - } - return pSource; - } - - /** - * Converts a string array to a string of comma-separated values. - * - * @param pStringArray the string array - * @return A string of comma-separated values - */ - public static String toCSVString(Object[] pStringArray) { - return toCSVString(pStringArray, ", "); - } - - /** - * Converts a string array to a string separated by the given delimiter. - * - * @param pStringArray the string array - * @param pDelimiterString the delimiter string - * @return string of delimiter separated values - * @throws IllegalArgumentException if {@code pDelimiterString == null} - */ - public static String toCSVString(Object[] pStringArray, String pDelimiterString) { - if (pStringArray == null) { - return ""; - } - if (pDelimiterString == null) { - throw new IllegalArgumentException("delimiter == null"); - } - - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < pStringArray.length; i++) { - if (i > 0) { - buffer.append(pDelimiterString); - } - - buffer.append(pStringArray[i]); - } - - return buffer.toString(); - } - - /** - * @param pObject the object - * @return a deep string representation of the given object - */ - public static String deepToString(Object pObject) { - return deepToString(pObject, false, 1); - } - - /** - * @param pObject the object - * @param pDepth the maximum depth - * @param pForceDeep {@code true} to force deep {@code toString}, even - * if object overrides toString - * @return a deep string representation of the given object - * @todo Array handling (print full type and length) - * @todo Register handlers for specific toDebugString handling? :-) - */ - public static String deepToString(Object pObject, boolean pForceDeep, int pDepth) { - // Null is null - if (pObject == null) { - return null; - } - - // Implements toString, use it as-is unless pForceDeep - if (!pForceDeep && !isIdentityToString(pObject)) { - return pObject.toString(); - } - - StringBuilder buffer = new StringBuilder(); - - if (pObject.getClass().isArray()) { - // Special array handling - Class componentClass = pObject.getClass(); - while (componentClass.isArray()) { - buffer.append('['); - buffer.append(Array.getLength(pObject)); - buffer.append(']'); - componentClass = componentClass.getComponentType(); - } - buffer.insert(0, componentClass); - buffer.append(" {hashCode="); - buffer.append(Integer.toHexString(pObject.hashCode())); - buffer.append("}"); - } - else { - // Append toString value only if overridden - if (isIdentityToString(pObject)) { - buffer.append(" {"); - } - else { - buffer.append(" {toString="); - buffer.append(pObject.toString()); - buffer.append(", "); - } - buffer.append("hashCode="); - buffer.append(Integer.toHexString(pObject.hashCode())); - // Loop through, and filter out any getters - Method[] methods = pObject.getClass().getMethods(); - for (Method method : methods) { - // Filter only public methods - if (Modifier.isPublic(method.getModifiers())) { - String methodName = method.getName(); - - // Find name of property - String name = null; - if (!methodName.equals("getClass") - && methodName.length() > 3 && methodName.startsWith("get") - && Character.isUpperCase(methodName.charAt(3))) { - name = methodName.substring(3); - } - else if (methodName.length() > 2 && methodName.startsWith("is") - && Character.isUpperCase(methodName.charAt(2))) { - name = methodName.substring(2); - } - - if (name != null) { - // If lowercase name, convert, else keep case - if (name.length() > 1 && Character.isLowerCase(name.charAt(1))) { - name = Character.toLowerCase(name.charAt(0)) + name.substring(1); - } - - Class[] paramTypes = method.getParameterTypes();// involves array copying... - boolean hasParams = (paramTypes != null && paramTypes.length > 0); - boolean isVoid = Void.TYPE.equals(method.getReturnType()); - - // Filter return type & parameters - if (!isVoid && !hasParams) { - try { - Object value = method.invoke(pObject); - buffer.append(", "); - buffer.append(name); - buffer.append('='); - if (pDepth != 0 && value != null && isIdentityToString(value)) { - buffer.append(deepToString(value, pForceDeep, pDepth > 0 ? pDepth - 1 : -1)); - } - else { - buffer.append(value); - } - } - catch (Exception e) { - // Next..! - } - } - } - } - } - buffer.append('}'); - - // Get toString from original object - buffer.insert(0, pObject.getClass().getName()); - } - - return buffer.toString(); - } - - /** - * Tests if the {@code toString} method of the given object is inherited - * from {@code Object}. - * - * @param pObject the object - * @return {@code true} if toString of class Object - */ - private static boolean isIdentityToString(Object pObject) { - try { - Method toString = pObject.getClass().getMethod("toString"); - if (toString.getDeclaringClass() == Object.class) { - return true; - } - } - catch (Exception ignore) { - // Ignore - } - - return false; - } - - /** - * Returns a string on the same format as {@code Object.toString()}. - * - * @param pObject the object - * @return the object as a {@code String} on the format of - * {@code Object.toString()} - */ - public static String identityToString(Object pObject) { - if (pObject == null) { - return null; - } - else { - return pObject.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(pObject)); - } - } - - /** - * Tells whether or not the given string string matches the given regular - * expression. - *

- * An invocation of this method of the form - * matches(str, regex) yields exactly the - * same result as the expression - *

- *

{@link Pattern}. - * {@link Pattern#matches(String, CharSequence) matches} - * (regex, str)
- * - * @param pString the string - * @param pRegex the regular expression to which this string is to be matched - * @return {@code true} if, and only if, this string matches the - * given regular expression - * @throws PatternSyntaxException if the regular expression's syntax is invalid - * @see Pattern - * @see String#matches(String) - */ - public boolean matches(String pString, String pRegex) throws PatternSyntaxException { - return Pattern.matches(pRegex, pString); - } - - /** - * Replaces the first substring of the given string that matches the given - * regular expression with the given pReplacement. - *

- * An invocation of this method of the form - * replaceFirst(str, regex, repl) - * yields exactly the same result as the expression - *

- *

- * {@link Pattern}.{@link Pattern#compile compile}(regex). - * {@link Pattern#matcher matcher}(str). - * {@link java.util.regex.Matcher#replaceFirst replaceFirst}(repl)
- * - * @param pString the string - * @param pRegex the regular expression to which this string is to be matched - * @param pReplacement the replacement text - * @return The resulting {@code String} - * @throws PatternSyntaxException if the regular expression's syntax is invalid - * @see Pattern - * @see java.util.regex.Matcher#replaceFirst(String) - */ - public String replaceFirst(String pString, String pRegex, String pReplacement) { - return Pattern.compile(pRegex).matcher(pString).replaceFirst(pReplacement); - } - - /** - * Replaces each substring of this string that matches the given - * regular expression with the given pReplacement. - *

- * An invocation of this method of the form - * replaceAll(str, pRegex, repl<) - * yields exactly the same result as the expression - *

- *

- * {@link Pattern}.{@link Pattern#compile compile}(pRegex). - * {@link Pattern#matcher matcher}(str{@code ). - * {@link java.util.regex.Matcher#replaceAll replaceAll}(}repl{@code )}
- * - * @param pString the string - * @param pRegex the regular expression to which this string is to be matched - * @param pReplacement the replacement string - * @return The resulting {@code String} - * @throws PatternSyntaxException if the regular expression's syntax is invalid - * @see Pattern - * @see String#replaceAll(String,String) - */ - public String replaceAll(String pString, String pRegex, String pReplacement) { - return Pattern.compile(pRegex).matcher(pString).replaceAll(pReplacement); - } - - /** - * Splits this string around matches of the given regular expression. - *

- * The array returned by this method contains each substring of this - * string that is terminated by another substring that matches the given - * expression or is terminated by the end of the string. The substrings in - * the array are in the order in which they occur in this string. If the - * expression does not match any part of the input then the resulting array - * has just one element, namely this string. - *

- * The {@code pLimit} parameter controls the number of times the - * pattern is applied and therefore affects the length of the resulting - * array. If the pLimit n is greater than zero then the pattern - * will be applied at most n - 1 times, the array's - * length will be no greater than n, and the array's last entry - * will contain all input beyond the last matched delimiter. If n - * is non-positive then the pattern will be applied as many times as - * possible and the array can have any length. If n is zero then - * the pattern will be applied as many times as possible, the array can - * have any length, and trailing empty strings will be discarded. - *

- * An invocation of this method of the form - * split(str, regex, n) - * yields the same result as the expression - *

- *

{@link Pattern}. - * {@link Pattern#compile compile}(regex). - * {@link Pattern#split(CharSequence,int) split}(str, n) - *
- * - * @param pString the string - * @param pRegex the delimiting regular expression - * @param pLimit the result threshold, as described above - * @return the array of strings computed by splitting this string - * around matches of the given regular expression - * @throws PatternSyntaxException - * if the regular expression's syntax is invalid - * @see Pattern - * @see String#split(String,int) - */ - public String[] split(String pString, String pRegex, int pLimit) { - return Pattern.compile(pRegex).split(pString, pLimit); - } - - /** - * Splits this string around matches of the given regular expression. - *

- * This method works as if by invoking the two-argument - * {@link #split(String,String,int) split} method with the given - * expression and a limit argument of zero. - * Trailing empty strings are therefore not included in the resulting array. - * - * @param pString the string - * @param pRegex the delimiting regular expression - * @return the array of strings computed by splitting this string - * around matches of the given regular expression - * @throws PatternSyntaxException if the regular expression's syntax is invalid - * @see Pattern - * @see String#split(String) - */ - public String[] split(String pString, String pRegex) { - return split(pString, pRegex, 0); - } - - /** - * Converts the input string - * from camel-style (Java in-fix) naming convention - * to Lisp-style naming convention (hyphen delimitted, all lower case). - * Other characters in the string are left untouched. - *

- * Eg. - * {@code "foo" => "foo"}, - * {@code "fooBar" => "foo-bar"}, - * {@code "myURL" => "my-url"}, - * {@code "HttpRequestWrapper" => "http-request-wrapper"} - * {@code "HttpURLConnection" => "http-url-connection"} - * {@code "my45Caliber" => "my-45-caliber"} - * {@code "allready-lisp" => "allready-lisp"} - * - * @param pString the camel-style input string - * @return the string converted to lisp-style naming convention - * @throws IllegalArgumentException if {@code pString == null} - * @see #lispToCamel(String) - */ - // TODO: RefactorMe! - public static String camelToLisp(final String pString) { - if (pString == null) { - throw new IllegalArgumentException("string == null"); - } - if (pString.length() == 0) { - return pString; - } - - StringBuilder buf = null; - int lastPos = 0; - boolean inCharSequence = false; - boolean inNumberSequence = false; - - // NOTE: Start at index 1, as first letter should never be hyphen - for (int i = 1; i < pString.length(); i++) { - char current = pString.charAt(i); - if (Character.isUpperCase(current)) { - // Init buffer if necessary - if (buf == null) { - buf = new StringBuilder(pString.length() + 3);// Allow for some growth - } - - if (inNumberSequence) { - // Sequence end - inNumberSequence = false; - - buf.append(pString.substring(lastPos, i)); - if (current != '-') { - buf.append('-'); - } - lastPos = i; - continue; - } - - // Treat multiple uppercase chars as single word - char previous = pString.charAt(i - 1); - if (i == lastPos || Character.isUpperCase(previous)) { - inCharSequence = true; - continue; - } - - // Append word - buf.append(pString.substring(lastPos, i).toLowerCase()); - if (previous != '-') { - buf.append('-'); - } - buf.append(Character.toLowerCase(current)); - - lastPos = i + 1; - } - else if (Character.isDigit(current)) { - // Init buffer if necessary - if (buf == null) { - buf = new StringBuilder(pString.length() + 3);// Allow for some growth - } - - if (inCharSequence) { - // Sequence end - inCharSequence = false; - - buf.append(pString.substring(lastPos, i).toLowerCase()); - if (current != '-') { - buf.append('-'); - } - lastPos = i; - continue; - } - - // Treat multiple digits as single word - char previous = pString.charAt(i - 1); - if (i == lastPos || Character.isDigit(previous)) { - inNumberSequence = true; - continue; - } - - // Append word - buf.append(pString.substring(lastPos, i).toLowerCase()); - if (previous != '-') { - buf.append('-'); - } - buf.append(Character.toLowerCase(current)); - - lastPos = i + 1; - } - else if (inNumberSequence) { - // Sequence end - inNumberSequence = false; - - buf.append(pString.substring(lastPos, i)); - if (current != '-') { - buf.append('-'); - } - lastPos = i; - } - else if (inCharSequence) { - // Sequence end - inCharSequence = false; - - // NOTE: Special treatment! Last upper case, is first char in - // next word, not last char in this word - buf.append(pString.substring(lastPos, i - 1).toLowerCase()); - if (current != '-') { - buf.append('-'); - } - lastPos = i - 1; - } - } - - if (buf != null) { - // Append the rest - buf.append(pString.substring(lastPos).toLowerCase()); - return buf.toString(); - } - else { - return Character.isUpperCase(pString.charAt(0)) ? pString.toLowerCase() : pString; - } - } - - /** - * Converts the input string - * from Lisp-style naming convention (hyphen delimitted, all lower case) - * to camel-style (Java in-fix) naming convention. - * Other characters in the string are left untouched. - *

- * Eg. - * {@code "foo" => "foo"}, - * {@code "foo-bar" => "fooBar"}, - * {@code "http-request-wrapper" => "httpRequestWrapper"} - * {@code "my-45-caliber" => "my45Caliber"} - * {@code "allreadyCamel" => "allreadyCamel"} - *

- * - * @param pString the lisp-style input string - * @return the string converted to camel-style - * @throws IllegalArgumentException if {@code pString == null} - * @see #lispToCamel(String,boolean) - * @see #camelToLisp(String) - */ - public static String lispToCamel(final String pString) { - return lispToCamel(pString, false); - } - - /** - * Converts the input string - * from Lisp-style naming convention (hyphen delimitted, all lower case) - * to camel-style (Java in-fix) naming convention. - * Other characters in the string are left untouched. - *

- * To create a string starting with a lower case letter - * (like Java variable names, etc), - * specify the {@code pFirstUpperCase} paramter to be {@code false}. - * Eg. - * {@code "foo" => "foo"}, - * {@code "foo-bar" => "fooBar"}, - * {@code "allreadyCamel" => "allreadyCamel"} - *

- * To create a string starting with an upper case letter - * (like Java class name, etc), - * specify the {@code pFirstUpperCase} paramter to be {@code true}. - * Eg. - * {@code "http-request-wrapper" => "HttpRequestWrapper"} - * {@code "my-45-caliber" => "My45Caliber"} - *

- * - * @param pString the lisp-style input string - * @param pFirstUpperCase {@code true} if the first char should be - * upper case - * @return the string converted to camel-style - * @throws IllegalArgumentException if {@code pString == null} - * @see #camelToLisp(String) - */ - public static String lispToCamel(final String pString, final boolean pFirstUpperCase) { - if (pString == null) { - throw new IllegalArgumentException("string == null"); - } - if (pString.length() == 0) { - return pString; - } - - StringBuilder buf = null; - int lastPos = 0; - - for (int i = 0; i < pString.length(); i++) { - char current = pString.charAt(i); - if (current == '-') { - - // Init buffer if necessary - if (buf == null) { - buf = new StringBuilder(pString.length() - 1);// Can't be larger - } - - // Append with upper case - if (lastPos != 0 || pFirstUpperCase) { - buf.append(Character.toUpperCase(pString.charAt(lastPos))); - lastPos++; - } - - buf.append(pString.substring(lastPos, i).toLowerCase()); - lastPos = i + 1; - } - } - - if (buf != null) { - buf.append(Character.toUpperCase(pString.charAt(lastPos))); - buf.append(pString.substring(lastPos + 1).toLowerCase()); - return buf.toString(); - } - else { - if (pFirstUpperCase && !Character.isUpperCase(pString.charAt(0))) { - return capitalize(pString, 0); - } - else - if (!pFirstUpperCase && Character.isUpperCase(pString.charAt(0))) { - return Character.toLowerCase(pString.charAt(0)) + pString.substring(1); - } - - return pString; - } - } - - public static String reverse(final String pString) { - final char[] chars = new char[pString.length()]; - pString.getChars(0, chars.length, chars, 0); - - for (int i = 0; i < chars.length / 2; i++) { - char temp = chars[i]; - chars[i] = chars[chars.length - 1 - i]; - chars[chars.length - 1 - i] = temp; - } - - return new String(chars); - } +/* + * 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.lang; + +import com.twelvemonkeys.util.StringTokenIterator; + +import java.awt.*; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.UnsupportedCharsetException; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * A utility class with some useful string manipulation methods. + * + * @author Harald Kuhr + * @author Eirik Torske + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java#2 $ + * return values, null-value handling and parameter names (cosmetics). + */ +// TODO: Consistency check: Method names, parameter sequence, Exceptions, +public final class StringUtil { + + /** + * The default delimiter string, used by the {@code toXXXArray()} + * methods. + * Its value is {@code ", \t\n\r\f"}. + * + * + * @see #toStringArray(String) + * @see #toIntArray(String) + * @see #toLongArray(String) + * @see #toDoubleArray(String) + */ + public final static String DELIMITER_STRING = ", \t\n\r\f"; + + // Avoid constructor showing up in API doc + private StringUtil() { + } + + /** + * Constructs a new {@link String} by decoding the specified sub array of bytes using the specified charset. + * Replacement for {@link String#String(byte[], int, int, String) new String(byte[], int, int, String)}, that does + * not throw the checked {@link UnsupportedEncodingException}, + * but instead the unchecked {@link UnsupportedCharsetException} if the character set is not supported. + * + * @param pData the bytes to be decoded to characters + * @param pOffset the index of the first byte to decode + * @param pLength the number of bytes to decode + * @param pCharset the name of a supported character set + * @return a newly created string. + * @throws UnsupportedCharsetException + * + * @see String#String(byte[], int, int, String) + */ + public static String decode(final byte[] pData, final int pOffset, final int pLength, final String pCharset) { + try { + return new String(pData, pOffset, pLength, pCharset); + } + catch (UnsupportedEncodingException e) { + throw new UnsupportedCharsetException(pCharset); + } + } + + /** + * Returns the value of the given {@code Object}, as a {@code String}. + * Unlike String.valueOf, this method returns {@code null} + * instead of the {@code String} "null", if {@code null} is given as + * the argument. + * + * @param pObj the Object to find the {@code String} value of. + * @return the String value of the given object, or {@code null} if the + * {@code pObj} == {@code null}. + * @see String#valueOf(Object) + * @see String#toString() + */ + public static String valueOf(Object pObj) { + return ((pObj != null) ? pObj.toString() : null); + } + + /** + * Converts a string to uppercase. + * + * @param pString the string to convert + * @return the string converted to uppercase, or null if the argument was + * null. + */ + public static String toUpperCase(String pString) { + if (pString != null) { + return pString.toUpperCase(); + } + return null; + } + + /** + * Converts a string to lowercase. + * + * @param pString the string to convert + * @return the string converted to lowercase, or null if the argument was + * null. + */ + public static String toLowerCase(String pString) { + if (pString != null) { + return pString.toLowerCase(); + } + return null; + } + + /** + * Tests if a String is null, or contains nothing but white-space. + * + * @param pString The string to test + * @return true if the string is null or contains only whitespace, + * otherwise false. + */ + public static boolean isEmpty(String pString) { + return ((pString == null) || (pString.trim().length() == 0)); + } + + /** + * Tests a string array, to see if all items are null or an empty string. + * + * @param pStringArray The string array to check. + * @return true if the string array is null or only contains string items + * that are null or contain only whitespace, otherwise false. + */ + public static boolean isEmpty(String[] pStringArray) { + // No elements to test + if (pStringArray == null) { + return true; + } + + // Test all the elements + for (String string : pStringArray) { + if (!isEmpty(string)) { + return false; + } + } + + // All elements are empty + return true; + } + + /** + * Tests if a string contains another string. + * + * @param pContainer The string to test + * @param pLookFor The string to look for + * @return {@code true} if the container string is contains the string, and + * both parameters are non-{@code null}, otherwise {@code false}. + */ + public static boolean contains(String pContainer, String pLookFor) { + return ((pContainer != null) && (pLookFor != null) && (pContainer.indexOf(pLookFor) >= 0)); + } + + /** + * Tests if a string contains another string, ignoring case. + * + * @param pContainer The string to test + * @param pLookFor The string to look for + * @return {@code true} if the container string is contains the string, and + * both parameters are non-{@code null}, otherwise {@code false}. + * @see #contains(String,String) + */ + public static boolean containsIgnoreCase(String pContainer, String pLookFor) { + return indexOfIgnoreCase(pContainer, pLookFor, 0) >= 0; + } + + /** + * Tests if a string contains a specific character. + * + * @param pString The string to check. + * @param pChar The character to search for. + * @return true if the string contains the specific character. + */ + public static boolean contains(final String pString, final int pChar) { + return ((pString != null) && (pString.indexOf(pChar) >= 0)); + } + + /** + * Tests if a string contains a specific character, ignoring case. + * + * @param pString The string to check. + * @param pChar The character to search for. + * @return true if the string contains the specific character. + */ + public static boolean containsIgnoreCase(String pString, int pChar) { + return ((pString != null) + && ((pString.indexOf(Character.toLowerCase((char) pChar)) >= 0) + || (pString.indexOf(Character.toUpperCase((char) pChar)) >= 0))); + + // NOTE: I don't convert the string to uppercase, but instead test + // the string (potentially) two times, as this is more efficient for + // long strings (in most cases). + } + + /** + * Returns the index within this string of the first occurrence of the + * specified substring. + * + * @param pString The string to test + * @param pLookFor The string to look for + * @return if the string argument occurs as a substring within this object, + * then the index of the first character of the first such substring is + * returned; if it does not occur as a substring, -1 is returned. + * @see String#indexOf(String) + */ + public static int indexOfIgnoreCase(String pString, String pLookFor) { + return indexOfIgnoreCase(pString, pLookFor, 0); + } + + /** + * Returns the index within this string of the first occurrence of the + * specified substring, starting at the specified index. + * + * @param pString The string to test + * @param pLookFor The string to look for + * @param pPos The first index to test + * @return if the string argument occurs as a substring within this object, + * then the index of the first character of the first such substring is + * returned; if it does not occur as a substring, -1 is returned. + * @see String#indexOf(String,int) + */ + public static int indexOfIgnoreCase(String pString, String pLookFor, int pPos) { + if ((pString == null) || (pLookFor == null)) { + return -1; + } + if (pLookFor.length() == 0) { + return pPos;// All strings "contains" the empty string + } + if (pLookFor.length() > pString.length()) { + return -1;// Cannot contain string longer than itself + } + + // Get first char + char firstL = Character.toLowerCase(pLookFor.charAt(0)); + char firstU = Character.toUpperCase(pLookFor.charAt(0)); + int indexLower = 0; + int indexUpper = 0; + + for (int i = pPos; i <= (pString.length() - pLookFor.length()); i++) { + + // Peek for first char + indexLower = ((indexLower >= 0) && (indexLower <= i)) + ? pString.indexOf(firstL, i) + : indexLower; + indexUpper = ((indexUpper >= 0) && (indexUpper <= i)) + ? pString.indexOf(firstU, i) + : indexUpper; + if (indexLower < 0) { + if (indexUpper < 0) { + return -1;// First char not found + } + else { + i = indexUpper;// Only upper + } + } + else if (indexUpper < 0) { + i = indexLower;// Only lower + } + else { + + // Both found, select first occurence + i = (indexLower < indexUpper) + ? indexLower + : indexUpper; + } + + // Only one? + if (pLookFor.length() == 1) { + return i;// The only char found! + } + + // Test if we still have enough chars + else if (i > (pString.length() - pLookFor.length())) { + return -1; + } + + // Test if last char equals! (regionMatches is expensive) + else if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1))) + && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) { + continue;// Nope, try next + } + + // Test from second char, until second-last char + else if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) { + return i; + } + } + return -1; + } + + /** + * Returns the index within this string of the rightmost occurrence of the + * specified substring. The rightmost empty string "" is considered to + * occur at the index value {@code pString.length() - 1}. + * + * @param pString The string to test + * @param pLookFor The string to look for + * @return If the string argument occurs one or more times as a substring + * within this object at a starting index no greater than fromIndex, then + * the index of the first character of the last such substring is returned. + * If it does not occur as a substring starting at fromIndex or earlier, -1 + * is returned. + * @see String#lastIndexOf(String) + */ + public static int lastIndexOfIgnoreCase(String pString, String pLookFor) { + return lastIndexOfIgnoreCase(pString, pLookFor, pString != null ? pString.length() - 1 : -1); + } + + /** + * Returns the index within this string of the rightmost occurrence of the + * specified substring. The rightmost empty string "" is considered to + * occur at the index value {@code pPos} + * + * @param pString The string to test + * @param pLookFor The string to look for + * @param pPos The last index to test + * @return If the string argument occurs one or more times as a substring + * within this object at a starting index no greater than fromIndex, then + * the index of the first character of the last such substring is returned. + * If it does not occur as a substring starting at fromIndex or earlier, -1 + * is returned. + * @see String#lastIndexOf(String,int) + */ + public static int lastIndexOfIgnoreCase(String pString, String pLookFor, int pPos) { + if ((pString == null) || (pLookFor == null)) { + return -1; + } + if (pLookFor.length() == 0) { + return pPos;// All strings "contains" the empty string + } + if (pLookFor.length() > pString.length()) { + return -1;// Cannot contain string longer than itself + } + + // Get first char + char firstL = Character.toLowerCase(pLookFor.charAt(0)); + char firstU = Character.toUpperCase(pLookFor.charAt(0)); + int indexLower = pPos; + int indexUpper = pPos; + + for (int i = pPos; i >= 0; i--) { + + // Peek for first char + indexLower = ((indexLower >= 0) && (indexLower >= i)) + ? pString.lastIndexOf(firstL, i) + : indexLower; + indexUpper = ((indexUpper >= 0) && (indexUpper >= i)) + ? pString.lastIndexOf(firstU, i) + : indexUpper; + if (indexLower < 0) { + if (indexUpper < 0) { + return -1;// First char not found + } + else { + i = indexUpper;// Only upper + } + } + else if (indexUpper < 0) { + i = indexLower;// Only lower + } + else { + + // Both found, select last occurence + i = (indexLower > indexUpper) + ? indexLower + : indexUpper; + } + + // Only one? + if (pLookFor.length() == 1) { + return i;// The only char found! + } + + // Test if we still have enough chars + else if (i > (pString.length() - pLookFor.length())) { + //return -1; + continue; + } + + // Test if last char equals! (regionMatches is expensive) + else + if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1))) + && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) { + continue;// Nope, try next + } + + // Test from second char, until second-last char + else + if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) { + return i; + } + } + return -1; + } + + /** + * Returns the index within this string of the first occurrence of the + * specified character. + * + * @param pString The string to test + * @param pChar The character to look for + * @return if the string argument occurs as a substring within this object, + * then the index of the first character of the first such substring is + * returned; if it does not occur as a substring, -1 is returned. + * @see String#indexOf(int) + */ + public static int indexOfIgnoreCase(String pString, int pChar) { + return indexOfIgnoreCase(pString, pChar, 0); + } + + /** + * Returns the index within this string of the first occurrence of the + * specified character, starting at the specified index. + * + * @param pString The string to test + * @param pChar The character to look for + * @param pPos The first index to test + * @return if the string argument occurs as a substring within this object, + * then the index of the first character of the first such substring is + * returned; if it does not occur as a substring, -1 is returned. + * @see String#indexOf(int,int) + */ + public static int indexOfIgnoreCase(String pString, int pChar, int pPos) { + if ((pString == null)) { + return -1; + } + + // Get first char + char lower = Character.toLowerCase((char) pChar); + char upper = Character.toUpperCase((char) pChar); + int indexLower; + int indexUpper; + + // Test for char + indexLower = pString.indexOf(lower, pPos); + indexUpper = pString.indexOf(upper, pPos); + if (indexLower < 0) { + + /* if (indexUpper < 0) + return -1; // First char not found + else */ + return indexUpper;// Only upper + } + else if (indexUpper < 0) { + return indexLower;// Only lower + } + else { + + // Both found, select first occurence + return (indexLower < indexUpper) + ? indexLower + : indexUpper; + } + } + + /** + * Returns the index within this string of the last occurrence of the + * specified character. + * + * @param pString The string to test + * @param pChar The character to look for + * @return if the string argument occurs as a substring within this object, + * then the index of the first character of the first such substring is + * returned; if it does not occur as a substring, -1 is returned. + * @see String#lastIndexOf(int) + */ + public static int lastIndexOfIgnoreCase(String pString, int pChar) { + return lastIndexOfIgnoreCase(pString, pChar, pString != null ? pString.length() : -1); + } + + /** + * Returns the index within this string of the last occurrence of the + * specified character, searching backward starting at the specified index. + * + * @param pString The string to test + * @param pChar The character to look for + * @param pPos The last index to test + * @return if the string argument occurs as a substring within this object, + * then the index of the first character of the first such substring is + * returned; if it does not occur as a substring, -1 is returned. + * @see String#lastIndexOf(int,int) + */ + public static int lastIndexOfIgnoreCase(String pString, int pChar, int pPos) { + if ((pString == null)) { + return -1; + } + + // Get first char + char lower = Character.toLowerCase((char) pChar); + char upper = Character.toUpperCase((char) pChar); + int indexLower; + int indexUpper; + + // Test for char + indexLower = pString.lastIndexOf(lower, pPos); + indexUpper = pString.lastIndexOf(upper, pPos); + if (indexLower < 0) { + + /* if (indexUpper < 0) + return -1; // First char not found + else */ + return indexUpper;// Only upper + } + else if (indexUpper < 0) { + return indexLower;// Only lower + } + else { + + // Both found, select last occurence + return (indexLower > indexUpper) + ? indexLower + : indexUpper; + } + } + + /** + * Trims the argument string for whitespace on the left side only. + * + * @param pString the string to trim + * @return the string with no whitespace on the left, or {@code null} if + * the string argument is {@code null}. + * @see #rtrim + * @see String#trim() + */ + public static String ltrim(String pString) { + if ((pString == null) || (pString.length() == 0)) { + return pString;// Null or empty string + } + for (int i = 0; i < pString.length(); i++) { + if (!Character.isWhitespace(pString.charAt(i))) { + if (i == 0) { + return pString;// First char is not whitespace + } + else { + return pString.substring(i);// Return rest after whitespace + } + } + } + + // If all whitespace, return empty string + return ""; + } + + /** + * Trims the argument string for whitespace on the right side only. + * + * @param pString the string to trim + * @return the string with no whitespace on the right, or {@code null} if + * the string argument is {@code null}. + * @see #ltrim + * @see String#trim() + */ + public static String rtrim(String pString) { + if ((pString == null) || (pString.length() == 0)) { + return pString;// Null or empty string + } + for (int i = pString.length(); i > 0; i--) { + if (!Character.isWhitespace(pString.charAt(i - 1))) { + if (i == pString.length()) { + return pString;// First char is not whitespace + } + else { + return pString.substring(0, i);// Return before whitespace + } + } + } + + // If all whitespace, return empty string + return ""; + } + + /** + * Replaces a substring of a string with another string. All matches are + * replaced. + * + * @param pSource The source String + * @param pPattern The pattern to replace + * @param pReplace The new String to be inserted instead of the + * replace String + * @return The new String with the pattern replaced + */ + public static String replace(String pSource, String pPattern, String pReplace) { + if (pPattern.length() == 0) { + return pSource;// Special case: No pattern to replace + } + + int match; + int offset = 0; + StringBuilder result = new StringBuilder(); + + // Loop string, until last occurence of pattern, and replace + while ((match = pSource.indexOf(pPattern, offset)) != -1) { + // Append everything until pattern + result.append(pSource.substring(offset, match)); + // Append the replace string + result.append(pReplace); + offset = match + pPattern.length(); + } + + // Append rest of string and return + result.append(pSource.substring(offset)); + + return result.toString(); + } + + /** + * Replaces a substring of a string with another string, ignoring case. + * All matches are replaced. + * + * @param pSource The source String + * @param pPattern The pattern to replace + * @param pReplace The new String to be inserted instead of the + * replace String + * @return The new String with the pattern replaced + * @see #replace(String,String,String) + */ + public static String replaceIgnoreCase(String pSource, String pPattern, String pReplace) { + if (pPattern.length() == 0) { + return pSource;// Special case: No pattern to replace + } + int match; + int offset = 0; + StringBuilder result = new StringBuilder(); + + while ((match = indexOfIgnoreCase(pSource, pPattern, offset)) != -1) { + result.append(pSource.substring(offset, match)); + result.append(pReplace); + offset = match + pPattern.length(); + } + result.append(pSource.substring(offset)); + return result.toString(); + } + + /** + * Cuts a string between two words, before a sepcified length, if the + * string is longer than the maxium lenght. The string is optionally padded + * with the pad argument. The method assumes words to be separated by the + * space character (" "). + * Note that the maximum length argument is absolute, and will also include + * the length of the padding. + * + * @param pString The string to cut + * @param pMaxLen The maximum length before cutting + * @param pPad The string to append at the end, aftrer cutting + * @return The cutted string with padding, or the original string, if it + * was shorter than the max length. + * @see #pad(String,int,String,boolean) + */ + public static String cut(String pString, int pMaxLen, String pPad) { + if (pString == null) { + return null; + } + if (pPad == null) { + pPad = ""; + } + int len = pString.length(); + + if (len > pMaxLen) { + len = pString.lastIndexOf(' ', pMaxLen - pPad.length()); + } + else { + return pString; + } + return pString.substring(0, len) + pPad; + } + + /** + * Makes the Nth letter of a String uppercase. If the index is outside the + * the length of the argument string, the argument is simply returned. + * + * @param pString The string to capitalize + * @param pIndex The base-0 index of the char to capitalize. + * @return The capitalized string, or null, if a null argument was given. + */ + public static String capitalize(String pString, int pIndex) { + if (pIndex < 0) { + throw new IndexOutOfBoundsException("Negative index not allowed: " + pIndex); + } + if (pString == null || pString.length() <= pIndex) { + return pString; + } + + // This is the fastest method, according to my tests + + // Skip array duplication if allready capitalized + if (Character.isUpperCase(pString.charAt(pIndex))) { + return pString; + } + + // Convert to char array, capitalize and create new String + char[] charArray = pString.toCharArray(); + charArray[pIndex] = Character.toUpperCase(charArray[pIndex]); + return new String(charArray); + + /** + StringBuilder buf = new StringBuilder(pString); + buf.setCharAt(pIndex, Character.toUpperCase(buf.charAt(pIndex))); + return buf.toString(); + //*/ + + /** + return pString.substring(0, pIndex) + + Character.toUpperCase(pString.charAt(pIndex)) + + pString.substring(pIndex + 1); + //*/ + } + + /** + * Makes the first letter of a String uppercase. + * + * @param pString The string to capitalize + * @return The capitalized string, or null, if a null argument was given. + */ + public static String capitalize(String pString) { + return capitalize(pString, 0); + } + + /** + * Formats a number with leading zeroes, to a specified length. + * + * @param pNum The number to format + * @param pLen The number of digits + * @return A string containing the formatted number + * @throws IllegalArgumentException Thrown, if the number contains + * more digits than allowed by the length argument. + * @see #pad(String,int,String,boolean) + * @deprecated Use StringUtil.pad instead! + */ + + /*public*/ + static String formatNumber(long pNum, int pLen) throws IllegalArgumentException { + StringBuilder result = new StringBuilder(); + + if (pNum >= Math.pow(10, pLen)) { + throw new IllegalArgumentException("The number to format cannot contain more digits than the length argument specifies!"); + } + for (int i = pLen; i > 1; i--) { + if (pNum < Math.pow(10, i - 1)) { + result.append('0'); + } + else { + break; + } + } + result.append(pNum); + return result.toString(); + } + + /** + * String length check with simple concatenation of selected pad-string. + * E.g. a zip number from 123 to the correct 0123. + * + * @param pSource The source string. + * @param pRequiredLength The accurate length of the resulting string. + * @param pPadString The string for concatenation. + * @param pPrepend The location of fill-ins, prepend (true), + * or append (false) + * @return a concatenated string. + * @see #cut(String,int,String) + */ + // TODO: What if source is allready longer than required length? + // TODO: Consistency with cut + public static String pad(String pSource, int pRequiredLength, String pPadString, boolean pPrepend) { + if (pPadString == null || pPadString.length() == 0) { + throw new IllegalArgumentException("Pad string: \"" + pPadString + "\""); + } + + if (pSource.length() >= pRequiredLength) { + return pSource; + } + + // TODO: Benchmark the new version against the old one, to see if it's really faster + // Rewrite to first create pad + // - pad += pad; - until length is >= gap + // then append the pad and cut if too long + int gap = pRequiredLength - pSource.length(); + StringBuilder result = new StringBuilder(pPadString); + while (result.length() < gap) { + result.append(result); + } + + if (result.length() > gap) { + result.delete(gap, result.length()); + } + + return pPrepend ? result.append(pSource).toString() : result.insert(0, pSource).toString(); + + /* + StringBuilder result = new StringBuilder(pSource); + + // Concatenation until proper string length + while (result.length() < pRequiredLength) { + // Prepend or append + if (pPrepend) { // Front + result.insert(0, pPadString); + } + else { // Back + result.append(pPadString); + } + } + + // Truncate + if (result.length() > pRequiredLength) { + if (pPrepend) { + result.delete(0, result.length() - pRequiredLength); + } + else { + result.delete(pRequiredLength, result.length()); + } + } + return result.toString(); + */ + } + + /** + * Converts the string to a date, using the default date format. + * + * @param pString the string to convert + * @return the date + * @see DateFormat + * @see DateFormat#getInstance() + */ + public static Date toDate(String pString) { + // Default + return toDate(pString, DateFormat.getInstance()); + } + + /** + * Converts the string to a date, using the given format. + * + * @param pString the string to convert + * @param pFormat the date format + * @return the date + * + * @see java.text.SimpleDateFormat + * @see java.text.SimpleDateFormat#SimpleDateFormat(String) + */ + // TODO: cache formats? + public static Date toDate(String pString, String pFormat) { + // Get the format from cache, or create new and insert + // Return new date + return toDate(pString, new SimpleDateFormat(pFormat)); + } + + /** + * Converts the string to a date, using the given format. + * + * @param pString the string to convert + * @param pFormat the date format + * @return the date + * @see SimpleDateFormat + * @see SimpleDateFormat#SimpleDateFormat(String) + * @see DateFormat + */ + public static Date toDate(final String pString, final DateFormat pFormat) { + try { + synchronized (pFormat) { + // Parse date using given format + return pFormat.parse(pString); + } + } + catch (ParseException pe) { + // Wrap in RuntimeException + throw new IllegalArgumentException(pe.getMessage()); + } + } + + /** + * Converts the string to a jdbc Timestamp, using the standard Timestamp + * escape format. + * + * @param pValue the value + * @return a new {@code Timestamp} + * @see java.sql.Timestamp + * @see java.sql.Timestamp#valueOf(String) + */ + public static Timestamp toTimestamp(final String pValue) { + // Parse date using default format + return Timestamp.valueOf(pValue); + } + + /** + * Converts a delimiter separated String to an array of Strings. + * + * @param pString The comma-separated string + * @param pDelimiters The delimiter string + * @return a {@code String} array containing the delimiter separated elements + */ + public static String[] toStringArray(String pString, String pDelimiters) { + if (isEmpty(pString)) { + return new String[0]; + } + + StringTokenIterator st = new StringTokenIterator(pString, pDelimiters); + List v = new ArrayList(); + + while (st.hasMoreElements()) { + v.add(st.nextToken()); + } + + return v.toArray(new String[v.size()]); + } + + /** + * Converts a comma-separated String to an array of Strings. + * + * @param pString The comma-separated string + * @return a {@code String} array containing the comma-separated elements + * @see #toStringArray(String,String) + */ + public static String[] toStringArray(String pString) { + return toStringArray(pString, DELIMITER_STRING); + } + + /** + * Converts a comma-separated String to an array of ints. + * + * @param pString The comma-separated string + * @param pDelimiters The delimiter string + * @param pBase The radix + * @return an {@code int} array + * @throws NumberFormatException if any of the elements are not parseable + * as an int + */ + public static int[] toIntArray(String pString, String pDelimiters, int pBase) { + if (isEmpty(pString)) { + return new int[0]; + } + + // Some room for improvement here... + String[] temp = toStringArray(pString, pDelimiters); + int[] array = new int[temp.length]; + + for (int i = 0; i < array.length; i++) { + array[i] = Integer.parseInt(temp[i], pBase); + } + return array; + } + + /** + * Converts a comma-separated String to an array of ints. + * + * @param pString The comma-separated string + * @return an {@code int} array + * @throws NumberFormatException if any of the elements are not parseable + * as an int + * @see #toStringArray(String,String) + * @see #DELIMITER_STRING + */ + public static int[] toIntArray(String pString) { + return toIntArray(pString, DELIMITER_STRING, 10); + } + + /** + * Converts a comma-separated String to an array of ints. + * + * @param pString The comma-separated string + * @param pDelimiters The delimiter string + * @return an {@code int} array + * @throws NumberFormatException if any of the elements are not parseable + * as an int + * @see #toIntArray(String,String) + */ + public static int[] toIntArray(String pString, String pDelimiters) { + return toIntArray(pString, pDelimiters, 10); + } + + /** + * Converts a comma-separated String to an array of longs. + * + * @param pString The comma-separated string + * @param pDelimiters The delimiter string + * @return a {@code long} array + * @throws NumberFormatException if any of the elements are not parseable + * as a long + */ + public static long[] toLongArray(String pString, String pDelimiters) { + if (isEmpty(pString)) { + return new long[0]; + } + + // Some room for improvement here... + String[] temp = toStringArray(pString, pDelimiters); + long[] array = new long[temp.length]; + + for (int i = 0; i < array.length; i++) { + array[i] = Long.parseLong(temp[i]); + } + return array; + } + + /** + * Converts a comma-separated String to an array of longs. + * + * @param pString The comma-separated string + * @return a {@code long} array + * @throws NumberFormatException if any of the elements are not parseable + * as a long + * @see #toStringArray(String,String) + * @see #DELIMITER_STRING + */ + public static long[] toLongArray(String pString) { + return toLongArray(pString, DELIMITER_STRING); + } + + /** + * Converts a comma-separated String to an array of doubles. + * + * @param pString The comma-separated string + * @param pDelimiters The delimiter string + * @return a {@code double} array + * @throws NumberFormatException if any of the elements are not parseable + * as a double + */ + public static double[] toDoubleArray(String pString, String pDelimiters) { + if (isEmpty(pString)) { + return new double[0]; + } + + // Some room for improvement here... + String[] temp = toStringArray(pString, pDelimiters); + double[] array = new double[temp.length]; + + for (int i = 0; i < array.length; i++) { + array[i] = Double.valueOf(temp[i]); + + // Double.parseDouble() is 1.2... + } + return array; + } + + /** + * Converts a comma-separated String to an array of doubles. + * + * @param pString The comma-separated string + * @return a {@code double} array + * @throws NumberFormatException if any of the elements are not parseable + * as a double + * @see #toDoubleArray(String,String) + * @see #DELIMITER_STRING + */ + public static double[] toDoubleArray(String pString) { + return toDoubleArray(pString, DELIMITER_STRING); + } + + /** + * Parses a string to a Color. + * The argument can be a color constant (static constant from + * {@link java.awt.Color java.awt.Color}), like {@code black} or + * {@code red}, or it can be HTML/CSS-style, on the format: + *

    + *
  • {@code #RRGGBB}, where RR, GG and BB means two digit + * hexadecimal for red, green and blue values respectively.
  • + *
  • {@code #AARRGGBB}, as above, with AA as alpha component.
  • + *
  • {@code #RGB}, where R, G and B means one digit + * hexadecimal for red, green and blue values respectively.
  • + *
  • {@code #ARGB}, as above, with A as alpha component.
  • + *
+ * + * @param pString the string representation of the color + * @return the {@code Color} object, or {@code null} if the argument + * is {@code null} + * @throws IllegalArgumentException if the string does not map to a color. + * @see java.awt.Color + */ + public static Color toColor(String pString) { + // No string, no color + if (pString == null) { + return null; + } + + // #RRGGBB format + if (pString.charAt(0) == '#') { + int r = 0; + int g = 0; + int b = 0; + int a = -1;// alpha + + if (pString.length() >= 7) { + int idx = 1; + + // AA + if (pString.length() >= 9) { + a = Integer.parseInt(pString.substring(idx, idx + 2), 0x10); + idx += 2; + } + // RR GG BB + r = Integer.parseInt(pString.substring(idx, idx + 2), 0x10); + g = Integer.parseInt(pString.substring(idx + 2, idx + 4), 0x10); + b = Integer.parseInt(pString.substring(idx + 4, idx + 6), 0x10); + + } + else if (pString.length() >= 4) { + int idx = 1; + + // A + if (pString.length() >= 5) { + a = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; + } + // R G B + r = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; + g = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; + b = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10; + } + if (a != -1) { + // With alpha + return new Color(r, g, b, a); + } + + // No alpha + return new Color(r, g, b); + } + + // Get color by name + try { + Class colorClass = Color.class; + Field field = null; + + // Workaround for stupidity in Color class constant field names + try { + field = colorClass.getField(pString); + } + catch (Exception e) { + // Don't care, this is just a workaround... + } + if (field == null) { + // NOTE: The toLowerCase() on the next line will lose darkGray + // and lightGray... + field = colorClass.getField(pString.toLowerCase()); + } + + // Only try to get public final fields + int mod = field.getModifiers(); + + if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) { + return (Color) field.get(null); + } + } + catch (NoSuchFieldException nsfe) { + // No such color, throw illegal argument? + throw new IllegalArgumentException("No such color: " + pString); + } + catch (SecurityException se) { + // Can't access field, return null + } + catch (IllegalAccessException iae) { + // Can't happen, as the field must be public static + } + catch (IllegalArgumentException iar) { + // Can't happen, as the field must be static + } + + // This should never be reached, but you never know... ;-) + return null; + } + + /** + * Creates a HTML/CSS String representation of the given color. + * The HTML/CSS color format is defined as: + *
    + *
  • {@code #RRGGBB}, where RR, GG and BB means two digit + * hexadecimal for red, green and blue values respectively.
  • + *
  • {@code #AARRGGBB}, as above, with AA as alpha component.
  • + *
+ *

+ * Examlples: {@code toColorString(Color.red) == "#ff0000"}, + * {@code toColorString(new Color(0xcc, 0xcc, 0xcc)) == "#cccccc"}. + *

+ * + * @param pColor the color + * @return A String representation of the color on HTML/CSS form + */ + // TODO: Consider moving to ImageUtil? + public static String toColorString(Color pColor) { + // Not a color... + if (pColor == null) { + return null; + } + + StringBuilder str = new StringBuilder(Integer.toHexString(pColor.getRGB())); + + // Make sure string is 8 chars + for (int i = str.length(); i < 8; i++) { + str.insert(0, '0'); + } + + // All opaque is default + if (str.charAt(0) == 'f' && str.charAt(1) == 'f') { + str.delete(0, 2); + } + + // Prepend hash + return str.insert(0, '#').toString(); + } + + /** + * Tests a string, to see if it is an number (element of Z). + * Valid integers are positive natural numbers (1, 2, 3, ...), + * their negatives (?1, ?2, ?3, ...) and the number zero. + *

+ * Note that there is no guarantees made, that this number can be + * represented as either an int or a long. + *

+ * + * @param pString The string to check. + * @return true if the String is a natural number. + */ + public static boolean isNumber(String pString) { + if (isEmpty(pString)) { + return false; + } + + // Special case for first char, may be minus sign ('-') + char ch = pString.charAt(0); + if (!(ch == '-' || Character.isDigit(ch))) { + return false; + } + + // Test every char + for (int i = 1; i < pString.length(); i++) { + if (!Character.isDigit(pString.charAt(i))) { + return false; + } + } + + // All digits must be a natural number + return true; + } + + /* + * This version is benchmarked against toStringArray and found to be + * increasingly slower, the more elements the string contains. + * Kept here + */ + + /** + * Removes all occurences of a specific character in a string. + * This method is not design for efficiency! + *

+ * + * @param pSource + * @param pSubstring + * @param pPosition + * @return the modified string. + */ + + /* + public static String removeChar(String pSourceString, final char pBadChar) { + + char[] sourceCharArray = pSourceString.toCharArray(); + List modifiedCharList = new Vector(sourceCharArray.length, 1); + + // Filter the string + for (int i = 0; i < sourceCharArray.length; i++) { + if (sourceCharArray[i] != pBadChar) { + modifiedCharList.add(new Character(sourceCharArray[i])); + } + } + + // Clean the character list + modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList); + + // Create new modified String + char[] modifiedCharArray = new char[modifiedCharList.size()]; + for (int i = 0; i < modifiedCharArray.length; i++) { + modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue(); + } + + return new String(modifiedCharArray); + } + */ + + /** + * + * This method is not design for efficiency! + *

+ * @param pSourceString The String for modification. + * @param pBadChars The char array containing the characters to remove from the source string. + * @return the modified string. + * @-deprecated Not tested yet! + * + */ + + /* + public static String removeChars(String pSourceString, final char[] pBadChars) { + + char[] sourceCharArray = pSourceString.toCharArray(); + List modifiedCharList = new Vector(sourceCharArray.length, 1); + + Map badCharMap = new Hashtable(); + Character dummyChar = new Character('*'); + for (int i = 0; i < pBadChars.length; i++) { + badCharMap.put(new Character(pBadChars[i]), dummyChar); + } + + // Filter the string + for (int i = 0; i < sourceCharArray.length; i++) { + Character arrayChar = new Character(sourceCharArray[i]); + if (!badCharMap.containsKey(arrayChar)) { + modifiedCharList.add(new Character(sourceCharArray[i])); + } + } + + // Clean the character list + modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList); + + // Create new modified String + char[] modifiedCharArray = new char[modifiedCharList.size()]; + for (int i = 0; i < modifiedCharArray.length; i++) { + modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue(); + } + + return new String(modifiedCharArray); + + } + */ + + /** + * Ensures that a string includes a given substring at a given position. + *

+ * Extends the string with a given string if it is not already there. + * E.g an URL "www.vg.no", to "http://www.vg.no". + *

+ * + * @param pSource The source string. + * @param pSubstring The substring to include. + * @param pPosition The location of the fill-in, the index starts with 0. + * @return the string, with the substring at the given location. + */ + static String ensureIncludesAt(String pSource, String pSubstring, int pPosition) { + StringBuilder newString = new StringBuilder(pSource); + + try { + String existingSubstring = pSource.substring(pPosition, pPosition + pSubstring.length()); + + if (!existingSubstring.equalsIgnoreCase(pSubstring)) { + newString.insert(pPosition, pSubstring); + } + } + catch (Exception e) { + // Do something!? + } + return newString.toString(); + } + + /** + * Ensures that a string does not include a given substring at a given + * position. + *

+ * Removes a given substring from a string if it is there. + * E.g an URL "http://www.vg.no", to "www.vg.no". + *

+ * + * @param pSource The source string. + * @param pSubstring The substring to check and possibly remove. + * @param pPosition The location of possible substring removal, the index starts with 0. + * @return the string, without the substring at the given location. + */ + static String ensureExcludesAt(String pSource, String pSubstring, int pPosition) { + StringBuilder newString = new StringBuilder(pSource); + + try { + String existingString = pSource.substring(pPosition + 1, pPosition + pSubstring.length() + 1); + + if (!existingString.equalsIgnoreCase(pSubstring)) { + newString.delete(pPosition, pPosition + pSubstring.length()); + } + } + catch (Exception e) { + // Do something!? + } + return newString.toString(); + } + + /** + * Gets the first substring between the given string boundaries. + * + * @param pSource The source string. + * @param pBeginBoundaryString The string that marks the beginning. + * @param pEndBoundaryString The string that marks the end. + * @param pOffset The index to start searching in the source + * string. If it is less than 0, the index will be set to 0. + * @return the substring demarcated by the given string boundaries or null + * if not both string boundaries are found. + */ + public static String substring(final String pSource, final String pBeginBoundaryString, final String pEndBoundaryString, + final int pOffset) { + // Check offset + int offset = (pOffset < 0) + ? 0 + : pOffset; + + // Find the start index + int startIndex = pSource.indexOf(pBeginBoundaryString, offset) + pBeginBoundaryString.length(); + + if (startIndex < 0) { + return null; + } + + // Find the end index + int endIndex = pSource.indexOf(pEndBoundaryString, startIndex); + + if (endIndex < 0) { + return null; + } + return pSource.substring(startIndex, endIndex); + } + + /** + * Removes the first substring demarcated by the given string boundaries. + * + * @param pSource The source string. + * @param pBeginBoundaryChar The character that marks the beginning of the + * unwanted substring. + * @param pEndBoundaryChar The character that marks the end of the + * unwanted substring. + * @param pOffset The index to start searching in the source + * string. If it is less than 0, the index will be set to 0. + * @return the source string with all the demarcated substrings removed, + * included the demarcation characters. + * @deprecated this method actually removes all demarcated substring.. doesn't it? + */ + + /*public*/ + static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) { + StringBuilder filteredString = new StringBuilder(); + boolean insideDemarcatedArea = false; + char[] charArray = pSource.toCharArray(); + + for (char c : charArray) { + if (!insideDemarcatedArea) { + if (c == pBeginBoundaryChar) { + insideDemarcatedArea = true; + } + else { + filteredString.append(c); + } + } + else { + if (c == pEndBoundaryChar) { + insideDemarcatedArea = false; + } + } + } + return filteredString.toString(); + } + + /** + * Removes all substrings demarcated by the given string boundaries. + * + * @param pSource The source string. + * @param pBeginBoundaryChar The character that marks the beginning of the unwanted substring. + * @param pEndBoundaryChar The character that marks the end of the unwanted substring. + * @return the source string with all the demarcated substrings removed, included the demarcation characters. + */ + /*public*/ + static String removeSubstrings(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar) { + StringBuilder filteredString = new StringBuilder(); + boolean insideDemarcatedArea = false; + char[] charArray = pSource.toCharArray(); + + for (char c : charArray) { + if (!insideDemarcatedArea) { + if (c == pBeginBoundaryChar) { + insideDemarcatedArea = true; + } + else { + filteredString.append(c); + } + } + else { + if (c == pEndBoundaryChar) { + insideDemarcatedArea = false; + } + } + } + return filteredString.toString(); + } + + /** + * Gets the first element of a {@code String} containing string elements delimited by a given delimiter. + * NB - Straightforward implementation! + * + * @param pSource The source string. + * @param pDelimiter The delimiter used in the source string. + * @return The last string element. + */ + // TODO: This method should be re-implemented for more efficient execution. + public static String getFirstElement(final String pSource, final String pDelimiter) { + if (pDelimiter == null) { + throw new IllegalArgumentException("delimiter == null"); + } + + if (StringUtil.isEmpty(pSource)) { + return pSource; + } + + int idx = pSource.indexOf(pDelimiter); + if (idx >= 0) { + return pSource.substring(0, idx); + } + return pSource; + } + + /** + * Gets the last element of a {@code String} containing string elements + * delimited by a given delimiter. + * NB - Straightforward implementation! + * + * @param pSource The source string. + * @param pDelimiter The delimiter used in the source string. + * @return The last string element. + */ + public static String getLastElement(final String pSource, final String pDelimiter) { + if (pDelimiter == null) { + throw new IllegalArgumentException("delimiter == null"); + } + + if (StringUtil.isEmpty(pSource)) { + return pSource; + } + int idx = pSource.lastIndexOf(pDelimiter); + if (idx >= 0) { + return pSource.substring(idx + 1); + } + return pSource; + } + + /** + * Converts a string array to a string of comma-separated values. + * + * @param pStringArray the string array + * @return A string of comma-separated values + */ + public static String toCSVString(Object[] pStringArray) { + return toCSVString(pStringArray, ", "); + } + + /** + * Converts a string array to a string separated by the given delimiter. + * + * @param pStringArray the string array + * @param pDelimiterString the delimiter string + * @return string of delimiter separated values + * @throws IllegalArgumentException if {@code pDelimiterString == null} + */ + public static String toCSVString(Object[] pStringArray, String pDelimiterString) { + if (pStringArray == null) { + return ""; + } + if (pDelimiterString == null) { + throw new IllegalArgumentException("delimiter == null"); + } + + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < pStringArray.length; i++) { + if (i > 0) { + buffer.append(pDelimiterString); + } + + buffer.append(pStringArray[i]); + } + + return buffer.toString(); + } + + /** + * @param pObject the object + * @return a deep string representation of the given object + */ + public static String deepToString(Object pObject) { + return deepToString(pObject, false, 1); + } + + /** + * @param pObject the object + * @param pDepth the maximum depth + * @param pForceDeep {@code true} to force deep {@code toString}, even + * if object overrides toString + * @return a deep string representation of the given object + */ + // TODO: Array handling (print full type and length) + // TODO: Register handlers for specific toDebugString handling? :-) + public static String deepToString(Object pObject, boolean pForceDeep, int pDepth) { + // Null is null + if (pObject == null) { + return null; + } + + // Implements toString, use it as-is unless pForceDeep + if (!pForceDeep && !isIdentityToString(pObject)) { + return pObject.toString(); + } + + StringBuilder buffer = new StringBuilder(); + + if (pObject.getClass().isArray()) { + // Special array handling + Class componentClass = pObject.getClass(); + while (componentClass.isArray()) { + buffer.append('['); + buffer.append(Array.getLength(pObject)); + buffer.append(']'); + componentClass = componentClass.getComponentType(); + } + buffer.insert(0, componentClass); + buffer.append(" {hashCode="); + buffer.append(Integer.toHexString(pObject.hashCode())); + buffer.append("}"); + } + else { + // Append toString value only if overridden + if (isIdentityToString(pObject)) { + buffer.append(" {"); + } + else { + buffer.append(" {toString="); + buffer.append(pObject.toString()); + buffer.append(", "); + } + buffer.append("hashCode="); + buffer.append(Integer.toHexString(pObject.hashCode())); + // Loop through, and filter out any getters + Method[] methods = pObject.getClass().getMethods(); + for (Method method : methods) { + // Filter only public methods + if (Modifier.isPublic(method.getModifiers())) { + String methodName = method.getName(); + + // Find name of property + String name = null; + if (!methodName.equals("getClass") + && methodName.length() > 3 && methodName.startsWith("get") + && Character.isUpperCase(methodName.charAt(3))) { + name = methodName.substring(3); + } + else if (methodName.length() > 2 && methodName.startsWith("is") + && Character.isUpperCase(methodName.charAt(2))) { + name = methodName.substring(2); + } + + if (name != null) { + // If lowercase name, convert, else keep case + if (name.length() > 1 && Character.isLowerCase(name.charAt(1))) { + name = Character.toLowerCase(name.charAt(0)) + name.substring(1); + } + + Class[] paramTypes = method.getParameterTypes();// involves array copying... + boolean hasParams = (paramTypes != null && paramTypes.length > 0); + boolean isVoid = Void.TYPE.equals(method.getReturnType()); + + // Filter return type & parameters + if (!isVoid && !hasParams) { + try { + Object value = method.invoke(pObject); + buffer.append(", "); + buffer.append(name); + buffer.append('='); + if (pDepth != 0 && value != null && isIdentityToString(value)) { + buffer.append(deepToString(value, pForceDeep, pDepth > 0 ? pDepth - 1 : -1)); + } + else { + buffer.append(value); + } + } + catch (Exception e) { + // Next..! + } + } + } + } + } + buffer.append('}'); + + // Get toString from original object + buffer.insert(0, pObject.getClass().getName()); + } + + return buffer.toString(); + } + + /** + * Tests if the {@code toString} method of the given object is inherited + * from {@code Object}. + * + * @param pObject the object + * @return {@code true} if toString of class Object + */ + private static boolean isIdentityToString(Object pObject) { + try { + Method toString = pObject.getClass().getMethod("toString"); + if (toString.getDeclaringClass() == Object.class) { + return true; + } + } + catch (Exception ignore) { + // Ignore + } + + return false; + } + + /** + * Returns a string on the same format as {@code Object.toString()}. + * + * @param pObject the object + * @return the object as a {@code String} on the format of + * {@code Object.toString()} + */ + public static String identityToString(Object pObject) { + if (pObject == null) { + return null; + } + else { + return pObject.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(pObject)); + } + } + + /** + * Tells whether or not the given string string matches the given regular + * expression. + *

+ * An invocation of this method of the form + * matches(str, regex) yields exactly the + * same result as the expression + *

+ *
{@link Pattern}. + * {@link Pattern#matches(String, CharSequence) matches} + * (regex, str)
+ * + * @param pString the string + * @param pRegex the regular expression to which this string is to be matched + * @return {@code true} if, and only if, this string matches the + * given regular expression + * @throws PatternSyntaxException if the regular expression's syntax is invalid + * @see Pattern + * @see String#matches(String) + */ + public boolean matches(String pString, String pRegex) throws PatternSyntaxException { + return Pattern.matches(pRegex, pString); + } + + /** + * Replaces the first substring of the given string that matches the given + * regular expression with the given pReplacement. + *

+ * An invocation of this method of the form + * + * replaceFirst(str, regex, repl) + * + * yields exactly the same result as the expression: + *

+ *
+ * {@link Pattern}.{@link Pattern#compile(String) compile}(regex). + * {@link Pattern#matcher matcher}(str). + * {@link java.util.regex.Matcher#replaceFirst replaceFirst}(repl) + *
+ * + * @param pString the string + * @param pRegex the regular expression to which this string is to be matched + * @param pReplacement the replacement text + * @return The resulting {@code String} + * @throws PatternSyntaxException if the regular expression's syntax is invalid + * @see Pattern + * @see java.util.regex.Matcher#replaceFirst(String) + */ + public String replaceFirst(String pString, String pRegex, String pReplacement) { + return Pattern.compile(pRegex).matcher(pString).replaceFirst(pReplacement); + } + + /** + * Replaces each substring of this string that matches the given + * regular expression with the given pReplacement. + *

+ * An invocation of this method of the form + * replaceAll(str, pRegex, repl) + * yields exactly the same result as the expression + *

+ *
+ * {@link Pattern}.{@link Pattern#compile(String) compile}(pRegex). + * {@link Pattern#matcher matcher}(str{@code ). + * {@link java.util.regex.Matcher#replaceAll replaceAll}(}repl{@code )} + *
+ * + * @param pString the string + * @param pRegex the regular expression to which this string is to be matched + * @param pReplacement the replacement string + * @return The resulting {@code String} + * @throws PatternSyntaxException if the regular expression's syntax is invalid + * @see Pattern + * @see String#replaceAll(String,String) + */ + public String replaceAll(String pString, String pRegex, String pReplacement) { + return Pattern.compile(pRegex).matcher(pString).replaceAll(pReplacement); + } + + /** + * Splits this string around matches of the given regular expression. + *

+ * The array returned by this method contains each substring of this + * string that is terminated by another substring that matches the given + * expression or is terminated by the end of the string. The substrings in + * the array are in the order in which they occur in this string. If the + * expression does not match any part of the input then the resulting array + * has just one element, namely this string. + *

+ *

+ * The {@code pLimit} parameter controls the number of times the + * pattern is applied and therefore affects the length of the resulting + * array. If the pLimit n is greater than zero then the pattern + * will be applied at most n - 1 times, the array's + * length will be no greater than n, and the array's last entry + * will contain all input beyond the last matched delimiter. If n + * is non-positive then the pattern will be applied as many times as + * possible and the array can have any length. If n is zero then + * the pattern will be applied as many times as possible, the array can + * have any length, and trailing empty strings will be discarded. + *

+ *

+ * An invocation of this method of the form + * split(str, regex, n) + * yields the same result as the expression: + *

+ *
{@link Pattern}. + * {@link Pattern#compile(String) compile}(regex). + * {@link Pattern#split(CharSequence,int) split}(str, n) + *
+ * + * @param pString the string + * @param pRegex the delimiting regular expression + * @param pLimit the result threshold, as described above + * @return the array of strings computed by splitting this string + * around matches of the given regular expression + * @throws PatternSyntaxException + * if the regular expression's syntax is invalid + * @see Pattern + * @see String#split(String,int) + */ + public String[] split(String pString, String pRegex, int pLimit) { + return Pattern.compile(pRegex).split(pString, pLimit); + } + + /** + * Splits this string around matches of the given regular expression. + *

+ * This method works as if by invoking the two-argument + * {@link #split(String,String,int) split} method with the given + * expression and a limit argument of zero. + * Trailing empty strings are therefore not included in the resulting array. + *

+ * + * @param pString the string + * @param pRegex the delimiting regular expression + * @return the array of strings computed by splitting this string + * around matches of the given regular expression + * @throws PatternSyntaxException if the regular expression's syntax is invalid + * @see Pattern + * @see String#split(String) + */ + public String[] split(String pString, String pRegex) { + return split(pString, pRegex, 0); + } + + /** + * Converts the input string + * from camel-style (Java in-fix) naming convention + * to Lisp-style naming convention (hyphen delimitted, all lower case). + * Other characters in the string are left untouched. + *

+ * Eg. + * {@code "foo" => "foo"}, + * {@code "fooBar" => "foo-bar"}, + * {@code "myURL" => "my-url"}, + * {@code "HttpRequestWrapper" => "http-request-wrapper"} + * {@code "HttpURLConnection" => "http-url-connection"} + * {@code "my45Caliber" => "my-45-caliber"} + * {@code "allready-lisp" => "allready-lisp"} + *

+ * + * @param pString the camel-style input string + * @return the string converted to lisp-style naming convention + * @throws IllegalArgumentException if {@code pString == null} + * @see #lispToCamel(String) + */ + // TODO: RefactorMe! + public static String camelToLisp(final String pString) { + if (pString == null) { + throw new IllegalArgumentException("string == null"); + } + if (pString.length() == 0) { + return pString; + } + + StringBuilder buf = null; + int lastPos = 0; + boolean inCharSequence = false; + boolean inNumberSequence = false; + + // NOTE: Start at index 1, as first letter should never be hyphen + for (int i = 1; i < pString.length(); i++) { + char current = pString.charAt(i); + if (Character.isUpperCase(current)) { + // Init buffer if necessary + if (buf == null) { + buf = new StringBuilder(pString.length() + 3);// Allow for some growth + } + + if (inNumberSequence) { + // Sequence end + inNumberSequence = false; + + buf.append(pString.substring(lastPos, i)); + if (current != '-') { + buf.append('-'); + } + lastPos = i; + continue; + } + + // Treat multiple uppercase chars as single word + char previous = pString.charAt(i - 1); + if (i == lastPos || Character.isUpperCase(previous)) { + inCharSequence = true; + continue; + } + + // Append word + buf.append(pString.substring(lastPos, i).toLowerCase()); + if (previous != '-') { + buf.append('-'); + } + buf.append(Character.toLowerCase(current)); + + lastPos = i + 1; + } + else if (Character.isDigit(current)) { + // Init buffer if necessary + if (buf == null) { + buf = new StringBuilder(pString.length() + 3);// Allow for some growth + } + + if (inCharSequence) { + // Sequence end + inCharSequence = false; + + buf.append(pString.substring(lastPos, i).toLowerCase()); + if (current != '-') { + buf.append('-'); + } + lastPos = i; + continue; + } + + // Treat multiple digits as single word + char previous = pString.charAt(i - 1); + if (i == lastPos || Character.isDigit(previous)) { + inNumberSequence = true; + continue; + } + + // Append word + buf.append(pString.substring(lastPos, i).toLowerCase()); + if (previous != '-') { + buf.append('-'); + } + buf.append(Character.toLowerCase(current)); + + lastPos = i + 1; + } + else if (inNumberSequence) { + // Sequence end + inNumberSequence = false; + + buf.append(pString.substring(lastPos, i)); + if (current != '-') { + buf.append('-'); + } + lastPos = i; + } + else if (inCharSequence) { + // Sequence end + inCharSequence = false; + + // NOTE: Special treatment! Last upper case, is first char in + // next word, not last char in this word + buf.append(pString.substring(lastPos, i - 1).toLowerCase()); + if (current != '-') { + buf.append('-'); + } + lastPos = i - 1; + } + } + + if (buf != null) { + // Append the rest + buf.append(pString.substring(lastPos).toLowerCase()); + return buf.toString(); + } + else { + return Character.isUpperCase(pString.charAt(0)) ? pString.toLowerCase() : pString; + } + } + + /** + * Converts the input string + * from Lisp-style naming convention (hyphen delimitted, all lower case) + * to camel-style (Java in-fix) naming convention. + * Other characters in the string are left untouched. + *

+ * Eg. + * {@code "foo" => "foo"}, + * {@code "foo-bar" => "fooBar"}, + * {@code "http-request-wrapper" => "httpRequestWrapper"} + * {@code "my-45-caliber" => "my45Caliber"} + * {@code "allreadyCamel" => "allreadyCamel"} + *

+ * + * @param pString the lisp-style input string + * @return the string converted to camel-style + * @throws IllegalArgumentException if {@code pString == null} + * @see #lispToCamel(String,boolean) + * @see #camelToLisp(String) + */ + public static String lispToCamel(final String pString) { + return lispToCamel(pString, false); + } + + /** + * Converts the input string + * from Lisp-style naming convention (hyphen delimitted, all lower case) + * to camel-style (Java in-fix) naming convention. + * Other characters in the string are left untouched. + *

+ * To create a string starting with a lower case letter + * (like Java variable names, etc), + * specify the {@code pFirstUpperCase} paramter to be {@code false}. + * Eg. + * {@code "foo" => "foo"}, + * {@code "foo-bar" => "fooBar"}, + * {@code "allreadyCamel" => "allreadyCamel"} + *

+ *

+ * To create a string starting with an upper case letter + * (like Java class name, etc), + * specify the {@code pFirstUpperCase} paramter to be {@code true}. + * Eg. + * {@code "http-request-wrapper" => "HttpRequestWrapper"} + * {@code "my-12-monkeys" => "My12Monkeys"} + *

+ * + * @param pString the lisp-style input string + * @param pFirstUpperCase {@code true} if the first char should be + * upper case + * @return the string converted to camel-style + * @throws IllegalArgumentException if {@code pString == null} + * @see #camelToLisp(String) + */ + public static String lispToCamel(final String pString, final boolean pFirstUpperCase) { + if (pString == null) { + throw new IllegalArgumentException("string == null"); + } + if (pString.length() == 0) { + return pString; + } + + StringBuilder buf = null; + int lastPos = 0; + + for (int i = 0; i < pString.length(); i++) { + char current = pString.charAt(i); + if (current == '-') { + + // Init buffer if necessary + if (buf == null) { + buf = new StringBuilder(pString.length() - 1);// Can't be larger + } + + // Append with upper case + if (lastPos != 0 || pFirstUpperCase) { + buf.append(Character.toUpperCase(pString.charAt(lastPos))); + lastPos++; + } + + buf.append(pString.substring(lastPos, i).toLowerCase()); + lastPos = i + 1; + } + } + + if (buf != null) { + buf.append(Character.toUpperCase(pString.charAt(lastPos))); + buf.append(pString.substring(lastPos + 1).toLowerCase()); + return buf.toString(); + } + else { + if (pFirstUpperCase && !Character.isUpperCase(pString.charAt(0))) { + return capitalize(pString, 0); + } + else + if (!pFirstUpperCase && Character.isUpperCase(pString.charAt(0))) { + return Character.toLowerCase(pString.charAt(0)) + pString.substring(1); + } + + return pString; + } + } + + public static String reverse(final String pString) { + final char[] chars = new char[pString.length()]; + pString.getChars(0, chars.length, chars, 0); + + for (int i = 0; i < chars.length / 2; i++) { + char temp = chars[i]; + chars[i] = chars[chars.length - 1 - i]; + chars[chars.length - 1 - i] = temp; + } + + return new String(chars); + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java index 9e79347e..d824624b 100644 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java @@ -1,688 +1,687 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.lang; - -//import com.twelvemonkeys.util.XMLProperties; - -import java.io.*; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * A utility class with some useful system-related functions. - *

- * NOTE: This class is not considered part of the public API and may be - * changed without notice - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/SystemUtil.java#3 $ - * - */ -public final class SystemUtil { - /** {@code ".xml"} */ - public static String XML_PROPERTIES = ".xml"; - /** {@code ".properties"} */ - public static String STD_PROPERTIES = ".properties"; - - // Disallow creating objects of this type - private SystemUtil() { - } - - /** This class marks an inputstream as containing XML, does nothing */ - private static class XMLPropertiesInputStream extends FilterInputStream { - public XMLPropertiesInputStream(InputStream pIS) { - super(pIS); - } - } - - /** - * Gets the named resource as a stream from the given Class' Classoader. - * If the pGuessSuffix parameter is true, the method will try to append - * typical properties file suffixes, such as ".properties" or ".xml". - * - * @param pClassLoader the class loader to use - * @param pName name of the resource - * @param pGuessSuffix guess suffix - * - * @return an input stream reading from the resource - */ - private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName, boolean pGuessSuffix) { - InputStream is; - - if (!pGuessSuffix) { - is = pClassLoader.getResourceAsStream(pName); - - // If XML, wrap stream - if (is != null && pName.endsWith(XML_PROPERTIES)) { - is = new XMLPropertiesInputStream(is); - } - } - else { - // Try normal properties - is = pClassLoader.getResourceAsStream(pName + STD_PROPERTIES); - - // Try XML - if (is == null) { - is = pClassLoader.getResourceAsStream(pName + XML_PROPERTIES); - - // Wrap stream - if (is != null) { - is = new XMLPropertiesInputStream(is); - } - } - } - - // Return stream - return is; - } - - /** - * Gets the named file as a stream from the current directory. - * If the pGuessSuffix parameter is true, the method will try to append - * typical properties file suffixes, such as ".properties" or ".xml". - * - * @param pName name of the resource - * @param pGuessSuffix guess suffix - * - * @return an input stream reading from the resource - */ - private static InputStream getFileAsStream(String pName, boolean pGuessSuffix) { - InputStream is = null; - File propertiesFile; - - try { - if (!pGuessSuffix) { - // Get file - propertiesFile = new File(pName); - - if (propertiesFile.exists()) { - is = new FileInputStream(propertiesFile); - - // If XML, wrap stream - if (pName.endsWith(XML_PROPERTIES)) { - is = new XMLPropertiesInputStream(is); - } - } - } - else { - // Try normal properties - propertiesFile = new File(pName + STD_PROPERTIES); - - if (propertiesFile.exists()) { - is = new FileInputStream(propertiesFile); - } - else { - // Try XML - propertiesFile = new File(pName + XML_PROPERTIES); - - if (propertiesFile.exists()) { - // Wrap stream - is = new XMLPropertiesInputStream(new FileInputStream(propertiesFile)); - } - } - } - } - catch (FileNotFoundException fnf) { - // Should not happen, as we always test that the file .exists() - // before creating InputStream - // assert false; - } - - return is; - } - - /** - * Utility method for loading a named properties-file for a class. - *

- * The properties-file is loaded through either: - *

    - *
  1. The given class' class loader (from classpath)
  2. - *
  3. Or, the system class loader (from classpath)
  4. - *
  5. Or, if it cannot be found in the classpath, an attempt to read from - * the current directory (or full path if given).
  6. - *
- *

- * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties - * are supported (XML-properties must have ".xml" as its file extension). - * - * @param pClass The class to load properties for. If this parameter is - * {@code null}, the method will work exactly as - * {@link #loadProperties(String)} - * @param pName The name of the properties-file. If this parameter is - * {@code null}, the method will work exactly as - * {@link #loadProperties(Class)} - * - * @return A Properties mapping read from the given file or for the given - * class. - * - * @throws NullPointerException if both {@code pName} and - * {@code pClass} paramters are {@code null} - * @throws IOException if an error occurs during load. - * @throws FileNotFoundException if no properties-file could be found. - * - * @see #loadProperties(String) - * @see #loadProperties(Class) - * @see java.lang.ClassLoader#getResourceAsStream - * @see java.lang.ClassLoader#getSystemResourceAsStream - * - * @todo Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html - * @todo Consider using Context Classloader instead? - */ - public static Properties loadProperties(Class pClass, String pName) throws IOException - { - // Convert to name the classloader understands - String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/'); - - // Should we try to guess suffix? - boolean guessSuffix = (pName == null || pName.indexOf('.') < 0); - - InputStream is; - - // TODO: WHAT IF MULTIPLE RESOURCES EXISTS?! - // Try loading resource through the current class' classloader - if (pClass != null && (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) { - //&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) { - // Nothing to do - //System.out.println(((is instanceof XMLPropertiesInputStream) ? - // "XML-properties" : "Normal .properties") - // + " from Class' ClassLoader"); - } - // If that fails, try the system classloader - else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(), name, guessSuffix)) != null) { - //else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) { - // Nothing to do - //System.out.println(((is instanceof XMLPropertiesInputStream) ? - // "XML-properties" : "Normal .properties") - // + " from System ClassLoader"); - } - // All failed, try loading from file - else if ((is = getFileAsStream(name, guessSuffix)) != null) { - //System.out.println(((is instanceof XMLPropertiesInputStream) ? - // "XML-properties" : "Normal .properties") - // + " from System ClassLoader"); - } - else { - if (guessSuffix) { - // TODO: file extension iterator or something... - throw new FileNotFoundException(name + ".properties or " + name + ".xml"); - } - else { - throw new FileNotFoundException(name); - } - } - - // We have inputstream now, load... - try { - return loadProperties(is); - } - finally { - // NOTE: If is == null, a FileNotFoundException must have been thrown above - try { - is.close(); - } - catch (IOException ioe) { - // Not critical... - } - } - } - - /** - * Utility method for loading a properties-file for a given class. - * The properties are searched for on the form - * "com/package/ClassName.properties" or - * "com/package/ClassName.xml". - *

- * The properties-file is loaded through either: - *

    - *
  1. The given class' class loader (from classpath)
  2. - *
  3. Or, the system class loader (from classpath)
  4. - *
  5. Or, if it cannot be found in the classpath, an attempt to read from - * the current directory (or full path if given).
  6. - *
- *

- * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties - * are supported (XML-properties must have ".xml" as its file extension). - * - * @param pClass The class to load properties for - * @return A Properties mapping for the given class. - * - * @throws NullPointerException if the {@code pClass} paramters is - * {@code null} - * @throws IOException if an error occurs during load. - * @throws FileNotFoundException if no properties-file could be found. - * - * @see #loadProperties(String) - * @see #loadProperties(Class, String) - * @see java.lang.ClassLoader#getResourceAsStream - * @see java.lang.ClassLoader#getSystemResourceAsStream - * - */ - public static Properties loadProperties(Class pClass) throws IOException { - return loadProperties(pClass, null); - } - - /** - * Utility method for loading a named properties-file. - *

- * The properties-file is loaded through either: - *

    - *
  1. The system class loader (from classpath)
  2. - *
  3. Or, if it cannot be found in the classpath, an attempt to read from - * the current directory.
  4. - *
- *

- * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties - * are supported (XML-properties must have ".xml" as its file extension). - * - * @param pName The name of the properties-file. - * @return A Properties mapping read from the given file. - * - * @throws NullPointerException if the {@code pName} paramters is - * {@code null} - * @throws IOException if an error occurs during load. - * @throws FileNotFoundException if no properties-file could be found. - * - * @see #loadProperties(Class) - * @see #loadProperties(Class, String) - * @see java.lang.ClassLoader#getSystemResourceAsStream - * - */ - public static Properties loadProperties(String pName) throws IOException { - return loadProperties(null, pName); - } - - /* - * Utility method for loading a properties-file. - *

- * The properties files may also be contained in a zip/jar-file named - * by the {@code com.twelvemonkeys.util.Config} system property (use "java -D" - * to override). Default is "config.zip" in the current directory. - * - * @param pName The name of the file to loaded - * @return A Properties mapping for the given class. If no properties- - * file was found, an empty Properties object is returned. - * - */ - /* - public static Properties loadProperties(String pName) throws IOException { - // Use XML? - boolean useXML = pName.endsWith(XML_PROPERTIES) ? true : false; - - InputStream is = null; - - File file = new File(pName); - - String configName = System.getProperty("com.twelvemonkeys.util.Config"); - File configArchive = new File(!StringUtil.isEmpty(configName) - ? configName : DEFAULT_CONFIG); - - // Get input stream to the file containing the properties - if (file.exists()) { - // Try reading from file, normal way - is = new FileInputStream(file); - } - else if (configArchive.exists()) { - // Try reading properties from zip-file - ZipFile zip = new ZipFile(configArchive); - ZipEntry ze = zip.getEntry(pName); - if (ze != null) { - is = zip.getInputStream(ze); - } - - } - - // Do the loading - try { - // Load the properties - return loadProperties(is, useXML); - } - finally { - // Try closing the archive to free resources - if (is != null) { - try { - is.close(); - } - catch (IOException ioe) { - // Not critical... - } - } - } - - } - */ - - /** - * Returns a Properties, loaded from the given inputstream. If the given - * inputstream is null, then an empty Properties object is returned. - * - * @param pInput the inputstream to read from - * - * @return a Properties object read from the given stream, or an empty - * Properties mapping, if the stream is null. - * - * @throws IOException if an error occurred when reading from the input - * stream. - * - */ - private static Properties loadProperties(InputStream pInput) - throws IOException { - - if (pInput == null) { - throw new IllegalArgumentException("InputStream == null!"); - } - - Properties mapping = new Properties(); - /*if (pInput instanceof XMLPropertiesInputStream) { - mapping = new XMLProperties(); - } - else { - mapping = new Properties(); - }*/ - - // Load the properties - mapping.load(pInput); - - return mapping; - } - - @SuppressWarnings({"SuspiciousSystemArraycopy"}) - public static Object clone(final Cloneable pObject) throws CloneNotSupportedException { - if (pObject == null) { - return null; // Null is clonable.. Easy. ;-) - } - - // All arrays does have a clone method, but it's invisible for reflection... - // By luck, multi-dimensional primitive arrays are instances of Object[] - if (pObject instanceof Object[]) { - return ((Object[]) pObject).clone(); - } - else if (pObject.getClass().isArray()) { - // One-dimensional primitive array, cloned manually - int lenght = Array.getLength(pObject); - Object clone = Array.newInstance(pObject.getClass().getComponentType(), lenght); - System.arraycopy(pObject, 0, clone, 0, lenght); - return clone; - } - - try { - // Find the clone method - Method clone = null; - Class clazz = pObject.getClass(); - do { - try { - clone = clazz.getDeclaredMethod("clone"); - break; // Found, or throws exception above - } - catch (NoSuchMethodException ignore) { - // Ignore - } - } - while ((clazz = clazz.getSuperclass()) != null); - - // NOTE: This should never happen - if (clone == null) { - throw new CloneNotSupportedException(pObject.getClass().getName()); - } - - // Override access if needed - if (!clone.isAccessible()) { - clone.setAccessible(true); - } - - // Invoke clone method on original object - return clone.invoke(pObject); - } - catch (SecurityException e) { - CloneNotSupportedException cns = new CloneNotSupportedException(pObject.getClass().getName()); - cns.initCause(e); - throw cns; - } - catch (IllegalAccessException e) { - throw new CloneNotSupportedException(pObject.getClass().getName()); - } - catch (InvocationTargetException e) { - if (e.getTargetException() instanceof CloneNotSupportedException) { - throw (CloneNotSupportedException) e.getTargetException(); - } - else if (e.getTargetException() instanceof RuntimeException) { - throw (RuntimeException) e.getTargetException(); - } - else if (e.getTargetException() instanceof Error) { - throw (Error) e.getTargetException(); - } - - throw new CloneNotSupportedException(pObject.getClass().getName()); - } - } - -// public static void loadLibrary(String pLibrary) { -// NativeLoader.loadLibrary(pLibrary); -// } -// -// public static void loadLibrary(String pLibrary, ClassLoader pLoader) { -// NativeLoader.loadLibrary(pLibrary, pLoader); -// } - - public static void main(String[] args) throws CloneNotSupportedException { - - System.out.println("clone: " + args.clone().length + " (" + args.length + ")"); - System.out.println("copy: " + ((String[]) clone(args)).length + " (" + args.length + ")"); - - int[] ints = {1,2,3}; - int[] copies = (int[]) clone(ints); - System.out.println("Copies: " + copies.length + " (" + ints.length + ")"); - - int[][] intsToo = {{1}, {2,3}, {4,5,6}}; - int[][] copiesToo = (int[][]) clone(intsToo); - System.out.println("Copies: " + copiesToo.length + " (" + intsToo.length + ")"); - System.out.println("Copies0: " + copiesToo[0].length + " (" + intsToo[0].length + ")"); - System.out.println("Copies1: " + copiesToo[1].length + " (" + intsToo[1].length + ")"); - System.out.println("Copies2: " + copiesToo[2].length + " (" + intsToo[2].length + ")"); - - Map map = new HashMap(); - - for (String arg : args) { - map.put(arg, arg); - } - - Map copy = (Map) clone((Cloneable) map); - - System.out.println("Map : " + map); - System.out.println("Copy: " + copy); - - /* - SecurityManager sm = System.getSecurityManager(); - - try { - System.setSecurityManager(new SecurityManager() { - public void checkPermission(Permission perm) { - if (perm.getName().equals("suppressAccessChecks")) { - throw new SecurityException(); - } - //super.checkPermission(perm); - } - }); - */ - - Cloneable cloneable = new Cloneable() {}; // No public clone method - Cloneable clone = (Cloneable) clone(cloneable); - - System.out.println("cloneable: " + cloneable); - System.out.println("clone: " + clone); - - /* - } - finally { - System.setSecurityManager(sm); - } - */ - - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - return null; - } - }, AccessController.getContext()); - - //String string = args.length > 0 ? args[0] : "jaffa"; - //clone(string); - } - - /** - * Tests if a named class is generally available. - * If a class is considered available, a call to - * {@code Class.forName(pClassName)} will not result in an exception. - * - * @param pClassName the class name to test - * @return {@code true} if available - */ - public static boolean isClassAvailable(String pClassName) { - return isClassAvailable(pClassName, (ClassLoader) null); - } - - /** - * Tests if a named class is available from another class. - * If a class is considered available, a call to - * {@code Class.forName(pClassName, true, pFromClass.getClassLoader())} - * will not result in an exception. - * - * @param pClassName the class name to test - * @param pFromClass the class to test from - * @return {@code true} if available - */ - public static boolean isClassAvailable(String pClassName, Class pFromClass) { - ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null; - return isClassAvailable(pClassName, loader); - } - - private static boolean isClassAvailable(String pClassName, ClassLoader pLoader) { - try { - // TODO: Sometimes init is not needed, but need to find a way to know... - getClass(pClassName, true, pLoader); - return true; - } - catch (SecurityException ignore) { - // Ignore - } - catch (ClassNotFoundException ignore) { - // Ignore - } - catch (LinkageError ignore) { - // Ignore - } - - return false; - } - - public static boolean isFieldAvailable(final String pClassName, final String pFieldName) { - return isFieldAvailable(pClassName, pFieldName, (ClassLoader) null); - } - - public static boolean isFieldAvailable(final String pClassName, final String pFieldName, final Class pFromClass) { - ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null; - return isFieldAvailable(pClassName, pFieldName, loader); - } - - private static boolean isFieldAvailable(final String pClassName, final String pFieldName, final ClassLoader pLoader) { - try { - Class cl = getClass(pClassName, false, pLoader); - - Field field = cl.getField(pFieldName); - if (field != null) { - return true; - } - } - catch (ClassNotFoundException ignore) { - // Ignore - } - catch (LinkageError ignore) { - // Ignore - } - catch (NoSuchFieldException ignore) { - // Ignore - } - return false; - } - - public static boolean isMethodAvailable(String pClassName, String pMethodName) { - // Finds void only - return isMethodAvailable(pClassName, pMethodName, null, (ClassLoader) null); - } - - public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams) { - return isMethodAvailable(pClassName, pMethodName, pParams, (ClassLoader) null); - } - - public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, Class pFromClass) { - ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null; - return isMethodAvailable(pClassName, pMethodName, pParams, loader); - } - - private static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, ClassLoader pLoader) { - try { - Class cl = getClass(pClassName, false, pLoader); - - Method method = cl.getMethod(pMethodName, pParams); - if (method != null) { - return true; - } - } - catch (ClassNotFoundException ignore) { - // Ignore - } - catch (LinkageError ignore) { - // Ignore - } - catch (NoSuchMethodException ignore) { - // Ignore - } - return false; - } - - private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException { - // NOTE: We need the context class loader, as SystemUtil's - // class loader may have a totally different class loader than - // the original caller class (as in Class.forName(cn, false, null)). - ClassLoader loader = pLoader != null ? pLoader : - Thread.currentThread().getContextClassLoader(); - - return Class.forName(pClassName, pInitialize, loader); - } -} +/* + * 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.lang; + +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * A utility class with some useful system-related functions. + *

+ * NOTE: This class is not considered part of the public API and may be + * changed without notice + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/SystemUtil.java#3 $ + * + */ +public final class SystemUtil { + /** {@code ".xml"} */ + public static String XML_PROPERTIES = ".xml"; + /** {@code ".properties"} */ + public static String STD_PROPERTIES = ".properties"; + + // Disallow creating objects of this type + private SystemUtil() { + } + + /** This class marks an inputstream as containing XML, does nothing */ + private static class XMLPropertiesInputStream extends FilterInputStream { + public XMLPropertiesInputStream(InputStream pIS) { + super(pIS); + } + } + + /** + * Gets the named resource as a stream from the given Class' Classoader. + * If the pGuessSuffix parameter is true, the method will try to append + * typical properties file suffixes, such as ".properties" or ".xml". + * + * @param pClassLoader the class loader to use + * @param pName name of the resource + * @param pGuessSuffix guess suffix + * + * @return an input stream reading from the resource + */ + private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName, boolean pGuessSuffix) { + InputStream is; + + if (!pGuessSuffix) { + is = pClassLoader.getResourceAsStream(pName); + + // If XML, wrap stream + if (is != null && pName.endsWith(XML_PROPERTIES)) { + is = new XMLPropertiesInputStream(is); + } + } + else { + // Try normal properties + is = pClassLoader.getResourceAsStream(pName + STD_PROPERTIES); + + // Try XML + if (is == null) { + is = pClassLoader.getResourceAsStream(pName + XML_PROPERTIES); + + // Wrap stream + if (is != null) { + is = new XMLPropertiesInputStream(is); + } + } + } + + // Return stream + return is; + } + + /** + * Gets the named file as a stream from the current directory. + * If the pGuessSuffix parameter is true, the method will try to append + * typical properties file suffixes, such as ".properties" or ".xml". + * + * @param pName name of the resource + * @param pGuessSuffix guess suffix + * + * @return an input stream reading from the resource + */ + private static InputStream getFileAsStream(String pName, boolean pGuessSuffix) { + InputStream is = null; + File propertiesFile; + + try { + if (!pGuessSuffix) { + // Get file + propertiesFile = new File(pName); + + if (propertiesFile.exists()) { + is = new FileInputStream(propertiesFile); + + // If XML, wrap stream + if (pName.endsWith(XML_PROPERTIES)) { + is = new XMLPropertiesInputStream(is); + } + } + } + else { + // Try normal properties + propertiesFile = new File(pName + STD_PROPERTIES); + + if (propertiesFile.exists()) { + is = new FileInputStream(propertiesFile); + } + else { + // Try XML + propertiesFile = new File(pName + XML_PROPERTIES); + + if (propertiesFile.exists()) { + // Wrap stream + is = new XMLPropertiesInputStream(new FileInputStream(propertiesFile)); + } + } + } + } + catch (FileNotFoundException fnf) { + // Should not happen, as we always test that the file .exists() + // before creating InputStream + // assert false; + } + + return is; + } + + /** + * Utility method for loading a named properties-file for a class. + *

+ * The properties-file is loaded through either: + *

    + *
  1. The given class' class loader (from classpath)
  2. + *
  3. Or, the system class loader (from classpath)
  4. + *
  5. Or, if it cannot be found in the classpath, an attempt to read from + * the current directory (or full path if given).
  6. + *
+ *

+ * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties + * are supported (XML-properties must have ".xml" as its file extension). + * + * @param pClass The class to load properties for. If this parameter is + * {@code null}, the method will work exactly as + * {@link #loadProperties(String)} + * @param pName The name of the properties-file. If this parameter is + * {@code null}, the method will work exactly as + * {@link #loadProperties(Class)} + * + * @return A Properties mapping read from the given file or for the given + * class. + * + * @throws NullPointerException if both {@code pName} and + * {@code pClass} paramters are {@code null} + * @throws IOException if an error occurs during load. + * @throws FileNotFoundException if no properties-file could be found. + * + * @see #loadProperties(String) + * @see #loadProperties(Class) + * @see java.lang.ClassLoader#getResourceAsStream + * @see java.lang.ClassLoader#getSystemResourceAsStream + */ + // TODO: Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html + // TODO: Consider using Context Classloader instead? + public static Properties loadProperties(Class pClass, String pName) throws IOException { + // Convert to name the classloader understands + String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/'); + + // Should we try to guess suffix? + boolean guessSuffix = (pName == null || pName.indexOf('.') < 0); + + InputStream is; + + // TODO: WHAT IF MULTIPLE RESOURCES EXISTS?! + // Try loading resource through the current class' classloader + if (pClass != null && (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) { + //&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) { + // Nothing to do + //System.out.println(((is instanceof XMLPropertiesInputStream) ? + // "XML-properties" : "Normal .properties") + // + " from Class' ClassLoader"); + } + // If that fails, try the system classloader + else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(), name, guessSuffix)) != null) { + //else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) { + // Nothing to do + //System.out.println(((is instanceof XMLPropertiesInputStream) ? + // "XML-properties" : "Normal .properties") + // + " from System ClassLoader"); + } + // All failed, try loading from file + else if ((is = getFileAsStream(name, guessSuffix)) != null) { + //System.out.println(((is instanceof XMLPropertiesInputStream) ? + // "XML-properties" : "Normal .properties") + // + " from System ClassLoader"); + } + else { + if (guessSuffix) { + // TODO: file extension iterator or something... + throw new FileNotFoundException(name + ".properties or " + name + ".xml"); + } + else { + throw new FileNotFoundException(name); + } + } + + // We have inputstream now, load... + try { + return loadProperties(is); + } + finally { + // NOTE: If is == null, a FileNotFoundException must have been thrown above + try { + is.close(); + } + catch (IOException ioe) { + // Not critical... + } + } + } + + /** + * Utility method for loading a properties-file for a given class. + * The properties are searched for on the form + * "com/package/ClassName.properties" or + * "com/package/ClassName.xml". + *

+ * The properties-file is loaded through either: + *

    + *
  1. The given class' class loader (from classpath)
  2. + *
  3. Or, the system class loader (from classpath)
  4. + *
  5. Or, if it cannot be found in the classpath, an attempt to read from + * the current directory (or full path if given).
  6. + *
+ *

+ * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties + * are supported (XML-properties must have ".xml" as its file extension). + * + * @param pClass The class to load properties for + * @return A Properties mapping for the given class. + * + * @throws NullPointerException if the {@code pClass} paramters is + * {@code null} + * @throws IOException if an error occurs during load. + * @throws FileNotFoundException if no properties-file could be found. + * + * @see #loadProperties(String) + * @see #loadProperties(Class, String) + * @see java.lang.ClassLoader#getResourceAsStream + * @see java.lang.ClassLoader#getSystemResourceAsStream + * + */ + public static Properties loadProperties(Class pClass) throws IOException { + return loadProperties(pClass, null); + } + + /** + * Utility method for loading a named properties-file. + *

+ * The properties-file is loaded through either: + *

    + *
  1. The system class loader (from classpath)
  2. + *
  3. Or, if it cannot be found in the classpath, an attempt to read from + * the current directory.
  4. + *
+ *

+ * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties + * are supported (XML-properties must have ".xml" as its file extension). + * + * @param pName The name of the properties-file. + * @return A Properties mapping read from the given file. + * + * @throws NullPointerException if the {@code pName} paramters is + * {@code null} + * @throws IOException if an error occurs during load. + * @throws FileNotFoundException if no properties-file could be found. + * + * @see #loadProperties(Class) + * @see #loadProperties(Class, String) + * @see java.lang.ClassLoader#getSystemResourceAsStream + * + */ + public static Properties loadProperties(String pName) throws IOException { + return loadProperties(null, pName); + } + + /* + * Utility method for loading a properties-file. + *

+ * The properties files may also be contained in a zip/jar-file named + * by the {@code com.twelvemonkeys.util.Config} system property (use "java -D" + * to override). Default is "config.zip" in the current directory. + * + * @param pName The name of the file to loaded + * @return A Properties mapping for the given class. If no properties- + * file was found, an empty Properties object is returned. + * + */ + /* + public static Properties loadProperties(String pName) throws IOException { + // Use XML? + boolean useXML = pName.endsWith(XML_PROPERTIES) ? true : false; + + InputStream is = null; + + File file = new File(pName); + + String configName = System.getProperty("com.twelvemonkeys.util.Config"); + File configArchive = new File(!StringUtil.isEmpty(configName) + ? configName : DEFAULT_CONFIG); + + // Get input stream to the file containing the properties + if (file.exists()) { + // Try reading from file, normal way + is = new FileInputStream(file); + } + else if (configArchive.exists()) { + // Try reading properties from zip-file + ZipFile zip = new ZipFile(configArchive); + ZipEntry ze = zip.getEntry(pName); + if (ze != null) { + is = zip.getInputStream(ze); + } + + } + + // Do the loading + try { + // Load the properties + return loadProperties(is, useXML); + } + finally { + // Try closing the archive to free resources + if (is != null) { + try { + is.close(); + } + catch (IOException ioe) { + // Not critical... + } + } + } + + } + */ + + /** + * Returns a Properties, loaded from the given inputstream. If the given + * inputstream is null, then an empty Properties object is returned. + * + * @param pInput the inputstream to read from + * + * @return a Properties object read from the given stream, or an empty + * Properties mapping, if the stream is null. + * + * @throws IOException if an error occurred when reading from the input + * stream. + * + */ + private static Properties loadProperties(InputStream pInput) + throws IOException { + + if (pInput == null) { + throw new IllegalArgumentException("InputStream == null!"); + } + + Properties mapping = new Properties(); + /*if (pInput instanceof XMLPropertiesInputStream) { + mapping = new XMLProperties(); + } + else { + mapping = new Properties(); + }*/ + + // Load the properties + mapping.load(pInput); + + return mapping; + } + + @SuppressWarnings({"SuspiciousSystemArraycopy"}) + public static Object clone(final Cloneable pObject) throws CloneNotSupportedException { + if (pObject == null) { + return null; // Null is clonable.. Easy. ;-) + } + + // All arrays does have a clone method, but it's invisible for reflection... + // By luck, multi-dimensional primitive arrays are instances of Object[] + if (pObject instanceof Object[]) { + return ((Object[]) pObject).clone(); + } + else if (pObject.getClass().isArray()) { + // One-dimensional primitive array, cloned manually + int lenght = Array.getLength(pObject); + Object clone = Array.newInstance(pObject.getClass().getComponentType(), lenght); + System.arraycopy(pObject, 0, clone, 0, lenght); + return clone; + } + + try { + // Find the clone method + Method clone = null; + Class clazz = pObject.getClass(); + do { + try { + clone = clazz.getDeclaredMethod("clone"); + break; // Found, or throws exception above + } + catch (NoSuchMethodException ignore) { + // Ignore + } + } + while ((clazz = clazz.getSuperclass()) != null); + + // NOTE: This should never happen + if (clone == null) { + throw new CloneNotSupportedException(pObject.getClass().getName()); + } + + // Override access if needed + if (!clone.isAccessible()) { + clone.setAccessible(true); + } + + // Invoke clone method on original object + return clone.invoke(pObject); + } + catch (SecurityException e) { + CloneNotSupportedException cns = new CloneNotSupportedException(pObject.getClass().getName()); + cns.initCause(e); + throw cns; + } + catch (IllegalAccessException e) { + throw new CloneNotSupportedException(pObject.getClass().getName()); + } + catch (InvocationTargetException e) { + if (e.getTargetException() instanceof CloneNotSupportedException) { + throw (CloneNotSupportedException) e.getTargetException(); + } + else if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } + else if (e.getTargetException() instanceof Error) { + throw (Error) e.getTargetException(); + } + + throw new CloneNotSupportedException(pObject.getClass().getName()); + } + } + +// public static void loadLibrary(String pLibrary) { +// NativeLoader.loadLibrary(pLibrary); +// } +// +// public static void loadLibrary(String pLibrary, ClassLoader pLoader) { +// NativeLoader.loadLibrary(pLibrary, pLoader); +// } + + public static void main(String[] args) throws CloneNotSupportedException { + + System.out.println("clone: " + args.clone().length + " (" + args.length + ")"); + System.out.println("copy: " + ((String[]) clone(args)).length + " (" + args.length + ")"); + + int[] ints = {1,2,3}; + int[] copies = (int[]) clone(ints); + System.out.println("Copies: " + copies.length + " (" + ints.length + ")"); + + int[][] intsToo = {{1}, {2,3}, {4,5,6}}; + int[][] copiesToo = (int[][]) clone(intsToo); + System.out.println("Copies: " + copiesToo.length + " (" + intsToo.length + ")"); + System.out.println("Copies0: " + copiesToo[0].length + " (" + intsToo[0].length + ")"); + System.out.println("Copies1: " + copiesToo[1].length + " (" + intsToo[1].length + ")"); + System.out.println("Copies2: " + copiesToo[2].length + " (" + intsToo[2].length + ")"); + + Map map = new HashMap(); + + for (String arg : args) { + map.put(arg, arg); + } + + Map copy = (Map) clone((Cloneable) map); + + System.out.println("Map : " + map); + System.out.println("Copy: " + copy); + + /* + SecurityManager sm = System.getSecurityManager(); + + try { + System.setSecurityManager(new SecurityManager() { + public void checkPermission(Permission perm) { + if (perm.getName().equals("suppressAccessChecks")) { + throw new SecurityException(); + } + //super.checkPermission(perm); + } + }); + */ + + Cloneable cloneable = new Cloneable() {}; // No public clone method + Cloneable clone = (Cloneable) clone(cloneable); + + System.out.println("cloneable: " + cloneable); + System.out.println("clone: " + clone); + + /* + } + finally { + System.setSecurityManager(sm); + } + */ + + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + return null; + } + }, AccessController.getContext()); + + //String string = args.length > 0 ? args[0] : "jaffa"; + //clone(string); + } + + /** + * Tests if a named class is generally available. + * If a class is considered available, a call to + * {@code Class.forName(pClassName)} will not result in an exception. + * + * @param pClassName the class name to test + * @return {@code true} if available + */ + public static boolean isClassAvailable(String pClassName) { + return isClassAvailable(pClassName, (ClassLoader) null); + } + + /** + * Tests if a named class is available from another class. + * If a class is considered available, a call to + * {@code Class.forName(pClassName, true, pFromClass.getClassLoader())} + * will not result in an exception. + * + * @param pClassName the class name to test + * @param pFromClass the class to test from + * @return {@code true} if available + */ + public static boolean isClassAvailable(String pClassName, Class pFromClass) { + ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null; + return isClassAvailable(pClassName, loader); + } + + private static boolean isClassAvailable(String pClassName, ClassLoader pLoader) { + try { + // TODO: Sometimes init is not needed, but need to find a way to know... + getClass(pClassName, true, pLoader); + return true; + } + catch (SecurityException ignore) { + // Ignore + } + catch (ClassNotFoundException ignore) { + // Ignore + } + catch (LinkageError ignore) { + // Ignore + } + + return false; + } + + public static boolean isFieldAvailable(final String pClassName, final String pFieldName) { + return isFieldAvailable(pClassName, pFieldName, (ClassLoader) null); + } + + public static boolean isFieldAvailable(final String pClassName, final String pFieldName, final Class pFromClass) { + ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null; + return isFieldAvailable(pClassName, pFieldName, loader); + } + + private static boolean isFieldAvailable(final String pClassName, final String pFieldName, final ClassLoader pLoader) { + try { + Class cl = getClass(pClassName, false, pLoader); + + Field field = cl.getField(pFieldName); + if (field != null) { + return true; + } + } + catch (ClassNotFoundException ignore) { + // Ignore + } + catch (LinkageError ignore) { + // Ignore + } + catch (NoSuchFieldException ignore) { + // Ignore + } + return false; + } + + public static boolean isMethodAvailable(String pClassName, String pMethodName) { + // Finds void only + return isMethodAvailable(pClassName, pMethodName, null, (ClassLoader) null); + } + + public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams) { + return isMethodAvailable(pClassName, pMethodName, pParams, (ClassLoader) null); + } + + public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, Class pFromClass) { + ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null; + return isMethodAvailable(pClassName, pMethodName, pParams, loader); + } + + private static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, ClassLoader pLoader) { + try { + Class cl = getClass(pClassName, false, pLoader); + + Method method = cl.getMethod(pMethodName, pParams); + if (method != null) { + return true; + } + } + catch (ClassNotFoundException ignore) { + // Ignore + } + catch (LinkageError ignore) { + // Ignore + } + catch (NoSuchMethodException ignore) { + // Ignore + } + return false; + } + + private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException { + // NOTE: We need the context class loader, as SystemUtil's + // class loader may have a totally different class loader than + // the original caller class (as in Class.forName(cn, false, null)). + ClassLoader loader = pLoader != null ? pLoader : + Thread.currentThread().getContextClassLoader(); + + return Class.forName(pClassName, pInitialize, loader); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java index 16897f31..6c61981d 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java @@ -1,3 +1,33 @@ +/* + * 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.lang; import java.util.Arrays; @@ -6,10 +36,11 @@ import java.util.Map; /** * Kind of like {@code org.apache.commons.lang.Validate}. Just smarter. ;-) - *

+ *

* Uses type parameterized return values, thus making it possible to check * constructor arguments before * they are passed on to {@code super} or {@code this} type constructors. + *

* * @author Harald Kuhr * @author last modified by $Author: haku $ diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/package_info.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/package_info.java index ced3971b..92c3c925 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/package_info.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/package_info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** *Contains utils/helpers for classes in {@code java.lang}. */ diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java index ab4e4fb7..bb144842 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java @@ -1,400 +1,404 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.*; -import java.io.Serializable; - -/** - * AbstractDecoratedMap - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java#2 $ - */ -// TODO: The generics in this class looks suspicious.. -abstract class AbstractDecoratedMap extends AbstractMap implements Map, Serializable, Cloneable { - protected Map> entries; - protected transient volatile int modCount; - - private transient volatile Set> entrySet = null; - private transient volatile Set keySet = null; - private transient volatile Collection values = null; - - /** - * Creates a {@code Map} backed by a {@code HashMap}. - */ - public AbstractDecoratedMap() { - this(new HashMap>(), null); - } - - /** - * Creates a {@code Map} backed by a {@code HashMap}, containing all - * key/value mappings from the given {@code Map}. - *

- * This is constructor is here to comply with the reccomendations for - * "standard" constructors in the {@code Map} interface. - * - * @see #AbstractDecoratedMap(java.util.Map, java.util.Map) - * - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - */ - public AbstractDecoratedMap(Map pContents) { - this(new HashMap>(), pContents); - } - - /** - * Creates a {@code Map} backed by the given backing-{@code Map}, - * containing all key/value mappings from the given contents-{@code Map}. - *

- * NOTE: The backing map is structuraly cahnged, and it should NOT be - * accessed directly, after the wrapped map is created. - * - * @param pBacking the backing map of this map. Must be either empty, or - * the same map as {@code pContents}. - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - * - * @throws IllegalArgumentException if {@code pBacking} is {@code null} - * or if {@code pBacking} differs from {@code pContent} and is not empty. - */ - public AbstractDecoratedMap(Map> pBacking, Map pContents) { - if (pBacking == null) { - throw new IllegalArgumentException("backing == null"); - } - - Entry[] entries = null; - if (pBacking == pContents) { - // NOTE: Special treatment to avoid ClassCastExceptions - Set> es = pContents.entrySet(); - //noinspection unchecked - entries = new Entry[es.size()]; - entries = es.toArray(entries); - pContents = null; - pBacking.clear(); - } - else if (!pBacking.isEmpty()) { - throw new IllegalArgumentException("backing must be empty"); - } - - this.entries = pBacking; - init(); - - if (pContents != null) { - putAll(pContents); - } - else if (entries != null) { - // Reinsert entries, this time wrapped - for (Entry entry : entries) { - put(entry.getKey(), entry.getValue()); - } - } - } - - /** - * Default implementation, does nothing. - */ - protected void init() { - } - - public int size() { - return entries.size(); - } - - public void clear() { - entries.clear(); - modCount++; - init(); - } - - public boolean isEmpty() { - return entries.isEmpty(); - } - - public boolean containsKey(Object pKey) { - return entries.containsKey(pKey); - } - - /** - * Returns {@code true} if this map maps one or more keys to the - * specified pValue. More formally, returns {@code true} if and only if - * this map contains at least one mapping to a pValue {@code v} such that - * {@code (pValue==null ? v==null : pValue.equals(v))}. - *

- * This implementation requires time linear in the map size for this - * operation. - * - * @param pValue pValue whose presence in this map is to be tested. - * @return {@code true} if this map maps one or more keys to the - * specified pValue. - */ - public boolean containsValue(Object pValue) { - for (V value : values()) { - if (value == pValue || (value != null && value.equals(pValue))) { - return true; - } - } - - return false; - } - - public Collection values() { - Collection values = this.values; - return values != null ? values : (this.values = new Values()); - } - - public Set> entrySet() { - Set> es = entrySet; - return es != null ? es : (entrySet = new EntrySet()); - } - - public Set keySet() { - Set ks = keySet; - return ks != null ? ks : (keySet = new KeySet()); - } - - /** - * Returns a shallow copy of this {@code AbstractMap} instance: the keys - * and values themselves are not cloned. - * - * @return a shallow copy of this map. - */ - protected Object clone() throws CloneNotSupportedException { - AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone(); - - map.values = null; - map.entrySet = null; - map.keySet = null; - - // TODO: Implement: Need to clone the backing map... - - return map; - } - - // Subclass overrides these to alter behavior of views' iterator() method - protected abstract Iterator newKeyIterator(); - - protected abstract Iterator newValueIterator(); - - protected abstract Iterator> newEntryIterator(); - - // TODO: Implement these (get/put/remove)? - public abstract V get(Object pKey); - - public abstract V remove(Object pKey); - - public abstract V put(K pKey, V pValue); - - /*protected*/ Entry createEntry(K pKey, V pValue) { - return new BasicEntry(pKey, pValue); - } - - /*protected*/ Entry getEntry(K pKey) { - return entries.get(pKey); - } - - /** - * Removes the given entry from the Map. - * - * @param pEntry the entry to be removed - * - * @return the removed entry, or {@code null} if nothing was removed. - */ - protected Entry removeEntry(Entry pEntry) { - if (pEntry == null) { - return null; - } - - // Find candidate entry for this key - Entry candidate = getEntry(pEntry.getKey()); - if (candidate == pEntry || (candidate != null && candidate.equals(pEntry))) { - // Remove - remove(pEntry.getKey()); - return pEntry; - } - return null; - } - - protected class Values extends AbstractCollection { - public Iterator iterator() { - return newValueIterator(); - } - - public int size() { - return AbstractDecoratedMap.this.size(); - } - - public boolean contains(Object o) { - return containsValue(o); - } - - public void clear() { - AbstractDecoratedMap.this.clear(); - } - } - - protected class EntrySet extends AbstractSet> { - public Iterator> iterator() { - return newEntryIterator(); - } - - public boolean contains(Object o) { - if (!(o instanceof Entry)) - return false; - Entry e = (Entry) o; - - //noinspection SuspiciousMethodCalls - Entry candidate = entries.get(e.getKey()); - return candidate != null && candidate.equals(e); - } - - public boolean remove(Object o) { - if (!(o instanceof Entry)) { - return false; - } - - /* - // NOTE: Extra cautions is taken, to only remove the entry if it - // equals the entry in the map - Object key = ((Entry) o).getKey(); - Entry entry = (Entry) entries.get(key); - - // Same entry? - if (entry != null && entry.equals(o)) { - return AbstractWrappedMap.this.remove(key) != null; - } - - return false; - */ - - //noinspection unchecked - return AbstractDecoratedMap.this.removeEntry((Entry) o) != null; - } - - public int size() { - return AbstractDecoratedMap.this.size(); - } - - public void clear() { - AbstractDecoratedMap.this.clear(); - } - } - - protected class KeySet extends AbstractSet { - public Iterator iterator() { - return newKeyIterator(); - } - public int size() { - return AbstractDecoratedMap.this.size(); - } - public boolean contains(Object o) { - return containsKey(o); - } - public boolean remove(Object o) { - return AbstractDecoratedMap.this.remove(o) != null; - } - public void clear() { - AbstractDecoratedMap.this.clear(); - } - } - - /** - * A simple Map.Entry implementaton. - */ - static class BasicEntry implements Entry, Serializable { - K mKey; - V mValue; - - BasicEntry(K pKey, V pValue) { - mKey = pKey; - mValue = pValue; - } - - /** - * Default implementation does nothing. - * - * @param pMap the map that is accessed - */ - protected void recordAccess(Map pMap) { - } - - /** - * Default implementation does nothing. - * @param pMap the map that is removed from - */ - protected void recordRemoval(Map pMap) { - } - - public V getValue() { - return mValue; - } - - public V setValue(V pValue) { - V oldValue = mValue; - mValue = pValue; - return oldValue; - } - - public K getKey() { - return mKey; - } - - public boolean equals(Object pOther) { - if (!(pOther instanceof Map.Entry)) { - return false; - } - - Map.Entry entry = (Map.Entry) pOther; - - Object k1 = mKey; - Object k2 = entry.getKey(); - - if (k1 == k2 || (k1 != null && k1.equals(k2))) { - Object v1 = mValue; - Object v2 = entry.getValue(); - - if (v1 == v2 || (v1 != null && v1.equals(v2))) { - return true; - } - } - - return false; - } - - public int hashCode() { - return (mKey == null ? 0 : mKey.hashCode()) ^ - (mValue == null ? 0 : mValue.hashCode()); - } - - public String toString() { - return getKey() + "=" + getValue(); - } - } +/* + * 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.util; + +import java.io.Serializable; +import java.util.*; + +/** + * AbstractDecoratedMap + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java#2 $ + */ +// TODO: The generics in this class looks suspicious.. +abstract class AbstractDecoratedMap extends AbstractMap implements Map, Serializable, Cloneable { + protected Map> entries; + protected transient volatile int modCount; + + private transient volatile Set> entrySet = null; + private transient volatile Set keySet = null; + private transient volatile Collection values = null; + + /** + * Creates a {@code Map} backed by a {@code HashMap}. + */ + public AbstractDecoratedMap() { + this(new HashMap>(), null); + } + + /** + * Creates a {@code Map} backed by a {@code HashMap}, containing all + * key/value mappings from the given {@code Map}. + *

+ * This is constructor is here to comply with the reccomendations for + * "standard" constructors in the {@code Map} interface. + *

+ * + * @see #AbstractDecoratedMap(java.util.Map, java.util.Map) + * + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + */ + public AbstractDecoratedMap(Map pContents) { + this(new HashMap>(), pContents); + } + + /** + * Creates a {@code Map} backed by the given backing-{@code Map}, + * containing all key/value mappings from the given contents-{@code Map}. + *

+ * NOTE: The backing map is structuraly cahnged, and it should NOT be + * accessed directly, after the wrapped map is created. + *

+ * + * @param pBacking the backing map of this map. Must be either empty, or + * the same map as {@code pContents}. + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + * + * @throws IllegalArgumentException if {@code pBacking} is {@code null} + * or if {@code pBacking} differs from {@code pContent} and is not empty. + */ + public AbstractDecoratedMap(Map> pBacking, Map pContents) { + if (pBacking == null) { + throw new IllegalArgumentException("backing == null"); + } + + Entry[] entries = null; + if (pBacking == pContents) { + // NOTE: Special treatment to avoid ClassCastExceptions + Set> es = pContents.entrySet(); + //noinspection unchecked + entries = new Entry[es.size()]; + entries = es.toArray(entries); + pContents = null; + pBacking.clear(); + } + else if (!pBacking.isEmpty()) { + throw new IllegalArgumentException("backing must be empty"); + } + + this.entries = pBacking; + init(); + + if (pContents != null) { + putAll(pContents); + } + else if (entries != null) { + // Reinsert entries, this time wrapped + for (Entry entry : entries) { + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + * Default implementation, does nothing. + */ + protected void init() { + } + + public int size() { + return entries.size(); + } + + public void clear() { + entries.clear(); + modCount++; + init(); + } + + public boolean isEmpty() { + return entries.isEmpty(); + } + + public boolean containsKey(Object pKey) { + return entries.containsKey(pKey); + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified pValue. More formally, returns {@code true} if and only if + * this map contains at least one mapping to a pValue {@code v} such that + * {@code (pValue==null ? v==null : pValue.equals(v))}. + *

+ * This implementation requires time linear in the map size for this + * operation. + *

+ * + * @param pValue pValue whose presence in this map is to be tested. + * @return {@code true} if this map maps one or more keys to the + * specified pValue. + */ + public boolean containsValue(Object pValue) { + for (V value : values()) { + if (value == pValue || (value != null && value.equals(pValue))) { + return true; + } + } + + return false; + } + + public Collection values() { + Collection values = this.values; + return values != null ? values : (this.values = new Values()); + } + + public Set> entrySet() { + Set> es = entrySet; + return es != null ? es : (entrySet = new EntrySet()); + } + + public Set keySet() { + Set ks = keySet; + return ks != null ? ks : (keySet = new KeySet()); + } + + /** + * Returns a shallow copy of this {@code AbstractMap} instance: the keys + * and values themselves are not cloned. + * + * @return a shallow copy of this map. + */ + protected Object clone() throws CloneNotSupportedException { + AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone(); + + map.values = null; + map.entrySet = null; + map.keySet = null; + + // TODO: Implement: Need to clone the backing map... + + return map; + } + + // Subclass overrides these to alter behavior of views' iterator() method + protected abstract Iterator newKeyIterator(); + + protected abstract Iterator newValueIterator(); + + protected abstract Iterator> newEntryIterator(); + + // TODO: Implement these (get/put/remove)? + public abstract V get(Object pKey); + + public abstract V remove(Object pKey); + + public abstract V put(K pKey, V pValue); + + /*protected*/ Entry createEntry(K pKey, V pValue) { + return new BasicEntry(pKey, pValue); + } + + /*protected*/ Entry getEntry(K pKey) { + return entries.get(pKey); + } + + /** + * Removes the given entry from the Map. + * + * @param pEntry the entry to be removed + * + * @return the removed entry, or {@code null} if nothing was removed. + */ + protected Entry removeEntry(Entry pEntry) { + if (pEntry == null) { + return null; + } + + // Find candidate entry for this key + Entry candidate = getEntry(pEntry.getKey()); + if (candidate == pEntry || (candidate != null && candidate.equals(pEntry))) { + // Remove + remove(pEntry.getKey()); + return pEntry; + } + return null; + } + + protected class Values extends AbstractCollection { + public Iterator iterator() { + return newValueIterator(); + } + + public int size() { + return AbstractDecoratedMap.this.size(); + } + + public boolean contains(Object o) { + return containsValue(o); + } + + public void clear() { + AbstractDecoratedMap.this.clear(); + } + } + + protected class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return newEntryIterator(); + } + + public boolean contains(Object o) { + if (!(o instanceof Entry)) + return false; + Entry e = (Entry) o; + + //noinspection SuspiciousMethodCalls + Entry candidate = entries.get(e.getKey()); + return candidate != null && candidate.equals(e); + } + + public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + + /* + // NOTE: Extra cautions is taken, to only remove the entry if it + // equals the entry in the map + Object key = ((Entry) o).getKey(); + Entry entry = (Entry) entries.get(key); + + // Same entry? + if (entry != null && entry.equals(o)) { + return AbstractWrappedMap.this.remove(key) != null; + } + + return false; + */ + + //noinspection unchecked + return AbstractDecoratedMap.this.removeEntry((Entry) o) != null; + } + + public int size() { + return AbstractDecoratedMap.this.size(); + } + + public void clear() { + AbstractDecoratedMap.this.clear(); + } + } + + protected class KeySet extends AbstractSet { + public Iterator iterator() { + return newKeyIterator(); + } + public int size() { + return AbstractDecoratedMap.this.size(); + } + public boolean contains(Object o) { + return containsKey(o); + } + public boolean remove(Object o) { + return AbstractDecoratedMap.this.remove(o) != null; + } + public void clear() { + AbstractDecoratedMap.this.clear(); + } + } + + /** + * A simple Map.Entry implementation. + */ + static class BasicEntry implements Entry, Serializable { + K mKey; + V mValue; + + BasicEntry(K pKey, V pValue) { + mKey = pKey; + mValue = pValue; + } + + /** + * Default implementation does nothing. + * + * @param pMap the map that is accessed + */ + protected void recordAccess(Map pMap) { + } + + /** + * Default implementation does nothing. + * @param pMap the map that is removed from + */ + protected void recordRemoval(Map pMap) { + } + + public V getValue() { + return mValue; + } + + public V setValue(V pValue) { + V oldValue = mValue; + mValue = pValue; + return oldValue; + } + + public K getKey() { + return mKey; + } + + public boolean equals(Object pOther) { + if (!(pOther instanceof Map.Entry)) { + return false; + } + + Map.Entry entry = (Map.Entry) pOther; + + Object k1 = mKey; + Object k2 = entry.getKey(); + + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + Object v1 = mValue; + Object v2 = entry.getValue(); + + if (v1 == v2 || (v1 != null && v1.equals(v2))) { + return true; + } + } + + return false; + } + + public int hashCode() { + return (mKey == null ? 0 : mKey.hashCode()) ^ + (mValue == null ? 0 : mValue.hashCode()); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java index 90e56161..bd4e284c 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java @@ -1,87 +1,88 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -/** - * Abstract base class for {@code TokenIterator}s to extend. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java#1 $ - */ -public abstract class AbstractTokenIterator implements TokenIterator { - - /** - * Not supported. - * - * @throws UnsupportedOperationException {@code remove} is not supported by - * this Iterator. - */ - public void remove() { - // TODO: This is not difficult: - // - Convert String to StringBuilder in constructor - // - delete(pos, next.lenght()) - // - Add toString() method - // BUT: Would it ever be useful? :-) - - throw new UnsupportedOperationException("remove"); - } - - public final boolean hasMoreTokens() { - return hasNext(); - } - - /** - * Returns the next element in the iteration as a {@code String}. - * This implementation simply returns {@code (String) next()}. - * - * @return the next element in the iteration. - * @exception java.util.NoSuchElementException iteration has no more elements. - * @see #next() - */ - public final String nextToken() { - return next(); - } - - /** - * This implementation simply returns {@code hasNext()}. - * @see #hasNext() - */ - public final boolean hasMoreElements() { - return hasNext(); - } - - /** - * This implementation simply returns {@code next()}. - * @see #next() - */ - public final String nextElement() { - return next(); - } -} +/* + * 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.util; + +/** + * Abstract base class for {@code TokenIterator}s to extend. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java#1 $ + */ +public abstract class AbstractTokenIterator implements TokenIterator { + + /** + * Not supported. + * + * @throws UnsupportedOperationException {@code remove} is not supported by + * this Iterator. + */ + public void remove() { + // TODO: This is not difficult: + // - Convert String to StringBuilder in constructor + // - delete(pos, next.lenght()) + // - Add toString() method + // BUT: Would it ever be useful? :-) + + throw new UnsupportedOperationException("remove"); + } + + public final boolean hasMoreTokens() { + return hasNext(); + } + + /** + * Returns the next element in the iteration as a {@code String}. + * This implementation simply returns {@code (String) next()}. + * + * @return the next element in the iteration. + * @exception java.util.NoSuchElementException iteration has no more elements. + * @see #next() + */ + public final String nextToken() { + return next(); + } + + /** + * This implementation simply returns {@code hasNext()}. + * @see #hasNext() + */ + public final boolean hasMoreElements() { + return hasNext(); + } + + /** + * This implementation simply returns {@code next()}. + * @see #next() + */ + public final String nextElement() { + return next(); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java index 8f830c00..6dd0edbe 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java @@ -1,245 +1,248 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.beans.IndexedPropertyDescriptor; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; -import java.io.Serializable; - -/** - * A {@code Map} adapter for a Java Bean. - *

- * Ruthlessly stolen from - * initDescriptors(Object pBean) throws IntrospectionException { - final Set descriptors = new HashSet(); - - PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(pBean.getClass()).getPropertyDescriptors(); - for (PropertyDescriptor descriptor : propertyDescriptors) { - // Skip Object.getClass(), as you probably never want it - if ("class".equals(descriptor.getName()) && descriptor.getPropertyType() == Class.class) { - continue; - } - - // Only support simple setter/getters. - if (!(descriptor instanceof IndexedPropertyDescriptor)) { - descriptors.add(descriptor); - } - } - - return Collections.unmodifiableSet(descriptors); - } - - public Set> entrySet() { - return new BeanSet(); - } - - public Object get(final Object pKey) { - return super.get(pKey); - } - - public Object put(final String pKey, final Object pValue) { - checkKey(pKey); - - for (Entry entry : entrySet()) { - if (entry.getKey().equals(pKey)) { - return entry.setValue(pValue); - } - } - - return null; - } - - public Object remove(final Object pKey) { - return super.remove(checkKey(pKey)); - } - - public int size() { - return descriptors.size(); - } - - private String checkKey(final Object pKey) { - if (pKey == null) { - throw new IllegalArgumentException("key == null"); - } - // NB - the cast forces CCE if key is the wrong type. - final String name = (String) pKey; - - if (!containsKey(name)) { - throw new IllegalArgumentException("Bad key: " + pKey); - } - - return name; - } - - private Object readResolve() throws IntrospectionException { - // Initialize the property descriptors - descriptors = initDescriptors(bean); - return this; - } - - private class BeanSet extends AbstractSet> { - public Iterator> iterator() { - return new BeanIterator(descriptors.iterator()); - } - - public int size() { - return descriptors.size(); - } - } - - private class BeanIterator implements Iterator> { - private final Iterator mIterator; - - public BeanIterator(final Iterator pIterator) { - mIterator = pIterator; - } - - public boolean hasNext() { - return mIterator.hasNext(); - } - - public BeanEntry next() { - return new BeanEntry(mIterator.next()); - } - - public void remove() { - mIterator.remove(); - } - } - - private class BeanEntry implements Entry { - private final PropertyDescriptor mDescriptor; - - public BeanEntry(final PropertyDescriptor pDescriptor) { - this.mDescriptor = pDescriptor; - } - - public String getKey() { - return mDescriptor.getName(); - } - - public Object getValue() { - return unwrap(new Wrapped() { - public Object run() throws IllegalAccessException, InvocationTargetException { - final Method method = mDescriptor.getReadMethod(); - // A write-only bean. - if (method == null) { - throw new UnsupportedOperationException("No getter: " + mDescriptor.getName()); - } - - return method.invoke(bean); - } - }); - } - - public Object setValue(final Object pValue) { - return unwrap(new Wrapped() { - public Object run() throws IllegalAccessException, InvocationTargetException { - final Method method = mDescriptor.getWriteMethod(); - // A read-only bean. - if (method == null) { - throw new UnsupportedOperationException("No write method for property: " + mDescriptor.getName()); - } - - final Object old = getValue(); - method.invoke(bean, pValue); - return old; - } - }); - } - - public boolean equals(Object pOther) { - if (!(pOther instanceof Map.Entry)) { - return false; - } - - Map.Entry entry = (Map.Entry) pOther; - - Object k1 = getKey(); - Object k2 = entry.getKey(); - - if (k1 == k2 || (k1 != null && k1.equals(k2))) { - Object v1 = getValue(); - Object v2 = entry.getValue(); - - if (v1 == v2 || (v1 != null && v1.equals(v2))) { - return true; - } - } - - return false; - } - - public int hashCode() { - return (getKey() == null ? 0 : getKey().hashCode()) ^ - (getValue() == null ? 0 : getValue().hashCode()); - } - - public String toString() { - return getKey() + "=" + getValue(); - } - } - - private static interface Wrapped { - Object run() throws IllegalAccessException, InvocationTargetException; - } - - private static Object unwrap(final Wrapped wrapped) { - try { - return wrapped.run(); - } - catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - catch (final InvocationTargetException e) { - // Javadocs for setValue indicate cast is ok. - throw (RuntimeException) e.getCause(); - } - } +/* + * 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.util; + +import java.beans.IndexedPropertyDescriptor; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +/** + * A {@code Map} adapter for a Java Bean. + *

+ * Ruthlessly stolen from + * Binkley's Blog + *

+ */ +public final class BeanMap extends AbstractMap implements Serializable, Cloneable { + private final Object bean; + private transient Set descriptors; + + public BeanMap(Object pBean) throws IntrospectionException { + if (pBean == null) { + throw new IllegalArgumentException("bean == null"); + } + + bean = pBean; + descriptors = initDescriptors(pBean); + } + + private static Set initDescriptors(Object pBean) throws IntrospectionException { + final Set descriptors = new HashSet(); + + PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(pBean.getClass()).getPropertyDescriptors(); + for (PropertyDescriptor descriptor : propertyDescriptors) { + // Skip Object.getClass(), as you probably never want it + if ("class".equals(descriptor.getName()) && descriptor.getPropertyType() == Class.class) { + continue; + } + + // Only support simple setter/getters. + if (!(descriptor instanceof IndexedPropertyDescriptor)) { + descriptors.add(descriptor); + } + } + + return Collections.unmodifiableSet(descriptors); + } + + public Set> entrySet() { + return new BeanSet(); + } + + public Object get(final Object pKey) { + return super.get(pKey); + } + + public Object put(final String pKey, final Object pValue) { + checkKey(pKey); + + for (Entry entry : entrySet()) { + if (entry.getKey().equals(pKey)) { + return entry.setValue(pValue); + } + } + + return null; + } + + public Object remove(final Object pKey) { + return super.remove(checkKey(pKey)); + } + + public int size() { + return descriptors.size(); + } + + private String checkKey(final Object pKey) { + if (pKey == null) { + throw new IllegalArgumentException("key == null"); + } + // NB - the cast forces CCE if key is the wrong type. + final String name = (String) pKey; + + if (!containsKey(name)) { + throw new IllegalArgumentException("Bad key: " + pKey); + } + + return name; + } + + private Object readResolve() throws IntrospectionException { + // Initialize the property descriptors + descriptors = initDescriptors(bean); + return this; + } + + private class BeanSet extends AbstractSet> { + public Iterator> iterator() { + return new BeanIterator(descriptors.iterator()); + } + + public int size() { + return descriptors.size(); + } + } + + private class BeanIterator implements Iterator> { + private final Iterator mIterator; + + public BeanIterator(final Iterator pIterator) { + mIterator = pIterator; + } + + public boolean hasNext() { + return mIterator.hasNext(); + } + + public BeanEntry next() { + return new BeanEntry(mIterator.next()); + } + + public void remove() { + mIterator.remove(); + } + } + + private class BeanEntry implements Entry { + private final PropertyDescriptor mDescriptor; + + public BeanEntry(final PropertyDescriptor pDescriptor) { + this.mDescriptor = pDescriptor; + } + + public String getKey() { + return mDescriptor.getName(); + } + + public Object getValue() { + return unwrap(new Wrapped() { + public Object run() throws IllegalAccessException, InvocationTargetException { + final Method method = mDescriptor.getReadMethod(); + // A write-only bean. + if (method == null) { + throw new UnsupportedOperationException("No getter: " + mDescriptor.getName()); + } + + return method.invoke(bean); + } + }); + } + + public Object setValue(final Object pValue) { + return unwrap(new Wrapped() { + public Object run() throws IllegalAccessException, InvocationTargetException { + final Method method = mDescriptor.getWriteMethod(); + // A read-only bean. + if (method == null) { + throw new UnsupportedOperationException("No write method for property: " + mDescriptor.getName()); + } + + final Object old = getValue(); + method.invoke(bean, pValue); + return old; + } + }); + } + + public boolean equals(Object pOther) { + if (!(pOther instanceof Map.Entry)) { + return false; + } + + Map.Entry entry = (Map.Entry) pOther; + + Object k1 = getKey(); + Object k2 = entry.getKey(); + + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + Object v1 = getValue(); + Object v2 = entry.getValue(); + + if (v1 == v2 || (v1 != null && v1.equals(v2))) { + return true; + } + } + + return false; + } + + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } + + private static interface Wrapped { + Object run() throws IllegalAccessException, InvocationTargetException; + } + + private static Object unwrap(final Wrapped wrapped) { + try { + return wrapped.run(); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (final InvocationTargetException e) { + // Javadocs for setValue indicate cast is ok. + throw (RuntimeException) e.getCause(); + } + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java index 63e205ba..b85b892a 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java @@ -1,633 +1,637 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import com.twelvemonkeys.lang.Validate; - -import java.lang.reflect.Array; -import java.util.*; - -import static com.twelvemonkeys.lang.Validate.isTrue; -import static com.twelvemonkeys.lang.Validate.notNull; - -/** - * A utility class with some useful collection-related functions. - * - * @author Harald Kuhr - * @author Eirik Torske - * @author last modified by $Author: haku $ - * @version $Id: com/twelvemonkeys/util/CollectionUtil.java#3 $ - * @see Collections - * @see Arrays - */ -public final class CollectionUtil { - - /** - * Testing only. - * - * @param pArgs command line arguents - */ - @SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"}) - public static void main(String[] pArgs) { - int howMany = 1000; - - if (pArgs.length > 0) { - howMany = Integer.parseInt(pArgs[0]); - } - long start; - long end; - - /* - int[] intArr1 = new int[] { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - }; - int[] intArr2 = new int[] { - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 - }; - start = System.currentTimeMillis(); - - for (int i = 0; i < howMany; i++) { - intArr1 = (int[]) mergeArrays(intArr1, 0, intArr1.length, intArr2, 0, intArr2.length); - - } - end = System.currentTimeMillis(); - - System.out.println("mergeArrays: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length - + ")"); - */ - - //////////////////////////////// - String[] stringArr1 = new String[]{ - "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" - }; - - /* - String[] stringArr2 = new String[] { - "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" - }; - - start = System.currentTimeMillis(); - for (int i = 0; i < howMany; i++) { - stringArr1 = (String[]) mergeArrays(stringArr1, 0, stringArr1.length, stringArr2, 0, stringArr2.length); - - } - end = System.currentTimeMillis(); - System.out.println("mergeArrays: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds (" - + stringArr1.length + ")"); - - - start = System.currentTimeMillis(); - while (intArr1.length > stringArr2.length) { - intArr1 = (int[]) subArray(intArr1, 0, intArr1.length - stringArr2.length); - - } - end = System.currentTimeMillis(); - - System.out.println("subArray: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length - + ")"); - - start = System.currentTimeMillis(); - while (stringArr1.length > stringArr2.length) { - stringArr1 = (String[]) subArray(stringArr1, stringArr2.length); - - } - end = System.currentTimeMillis(); - System.out.println("subArray: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds (" - + stringArr1.length + ")"); - - */ - stringArr1 = new String[]{ - "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", - "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" - }; - System.out.println("\nFilterIterators:\n"); - List list = Arrays.asList(stringArr1); - Iterator iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() { - - public boolean accept(Object pElement) { - return ((String) pElement).length() > 5; - } - }); - - while (iter.hasNext()) { - String str = (String) iter.next(); - - System.out.println(str + " has more than 5 letters!"); - } - iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() { - - public boolean accept(Object pElement) { - return ((String) pElement).length() <= 5; - } - }); - - while (iter.hasNext()) { - String str = (String) iter.next(); - - System.out.println(str + " has less than, or exactly 5 letters!"); - } - start = System.currentTimeMillis(); - - for (int i = 0; i < howMany; i++) { - iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() { - - public boolean accept(Object pElement) { - return ((String) pElement).length() <= 5; - } - }); - while (iter.hasNext()) { - iter.next(); - System.out.print(""); - } - } - -// end = System.currentTimeMillis(); -// System.out.println("Time: " + (end - start) + " ms"); -// System.out.println("\nClosureCollection:\n"); -// forEach(list, new Closure() { -// -// public void execute(Object pElement) { -// -// String str = (String) pElement; -// -// if (str.length() > 5) { -// System.out.println(str + " has more than 5 letters!"); -// } -// else { -// System.out.println(str + " has less than, or exactly 5 letters!"); -// } -// } -// }); -// start = System.currentTimeMillis(); -// for (int i = 0; i < howMany; i++) { -// forEach(list, new Closure() { -// -// public void execute(Object pElement) { -// -// String str = (String) pElement; -// -// if (str.length() <= 5) { -// System.out.print(""); -// } -// } -// }); -// } -// end = System.currentTimeMillis(); -// System.out.println("Time: " + (end - start) + " ms"); - } - - // Disallow creating objects of this type - private CollectionUtil() {} - - /** - * Merges two arrays into a new array. Elements from array1 and array2 will - * be copied into a new array, that has array1.length + array2.length - * elements. - * - * @param pArray1 First array - * @param pArray2 Second array, must be compatible with (assignable from) - * the first array - * @return A new array, containing the values of array1 and array2. The - * array (wrapped as an object), will have the length of array1 + - * array2, and can be safely cast to the type of the array1 - * parameter. - * @see #mergeArrays(Object,int,int,Object,int,int) - * @see java.lang.System#arraycopy(Object,int,Object,int,int) - */ - public static Object mergeArrays(Object pArray1, Object pArray2) { - return mergeArrays(pArray1, 0, Array.getLength(pArray1), pArray2, 0, Array.getLength(pArray2)); - } - - /** - * Merges two arrays into a new array. Elements from pArray1 and pArray2 will - * be copied into a new array, that has pLength1 + pLength2 elements. - * - * @param pArray1 First array - * @param pOffset1 the offset into the first array - * @param pLength1 the number of elements to copy from the first array - * @param pArray2 Second array, must be compatible with (assignable from) - * the first array - * @param pOffset2 the offset into the second array - * @param pLength2 the number of elements to copy from the second array - * @return A new array, containing the values of pArray1 and pArray2. The - * array (wrapped as an object), will have the length of pArray1 + - * pArray2, and can be safely cast to the type of the pArray1 - * parameter. - * @see java.lang.System#arraycopy(Object,int,Object,int,int) - */ - @SuppressWarnings({"SuspiciousSystemArraycopy"}) - public static Object mergeArrays(Object pArray1, int pOffset1, int pLength1, Object pArray2, int pOffset2, int pLength2) { - Class class1 = pArray1.getClass(); - Class type = class1.getComponentType(); - - // Create new array of the new length - Object array = Array.newInstance(type, pLength1 + pLength2); - - System.arraycopy(pArray1, pOffset1, array, 0, pLength1); - System.arraycopy(pArray2, pOffset2, array, pLength1, pLength2); - return array; - } - - /** - * Creates an array containing a subset of the original array. - * If the sub array is same length as the original - * ({@code pStart == 0}), the original array will be returned. - * - * @param pArray the original array - * @param pStart the start index of the original array - * @return a subset of the original array, or the original array itself, - * if {@code pStart} is 0. - * - * @throws IllegalArgumentException if {@code pArray} is {@code null} or - * if {@code pArray} is not an array. - * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 - */ - public static Object subArray(Object pArray, int pStart) { - return subArray(pArray, pStart, -1); - } - - /** - * Creates an array containing a subset of the original array. - * If the sub array is same length as the original - * ({@code pStart == 0}), the original array will be returned. - * - * @param pArray the original array - * @param pStart the start index of the original array - * @return a subset of the original array, or the original array itself, - * if {@code pStart} is 0. - * - * @throws IllegalArgumentException if {@code pArray} is {@code null} - * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 - */ - public static T[] subArray(T[] pArray, int pStart) { - return subArray(pArray, pStart, -1); - } - - /** - * Creates an array containing a subset of the original array. - * If the {@code pLength} parameter is negative, it will be ignored. - * If there are not {@code pLength} elements in the original array - * after {@code pStart}, the {@code pLength} parameter will be - * ignored. - * If the sub array is same length as the original, the original array will - * be returned. - * - * @param pArray the original array - * @param pStart the start index of the original array - * @param pLength the length of the new array - * @return a subset of the original array, or the original array itself, - * if {@code pStart} is 0 and {@code pLength} is either - * negative, or greater or equal to {@code pArray.length}. - * - * @throws IllegalArgumentException if {@code pArray} is {@code null} or - * if {@code pArray} is not an array. - * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 - */ - @SuppressWarnings({"SuspiciousSystemArraycopy"}) - public static Object subArray(Object pArray, int pStart, int pLength) { - Validate.notNull(pArray, "array"); - - // Get component type - Class type; - - // Sanity check start index - if (pStart < 0) { - throw new ArrayIndexOutOfBoundsException(pStart + " < 0"); - } - // Check if argument is array - else if ((type = pArray.getClass().getComponentType()) == null) { - // NOTE: No need to test class.isArray(), really - throw new IllegalArgumentException("Not an array: " + pArray); - } - - // Store original length - int originalLength = Array.getLength(pArray); - - // Find new length, stay within bounds - int newLength = (pLength < 0) - ? Math.max(0, originalLength - pStart) - : Math.min(pLength, Math.max(0, originalLength - pStart)); - - // Store result - Object result; - - if (newLength < originalLength) { - // Create sub array & copy into - result = Array.newInstance(type, newLength); - System.arraycopy(pArray, pStart, result, 0, newLength); - } - else { - // Just return original array - // NOTE: This can ONLY happen if pStart == 0 - result = pArray; - } - - // Return - return result; - } - - /** - * Creates an array containing a subset of the original array. - * If the {@code pLength} parameter is negative, it will be ignored. - * If there are not {@code pLength} elements in the original array - * after {@code pStart}, the {@code pLength} parameter will be - * ignored. - * If the sub array is same length as the original, the original array will - * be returned. - * - * @param pArray the original array - * @param pStart the start index of the original array - * @param pLength the length of the new array - * @return a subset of the original array, or the original array itself, - * if {@code pStart} is 0 and {@code pLength} is either - * negative, or greater or equal to {@code pArray.length}. - * - * @throws IllegalArgumentException if {@code pArray} is {@code null} - * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 - */ - @SuppressWarnings("unchecked") - public static T[] subArray(T[] pArray, int pStart, int pLength) { - return (T[]) subArray((Object) pArray, pStart, pLength); - } - - public static Iterator iterator(final Enumeration pEnum) { - notNull(pEnum, "enumeration"); - - return new Iterator() { - public boolean hasNext() { - return pEnum.hasMoreElements(); - } - - public T next() { - return pEnum.nextElement(); - } - - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - /** - * Adds all elements of the iterator to the collection. - * - * @param pCollection the collection - * @param pIterator the elements to add - * - * @throws UnsupportedOperationException if {@code add} is not supported by - * the given collection. - * @throws ClassCastException class of the specified element prevents it - * from being added to this collection. - * @throws NullPointerException if the specified element is {@code null} and this - * collection does not support {@code null} elements. - * @throws IllegalArgumentException some aspect of this element prevents - * it from being added to this collection. - */ - public static void addAll(Collection pCollection, Iterator pIterator) { - while (pIterator.hasNext()) { - pCollection.add(pIterator.next()); - } - } - - // Is there a use case where Arrays.asList(pArray).iterator() can't ne used? - /** - * Creates a thin {@link Iterator} wrapper around an array. - * - * @param pArray the array to iterate - * @return a new {@link ListIterator} - * @throws IllegalArgumentException if {@code pArray} is {@code null}, - * {@code pStart < 0}, or - * {@code pLength > pArray.length - pStart} - */ - public static ListIterator iterator(final E[] pArray) { - return iterator(pArray, 0, notNull(pArray).length); - } - - /** - * Creates a thin {@link Iterator} wrapper around an array. - * - * @param pArray the array to iterate - * @param pStart the offset into the array - * @param pLength the number of elements to include in the iterator - * @return a new {@link ListIterator} - * @throws IllegalArgumentException if {@code pArray} is {@code null}, - * {@code pStart < 0}, or - * {@code pLength > pArray.length - pStart} - */ - public static ListIterator iterator(final E[] pArray, final int pStart, final int pLength) { - return new ArrayIterator(pArray, pStart, pLength); - } - - /** - * Creates an inverted mapping of the key/value pairs in the given map. - * - * @param pSource the source map - * @return a new {@code Map} of same type as {@code pSource} - * @throws IllegalArgumentException if {@code pSource == null}, - * or if a new map can't be instantiated, - * or if source map contains duplicates. - * - * @see #invert(java.util.Map, java.util.Map, DuplicateHandler) - */ - public static Map invert(Map pSource) { - return invert(pSource, null, null); - } - - /** - * Creates an inverted mapping of the key/value pairs in the given map. - * Optionally, a duplicate handler may be specified, to resolve duplicate keys in the result map. - * - * @param pSource the source map - * @param pResult the map used to contain the result, may be {@code null}, - * in that case a new {@code Map} of same type as {@code pSource} is created. - * The result map should be empty, otherwise duplicate values will need to be resolved. - * @param pHandler duplicate handler, may be {@code null} if source map don't contain duplicate values - * @return {@code pResult}, or a new {@code Map} if {@code pResult == null} - * @throws IllegalArgumentException if {@code pSource == null}, - * or if result map is {@code null} and a new map can't be instantiated, - * or if source map contains duplicate values and {@code pHandler == null}. - */ - // TODO: Create a better duplicate handler, that takes Entries as parameters and returns an Entry - public static Map invert(Map pSource, Map pResult, DuplicateHandler pHandler) { - if (pSource == null) { - throw new IllegalArgumentException("source == null"); - } - - Map result = pResult; - if (result == null) { - try { - //noinspection unchecked - result = pSource.getClass().newInstance(); - } - catch (InstantiationException e) { - // Handled below - } - catch (IllegalAccessException e) { - // Handled below - } - - if (result == null) { - throw new IllegalArgumentException("result == null and source class " + pSource.getClass() + " cannot be instantiated."); - } - } - - // Copy entries into result map, inversed - Set> entries = pSource.entrySet(); - for (Map.Entry entry : entries) { - V newKey = entry.getValue(); - K newValue = entry.getKey(); - - // Handle dupliates - if (result.containsKey(newKey)) { - if (pHandler != null) { - newValue = pHandler.resolve(result.get(newKey), newValue); - } - else { - throw new IllegalArgumentException("Result would include duplicate keys, but no DuplicateHandler specified."); - } - } - - result.put(newKey, newValue); - } - - return result; - } - - public static Comparator reverseOrder(final Comparator pOriginal) { - return new ReverseComparator(pOriginal); - } - - private static class ReverseComparator implements Comparator { - private final Comparator comparator; - - public ReverseComparator(final Comparator pComparator) { - comparator = notNull(pComparator); - } - - - public int compare(T pLeft, T pRight) { - int result = comparator.compare(pLeft, pRight); - - // We can't simply return -result, as -Integer.MIN_VALUE == Integer.MIN_VALUE. - return -(result | (result >>> 1)); - } - } - - @SuppressWarnings({"unchecked", "UnusedDeclaration"}) - static , E> T generify(final Iterator pIterator, final Class pElementType) { - return (T) pIterator; - } - - @SuppressWarnings({"unchecked", "UnusedDeclaration"}) - static , E> T generify(final Collection pCollection, final Class pElementType) { - return (T) pCollection; - } - - @SuppressWarnings({"unchecked", "UnusedDeclaration"}) - static , K, V> T generify(final Map pMap, final Class pKeyType, final Class pValueType) { - return (T) pMap; - } - - @SuppressWarnings({"unchecked"}) - static , E> T generify2(Collection pCollection) { - return (T) pCollection; - } - - private static class ArrayIterator implements ListIterator { - private int next; - private final int start; - private final int length; - private final E[] array; - - public ArrayIterator(final E[] pArray, final int pStart, final int pLength) { - array = notNull(pArray, "array"); - start = isTrue(pStart >= 0, pStart, "start < 0: %d"); - length = isTrue(pLength <= pArray.length - pStart, pLength, "length > array.length - start: %d"); - next = start; - } - - public boolean hasNext() { - return next < length + start; - } - - public E next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - - try { - return array[next++]; - } - catch (ArrayIndexOutOfBoundsException e) { - NoSuchElementException nse = new NoSuchElementException(e.getMessage()); - nse.initCause(e); - throw nse; - } - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - public void add(E pElement) { - throw new UnsupportedOperationException(); - } - - public boolean hasPrevious() { - return next > start; - } - - public int nextIndex() { - return next - start; - } - - public E previous() { - if (!hasPrevious()) { - throw new NoSuchElementException(); - } - - try { - return array[--next]; - } - catch (ArrayIndexOutOfBoundsException e) { - NoSuchElementException nse = new NoSuchElementException(e.getMessage()); - nse.initCause(e); - throw nse; - } - } - - public int previousIndex() { - return nextIndex() - 1; - } - - public void set(E pElement) { - array[next - 1] = pElement; - } - } +/* + * 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.util; + +import com.twelvemonkeys.lang.Validate; + +import java.lang.reflect.Array; +import java.util.*; + +import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * A utility class with some useful collection-related functions. + * + * @author Harald Kuhr + * @author Eirik Torske + * @author last modified by $Author: haku $ + * @version $Id: com/twelvemonkeys/util/CollectionUtil.java#3 $ + * @see Collections + * @see Arrays + */ +public final class CollectionUtil { + + /** + * Testing only. + * + * @param pArgs command line arguents + */ + @SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"}) + public static void main(String[] pArgs) { + int howMany = 1000; + + if (pArgs.length > 0) { + howMany = Integer.parseInt(pArgs[0]); + } + long start; + long end; + + /* + int[] intArr1 = new int[] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; + int[] intArr2 = new int[] { + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 + }; + start = System.currentTimeMillis(); + + for (int i = 0; i < howMany; i++) { + intArr1 = (int[]) mergeArrays(intArr1, 0, intArr1.length, intArr2, 0, intArr2.length); + + } + end = System.currentTimeMillis(); + + System.out.println("mergeArrays: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length + + ")"); + */ + + //////////////////////////////// + String[] stringArr1 = new String[]{ + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" + }; + + /* + String[] stringArr2 = new String[] { + "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" + }; + + start = System.currentTimeMillis(); + for (int i = 0; i < howMany; i++) { + stringArr1 = (String[]) mergeArrays(stringArr1, 0, stringArr1.length, stringArr2, 0, stringArr2.length); + + } + end = System.currentTimeMillis(); + System.out.println("mergeArrays: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds (" + + stringArr1.length + ")"); + + + start = System.currentTimeMillis(); + while (intArr1.length > stringArr2.length) { + intArr1 = (int[]) subArray(intArr1, 0, intArr1.length - stringArr2.length); + + } + end = System.currentTimeMillis(); + + System.out.println("subArray: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length + + ")"); + + start = System.currentTimeMillis(); + while (stringArr1.length > stringArr2.length) { + stringArr1 = (String[]) subArray(stringArr1, stringArr2.length); + + } + end = System.currentTimeMillis(); + System.out.println("subArray: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds (" + + stringArr1.length + ")"); + + */ + stringArr1 = new String[]{ + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", + "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" + }; + System.out.println("\nFilterIterators:\n"); + List list = Arrays.asList(stringArr1); + Iterator iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() { + + public boolean accept(Object pElement) { + return ((String) pElement).length() > 5; + } + }); + + while (iter.hasNext()) { + String str = (String) iter.next(); + + System.out.println(str + " has more than 5 letters!"); + } + iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() { + + public boolean accept(Object pElement) { + return ((String) pElement).length() <= 5; + } + }); + + while (iter.hasNext()) { + String str = (String) iter.next(); + + System.out.println(str + " has less than, or exactly 5 letters!"); + } + start = System.currentTimeMillis(); + + for (int i = 0; i < howMany; i++) { + iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() { + + public boolean accept(Object pElement) { + return ((String) pElement).length() <= 5; + } + }); + while (iter.hasNext()) { + iter.next(); + System.out.print(""); + } + } + +// end = System.currentTimeMillis(); +// System.out.println("Time: " + (end - start) + " ms"); +// System.out.println("\nClosureCollection:\n"); +// forEach(list, new Closure() { +// +// public void execute(Object pElement) { +// +// String str = (String) pElement; +// +// if (str.length() > 5) { +// System.out.println(str + " has more than 5 letters!"); +// } +// else { +// System.out.println(str + " has less than, or exactly 5 letters!"); +// } +// } +// }); +// start = System.currentTimeMillis(); +// for (int i = 0; i < howMany; i++) { +// forEach(list, new Closure() { +// +// public void execute(Object pElement) { +// +// String str = (String) pElement; +// +// if (str.length() <= 5) { +// System.out.print(""); +// } +// } +// }); +// } +// end = System.currentTimeMillis(); +// System.out.println("Time: " + (end - start) + " ms"); + } + + // Disallow creating objects of this type + private CollectionUtil() {} + + /** + * Merges two arrays into a new array. Elements from array1 and array2 will + * be copied into a new array, that has array1.length + array2.length + * elements. + * + * @param pArray1 First array + * @param pArray2 Second array, must be compatible with (assignable from) + * the first array + * @return A new array, containing the values of array1 and array2. The + * array (wrapped as an object), will have the length of array1 + + * array2, and can be safely cast to the type of the array1 + * parameter. + * @see #mergeArrays(Object,int,int,Object,int,int) + * @see java.lang.System#arraycopy(Object,int,Object,int,int) + */ + public static Object mergeArrays(Object pArray1, Object pArray2) { + return mergeArrays(pArray1, 0, Array.getLength(pArray1), pArray2, 0, Array.getLength(pArray2)); + } + + /** + * Merges two arrays into a new array. Elements from pArray1 and pArray2 will + * be copied into a new array, that has pLength1 + pLength2 elements. + * + * @param pArray1 First array + * @param pOffset1 the offset into the first array + * @param pLength1 the number of elements to copy from the first array + * @param pArray2 Second array, must be compatible with (assignable from) + * the first array + * @param pOffset2 the offset into the second array + * @param pLength2 the number of elements to copy from the second array + * @return A new array, containing the values of pArray1 and pArray2. The + * array (wrapped as an object), will have the length of pArray1 + + * pArray2, and can be safely cast to the type of the pArray1 + * parameter. + * @see java.lang.System#arraycopy(Object,int,Object,int,int) + */ + @SuppressWarnings({"SuspiciousSystemArraycopy"}) + public static Object mergeArrays(Object pArray1, int pOffset1, int pLength1, Object pArray2, int pOffset2, int pLength2) { + Class class1 = pArray1.getClass(); + Class type = class1.getComponentType(); + + // Create new array of the new length + Object array = Array.newInstance(type, pLength1 + pLength2); + + System.arraycopy(pArray1, pOffset1, array, 0, pLength1); + System.arraycopy(pArray2, pOffset2, array, pLength1, pLength2); + return array; + } + + /** + * Creates an array containing a subset of the original array. + * If the sub array is same length as the original + * ({@code pStart == 0}), the original array will be returned. + * + * @param pArray the original array + * @param pStart the start index of the original array + * @return a subset of the original array, or the original array itself, + * if {@code pStart} is 0. + * + * @throws IllegalArgumentException if {@code pArray} is {@code null} or + * if {@code pArray} is not an array. + * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 + */ + public static Object subArray(Object pArray, int pStart) { + return subArray(pArray, pStart, -1); + } + + /** + * Creates an array containing a subset of the original array. + * If the sub array is same length as the original + * ({@code pStart == 0}), the original array will be returned. + * + * @param the type of array + * @param pArray the original array + * @param pStart the start index of the original array + * @return a subset of the original array, or the original array itself, + * if {@code pStart} is 0. + * + * @throws IllegalArgumentException if {@code pArray} is {@code null} + * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 + */ + public static T[] subArray(T[] pArray, int pStart) { + return subArray(pArray, pStart, -1); + } + + /** + * Creates an array containing a subset of the original array. + * If the {@code pLength} parameter is negative, it will be ignored. + * If there are not {@code pLength} elements in the original array + * after {@code pStart}, the {@code pLength} parameter will be + * ignored. + * If the sub array is same length as the original, the original array will + * be returned. + * + * @param pArray the original array + * @param pStart the start index of the original array + * @param pLength the length of the new array + * @return a subset of the original array, or the original array itself, + * if {@code pStart} is 0 and {@code pLength} is either + * negative, or greater or equal to {@code pArray.length}. + * + * @throws IllegalArgumentException if {@code pArray} is {@code null} or + * if {@code pArray} is not an array. + * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 + */ + @SuppressWarnings({"SuspiciousSystemArraycopy"}) + public static Object subArray(Object pArray, int pStart, int pLength) { + Validate.notNull(pArray, "array"); + + // Get component type + Class type; + + // Sanity check start index + if (pStart < 0) { + throw new ArrayIndexOutOfBoundsException(pStart + " < 0"); + } + // Check if argument is array + else if ((type = pArray.getClass().getComponentType()) == null) { + // NOTE: No need to test class.isArray(), really + throw new IllegalArgumentException("Not an array: " + pArray); + } + + // Store original length + int originalLength = Array.getLength(pArray); + + // Find new length, stay within bounds + int newLength = (pLength < 0) + ? Math.max(0, originalLength - pStart) + : Math.min(pLength, Math.max(0, originalLength - pStart)); + + // Store result + Object result; + + if (newLength < originalLength) { + // Create sub array & copy into + result = Array.newInstance(type, newLength); + System.arraycopy(pArray, pStart, result, 0, newLength); + } + else { + // Just return original array + // NOTE: This can ONLY happen if pStart == 0 + result = pArray; + } + + // Return + return result; + } + + /** + * Creates an array containing a subset of the original array. + * If the {@code pLength} parameter is negative, it will be ignored. + * If there are not {@code pLength} elements in the original array + * after {@code pStart}, the {@code pLength} parameter will be + * ignored. + * If the sub array is same length as the original, the original array will + * be returned. + * + * @param the type of array + * @param pArray the original array + * @param pStart the start index of the original array + * @param pLength the length of the new array + * @return a subset of the original array, or the original array itself, + * if {@code pStart} is 0 and {@code pLength} is either + * negative, or greater or equal to {@code pArray.length}. + * + * @throws IllegalArgumentException if {@code pArray} is {@code null} + * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0 + */ + @SuppressWarnings("unchecked") + public static T[] subArray(T[] pArray, int pStart, int pLength) { + return (T[]) subArray((Object) pArray, pStart, pLength); + } + + public static Iterator iterator(final Enumeration pEnum) { + notNull(pEnum, "enumeration"); + + return new Iterator() { + public boolean hasNext() { + return pEnum.hasMoreElements(); + } + + public T next() { + return pEnum.nextElement(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Adds all elements of the iterator to the collection. + * + * @param pCollection the collection + * @param pIterator the elements to add + * + * @throws UnsupportedOperationException if {@code add} is not supported by + * the given collection. + * @throws ClassCastException class of the specified element prevents it + * from being added to this collection. + * @throws NullPointerException if the specified element is {@code null} and this + * collection does not support {@code null} elements. + * @throws IllegalArgumentException some aspect of this element prevents + * it from being added to this collection. + */ + public static void addAll(Collection pCollection, Iterator pIterator) { + while (pIterator.hasNext()) { + pCollection.add(pIterator.next()); + } + } + + // Is there a use case where Arrays.asList(pArray).iterator() can't ne used? + /** + * Creates a thin {@link Iterator} wrapper around an array. + * + * @param pArray the array to iterate + * @return a new {@link ListIterator} + * @throws IllegalArgumentException if {@code pArray} is {@code null}, + * {@code pStart < 0}, or + * {@code pLength > pArray.length - pStart} + */ + public static ListIterator iterator(final E[] pArray) { + return iterator(pArray, 0, notNull(pArray).length); + } + + /** + * Creates a thin {@link Iterator} wrapper around an array. + * + * @param pArray the array to iterate + * @param pStart the offset into the array + * @param pLength the number of elements to include in the iterator + * @return a new {@link ListIterator} + * @throws IllegalArgumentException if {@code pArray} is {@code null}, + * {@code pStart < 0}, or + * {@code pLength > pArray.length - pStart} + */ + public static ListIterator iterator(final E[] pArray, final int pStart, final int pLength) { + return new ArrayIterator(pArray, pStart, pLength); + } + + /** + * Creates an inverted mapping of the key/value pairs in the given map. + * + * @param pSource the source map + * @return a new {@code Map} of same type as {@code pSource} + * @throws IllegalArgumentException if {@code pSource == null}, + * or if a new map can't be instantiated, + * or if source map contains duplicates. + * + * @see #invert(java.util.Map, java.util.Map, DuplicateHandler) + */ + public static Map invert(Map pSource) { + return invert(pSource, null, null); + } + + /** + * Creates an inverted mapping of the key/value pairs in the given map. + * Optionally, a duplicate handler may be specified, to resolve duplicate keys in the result map. + * + * @param pSource the source map + * @param pResult the map used to contain the result, may be {@code null}, + * in that case a new {@code Map} of same type as {@code pSource} is created. + * The result map should be empty, otherwise duplicate values will need to be resolved. + * @param pHandler duplicate handler, may be {@code null} if source map don't contain duplicate values + * @return {@code pResult}, or a new {@code Map} if {@code pResult == null} + * @throws IllegalArgumentException if {@code pSource == null}, + * or if result map is {@code null} and a new map can't be instantiated, + * or if source map contains duplicate values and {@code pHandler == null}. + */ + // TODO: Create a better duplicate handler, that takes Entries as parameters and returns an Entry + public static Map invert(Map pSource, Map pResult, DuplicateHandler pHandler) { + if (pSource == null) { + throw new IllegalArgumentException("source == null"); + } + + Map result = pResult; + if (result == null) { + try { + //noinspection unchecked + result = pSource.getClass().newInstance(); + } + catch (InstantiationException e) { + // Handled below + } + catch (IllegalAccessException e) { + // Handled below + } + + if (result == null) { + throw new IllegalArgumentException("result == null and source class " + pSource.getClass() + " cannot be instantiated."); + } + } + + // Copy entries into result map, inversed + Set> entries = pSource.entrySet(); + for (Map.Entry entry : entries) { + V newKey = entry.getValue(); + K newValue = entry.getKey(); + + // Handle dupliates + if (result.containsKey(newKey)) { + if (pHandler != null) { + newValue = pHandler.resolve(result.get(newKey), newValue); + } + else { + throw new IllegalArgumentException("Result would include duplicate keys, but no DuplicateHandler specified."); + } + } + + result.put(newKey, newValue); + } + + return result; + } + + public static Comparator reverseOrder(final Comparator pOriginal) { + return new ReverseComparator(pOriginal); + } + + private static class ReverseComparator implements Comparator { + private final Comparator comparator; + + public ReverseComparator(final Comparator pComparator) { + comparator = notNull(pComparator); + } + + + public int compare(T pLeft, T pRight) { + int result = comparator.compare(pLeft, pRight); + + // We can't simply return -result, as -Integer.MIN_VALUE == Integer.MIN_VALUE. + return -(result | (result >>> 1)); + } + } + + @SuppressWarnings({"unchecked", "UnusedDeclaration"}) + static , E> T generify(final Iterator pIterator, final Class pElementType) { + return (T) pIterator; + } + + @SuppressWarnings({"unchecked", "UnusedDeclaration"}) + static , E> T generify(final Collection pCollection, final Class pElementType) { + return (T) pCollection; + } + + @SuppressWarnings({"unchecked", "UnusedDeclaration"}) + static , K, V> T generify(final Map pMap, final Class pKeyType, final Class pValueType) { + return (T) pMap; + } + + @SuppressWarnings({"unchecked"}) + static , E> T generify2(Collection pCollection) { + return (T) pCollection; + } + + private static class ArrayIterator implements ListIterator { + private int next; + private final int start; + private final int length; + private final E[] array; + + public ArrayIterator(final E[] pArray, final int pStart, final int pLength) { + array = notNull(pArray, "array"); + start = isTrue(pStart >= 0, pStart, "start < 0: %d"); + length = isTrue(pLength <= pArray.length - pStart, pLength, "length > array.length - start: %d"); + next = start; + } + + public boolean hasNext() { + return next < length + start; + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + try { + return array[next++]; + } + catch (ArrayIndexOutOfBoundsException e) { + NoSuchElementException nse = new NoSuchElementException(e.getMessage()); + nse.initCause(e); + throw nse; + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void add(E pElement) { + throw new UnsupportedOperationException(); + } + + public boolean hasPrevious() { + return next > start; + } + + public int nextIndex() { + return next - start; + } + + public E previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + + try { + return array[--next]; + } + catch (ArrayIndexOutOfBoundsException e) { + NoSuchElementException nse = new NoSuchElementException(e.getMessage()); + nse.initCause(e); + throw nse; + } + } + + public int previousIndex() { + return nextIndex() - 1; + } + + public void set(E pElement) { + array[next - 1] = pElement; + } + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java index 626eefb4..eab84167 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java @@ -1,151 +1,152 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -/** - * DuplicateHandler - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java#2 $ - */ -public interface DuplicateHandler { - - /** - * Resolves duplicates according to a certain strategy. - * - * @param pOld the old value - * @param pNew the new value - * - * @return the resolved value. - * - * @throws IllegalArgumentException is the arguments cannot be resolved for - * some reason. - */ - public T resolve(T pOld, T pNew); - - /** - * Will use the first (old) value. Any new values will be discarded. - * - * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) - */ - public final static DuplicateHandler USE_FIRST_VALUE = new DuplicateHandler() { - /** - * Returns {@code pOld}. - * - * @param pOld the old value - * @param pNew the new value - * - * @return {@code pOld} - */ - public Object resolve(Object pOld, Object pNew) { - return pOld; - } - }; - - /** - * Will use the last (new) value. Any old values will be discarded - * (overwritten). - * - * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) - */ - public final static DuplicateHandler USE_LAST_VALUE = new DuplicateHandler() { - /** - * Returns {@code pNew}. - * - * @param pOld the old value - * @param pNew the new value - * - * @return {@code pNew} - */ - public Object resolve(Object pOld, Object pNew) { - return pNew; - } - }; - - /** - * Converts duplicats to an {@code Object} array. - * - * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) - */ - public final static DuplicateHandler DUPLICATES_AS_ARRAY = new DuplicateHandler() { - /** - * Returns an {@code Object} array, containing {@code pNew} as its - * last element. - * - * @param pOld the old value - * @param pNew the new value - * - * @return an {@code Object} array, containing {@code pNew} as its - * last element. - */ - public Object resolve(Object pOld, Object pNew) { - Object[] result; - - if (pOld instanceof Object[]) { - Object[] old = ((Object[]) pOld); - result = new Object[old.length + 1]; - System.arraycopy(old, 0, result, 0, old.length); - result[old.length] = pNew; - } - else { - result = new Object[] {pOld, pNew}; - } - - return result; - } - }; - - /** - * Converts duplicates to a comma-separated {@code String}. - * Note that all values should allready be {@code String}s if using this - * handler. - * - * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) - */ - public final static DuplicateHandler DUPLICATES_AS_CSV = new DuplicateHandler() { - /** - * Returns a comma-separated {@code String}, with the string - * representation of {@code pNew} as the last element. - * - * @param pOld the old value - * @param pNew the new value - * - * @return a comma-separated {@code String}, with the string - * representation of {@code pNew} as the last element. - */ - public String resolve(String pOld, String pNew) { - StringBuilder result = new StringBuilder(String.valueOf(pOld)); - result.append(','); - result.append(pNew); - - return result.toString(); - } - }; -} +/* + * 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.util; + +/** + * DuplicateHandler + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java#2 $ + */ +public interface DuplicateHandler { + + /** + * Resolves duplicates according to a certain strategy. + * + * @param pOld the old value + * @param pNew the new value + * + * @return the resolved value. + * + * @throws IllegalArgumentException is the arguments cannot be resolved for + * some reason. + */ + public T resolve(T pOld, T pNew); + + /** + * Will use the first (old) value. Any new values will be discarded. + * + * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) + */ + public final static DuplicateHandler USE_FIRST_VALUE = new DuplicateHandler() { + /** + * Returns {@code pOld}. + * + * @param pOld the old value + * @param pNew the new value + * + * @return {@code pOld} + */ + public Object resolve(Object pOld, Object pNew) { + return pOld; + } + }; + + /** + * Will use the last (new) value. Any old values will be discarded + * (overwritten). + * + * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) + */ + public final static DuplicateHandler USE_LAST_VALUE = new DuplicateHandler() { + /** + * Returns {@code pNew}. + * + * @param pOld the old value + * @param pNew the new value + * + * @return {@code pNew} + */ + public Object resolve(Object pOld, Object pNew) { + return pNew; + } + }; + + /** + * Converts duplicats to an {@code Object} array. + * + * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) + */ + public final static DuplicateHandler DUPLICATES_AS_ARRAY = new DuplicateHandler() { + /** + * Returns an {@code Object} array, containing {@code pNew} as its + * last element. + * + * @param pOld the old value + * @param pNew the new value + * + * @return an {@code Object} array, containing {@code pNew} as its + * last element. + */ + public Object resolve(Object pOld, Object pNew) { + Object[] result; + + if (pOld instanceof Object[]) { + Object[] old = ((Object[]) pOld); + result = new Object[old.length + 1]; + System.arraycopy(old, 0, result, 0, old.length); + result[old.length] = pNew; + } + else { + result = new Object[] {pOld, pNew}; + } + + return result; + } + }; + + /** + * Converts duplicates to a comma-separated {@code String}. + * Note that all values should allready be {@code String}s if using this + * handler. + * + * @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler) + */ + public final static DuplicateHandler DUPLICATES_AS_CSV = new DuplicateHandler() { + /** + * Returns a comma-separated {@code String}, with the string + * representation of {@code pNew} as the last element. + * + * @param pOld the old value + * @param pNew the new value + * + * @return a comma-separated {@code String}, with the string + * representation of {@code pNew} as the last element. + */ + public String resolve(String pOld, String pNew) { + StringBuilder result = new StringBuilder(String.valueOf(pOld)); + result.append(','); + result.append(pNew); + + return result.toString(); + } + }; +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/ExpiringMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/ExpiringMap.java index 5e2f7490..affa1a23 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/ExpiringMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/ExpiringMap.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/FilterIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/FilterIterator.java index 0e5e8586..3b3fbec2 100644 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/FilterIterator.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/FilterIterator.java @@ -1,151 +1,154 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * Wraps (decorates) an {@code Iterator} with extra functionality, to allow - * element filtering. Each - * element is filtered against the given {@code Filter}, and only elements - * that are {@code accept}ed are returned by the {@code next} method. - *

- * The optional {@code remove} operation is implemented, but may throw - * {@code UnsupportedOperationException} if the underlying iterator does not - * support the remove operation. - * - * @see FilterIterator.Filter - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/FilterIterator.java#1 $ - */ -public class FilterIterator implements Iterator { - - protected final Filter filter; - protected final Iterator iterator; - - private E next = null; - private E current = null; - - /** - * Creates a {@code FilterIterator} that wraps the {@code Iterator}. Each - * element is filtered against the given {@code Filter}, and only elements - * that are {@code accept}ed are returned by the {@code next} method. - * - * @param pIterator the iterator to filter - * @param pFilter the filter - * @see FilterIterator.Filter - */ - public FilterIterator(final Iterator pIterator, final Filter pFilter) { - if (pIterator == null) { - throw new IllegalArgumentException("iterator == null"); - } - if (pFilter == null) { - throw new IllegalArgumentException("filter == null"); - } - - iterator = pIterator; - filter = pFilter; - } - - /** - * Returns {@code true} if the iteration has more elements. (In other - * words, returns {@code true} if {@code next} would return an element - * rather than throwing an exception.) - * - * @return {@code true} if the iterator has more elements. - * @see FilterIterator.Filter#accept - */ - public boolean hasNext() { - while (next == null && iterator.hasNext()) { - E element = iterator.next(); - - if (filter.accept(element)) { - next = element; - break; - } - } - - return next != null; - } - - /** - * Returns the next element in the iteration. - * - * @return the next element in the iteration. - * @see FilterIterator.Filter#accept - */ - public E next() { - if (hasNext()) { - current = next; - - // Make sure we advance next time - next = null; - return current; - } - else { - throw new NoSuchElementException("Iteration has no more elements."); - } - } - - /** - * Removes from the underlying collection the last element returned by the - * iterator (optional operation). This method can be called only once per - * call to {@code next}. The behavior of an iterator is unspecified if - * the underlying collection is modified while the iteration is in - * progress in any way other than by calling this method. - */ - public void remove() { - if (current != null) { - iterator.remove(); - } - else { - throw new IllegalStateException("Iteration has no current element."); - } - } - - /** - * Used to tests whether or not an element fulfills certain criteria, and - * hence should be accepted by the FilterIterator instance. - */ - public static interface Filter { - - /** - * Tests whether or not the element fulfills certain criteria, and hence - * should be accepted. - * - * @param pElement the element to test - * @return {@code true} if the object is accepted, otherwise - * {@code false} - */ - public boolean accept(E pElement); - } +/* + * 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.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Wraps (decorates) an {@code Iterator} with extra functionality, to allow + * element filtering. Each + * element is filtered against the given {@code Filter}, and only elements + * that are {@code accept}ed are returned by the {@code next} method. + *

+ * The optional {@code remove} operation is implemented, but may throw + * {@code UnsupportedOperationException} if the underlying iterator does not + * support the remove operation. + *

+ * + * @see FilterIterator.Filter + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/FilterIterator.java#1 $ + */ +public class FilterIterator implements Iterator { + + protected final Filter filter; + protected final Iterator iterator; + + private E next = null; + private E current = null; + + /** + * Creates a {@code FilterIterator} that wraps the {@code Iterator}. Each + * element is filtered against the given {@code Filter}, and only elements + * that are {@code accept}ed are returned by the {@code next} method. + * + * @param pIterator the iterator to filter + * @param pFilter the filter + * @see FilterIterator.Filter + */ + public FilterIterator(final Iterator pIterator, final Filter pFilter) { + if (pIterator == null) { + throw new IllegalArgumentException("iterator == null"); + } + if (pFilter == null) { + throw new IllegalArgumentException("filter == null"); + } + + iterator = pIterator; + filter = pFilter; + } + + /** + * Returns {@code true} if the iteration has more elements. (In other + * words, returns {@code true} if {@code next} would return an element + * rather than throwing an exception.) + * + * @return {@code true} if the iterator has more elements. + * @see FilterIterator.Filter#accept + */ + public boolean hasNext() { + while (next == null && iterator.hasNext()) { + E element = iterator.next(); + + if (filter.accept(element)) { + next = element; + break; + } + } + + return next != null; + } + + /** + * Returns the next element in the iteration. + * + * @return the next element in the iteration. + * @see FilterIterator.Filter#accept + */ + public E next() { + if (hasNext()) { + current = next; + + // Make sure we advance next time + next = null; + return current; + } + else { + throw new NoSuchElementException("Iteration has no more elements."); + } + } + + /** + * Removes from the underlying collection the last element returned by the + * iterator (optional operation). This method can be called only once per + * call to {@code next}. The behavior of an iterator is unspecified if + * the underlying collection is modified while the iteration is in + * progress in any way other than by calling this method. + */ + public void remove() { + if (current != null) { + iterator.remove(); + } + else { + throw new IllegalStateException("Iteration has no current element."); + } + } + + /** + * Used to tests whether or not an element fulfills certain criteria, and + * hence should be accepted by the FilterIterator instance. + */ + public static interface Filter { + + /** + * Tests whether or not the element fulfills certain criteria, and hence + * should be accepted. + * + * @param pElement the element to test + * @return {@code true} if the object is accepted, otherwise + * {@code false} + */ + public boolean accept(E pElement); + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/IgnoreCaseMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/IgnoreCaseMap.java index 4258d723..035f06bb 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/IgnoreCaseMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/IgnoreCaseMap.java @@ -1,176 +1,180 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.Iterator; -import java.util.Map; -import java.io.Serializable; - -/** - * A {@code Map} decorator that makes the mappings in the backing map - * case insensitive - * (this is implemented by converting all keys to uppercase), - * if the keys used are {@code Strings}. If the keys - * used are not {@code String}s, it wil work as a normal - * {@code java.util.Map}. - *

- * - * @see java.util.Map - * - * @author Harald Kuhr - */ -public class IgnoreCaseMap extends AbstractDecoratedMap implements Serializable, Cloneable { - - /** - * Constructs a new empty {@code Map}. - * The backing map will be a {@link java.util.HashMap} - */ - public IgnoreCaseMap() { - super(); - } - - /** - * Constructs a new {@code Map} with the same key-value mappings as the - * given {@code Map}. - * The backing map will be a {@link java.util.HashMap} - *

- * NOTE: As the keys in the given map parameter will be converted to - * uppercase (if they are strings), any duplicate key/value pair where - * {@code key instanceof String && key.equalsIgnoreCase(otherKey)} - * is true, will be lost. - * - * @param pMap the map whose mappings are to be placed in this map. - */ - public IgnoreCaseMap(Map pMap) { - super(pMap); - } - - /** - * Constructs a new {@code Map} with the same key-value mappings as the - * given {@code Map}. - *

- * NOTE: The backing map is structuraly cahnged, and it should NOT be - * accessed directly, after the wrapped map is created. - *

- * NOTE: As the keys in the given map parameter will be converted to - * uppercase (if they are strings), any duplicate key/value pair where - * {@code key instanceof String && key.equalsIgnoreCase(otherKey)} - * is true, will be lost. - * - * @param pBacking the backing map of this map. Must be either empty, or - * the same map as {@code pContents}. - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null} - * - * @throws IllegalArgumentException if {@code pBacking} is {@code null} - * @throws IllegalArgumentException if {@code pBacking} differs from - * {@code pContent} and is not empty. - */ - public IgnoreCaseMap(Map pBacking, Map pContents) { - super(pBacking, pContents); - } - - /** - * Maps the specified key to the specified value in this map. - * Note: If the key used is a string, the key will not be case-sensitive. - * - * @param pKey the map key. - * @param pValue the value. - * @return the previous value of the specified key in this map, - * or null if it did not have one. - */ - public V put(String pKey, V pValue) { - String key = (String) toUpper(pKey); - return unwrap(entries.put(key, new BasicEntry(key, pValue))); - } - - private V unwrap(Entry pEntry) { - return pEntry != null ? pEntry.getValue() : null; - } - - /** - * Returns the value to which the specified key is mapped in this - * map. - * Note: If the key used is a string, the key will not be case-sensitive. - * - * @param pKey a key in the map - * @return the value to which the key is mapped in this map; null if - * the key is not mapped to any value in this map. - */ - public V get(Object pKey) { - return unwrap(entries.get(toUpper(pKey))); - } - - /** - * Removes the key (and its corresponding value) from this map. This - * method does nothing if the key is not in the map. - * Note: If the key used is a string, the key will not be case-sensitive. - * - * @param pKey the key that needs to be removed. - * @return the value to which the key had been mapped in this map, - * or null if the key did not have a mapping. - */ - public V remove(Object pKey) { - return unwrap(entries.remove(toUpper(pKey))); - } - - /** - * Tests if the specified object is a key in this map. - * Note: If the key used is a string, the key will not be case-sensitive. - * - * @param pKey possible key. - * @return true if and only if the specified object is a key in this - * map, as determined by the equals method; false otherwise. - */ - public boolean containsKey(Object pKey) { - return entries.containsKey(toUpper(pKey)); - } - - /** - * Converts the parameter to uppercase, if it's a String. - */ - protected static Object toUpper(final Object pObject) { - if (pObject instanceof String) { - return ((String) pObject).toUpperCase(); - } - return pObject; - } - - protected Iterator> newEntryIterator() { - return (Iterator) entries.entrySet().iterator(); - } - - protected Iterator newKeyIterator() { - return entries.keySet().iterator(); - } - - protected Iterator newValueIterator() { - return (Iterator) entries.values().iterator(); - } -} +/* + * 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.util; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; + +/** + * A {@code Map} decorator that makes the mappings in the backing map + * case insensitive + * (this is implemented by converting all keys to uppercase), + * if the keys used are {@code Strings}. If the keys + * used are not {@code String}s, it wil work as a normal + * {@code java.util.Map}. + * + * @see java.util.Map + * + * @author Harald Kuhr + */ +public class IgnoreCaseMap extends AbstractDecoratedMap implements Serializable, Cloneable { + + /** + * Constructs a new empty {@code Map}. + * The backing map will be a {@link java.util.HashMap} + */ + public IgnoreCaseMap() { + super(); + } + + /** + * Constructs a new {@code Map} with the same key-value mappings as the + * given {@code Map}. + * The backing map will be a {@link java.util.HashMap} + *

+ * NOTE: As the keys in the given map parameter will be converted to + * uppercase (if they are strings), any duplicate key/value pair where + * {@code key instanceof String && key.equalsIgnoreCase(otherKey)} + * is true, will be lost. + *

+ * + * @param pMap the map whose mappings are to be placed in this map. + */ + public IgnoreCaseMap(Map pMap) { + super(pMap); + } + + /** + * Constructs a new {@code Map} with the same key-value mappings as the + * given {@code Map}. + *

+ * NOTE: The backing map is structuraly cahnged, and it should NOT be + * accessed directly, after the wrapped map is created. + *

+ *

+ * NOTE: As the keys in the given map parameter will be converted to + * uppercase (if they are strings), any duplicate key/value pair where + * {@code key instanceof String && key.equalsIgnoreCase(otherKey)} + * is true, will be lost. + *

+ * + * @param pBacking the backing map of this map. Must be either empty, or + * the same map as {@code pContents}. + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null} + * + * @throws IllegalArgumentException if {@code pBacking} is {@code null} + * @throws IllegalArgumentException if {@code pBacking} differs from + * {@code pContent} and is not empty. + */ + public IgnoreCaseMap(Map pBacking, Map pContents) { + super(pBacking, pContents); + } + + /** + * Maps the specified key to the specified value in this map. + * Note: If the key used is a string, the key will not be case-sensitive. + * + * @param pKey the map key. + * @param pValue the value. + * @return the previous value of the specified key in this map, + * or null if it did not have one. + */ + public V put(String pKey, V pValue) { + String key = (String) toUpper(pKey); + return unwrap(entries.put(key, new BasicEntry(key, pValue))); + } + + private V unwrap(Entry pEntry) { + return pEntry != null ? pEntry.getValue() : null; + } + + /** + * Returns the value to which the specified key is mapped in this + * map. + * Note: If the key used is a string, the key will not be case-sensitive. + * + * @param pKey a key in the map + * @return the value to which the key is mapped in this map; null if + * the key is not mapped to any value in this map. + */ + public V get(Object pKey) { + return unwrap(entries.get(toUpper(pKey))); + } + + /** + * Removes the key (and its corresponding value) from this map. This + * method does nothing if the key is not in the map. + * Note: If the key used is a string, the key will not be case-sensitive. + * + * @param pKey the key that needs to be removed. + * @return the value to which the key had been mapped in this map, + * or null if the key did not have a mapping. + */ + public V remove(Object pKey) { + return unwrap(entries.remove(toUpper(pKey))); + } + + /** + * Tests if the specified object is a key in this map. + * Note: If the key used is a string, the key will not be case-sensitive. + * + * @param pKey possible key. + * @return true if and only if the specified object is a key in this + * map, as determined by the equals method; false otherwise. + */ + public boolean containsKey(Object pKey) { + return entries.containsKey(toUpper(pKey)); + } + + /** + * Converts the parameter to uppercase, if it's a String. + */ + protected static Object toUpper(final Object pObject) { + if (pObject instanceof String) { + return ((String) pObject).toUpperCase(); + } + return pObject; + } + + protected Iterator> newEntryIterator() { + return (Iterator) entries.entrySet().iterator(); + } + + protected Iterator newKeyIterator() { + return entries.keySet().iterator(); + } + + protected Iterator newValueIterator() { + return (Iterator) entries.values().iterator(); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java index eee68eeb..53bf7610 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java @@ -4,33 +4,35 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; -import java.util.Map; -import java.util.LinkedHashMap; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; /** * Map implementation with size limit, that keeps its entries in LRU diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java index 040ed014..28a01d30 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java index e9a071a0..5910b35c 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java @@ -1,466 +1,466 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.*; -import java.io.Serializable; - -/** - * Generic map and linked list implementation of the {@code Map} interface, - * with predictable iteration order. - *

- * Resembles {@code LinkedHashMap} from JDK 1.4+, but is backed by a generic - * {@code Map}, rather than implementing a particular algoritm. - *

- * This linked list defines the iteration ordering, which is normally the order - * in which keys were inserted into the map (insertion-order). - * Note that insertion order is not affected if a key is re-inserted - * into the map (a key {@code k} is reinserted into a map {@code m} if - * {@code m.put(k, v)} is invoked when {@code m.containsKey(k)} would return - * {@code true} immediately prior to the invocation). - *

- * A special {@link #LinkedMap(boolean) constructor} is provided to create a - * linked hash map whose order of iteration is the order in which its entries - * were last accessed, from least-recently accessed to most-recently - * (access-order). - * This kind of map is well-suited to building LRU caches. - * Invoking the {@code put} or {@code get} method results in an access to the - * corresponding entry (assuming it exists after the invocation completes). - * The {@code putAll} method generates one entry access for each mapping in - * the specified map, in the order that key-value mappings are provided by the - * specified map's entry set iterator. - * No other methods generate entry accesses. - * In particular, operations on collection-views do not affect the order of - * iteration of the backing map. - *

- * The {@link #removeEldestEntry(Map.Entry)} method may be overridden to impose - * a policy for removing stale mappings automatically when new mappings are - * added to the map. - * - * @author inspired by LinkedHashMap from JDK 1.4+, by Josh Bloch - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedMap.java#1 $ - * - * @see LinkedHashMap - * @see LRUMap - */ -public class LinkedMap extends AbstractDecoratedMap implements Serializable { - - transient LinkedEntry head; - protected final boolean accessOrder; - - /** - * Creates a {@code LinkedMap} backed by a {@code HashMap}, with default - * (insertion) order. - */ - public LinkedMap() { - this(null, false); - } - - /** - * Creates a {@code LinkedMap} backed by a {@code HashMap}, with the - * given order. - * - * @param pAccessOrder if {@code true}, ordering will be "least recently - * accessed item" to "latest accessed item", otherwise "first inserted item" - * to "latest inserted item". - */ - public LinkedMap(boolean pAccessOrder) { - this(null, pAccessOrder); - } - - /** - * Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value - * pairs copied from {@code pContents} and default (insertion) order. - * - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - */ - public LinkedMap(Map pContents) { - this(pContents, false); - } - - /** - * Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value - * pairs copied from {@code pContents} and the given order. - * - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - * @param pAccessOrder if {@code true}, ordering will be "least recently - * accessed item" to "latest accessed item", otherwise "first inserted item" - * to "latest inserted item". - */ - public LinkedMap(Map pContents, boolean pAccessOrder) { - super(pContents); - accessOrder = pAccessOrder; - } - - /** - * Creates a {@code LinkedMap} backed by the given map, with key/value - * pairs copied from {@code pContents} and default (insertion) order. - * - * @param pBacking the backing map of this map. Must be either empty, or - * the same map as {@code pContents}. - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - */ - public LinkedMap(Map> pBacking, Map pContents) { - this(pBacking, pContents, false); - } - - /** - * Creates a {@code LinkedMap} backed by the given map, with key/value - * pairs copied from {@code pContents} and the given order. - * - * @param pBacking the backing map of this map. Must be either empty, or - * the same map as {@code pContents}. - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - * @param pAccessOrder if {@code true}, ordering will be "least recently - * accessed item" to "latest accessed item", otherwise "first inserted item" - * to "latest inserted item". - */ - public LinkedMap(Map> pBacking, Map pContents, boolean pAccessOrder) { - super(pBacking, pContents); - accessOrder = pAccessOrder; - } - - protected void init() { - head = new LinkedEntry(null, null, null) { - void addBefore(LinkedEntry pExisting) { - throw new Error(); - } - void remove() { - throw new Error(); - } - public void recordAccess(Map pMap) { - throw new Error(); - } - public void recordRemoval(Map pMap) { - throw new Error(); - } - public void recordRemoval() { - throw new Error(); - } - public V getValue() { - throw new Error(); - } - public V setValue(V pValue) { - throw new Error(); - } - public K getKey() { - throw new Error(); - } - public String toString() { - return "head"; - } - }; - head.previous = head.next = head; - } - - public boolean containsValue(Object pValue) { - // Overridden to take advantage of faster iterator - if (pValue == null) { - for (LinkedEntry e = head.next; e != head; e = e.next) { - if (e.mValue == null) { - return true; - } - } - } else { - for (LinkedEntry e = head.next; e != head; e = e.next) { - if (pValue.equals(e.mValue)) { - return true; - } - } - } - return false; - } - - protected Iterator newKeyIterator() { - return new KeyIterator(); - } - - protected Iterator newValueIterator() { - return new ValueIterator(); - } - - protected Iterator> newEntryIterator() { - return new EntryIterator(); - } - - private abstract class LinkedMapIterator implements Iterator { - LinkedEntry mNextEntry = head.next; - LinkedEntry mLastReturned = null; - - /** - * The modCount value that the iterator believes that the backing - * List should have. If this expectation is violated, the iterator - * has detected concurrent modification. - */ - int mExpectedModCount = modCount; - - public boolean hasNext() { - return mNextEntry != head; - } - - public void remove() { - if (mLastReturned == null) { - throw new IllegalStateException(); - } - - if (modCount != mExpectedModCount) { - throw new ConcurrentModificationException(); - } - - LinkedMap.this.remove(mLastReturned.mKey); - mLastReturned = null; - - mExpectedModCount = modCount; - } - - LinkedEntry nextEntry() { - if (modCount != mExpectedModCount) { - throw new ConcurrentModificationException(); - } - - if (mNextEntry == head) { - throw new NoSuchElementException(); - } - - LinkedEntry e = mLastReturned = mNextEntry; - mNextEntry = e.next; - - return e; - } - } - - private class KeyIterator extends LinkedMap.LinkedMapIterator { - public K next() { - return nextEntry().mKey; - } - } - - private class ValueIterator extends LinkedMap.LinkedMapIterator { - public V next() { - return nextEntry().mValue; - } - } - - private class EntryIterator extends LinkedMap.LinkedMapIterator> { - public Entry next() { - return nextEntry(); - } - } - - public V get(Object pKey) { - LinkedEntry entry = (LinkedEntry) entries.get(pKey); - - if (entry != null) { - entry.recordAccess(this); - return entry.mValue; - } - - return null; - } - - public V remove(Object pKey) { - LinkedEntry entry = (LinkedEntry) entries.remove(pKey); - - if (entry != null) { - entry.remove(); - modCount++; - - return entry.mValue; - } - return null; - } - - public V put(K pKey, V pValue) { - LinkedEntry entry = (LinkedEntry) entries.get(pKey); - V oldValue; - - if (entry == null) { - oldValue = null; - - // Remove eldest entry if instructed, else grow capacity if appropriate - LinkedEntry eldest = head.next; - if (removeEldestEntry(eldest)) { - removeEntry(eldest); - } - - entry = createEntry(pKey, pValue); - entry.addBefore(head); - - entries.put(pKey, entry); - } - else { - oldValue = entry.mValue; - - entry.mValue = pValue; - entry.recordAccess(this); - } - - modCount++; - - return oldValue; - } - - /** - * Creates a new {@code LinkedEntry}. - * - * @param pKey the key - * @param pValue the value - * @return a new LinkedEntry - */ - /*protected*/ LinkedEntry createEntry(K pKey, V pValue) { - return new LinkedEntry(pKey, pValue, null); - } - - /** - * @todo - * - * @return a copy of this map, with the same order and same key/value pairs. - */ - public Object clone() throws CloneNotSupportedException { - LinkedMap map; - - map = (LinkedMap) super.clone(); - - // TODO: The rest of the work is PROBABLY handled by - // AbstractDecoratedMap, but need to verify that. - - return map; - } - - /** - * Returns {@code true} if this map should remove its eldest entry. - * This method is invoked by {@code put} and {@code putAll} after - * inserting a new entry into the map. It provides the implementer - * with the opportunity to remove the eldest entry each time a new one - * is added. This is useful if the map represents a cache: it allows - * the map to reduce memory consumption by deleting stale entries. - * - *

Sample use: this override will allow the map to grow up to 100 - * entries and then delete the eldest entry each time a new entry is - * added, maintaining a steady state of 100 entries. - *

-     *     private static final int MAX_ENTRIES = 100;
-     *
-     *     protected boolean removeEldestEntry(Map.Entry eldest) {
-     *        return size() > MAX_ENTRIES;
-     *     }
-     * 
- * - *

This method typically does not modify the map in any way, - * instead allowing the map to modify itself as directed by its - * return value. It is permitted for this method to modify - * the map directly, but if it does so, it must return - * {@code false} (indicating that the map should not attempt any - * further modification). The effects of returning {@code true} - * after modifying the map from within this method are unspecified. - * - *

This implementation merely returns {@code false} (so that this - * map acts like a normal map - the eldest element is never removed). - * - * @param pEldest The least recently inserted entry in the map, or if - * this is an access-ordered map, the least recently accessed - * entry. This is the entry that will be removed it this - * method returns {@code true}. If the map was empty prior - * to the {@code put} or {@code putAll} invocation resulting - * in this invocation, this will be the entry that was just - * inserted; in other words, if the map contains a single - * entry, the eldest entry is also the newest. - * @return {@code true} if the eldest entry should be removed - * from the map; {@code false} if it should be retained. - */ - protected boolean removeEldestEntry(Entry pEldest) { - return false; - } - - /** - * Linked list implementation of {@code Map.Entry}. - */ - protected static class LinkedEntry extends BasicEntry implements Serializable { - LinkedEntry previous; - LinkedEntry next; - - LinkedEntry(K pKey, V pValue, LinkedEntry pNext) { - super(pKey, pValue); - - next = pNext; - } - - /** - * Adds this entry before the given entry (which must be an existing - * entry) in the list. - * - * @param pExisting the entry to add before - */ - void addBefore(LinkedEntry pExisting) { - next = pExisting; - previous = pExisting.previous; - - previous.next = this; - next.previous = this; - } - - /** - * Removes this entry from the linked list. - */ - void remove() { - previous.next = next; - next.previous = previous; - } - - /** - * If the entry is part of an access ordered list, moves the entry to - * the end of the list. - * - * @param pMap the map to record access for - */ - protected void recordAccess(Map pMap) { - LinkedMap linkedMap = (LinkedMap) pMap; - if (linkedMap.accessOrder) { - linkedMap.modCount++; - remove(); - addBefore(linkedMap.head); - } - } - - /** - * Removes this entry from the linked list. - * - * @param pMap the map to record removal from - */ - protected void recordRemoval(Map pMap) { - // TODO: Is this REALLY correct? - remove(); - } - } +/* + * 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.util; + +import java.io.Serializable; +import java.util.*; + +/** + * Generic map and linked list implementation of the {@code Map} interface, + * with predictable iteration order. + *

+ * Resembles {@code LinkedHashMap} from JDK 1.4+, but is backed by a generic + * {@code Map}, rather than implementing a particular algoritm. + *

+ * This linked list defines the iteration ordering, which is normally the order + * in which keys were inserted into the map (insertion-order). + * Note that insertion order is not affected if a key is re-inserted + * into the map (a key {@code k} is reinserted into a map {@code m} if + * {@code m.put(k, v)} is invoked when {@code m.containsKey(k)} would return + * {@code true} immediately prior to the invocation). + *

+ * A special {@link #LinkedMap(boolean) constructor} is provided to create a + * linked hash map whose order of iteration is the order in which its entries + * were last accessed, from least-recently accessed to most-recently + * (access-order). + * This kind of map is well-suited to building LRU caches. + * Invoking the {@code put} or {@code get} method results in an access to the + * corresponding entry (assuming it exists after the invocation completes). + * The {@code putAll} method generates one entry access for each mapping in + * the specified map, in the order that key-value mappings are provided by the + * specified map's entry set iterator. + * No other methods generate entry accesses. + * In particular, operations on collection-views do not affect the order of + * iteration of the backing map. + *

+ * The {@link #removeEldestEntry(Map.Entry)} method may be overridden to impose + * a policy for removing stale mappings automatically when new mappings are + * added to the map. + * + * @author inspired by LinkedHashMap from JDK 1.4+, by Josh Bloch + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedMap.java#1 $ + * + * @see LinkedHashMap + * @see LRUMap + */ +public class LinkedMap extends AbstractDecoratedMap implements Serializable { + + transient LinkedEntry head; + protected final boolean accessOrder; + + /** + * Creates a {@code LinkedMap} backed by a {@code HashMap}, with default + * (insertion) order. + */ + public LinkedMap() { + this(null, false); + } + + /** + * Creates a {@code LinkedMap} backed by a {@code HashMap}, with the + * given order. + * + * @param pAccessOrder if {@code true}, ordering will be "least recently + * accessed item" to "latest accessed item", otherwise "first inserted item" + * to "latest inserted item". + */ + public LinkedMap(boolean pAccessOrder) { + this(null, pAccessOrder); + } + + /** + * Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value + * pairs copied from {@code pContents} and default (insertion) order. + * + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + */ + public LinkedMap(Map pContents) { + this(pContents, false); + } + + /** + * Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value + * pairs copied from {@code pContents} and the given order. + * + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + * @param pAccessOrder if {@code true}, ordering will be "least recently + * accessed item" to "latest accessed item", otherwise "first inserted item" + * to "latest inserted item". + */ + public LinkedMap(Map pContents, boolean pAccessOrder) { + super(pContents); + accessOrder = pAccessOrder; + } + + /** + * Creates a {@code LinkedMap} backed by the given map, with key/value + * pairs copied from {@code pContents} and default (insertion) order. + * + * @param pBacking the backing map of this map. Must be either empty, or + * the same map as {@code pContents}. + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + */ + public LinkedMap(Map> pBacking, Map pContents) { + this(pBacking, pContents, false); + } + + /** + * Creates a {@code LinkedMap} backed by the given map, with key/value + * pairs copied from {@code pContents} and the given order. + * + * @param pBacking the backing map of this map. Must be either empty, or + * the same map as {@code pContents}. + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + * @param pAccessOrder if {@code true}, ordering will be "least recently + * accessed item" to "latest accessed item", otherwise "first inserted item" + * to "latest inserted item". + */ + public LinkedMap(Map> pBacking, Map pContents, boolean pAccessOrder) { + super(pBacking, pContents); + accessOrder = pAccessOrder; + } + + protected void init() { + head = new LinkedEntry(null, null, null) { + void addBefore(LinkedEntry pExisting) { + throw new Error(); + } + void remove() { + throw new Error(); + } + public void recordAccess(Map pMap) { + throw new Error(); + } + public void recordRemoval(Map pMap) { + throw new Error(); + } + public void recordRemoval() { + throw new Error(); + } + public V getValue() { + throw new Error(); + } + public V setValue(V pValue) { + throw new Error(); + } + public K getKey() { + throw new Error(); + } + public String toString() { + return "head"; + } + }; + head.previous = head.next = head; + } + + public boolean containsValue(Object pValue) { + // Overridden to take advantage of faster iterator + if (pValue == null) { + for (LinkedEntry e = head.next; e != head; e = e.next) { + if (e.mValue == null) { + return true; + } + } + } else { + for (LinkedEntry e = head.next; e != head; e = e.next) { + if (pValue.equals(e.mValue)) { + return true; + } + } + } + return false; + } + + protected Iterator newKeyIterator() { + return new KeyIterator(); + } + + protected Iterator newValueIterator() { + return new ValueIterator(); + } + + protected Iterator> newEntryIterator() { + return new EntryIterator(); + } + + private abstract class LinkedMapIterator implements Iterator { + LinkedEntry mNextEntry = head.next; + LinkedEntry mLastReturned = null; + + /** + * The modCount value that the iterator believes that the backing + * List should have. If this expectation is violated, the iterator + * has detected concurrent modification. + */ + int mExpectedModCount = modCount; + + public boolean hasNext() { + return mNextEntry != head; + } + + public void remove() { + if (mLastReturned == null) { + throw new IllegalStateException(); + } + + if (modCount != mExpectedModCount) { + throw new ConcurrentModificationException(); + } + + LinkedMap.this.remove(mLastReturned.mKey); + mLastReturned = null; + + mExpectedModCount = modCount; + } + + LinkedEntry nextEntry() { + if (modCount != mExpectedModCount) { + throw new ConcurrentModificationException(); + } + + if (mNextEntry == head) { + throw new NoSuchElementException(); + } + + LinkedEntry e = mLastReturned = mNextEntry; + mNextEntry = e.next; + + return e; + } + } + + private class KeyIterator extends LinkedMap.LinkedMapIterator { + public K next() { + return nextEntry().mKey; + } + } + + private class ValueIterator extends LinkedMap.LinkedMapIterator { + public V next() { + return nextEntry().mValue; + } + } + + private class EntryIterator extends LinkedMap.LinkedMapIterator> { + public Entry next() { + return nextEntry(); + } + } + + public V get(Object pKey) { + LinkedEntry entry = (LinkedEntry) entries.get(pKey); + + if (entry != null) { + entry.recordAccess(this); + return entry.mValue; + } + + return null; + } + + public V remove(Object pKey) { + LinkedEntry entry = (LinkedEntry) entries.remove(pKey); + + if (entry != null) { + entry.remove(); + modCount++; + + return entry.mValue; + } + return null; + } + + public V put(K pKey, V pValue) { + LinkedEntry entry = (LinkedEntry) entries.get(pKey); + V oldValue; + + if (entry == null) { + oldValue = null; + + // Remove eldest entry if instructed, else grow capacity if appropriate + LinkedEntry eldest = head.next; + if (removeEldestEntry(eldest)) { + removeEntry(eldest); + } + + entry = createEntry(pKey, pValue); + entry.addBefore(head); + + entries.put(pKey, entry); + } + else { + oldValue = entry.mValue; + + entry.mValue = pValue; + entry.recordAccess(this); + } + + modCount++; + + return oldValue; + } + + /** + * Creates a new {@code LinkedEntry}. + * + * @param pKey the key + * @param pValue the value + * @return a new LinkedEntry + */ + /*protected*/ LinkedEntry createEntry(K pKey, V pValue) { + return new LinkedEntry(pKey, pValue, null); + } + + /** + * @return a copy of this map, with the same order and same key/value pairs. + */ + public Object clone() throws CloneNotSupportedException { + LinkedMap map; + + map = (LinkedMap) super.clone(); + + // TODO: The rest of the work is PROBABLY handled by + // AbstractDecoratedMap, but need to verify that. + + return map; + } + + /** + * Returns {@code true} if this map should remove its eldest entry. + * This method is invoked by {@code put} and {@code putAll} after + * inserting a new entry into the map. It provides the implementer + * with the opportunity to remove the eldest entry each time a new one + * is added. This is useful if the map represents a cache: it allows + * the map to reduce memory consumption by deleting stale entries. + * + *

Sample use: this override will allow the map to grow up to 100 + * entries and then delete the eldest entry each time a new entry is + * added, maintaining a steady state of 100 entries. + *

+     *     private static final int MAX_ENTRIES = 100;
+     *
+     *     protected boolean removeEldestEntry(Map.Entry eldest) {
+     *        return size() > MAX_ENTRIES;
+     *     }
+     * 
+ * + *

This method typically does not modify the map in any way, + * instead allowing the map to modify itself as directed by its + * return value. It is permitted for this method to modify + * the map directly, but if it does so, it must return + * {@code false} (indicating that the map should not attempt any + * further modification). The effects of returning {@code true} + * after modifying the map from within this method are unspecified. + * + *

This implementation merely returns {@code false} (so that this + * map acts like a normal map - the eldest element is never removed). + * + * @param pEldest The least recently inserted entry in the map, or if + * this is an access-ordered map, the least recently accessed + * entry. This is the entry that will be removed it this + * method returns {@code true}. If the map was empty prior + * to the {@code put} or {@code putAll} invocation resulting + * in this invocation, this will be the entry that was just + * inserted; in other words, if the map contains a single + * entry, the eldest entry is also the newest. + * @return {@code true} if the eldest entry should be removed + * from the map; {@code false} if it should be retained. + */ + protected boolean removeEldestEntry(Entry pEldest) { + return false; + } + + /** + * Linked list implementation of {@code Map.Entry}. + */ + protected static class LinkedEntry extends BasicEntry implements Serializable { + LinkedEntry previous; + LinkedEntry next; + + LinkedEntry(K pKey, V pValue, LinkedEntry pNext) { + super(pKey, pValue); + + next = pNext; + } + + /** + * Adds this entry before the given entry (which must be an existing + * entry) in the list. + * + * @param pExisting the entry to add before + */ + void addBefore(LinkedEntry pExisting) { + next = pExisting; + previous = pExisting.previous; + + previous.next = this; + next.previous = this; + } + + /** + * Removes this entry from the linked list. + */ + void remove() { + previous.next = next; + next.previous = previous; + } + + /** + * If the entry is part of an access ordered list, moves the entry to + * the end of the list. + * + * @param pMap the map to record access for + */ + protected void recordAccess(Map pMap) { + LinkedMap linkedMap = (LinkedMap) pMap; + if (linkedMap.accessOrder) { + linkedMap.modCount++; + remove(); + addBefore(linkedMap.head); + } + } + + /** + * Removes this entry from the linked list. + * + * @param pMap the map to record removal from + */ + protected void recordRemoval(Map pMap) { + // TODO: Is this REALLY correct? + remove(); + } + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedSet.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedSet.java index 8a56b367..302e8646 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedSet.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedSet.java @@ -1,82 +1,85 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.*; -import java.io.Serializable; - -/** - * Generic map and linked list implementation of the {@code Set} interface, - * with predictable iteration order. - *

- * Resembles {@code LinkedHashSet} from JDK 1.4+, but is backed by a generic - * {@code LinkedMap}, rather than implementing a particular algoritm. - *

- * @see LinkedMap - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedSet.java#1 $ - */ -public class LinkedSet extends AbstractSet implements Set, Cloneable, Serializable { - - private final static Object DUMMY = new Object(); - - private final Map map; - - public LinkedSet() { - map = new LinkedMap(); - } - - public LinkedSet(Collection pCollection) { - this(); - addAll(pCollection); - } - - public boolean addAll(Collection pCollection) { - boolean changed = false; - for (E value : pCollection) { - if (add(value) && !changed) { - changed = true; - } - } - return changed; - } - - public boolean add(E pValue) { - return map.put(pValue, DUMMY) == null; - } - - public int size() { - return map.size(); - } - - public Iterator iterator() { - return map.keySet().iterator(); - } -} +/* + * 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.util; + +import java.io.Serializable; +import java.util.*; + +/** + * Generic map and linked list implementation of the {@code Set} interface, + * with predictable iteration order. + *

+ * Resembles {@code LinkedHashSet} from JDK 1.4+, but is backed by a generic + * {@code LinkedMap}, rather than implementing a particular algoritm. + *

+ * + * @see LinkedMap + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedSet.java#1 $ + */ +public class LinkedSet extends AbstractSet implements Set, Cloneable, Serializable { + + private final static Object DUMMY = new Object(); + + private final Map map; + + public LinkedSet() { + map = new LinkedMap(); + } + + public LinkedSet(Collection pCollection) { + this(); + addAll(pCollection); + } + + public boolean addAll(Collection pCollection) { + boolean changed = false; + for (E value : pCollection) { + if (add(value) && !changed) { + changed = true; + } + } + return changed; + } + + public boolean add(E pValue) { + return map.put(pValue, DUMMY) == null; + } + + public int size() { + return map.size(); + } + + public Iterator iterator() { + return map.keySet().iterator(); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/NullMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/NullMap.java index eb538915..d9f8dca1 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/NullMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/NullMap.java @@ -1,117 +1,123 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.*; -import java.io.Serializable; - -/** - * An (immutable) empty {@link Map}, that supports all {@code Map} operations - * without throwing exceptions (in contrast to {@link Collections#EMPTY_MAP} - * that will throw exceptions on {@code put}/{@code remove}). - *

- * NOTE: This is not a general purpose {@code Map} implementation, - * as the {@code put} and {@code putAll} methods will not modify the map. - * Instances of this class will always be an empty map. - * - * @author Harald Kuhr - * @version $Id: com/twelvemonkeys/util/NullMap.java#2 $ - */ -public final class NullMap implements Map, Serializable { - public final int size() { - return 0; - } - - public final void clear() { - } - - public final boolean isEmpty() { - return true; - } - - public final boolean containsKey(Object pKey) { - return false; - } - - public final boolean containsValue(Object pValue) { - return false; - } - - public final Collection values() { - return Collections.emptyList(); - } - - public final void putAll(Map pMap) { - } - - public final Set> entrySet() { - return Collections.emptySet(); - } - - public final Set keySet() { - return Collections.emptySet(); - } - - public final V get(Object pKey) { - return null; - } - - public final V remove(Object pKey) { - return null; - } - - public final V put(Object pKey, Object pValue) { - return null; - } - - /** - * Tests the given object for equality (wether it is also an empty - * {@code Map}). - * This is consistent with the standard {@code Map} implementations of the - * Java Collections Framework. - * - * @param pOther the object to compare with - * @return {@code true} if {@code pOther} is an empty {@code Map}, - * otherwise {@code false} - */ - public boolean equals(Object pOther) { - return (pOther instanceof Map) && ((Map) pOther).isEmpty(); - } - - /** - * Returns the {@code hashCode} of the empty map, {@code 0}. - * This is consistent with the standard {@code Map} implementations of the - * Java Collections Framework. - * - * @return {@code 0}, always - */ - public int hashCode() { - return 0; - } +/* + * 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.util; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * An (immutable) empty {@link Map}, that supports all {@code Map} operations + * without throwing exceptions (in contrast to {@link Collections#EMPTY_MAP} + * that will throw exceptions on {@code put}/{@code remove}). + *

+ * NOTE: This is not a general purpose {@code Map} implementation, + * as the {@code put} and {@code putAll} methods will not modify the map. + * Instances of this class will always be an empty map. + *

+ * + * @author Harald Kuhr + * @version $Id: com/twelvemonkeys/util/NullMap.java#2 $ + */ +public final class NullMap implements Map, Serializable { + public final int size() { + return 0; + } + + public final void clear() { + } + + public final boolean isEmpty() { + return true; + } + + public final boolean containsKey(Object pKey) { + return false; + } + + public final boolean containsValue(Object pValue) { + return false; + } + + public final Collection values() { + return Collections.emptyList(); + } + + public final void putAll(Map pMap) { + } + + public final Set> entrySet() { + return Collections.emptySet(); + } + + public final Set keySet() { + return Collections.emptySet(); + } + + public final V get(Object pKey) { + return null; + } + + public final V remove(Object pKey) { + return null; + } + + public final V put(Object pKey, Object pValue) { + return null; + } + + /** + * Tests the given object for equality (wether it is also an empty + * {@code Map}). + * This is consistent with the standard {@code Map} implementations of the + * Java Collections Framework. + * + * @param pOther the object to compare with + * @return {@code true} if {@code pOther} is an empty {@code Map}, + * otherwise {@code false} + */ + public boolean equals(Object pOther) { + return (pOther instanceof Map) && ((Map) pOther).isEmpty(); + } + + /** + * Returns the {@code hashCode} of the empty map, {@code 0}. + * This is consistent with the standard {@code Map} implementations of the + * Java Collections Framework. + * + * @return {@code 0}, always + */ + public int hashCode() { + return 0; + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/StringTokenIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/StringTokenIterator.java index dc95c5d6..9ee89556 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/StringTokenIterator.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/StringTokenIterator.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/Time.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/Time.java index c7be02a7..a33fcd40 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/Time.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/Time.java @@ -1,181 +1,182 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -/** - * Utility class for storing times in a simple way. The internal time is stored - * as an int, counting seconds. - * - * @author Harald Kuhr - * @todo Milliseconds! - */ -public class Time { - - private int time = -1; - public final static int SECONDS_IN_MINUTE = 60; - - /** - * Creates a new time with 0 seconds, 0 minutes. - */ - public Time() { - this(0); - } - - /** - * Creates a new time with the given time (in seconds). - */ - public Time(int pTime) { - setTime(pTime); - } - - /** - * Sets the full time in seconds - */ - public void setTime(int pTime) { - if (pTime < 0) { - throw new IllegalArgumentException("Time argument must be 0 or positive!"); - } - time = pTime; - } - - /** - * Gets the full time in seconds. - */ - public int getTime() { - return time; - } - - /** - * Gets the full time in milliseconds, for use in creating dates or - * similar. - * - * @see java.util.Date#setTime(long) - */ - public long getTimeInMillis() { - return (long) time * 1000L; - } - - /** - * Sets the seconds part of the time. Note, if the seconds argument is 60 - * or greater, the value will "wrap", and increase the minutes also. - * - * @param pSeconds an integer that should be between 0 and 59. - */ - public void setSeconds(int pSeconds) { - time = getMinutes() * SECONDS_IN_MINUTE + pSeconds; - } - - /** - * Gets the seconds part of the time. - * - * @return an integer between 0 and 59 - */ - public int getSeconds() { - return time % SECONDS_IN_MINUTE; - } - - /** - * Sets the minutes part of the time. - * - * @param pMinutes an integer - */ - public void setMinutes(int pMinutes) { - time = pMinutes * SECONDS_IN_MINUTE + getSeconds(); - } - - /** - * Gets the minutes part of the time. - * - * @return an integer - */ - public int getMinutes() { - return time / SECONDS_IN_MINUTE; - } - - /** - * Creates a string representation of the time object. - * The string is returned on the form m:ss, - * where m is variable digits minutes and ss is two digits seconds. - * - * @return a string representation of the time object - * @see #toString(String) - */ - public String toString() { - return "" + getMinutes() + ":" - + (getSeconds() < 10 ? "0" : "") + getSeconds(); - } - - /** - * Creates a string representation of the time object. - * The string returned is on the format of the formatstring. - *
- *
m (or any multiple of m's) - *
the minutes part (padded with 0's, if number has less digits than - * the number of m's) - * m -> 0,1,...,59,60,61,... - * mm -> 00,01,...,59,60,61,... - *
s or ss - *
the seconds part (padded with 0's, if number has less digits than - * the number of s's) - * s -> 0,1,...,59 - * ss -> 00,01,...,59 - *
S - *
all seconds (including the ones above 59) - *
- * - * @param pFormatStr the format where - * @return a string representation of the time object - * @throws NumberFormatException - * @see TimeFormat#format(Time) - * @see #parseTime(String) - * @deprecated - */ - public String toString(String pFormatStr) { - TimeFormat tf = new TimeFormat(pFormatStr); - - return tf.format(this); - } - - /** - * Creates a string representation of the time object. - * The string is returned on the form m:ss, - * where m is variable digits minutes and ss is two digits seconds. - * - * @return a string representation of the time object - * @throws NumberFormatException - * @see TimeFormat#parse(String) - * @see #toString(String) - * @deprecated - */ - public static Time parseTime(String pStr) { - TimeFormat tf = TimeFormat.getInstance(); - - return tf.parse(pStr); - } -} +/* + * 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.util; + +/** + * Utility class for storing times in a simple way. The internal time is stored + * as an int, counting seconds. + * + * @author Harald Kuhr + */ +// TODO: Milliseconds! +public class Time { + + private int time = -1; + public final static int SECONDS_IN_MINUTE = 60; + + /** + * Creates a new time with 0 seconds, 0 minutes. + */ + public Time() { + this(0); + } + + /** + * Creates a new time with the given time (in seconds). + */ + public Time(int pTime) { + setTime(pTime); + } + + /** + * Sets the full time in seconds + */ + public void setTime(int pTime) { + if (pTime < 0) { + throw new IllegalArgumentException("Time argument must be 0 or positive!"); + } + time = pTime; + } + + /** + * Gets the full time in seconds. + */ + public int getTime() { + return time; + } + + /** + * Gets the full time in milliseconds, for use in creating dates or + * similar. + * + * @see java.util.Date#setTime(long) + */ + public long getTimeInMillis() { + return (long) time * 1000L; + } + + /** + * Sets the seconds part of the time. Note, if the seconds argument is 60 + * or greater, the value will "wrap", and increase the minutes also. + * + * @param pSeconds an integer that should be between 0 and 59. + */ + public void setSeconds(int pSeconds) { + time = getMinutes() * SECONDS_IN_MINUTE + pSeconds; + } + + /** + * Gets the seconds part of the time. + * + * @return an integer between 0 and 59 + */ + public int getSeconds() { + return time % SECONDS_IN_MINUTE; + } + + /** + * Sets the minutes part of the time. + * + * @param pMinutes an integer + */ + public void setMinutes(int pMinutes) { + time = pMinutes * SECONDS_IN_MINUTE + getSeconds(); + } + + /** + * Gets the minutes part of the time. + * + * @return an integer + */ + public int getMinutes() { + return time / SECONDS_IN_MINUTE; + } + + /** + * Creates a string representation of the time object. + * The string is returned on the form m:ss, + * where m is variable digits minutes and ss is two digits seconds. + * + * @return a string representation of the time object + * @see #toString(String) + */ + public String toString() { + return getMinutes() + ":" + (getSeconds() < 10 ? "0" : "") + getSeconds(); + } + + /** + * Creates a string representation of the time object. + * The string returned is on the format of the formatstring. + *
+ *
m (or any multiple of m's) + *
the minutes part (padded with 0's, if number has less digits than + * the number of m's) + * m -> 0,1,...,59,60,61,... + * mm -> 00,01,...,59,60,61,... + *
s or ss + *
the seconds part (padded with 0's, if number has less digits than + * the number of s's) + * s -> 0,1,...,59 + * ss -> 00,01,...,59 + *
S + *
all seconds (including the ones above 59) + *
+ * + * @param pFormatStr the format where + * @return a string representation of the time object + * @throws NumberFormatException + * @see TimeFormat#format(Time) + * @see #parseTime(String) + * @deprecated + */ + public String toString(String pFormatStr) { + TimeFormat tf = new TimeFormat(pFormatStr); + + return tf.format(this); + } + + /** + * Creates a string representation of the time object. + * The string is returned on the form m:ss, + * where m is variable digits minutes and ss is two digits seconds. + * + * @return a string representation of the time object + * @throws NumberFormatException + * @see TimeFormat#parse(String) + * @see #toString(String) + * @deprecated + */ + public static Time parseTime(String pStr) { + TimeFormat tf = TimeFormat.getInstance(); + + return tf.parse(pStr); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeFormat.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeFormat.java index 68fb1d33..8b9ca532 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeFormat.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeFormat.java @@ -1,451 +1,452 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import com.twelvemonkeys.lang.StringUtil; - -import java.text.FieldPosition; -import java.text.Format; -import java.text.ParsePosition; -import java.util.StringTokenizer; -import java.util.Vector; - -/** - * Format for converting and parsing time. - *

- * The format is expressed in a string as follows: - *

- *
m (or any multiple of m's) - *
the minutes part (padded with 0's, if number has less digits than - * the number of m's) - * m -> 0,1,...,59,60,61,... - * mm -> 00,01,...,59,60,61,... - *
s or ss - *
the seconds part (padded with 0's, if number has less digits than - * the number of s's) - * s -> 0,1,...,59 - * ss -> 00,01,...,59 - *
S - *
all seconds (including the ones above 59) - *
- *

- * May not handle all cases, and formats... ;-) - * Safest is: Always delimiters between the minutes (m) and seconds (s) part. - *

- * TODO: - * Move to com.twelvemonkeys.text? - * Milliseconds! - * Fix bugs. - * Known bugs: - *

- * The last character in the formatString is not escaped, while it should be. - * The first character after an escaped character is escaped while is shouldn't - * be. - *

- * This is not a 100% compatible implementation of a java.text.Format. - * - * @see com.twelvemonkeys.util.Time - * - * @author Harald Kuhr - */ -public class TimeFormat extends Format { - final static String MINUTE = "m"; - final static String SECOND = "s"; - final static String TIME = "S"; - final static String ESCAPE = "\\"; - - /** - * The default time format - */ - - private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss"); - protected String formatString = null; - - /** - * Main method for testing ONLY - */ - - static void main(String[] argv) { - Time time = null; - TimeFormat in = null; - TimeFormat out = null; - - if (argv.length >= 3) { - System.out.println("Creating out TimeFormat: \"" + argv[2] + "\""); - out = new TimeFormat(argv[2]); - } - - if (argv.length >= 2) { - System.out.println("Creating in TimeFormat: \"" + argv[1] + "\""); - in = new TimeFormat(argv[1]); - } - else { - System.out.println("Using default format for in"); - in = DEFAULT_FORMAT; - } - - if (out == null) - out = in; - - if (argv.length >= 1) { - System.out.println("Parsing: \"" + argv[0] + "\" with format \"" - + in.formatString + "\""); - time = in.parse(argv[0]); - } - else - time = new Time(); - - System.out.println("Time is \"" + out.format(time) + - "\" according to format \"" + out.formatString + "\""); - } - - - /** - * The formatter array. - */ - - protected TimeFormatter[] formatter; - - /** - * Creates a new TimeFormat with the given formatString, - */ - - public TimeFormat(String pStr) { - formatString = pStr; - - Vector formatter = new Vector(); - StringTokenizer tok = new StringTokenizer(pStr, "\\msS", true); - - String previous = null; - String current = null; - int previousCount = 0; - - while (tok.hasMoreElements()) { - current = tok.nextToken(); - - if (previous != null && previous.equals(ESCAPE)) { - // Handle escaping of s, S or m - current = ((current != null) ? current : "") - + (tok.hasMoreElements() ? tok.nextToken() : ""); - previous = null; - previousCount = 0; - } - - // Skip over first, - // or if current is the same, increase count, and try again - if (previous == null || previous.equals(current)) { - previousCount++; - previous = current; - } - else { - // Create new formatter for each part - if (previous.equals(MINUTE)) - formatter.add(new MinutesFormatter(previousCount)); - else if (previous.equals(SECOND)) - formatter.add(new SecondsFormatter(previousCount)); - else if (previous.equals(TIME)) - formatter.add(new SecondsFormatter(-1)); - else - formatter.add(new TextFormatter(previous)); - - previousCount = 1; - previous = current; - - } - } - - // Add new formatter for last part - if (previous != null) { - if (previous.equals(MINUTE)) - formatter.add(new MinutesFormatter(previousCount)); - else if (previous.equals(SECOND)) - formatter.add(new SecondsFormatter(previousCount)); - else if (previous.equals(TIME)) - formatter.add(new SecondsFormatter(-1)); - else - formatter.add(new TextFormatter(previous)); - } - - // Debug - /* - for (int i = 0; i < formatter.size(); i++) { - System.out.println("Formatter " + formatter.get(i).getClass() - + ": length=" + ((TimeFormatter) formatter.get(i)).digits); - } - */ - this.formatter = (TimeFormatter[]) - formatter.toArray(new TimeFormatter[formatter.size()]); - - } - - /** - * DUMMY IMPLEMENTATION!! - * Not locale specific. - */ - - public static TimeFormat getInstance() { - return DEFAULT_FORMAT; - } - - /** DUMMY IMPLEMENTATION!! */ - /* Not locale specific - public static TimeFormat getInstance(Locale pLocale) { - return DEFAULT_FORMAT; - } - */ - - /** DUMMY IMPLEMENTATION!! */ - /* Not locale specific - public static Locale[] getAvailableLocales() { - return new Locale[] {Locale.getDefault()}; - } - */ - - /** Gets the format string. */ - public String getFormatString() { - return formatString; - } - - /** DUMMY IMPLEMENTATION!! */ - public StringBuffer format(Object pObj, StringBuffer pToAppendTo, - FieldPosition pPos) { - if (!(pObj instanceof Time)) { - throw new IllegalArgumentException("Must be instance of " + Time.class); - } - - return pToAppendTo.append(format(pObj)); - } - - /** - * Formats the the given time, using this format. - */ - - public String format(Time pTime) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < formatter.length; i++) { - buf.append(formatter[i].format(pTime)); - } - return buf.toString(); - } - - /** DUMMY IMPLEMENTATION!! */ - public Object parseObject(String pStr, ParsePosition pStatus) { - Time t = parse(pStr); - - pStatus.setIndex(pStr.length()); // Not 100% - - return t; - } - - /** - * Parses a Time, according to this format. - *

- * Will bug on some formats. It's safest to always use delimiters between - * the minutes (m) and seconds (s) part. - * - */ - public Time parse(String pStr) { - Time time = new Time(); - - int sec = 0; - int min = 0; - int pos = 0; - int skip = 0; - - boolean onlyUseSeconds = false; - - for (int i = 0; (i < formatter.length) - && (pos + skip < pStr.length()) ; i++) { - // Go to next offset - pos += skip; - - if (formatter[i] instanceof MinutesFormatter) { - // Parse MINUTES - if ((i + 1) < formatter.length - && formatter[i + 1] instanceof TextFormatter) { - // Skip until next format element - skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); - // Error in format, try parsing to end - if (skip < 0) - skip = pStr.length(); - } - else if ((i + 1) >= formatter.length) { - // Skip until end of string - skip = pStr.length(); - } - else { - // Hope this is correct... - skip = formatter[i].digits; - } - - // May be first char - if (skip > pos) - min = Integer.parseInt(pStr.substring(pos, skip)); - } - else if (formatter[i] instanceof SecondsFormatter) { - // Parse SECONDS - if (formatter[i].digits == -1) { - // Only seconds (or full TIME) - if ((i + 1) < formatter.length - && formatter[i + 1] instanceof TextFormatter) { - // Skip until next format element - skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); - - } - else if ((i + 1) >= formatter.length) { - // Skip until end of string - skip = pStr.length(); - } - else { - // Cannot possibly know how long? - skip = 0; - continue; - } - - // Get seconds - sec = Integer.parseInt(pStr.substring(pos, skip)); - // System.out.println("Only seconds: " + sec); - - onlyUseSeconds = true; - break; - } - else { - // Normal SECONDS - if ((i + 1) < formatter.length - && formatter[i + 1] instanceof TextFormatter) { - // Skip until next format element - skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); - - } - else if ((i + 1) >= formatter.length) { - // Skip until end of string - skip = pStr.length(); - } - else { - skip = formatter[i].digits; - } - // Get seconds - sec = Integer.parseInt(pStr.substring(pos, skip)); - } - } - else if (formatter[i] instanceof TextFormatter) { - skip = formatter[i].digits; - } - - } - - // Set the minutes part if we should - if (!onlyUseSeconds) - time.setMinutes(min); - - // Set the seconds part - time.setSeconds(sec); - - return time; - } -} - -/** - * The base class of TimeFormatters - */ -abstract class TimeFormatter { - int digits = 0; - - abstract String format(Time t); -} - -/** - * Formats the seconds part of the Time - */ -class SecondsFormatter extends TimeFormatter { - - SecondsFormatter(int pDigits) { - digits = pDigits; - } - - String format(Time t) { - // Negative number of digits, means all seconds, no padding - if (digits < 0) { - return Integer.toString(t.getTime()); - } - - // If seconds is more than digits long, simply return it - if (t.getSeconds() >= Math.pow(10, digits)) { - return Integer.toString(t.getSeconds()); - } - - // Else return it with leading 0's - //return StringUtil.formatNumber(t.getSeconds(), digits); - return StringUtil.pad("" + t.getSeconds(), digits, "0", true); - } -} - -/** - * Formats the minutes part of the Time - */ -class MinutesFormatter extends TimeFormatter { - - MinutesFormatter(int pDigits) { - digits = pDigits; - } - - String format(Time t) { - // If minutes is more than digits long, simply return it - if (t.getMinutes() >= Math.pow(10, digits)) { - return Integer.toString(t.getMinutes()); - } - - // Else return it with leading 0's - //return StringUtil.formatNumber(t.getMinutes(), digits); - return StringUtil.pad("" + t.getMinutes(), digits, "0", true); - } -} - -/** - * Formats text constant part of the Time - */ -class TextFormatter extends TimeFormatter { - String text = null; - - TextFormatter(String pText) { - text = pText; - - // Just to be able to skip over - if (pText != null) { - digits = pText.length(); - } - } - - String format(Time t) { - // Simply return the text - return text; - } - -} +/* + * 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.util; + +import com.twelvemonkeys.lang.StringUtil; + +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Format for converting and parsing time. + *

+ * The format is expressed in a string as follows: + *

+ *
m (or any multiple of m's) + *
the minutes part (padded with 0's, if number has less digits than + * the number of m's) + * m -> 0,1,...,59,60,61,... + * mm -> 00,01,...,59,60,61,... + *
s or ss + *
the seconds part (padded with 0's, if number has less digits than + * the number of s's) + * s -> 0,1,...,59 + * ss -> 00,01,...,59 + *
S + *
all seconds (including the ones above 59) + *
+ *

+ * May not handle all cases, and formats... ;-) + * Safest is: Always delimiters between the minutes (m) and seconds (s) part. + *

+ * Known bugs: + *

+ * The last character in the formatString is not escaped, while it should be. + * The first character after an escaped character is escaped while is shouldn't + * be. + *

+ * This is not a 100% compatible implementation of a java.text.Format. + * + * @see com.twelvemonkeys.util.Time + * + * @author Harald Kuhr + */ +// TODO: +// Move to com.twelvemonkeys.text? +// Milliseconds! +// Fix bugs. +public class TimeFormat extends Format { + final static String MINUTE = "m"; + final static String SECOND = "s"; + final static String TIME = "S"; + final static String ESCAPE = "\\"; + + /** + * The default time format + */ + + private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss"); + protected String formatString = null; + + /** + * Main method for testing ONLY + */ + + static void main(String[] argv) { + Time time = null; + TimeFormat in = null; + TimeFormat out = null; + + if (argv.length >= 3) { + System.out.println("Creating out TimeFormat: \"" + argv[2] + "\""); + out = new TimeFormat(argv[2]); + } + + if (argv.length >= 2) { + System.out.println("Creating in TimeFormat: \"" + argv[1] + "\""); + in = new TimeFormat(argv[1]); + } + else { + System.out.println("Using default format for in"); + in = DEFAULT_FORMAT; + } + + if (out == null) + out = in; + + if (argv.length >= 1) { + System.out.println("Parsing: \"" + argv[0] + "\" with format \"" + + in.formatString + "\""); + time = in.parse(argv[0]); + } + else + time = new Time(); + + System.out.println("Time is \"" + out.format(time) + + "\" according to format \"" + out.formatString + "\""); + } + + + /** + * The formatter array. + */ + + protected TimeFormatter[] formatter; + + /** + * Creates a new TimeFormat with the given formatString, + */ + + public TimeFormat(String pStr) { + formatString = pStr; + + Vector formatter = new Vector(); + StringTokenizer tok = new StringTokenizer(pStr, "\\msS", true); + + String previous = null; + String current = null; + int previousCount = 0; + + while (tok.hasMoreElements()) { + current = tok.nextToken(); + + if (previous != null && previous.equals(ESCAPE)) { + // Handle escaping of s, S or m + current = ((current != null) ? current : "") + + (tok.hasMoreElements() ? tok.nextToken() : ""); + previous = null; + previousCount = 0; + } + + // Skip over first, + // or if current is the same, increase count, and try again + if (previous == null || previous.equals(current)) { + previousCount++; + previous = current; + } + else { + // Create new formatter for each part + if (previous.equals(MINUTE)) + formatter.add(new MinutesFormatter(previousCount)); + else if (previous.equals(SECOND)) + formatter.add(new SecondsFormatter(previousCount)); + else if (previous.equals(TIME)) + formatter.add(new SecondsFormatter(-1)); + else + formatter.add(new TextFormatter(previous)); + + previousCount = 1; + previous = current; + + } + } + + // Add new formatter for last part + if (previous != null) { + if (previous.equals(MINUTE)) + formatter.add(new MinutesFormatter(previousCount)); + else if (previous.equals(SECOND)) + formatter.add(new SecondsFormatter(previousCount)); + else if (previous.equals(TIME)) + formatter.add(new SecondsFormatter(-1)); + else + formatter.add(new TextFormatter(previous)); + } + + // Debug + /* + for (int i = 0; i < formatter.size(); i++) { + System.out.println("Formatter " + formatter.get(i).getClass() + + ": length=" + ((TimeFormatter) formatter.get(i)).digits); + } + */ + this.formatter = (TimeFormatter[]) + formatter.toArray(new TimeFormatter[formatter.size()]); + + } + + /** + * DUMMY IMPLEMENTATION!! + * Not locale specific. + */ + + public static TimeFormat getInstance() { + return DEFAULT_FORMAT; + } + + /** DUMMY IMPLEMENTATION!! */ + /* Not locale specific + public static TimeFormat getInstance(Locale pLocale) { + return DEFAULT_FORMAT; + } + */ + + /** DUMMY IMPLEMENTATION!! */ + /* Not locale specific + public static Locale[] getAvailableLocales() { + return new Locale[] {Locale.getDefault()}; + } + */ + + /** Gets the format string. */ + public String getFormatString() { + return formatString; + } + + /** DUMMY IMPLEMENTATION!! */ + public StringBuffer format(Object pObj, StringBuffer pToAppendTo, + FieldPosition pPos) { + if (!(pObj instanceof Time)) { + throw new IllegalArgumentException("Must be instance of " + Time.class); + } + + return pToAppendTo.append(format(pObj)); + } + + /** + * Formats the the given time, using this format. + */ + + public String format(Time pTime) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < formatter.length; i++) { + buf.append(formatter[i].format(pTime)); + } + return buf.toString(); + } + + /** DUMMY IMPLEMENTATION!! */ + public Object parseObject(String pStr, ParsePosition pStatus) { + Time t = parse(pStr); + + pStatus.setIndex(pStr.length()); // Not 100% + + return t; + } + + /** + * Parses a Time, according to this format. + *

+ * Will bug on some formats. It's safest to always use delimiters between + * the minutes (m) and seconds (s) part. + * + */ + public Time parse(String pStr) { + Time time = new Time(); + + int sec = 0; + int min = 0; + int pos = 0; + int skip = 0; + + boolean onlyUseSeconds = false; + + for (int i = 0; (i < formatter.length) + && (pos + skip < pStr.length()) ; i++) { + // Go to next offset + pos += skip; + + if (formatter[i] instanceof MinutesFormatter) { + // Parse MINUTES + if ((i + 1) < formatter.length + && formatter[i + 1] instanceof TextFormatter) { + // Skip until next format element + skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); + // Error in format, try parsing to end + if (skip < 0) + skip = pStr.length(); + } + else if ((i + 1) >= formatter.length) { + // Skip until end of string + skip = pStr.length(); + } + else { + // Hope this is correct... + skip = formatter[i].digits; + } + + // May be first char + if (skip > pos) + min = Integer.parseInt(pStr.substring(pos, skip)); + } + else if (formatter[i] instanceof SecondsFormatter) { + // Parse SECONDS + if (formatter[i].digits == -1) { + // Only seconds (or full TIME) + if ((i + 1) < formatter.length + && formatter[i + 1] instanceof TextFormatter) { + // Skip until next format element + skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); + + } + else if ((i + 1) >= formatter.length) { + // Skip until end of string + skip = pStr.length(); + } + else { + // Cannot possibly know how long? + skip = 0; + continue; + } + + // Get seconds + sec = Integer.parseInt(pStr.substring(pos, skip)); + // System.out.println("Only seconds: " + sec); + + onlyUseSeconds = true; + break; + } + else { + // Normal SECONDS + if ((i + 1) < formatter.length + && formatter[i + 1] instanceof TextFormatter) { + // Skip until next format element + skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); + + } + else if ((i + 1) >= formatter.length) { + // Skip until end of string + skip = pStr.length(); + } + else { + skip = formatter[i].digits; + } + // Get seconds + sec = Integer.parseInt(pStr.substring(pos, skip)); + } + } + else if (formatter[i] instanceof TextFormatter) { + skip = formatter[i].digits; + } + + } + + // Set the minutes part if we should + if (!onlyUseSeconds) + time.setMinutes(min); + + // Set the seconds part + time.setSeconds(sec); + + return time; + } +} + +/** + * The base class of TimeFormatters + */ +abstract class TimeFormatter { + int digits = 0; + + abstract String format(Time t); +} + +/** + * Formats the seconds part of the Time + */ +class SecondsFormatter extends TimeFormatter { + + SecondsFormatter(int pDigits) { + digits = pDigits; + } + + String format(Time t) { + // Negative number of digits, means all seconds, no padding + if (digits < 0) { + return Integer.toString(t.getTime()); + } + + // If seconds is more than digits long, simply return it + if (t.getSeconds() >= Math.pow(10, digits)) { + return Integer.toString(t.getSeconds()); + } + + // Else return it with leading 0's + //return StringUtil.formatNumber(t.getSeconds(), digits); + return StringUtil.pad(String.valueOf(t.getSeconds()), digits, "0", true); + } +} + +/** + * Formats the minutes part of the Time + */ +class MinutesFormatter extends TimeFormatter { + + MinutesFormatter(int pDigits) { + digits = pDigits; + } + + String format(Time t) { + // If minutes is more than digits long, simply return it + if (t.getMinutes() >= Math.pow(10, digits)) { + return Integer.toString(t.getMinutes()); + } + + // Else return it with leading 0's + //return StringUtil.formatNumber(t.getMinutes(), digits); + return StringUtil.pad(String.valueOf(t.getMinutes()), digits, "0", true); + } +} + +/** + * Formats text constant part of the Time + */ +class TextFormatter extends TimeFormatter { + String text = null; + + TextFormatter(String pText) { + text = pText; + + // Just to be able to skip over + if (pText != null) { + digits = pText.length(); + } + } + + String format(Time t) { + // Simply return the text + return text; + } + +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java index 800ce922..50dd8839 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java @@ -1,449 +1,461 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.io.Serializable; -import java.util.*; - -/** - * A {@code Map} implementation that removes (exipres) its elements after - * a given period. The map is by default backed by a {@link java.util.HashMap}, - * or can be instantiated with any given {@code Map} as backing. - *

- * Notes to consider when using this map: - *

    - *
  • Elements may not expire on the exact millisecond as expected.
  • - *
  • The value returned by the {@code size()} method of the map, or any of - * its collection views, may not represent - * the exact number of entries in the map at any given time.
  • - *
  • Elements in this map may expire at any time - * (but never between invocations of {@code Iterator.hasNext()} - * and {@code Iterator.next()} or {@code Iterator.remove()}, - * when iterating the collection views).
  • - *
- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TimeoutMap.java#2 $ - * - * @todo Consider have this Map extend LinkedMap.. That way the removeExpired - * method only have to run from the first element, until it finds an element - * that should not expire, as elements are in insertion order. - * and next expiry time would be the time of the first element. - * @todo Consider running the removeExpiredEntries method in a separate (deamon) thread - * @todo - or document why it is not such a good idea. - */ -public class TimeoutMap extends AbstractDecoratedMap implements ExpiringMap, Serializable, Cloneable { - /** - * Expiry time - */ - protected long expiryTime = 60000L; // 1 minute - - ////////////////////// - private volatile long nextExpiryTime; - ////////////////////// - - /** - * Creates a {@code TimeoutMap} with the default expiry time of 1 minute. - * This {@code TimeoutMap} will be backed by a new {@code HashMap} instance. - *

- * This is constructor is here to comply with the reccomendations for - * "standard" constructors in the {@code Map} interface. - * - * @see #TimeoutMap(long) - */ - public TimeoutMap() { - super(); - } - - /** - * Creates a {@code TimeoutMap} containing the same elements as the given map - * with the default expiry time of 1 minute. - * This {@code TimeoutMap} will be backed by a new {@code HashMap} instance, - * and not the map passed in as a paramter. - *

- * This is constructor is here to comply with the reccomendations for - * "standard" constructors in the {@code Map} interface. - * - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - * @see #TimeoutMap(java.util.Map, Map, long) - * @see java.util.Map - */ - public TimeoutMap(Map pContents) { - super(pContents); - } - - /** - * Creates a {@code TimeoutMap} with the given expiry time (milliseconds). - * This {@code TimeoutMap} will be backed by a new {@code HashMap} instance. - * - * @param pExpiryTime the expiry time (time to live) for elements in this map - */ - public TimeoutMap(long pExpiryTime) { - this(); - expiryTime = pExpiryTime; - } - - /** - * Creates a {@code TimeoutMap} with the given expiry time (milliseconds). - * This {@code TimeoutMap} will be backed by the given {@code Map}. - *

- * Note that structurally modifying the backing map directly (not - * through this map or its collection views), is not allowed, and will - * produce undeterministic exceptions. - * - * @param pBacking the map that will be used as backing. - * @param pContents the map whose mappings are to be placed in this map. - * May be {@code null}. - * @param pExpiryTime the expiry time (time to live) for elements in this map - */ - public TimeoutMap(Map> pBacking, Map pContents, long pExpiryTime) { - super(pBacking, pContents); - expiryTime = pExpiryTime; - } - - /** - * Gets the maximum time any value will be kept in the map, before it expires. - * - * @return the expiry time - */ - public long getExpiryTime() { - return expiryTime; - } - - /** - * Sets the maximum time any value will be kept in the map, before it expires. - * Removes any items that are older than the specified time. - * - * @param pExpiryTime the expiry time (time to live) for elements in this map - */ - public void setExpiryTime(long pExpiryTime) { - long oldEexpiryTime = expiryTime; - - expiryTime = pExpiryTime; - - if (expiryTime < oldEexpiryTime) { - // Expire now - nextExpiryTime = 0; - removeExpiredEntries(); - } - } - - /** - * Returns the number of key-value mappings in this map. If the - * map contains more than {@code Integer.MAX_VALUE} elements, returns - * {@code Integer.MAX_VALUE}. - * - * @return the number of key-value mappings in this map. - */ - public int size() { - removeExpiredEntries(); - return entries.size(); - } - - /** - * Returns {@code true} if this map contains no key-value mappings. - * - * @return {@code true} if this map contains no key-value mappings. - */ - public boolean isEmpty() { - return (size() <= 0); - } - - /** - * Returns {@code true} if this map contains a mapping for the specified - * pKey. - * - * @param pKey pKey whose presence in this map is to be tested. - * @return {@code true} if this map contains a mapping for the specified - * pKey. - */ - public boolean containsKey(Object pKey) { - removeExpiredEntries(); - return entries.containsKey(pKey); - } - - /** - * Returns the value to which this map maps the specified pKey. Returns - * {@code null} if the map contains no mapping for this pKey. A return - * value of {@code null} does not necessarily indicate that the - * map contains no mapping for the pKey; it's also possible that the map - * explicitly maps the pKey to {@code null}. The {@code containsKey} - * operation may be used to distinguish these two cases. - * - * @param pKey pKey whose associated value is to be returned. - * @return the value to which this map maps the specified pKey, or - * {@code null} if the map contains no mapping for this pKey. - * @see #containsKey(java.lang.Object) - */ - public V get(Object pKey) { - TimedEntry entry = (TimedEntry) entries.get(pKey); - - if (entry == null) { - return null; - } - else if (entry.isExpired()) { - //noinspection SuspiciousMethodCalls - entries.remove(pKey); - processRemoved(entry); - return null; - } - return entry.getValue(); - } - - /** - * Associates the specified pValue with the specified pKey in this map - * (optional operation). If the map previously contained a mapping for - * this pKey, the old pValue is replaced. - * - * @param pKey pKey with which the specified pValue is to be associated. - * @param pValue pValue to be associated with the specified pKey. - * @return previous pValue associated with specified pKey, or {@code null} - * if there was no mapping for pKey. A {@code null} return can - * also indicate that the map previously associated {@code null} - * with the specified pKey, if the implementation supports - * {@code null} values. - */ - public V put(K pKey, V pValue) { - TimedEntry entry = (TimedEntry) entries.get(pKey); - V oldValue; - - if (entry == null) { - oldValue = null; - - entry = createEntry(pKey, pValue); - - entries.put(pKey, entry); - } - else { - oldValue = entry.mValue; - entry.setValue(pValue); - entry.recordAccess(this); - } - - // Need to remove expired objects every now and then - // We do it in the put method, to avoid resource leaks over time. - removeExpiredEntries(); - modCount++; - - return oldValue; - } - - /** - * Removes the mapping for this pKey from this map if present (optional - * operation). - * - * @param pKey pKey whose mapping is to be removed from the map. - * @return previous value associated with specified pKey, or {@code null} - * if there was no mapping for pKey. A {@code null} return can - * also indicate that the map previously associated {@code null} - * with the specified pKey, if the implementation supports - * {@code null} values. - */ - public V remove(Object pKey) { - TimedEntry entry = (TimedEntry) entries.remove(pKey); - return (entry != null) ? entry.getValue() : null; - } - - /** - * Removes all mappings from this map. - */ - public void clear() { - entries.clear(); // Finally something straightforward.. :-) - init(); - } - - /*protected*/ TimedEntry createEntry(K pKey, V pValue) { - return new TimedEntry(pKey, pValue); - } - - /** - * Removes any expired mappings. - * - */ - protected void removeExpiredEntries() { - // Remove any expired elements - long now = System.currentTimeMillis(); - if (now > nextExpiryTime) { - removeExpiredEntriesSynced(now); - } - } - - /** - * Okay, I guess this do resemble DCL... - * - * @todo Write some exhausting multi-threaded unit-tests. - * - * @param pTime now - */ - private synchronized void removeExpiredEntriesSynced(long pTime) { - if (pTime > nextExpiryTime) { - //// - long next = Long.MAX_VALUE; - nextExpiryTime = next; // Avoid multiple runs... - for (Iterator> iterator = new EntryIterator(); iterator.hasNext();) { - TimedEntry entry = (TimedEntry) iterator.next(); - //// - long expires = entry.expires(); - if (expires < next) { - next = expires; - } - //// - } - //// - nextExpiryTime = next; - } - } - - public Collection values() { - removeExpiredEntries(); - return super.values(); - } - - public Set> entrySet() { - removeExpiredEntries(); - return super.entrySet(); - } - - public Set keySet() { - removeExpiredEntries(); - return super.keySet(); - } - - // Subclass overrides these to alter behavior of views' iterator() method - protected Iterator newKeyIterator() { - return new KeyIterator(); - } - - protected Iterator newValueIterator() { - return new ValueIterator(); - } - - protected Iterator> newEntryIterator() { - return new EntryIterator(); - } - - public void processRemoved(Entry pRemoved) { - } - - /** - * Note: Iterating through this iterator will remove any expired values. - */ - private abstract class TimeoutMapIterator implements Iterator { - Iterator>> mIterator = entries.entrySet().iterator(); - BasicEntry mNext; - long mNow = System.currentTimeMillis(); - - public void remove() { - mNext = null; // advance - mIterator.remove(); - } - - public boolean hasNext() { - if (mNext != null) { - return true; // Never expires between hasNext and next/remove! - } - - while (mNext == null && mIterator.hasNext()) { - Entry> entry = mIterator.next(); - TimedEntry timed = (TimedEntry) entry.getValue(); - - if (timed.isExpiredBy(mNow)) { - // Remove from map, and continue - mIterator.remove(); - processRemoved(timed); - } - else { - // Go with this entry - mNext = timed; - return true; - } - } - - return false; - } - - BasicEntry nextEntry() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - - BasicEntry entry = mNext; - mNext = null; // advance - return entry; - } - } - - private class KeyIterator extends TimeoutMapIterator { - public K next() { - return nextEntry().mKey; - } - } - - private class ValueIterator extends TimeoutMapIterator { - public V next() { - return nextEntry().mValue; - } - } - - private class EntryIterator extends TimeoutMapIterator> { - public Entry next() { - return nextEntry(); - } - } - - /** - * Keeps track of timed objects - */ - private class TimedEntry extends BasicEntry { - private long mTimestamp; - - TimedEntry(K pKey, V pValue) { - super(pKey, pValue); - mTimestamp = System.currentTimeMillis(); - } - - public V setValue(V pValue) { - mTimestamp = System.currentTimeMillis(); - return super.setValue(pValue); - } - - final boolean isExpired() { - return isExpiredBy(System.currentTimeMillis()); - } - - final boolean isExpiredBy(final long pTime) { - return pTime > expires(); - } - - final long expires() { - return mTimestamp + expiryTime; - } - } -} +/* + * 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.util; + +import java.io.Serializable; +import java.util.*; + +/** + * A {@code Map} implementation that removes (expires) its elements after + * a given period. The map is by default backed by a {@link java.util.HashMap}, + * or can be instantiated with any given {@code Map} as backing. + *

+ * Notes to consider when using this map: + *

+ *
    + *
  • Elements may not expire on the exact millisecond as expected.
  • + *
  • The value returned by the {@code size()} method of the map, or any of + * its collection views, may not represent + * the exact number of entries in the map at any given time.
  • + *
  • Elements in this map may expire at any time + * (but never between invocations of {@code Iterator.hasNext()} + * and {@code Iterator.next()} or {@code Iterator.remove()}, + * when iterating the collection views).
  • + *
+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TimeoutMap.java#2 $ + */ + // TODO: Consider have this Map extend LinkedMap.. That way the removeExpired + // method only have to run from the first element, until it finds an element + // that should not expire, as elements are in insertion order. + // and next expiry time would be the time of the first element. + // TODO: Consider running the removeExpiredEntries method in a separate (deamon) thread + // TODO: - or document why it is not such a good idea. +public class TimeoutMap extends AbstractDecoratedMap implements ExpiringMap, Serializable, Cloneable { + /** + * Expiry time + */ + protected long expiryTime = 60000L; // 1 minute + + ////////////////////// + private volatile long nextExpiryTime = Long.MAX_VALUE; + ////////////////////// + + /** + * Creates a {@code TimeoutMap} with the default expiry time of 1 minute. + * This {@code TimeoutMap} will be backed by a new {@code HashMap} instance. + *

+ * This is constructor is here to comply with the recommendations for + * "standard" constructors in the {@code Map} interface. + *

+ * + * @see #TimeoutMap(long) + */ + public TimeoutMap() { + super(); + } + + /** + * Creates a {@code TimeoutMap} containing the same elements as the given map + * with the default expiry time of 1 minute. + * This {@code TimeoutMap} will be backed by a new {@code HashMap} instance, + * and not the map passed in as a paramter. + *

+ * This is constructor is here to comply with the recommendations for + * "standard" constructors in the {@code Map} interface. + *

+ * + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + * @see #TimeoutMap(java.util.Map, Map, long) + * @see java.util.Map + */ + public TimeoutMap(Map pContents) { + super(pContents); + } + + /** + * Creates a {@code TimeoutMap} with the given expiry time (milliseconds). + * This {@code TimeoutMap} will be backed by a new {@code HashMap} instance. + * + * @param pExpiryTime the expiry time (time to live) for elements in this map + */ + public TimeoutMap(long pExpiryTime) { + this(); + expiryTime = pExpiryTime; + } + + /** + * Creates a {@code TimeoutMap} with the given expiry time (milliseconds). + * This {@code TimeoutMap} will be backed by the given {@code Map}. + *

+ * Note that structurally modifying the backing map directly (not + * through this map or its collection views), is not allowed, and will + * produce undeterministic exceptions. + *

+ * + * @param pBacking the map that will be used as backing. + * @param pContents the map whose mappings are to be placed in this map. + * May be {@code null}. + * @param pExpiryTime the expiry time (time to live) for elements in this map + */ + public TimeoutMap(Map> pBacking, Map pContents, long pExpiryTime) { + super(pBacking, pContents); + expiryTime = pExpiryTime; + } + + /** + * Gets the maximum time any value will be kept in the map, before it expires. + * + * @return the expiry time + */ + public long getExpiryTime() { + return expiryTime; + } + + /** + * Sets the maximum time any value will be kept in the map, before it expires. + * Removes any items that are older than the specified time. + * + * @param pExpiryTime the expiry time (time to live) for elements in this map + */ + public void setExpiryTime(long pExpiryTime) { + long oldEexpiryTime = expiryTime; + + expiryTime = pExpiryTime; + + if (expiryTime < oldEexpiryTime) { + // Expire now + nextExpiryTime = 0; + removeExpiredEntries(); + } + } + + /** + * Returns the number of key-value mappings in this map. If the + * map contains more than {@code Integer.MAX_VALUE} elements, returns + * {@code Integer.MAX_VALUE}. + * + * @return the number of key-value mappings in this map. + */ + public int size() { + removeExpiredEntries(); + return entries.size(); + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings. + */ + public boolean isEmpty() { + return size() <= 0; + } + + /** + * Returns {@code true} if this map contains a mapping for the specified + * pKey. + * + * @param pKey pKey whose presence in this map is to be tested. + * @return {@code true} if this map contains a mapping for the specified + * pKey. + */ + public boolean containsKey(Object pKey) { + removeExpiredEntries(); + return entries.containsKey(pKey); + } + + /** + * Returns the value to which this map maps the specified pKey. Returns + * {@code null} if the map contains no mapping for this pKey. A return + * value of {@code null} does not necessarily indicate that the + * map contains no mapping for the pKey; it's also possible that the map + * explicitly maps the pKey to {@code null}. The {@code containsKey} + * operation may be used to distinguish these two cases. + * + * @param pKey pKey whose associated value is to be returned. + * @return the value to which this map maps the specified pKey, or + * {@code null} if the map contains no mapping for this pKey. + * @see #containsKey(java.lang.Object) + */ + public V get(Object pKey) { + TimedEntry entry = (TimedEntry) entries.get(pKey); + + if (entry == null) { + return null; + } + else if (entry.isExpired()) { + //noinspection SuspiciousMethodCalls + entries.remove(pKey); + processRemoved(entry); + return null; + } + return entry.getValue(); + } + + /** + * Associates the specified pValue with the specified pKey in this map + * (optional operation). If the map previously contained a mapping for + * this pKey, the old pValue is replaced. + * + * @param pKey pKey with which the specified pValue is to be associated. + * @param pValue pValue to be associated with the specified pKey. + * @return previous pValue associated with specified pKey, or {@code null} + * if there was no mapping for pKey. A {@code null} return can + * also indicate that the map previously associated {@code null} + * with the specified pKey, if the implementation supports + * {@code null} values. + */ + public V put(K pKey, V pValue) { + TimedEntry entry = (TimedEntry) entries.get(pKey); + V oldValue; + + if (entry == null) { + oldValue = null; + + entry = createEntry(pKey, pValue); + + entries.put(pKey, entry); + } + else { + oldValue = entry.mValue; + entry.setValue(pValue); + entry.recordAccess(this); + } + + // Need to remove expired objects every now and then + // We do it in the put method, to avoid resource leaks over time. + removeExpiredEntries(); + modCount++; + + return oldValue; + } + + /** + * Removes the mapping for this pKey from this map if present (optional + * operation). + * + * @param pKey pKey whose mapping is to be removed from the map. + * @return previous value associated with specified pKey, or {@code null} + * if there was no mapping for pKey. A {@code null} return can + * also indicate that the map previously associated {@code null} + * with the specified pKey, if the implementation supports + * {@code null} values. + */ + public V remove(Object pKey) { + TimedEntry entry = (TimedEntry) entries.remove(pKey); + return (entry != null) ? entry.getValue() : null; + } + + /** + * Removes all mappings from this map. + */ + public void clear() { + entries.clear(); // Finally something straightforward.. :-) + init(); + } + + /*protected*/ TimedEntry createEntry(K pKey, V pValue) { + return new TimedEntry(pKey, pValue); + } + + /** + * Removes any expired mappings. + */ + protected void removeExpiredEntries() { + // Remove any expired elements + long now = System.currentTimeMillis(); + if (now > nextExpiryTime) { + removeExpiredEntriesSynced(now); + } + } + + /** + * Okay, I guess this do resemble DCL... + * + * @param pTime now + */ + // TODO: Write some exhausting multi-threaded unit-tests. + private synchronized void removeExpiredEntriesSynced(long pTime) { + if (pTime > nextExpiryTime) { + //// + long next = Long.MAX_VALUE; + nextExpiryTime = next; // Avoid multiple runs... + for (Iterator> iterator = new EntryIterator(); iterator.hasNext();) { + TimedEntry entry = (TimedEntry) iterator.next(); + //// + long expires = entry.expires(); + if (expires < next) { + next = expires; + } + //// + } + //// + nextExpiryTime = next; + } + } + + public Collection values() { + removeExpiredEntries(); + return super.values(); + } + + public Set> entrySet() { + removeExpiredEntries(); + return super.entrySet(); + } + + public Set keySet() { + removeExpiredEntries(); + return super.keySet(); + } + + // Subclass overrides these to alter behavior of views' iterator() method + protected Iterator newKeyIterator() { + return new KeyIterator(); + } + + protected Iterator newValueIterator() { + return new ValueIterator(); + } + + protected Iterator> newEntryIterator() { + return new EntryIterator(); + } + + public void processRemoved(Entry pRemoved) { + } + + /** + * Note: Iterating through this iterator will remove any expired values. + */ + private abstract class TimeoutMapIterator implements Iterator { + Iterator>> mIterator = entries.entrySet().iterator(); + BasicEntry mNext; + long mNow = System.currentTimeMillis(); + + public void remove() { + mNext = null; // advance + mIterator.remove(); + } + + public boolean hasNext() { + if (mNext != null) { + return true; // Never expires between hasNext and next/remove! + } + + while (mNext == null && mIterator.hasNext()) { + Entry> entry = mIterator.next(); + TimedEntry timed = (TimedEntry) entry.getValue(); + + if (timed.isExpiredBy(mNow)) { + // Remove from map, and continue + mIterator.remove(); + processRemoved(timed); + } + else { + // Go with this entry + mNext = timed; + return true; + } + } + + return false; + } + + BasicEntry nextEntry() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + BasicEntry entry = mNext; + mNext = null; // advance + return entry; + } + } + + private class KeyIterator extends TimeoutMapIterator { + public K next() { + return nextEntry().mKey; + } + } + + private class ValueIterator extends TimeoutMapIterator { + public V next() { + return nextEntry().mValue; + } + } + + private class EntryIterator extends TimeoutMapIterator> { + public Entry next() { + return nextEntry(); + } + } + + /** + * Keeps track of timed objects + */ + private class TimedEntry extends BasicEntry { + private long mTimestamp; + + TimedEntry(K pKey, V pValue) { + super(pKey, pValue); + updateTimestamp(); + } + + public V setValue(V pValue) { + updateTimestamp(); + return super.setValue(pValue); + } + + private void updateTimestamp() { + mTimestamp = System.currentTimeMillis(); + + long expires = expires(); + if (expires < nextExpiryTime) { + nextExpiryTime = expires; + } + } + + final boolean isExpired() { + return isExpiredBy(System.currentTimeMillis()); + } + + final boolean isExpiredBy(final long pTime) { + return pTime > expires(); + } + + final long expires() { + return mTimestamp + expiryTime; + } + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/TokenIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/TokenIterator.java index 7fa70b7a..a2aa2438 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/TokenIterator.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/TokenIterator.java @@ -1,57 +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 "TwelveMonkeys" 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 OWNER 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.util; - -import java.util.Iterator; -import java.util.Enumeration; - -/** - * TokenIterator, Iterator-based replacement for StringTokenizer. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TokenIterator.java#1 $ - */ -public interface TokenIterator extends Iterator, Enumeration { - boolean hasMoreTokens(); - - /** - * Returns the next element in the iteration as a {@code String}. - * - * @return the next element in the iteration. - * @exception java.util.NoSuchElementException iteration has no more elements. - */ - String nextToken(); - - /** - * Resets this iterator. - * - */ - void reset(); -} +/* + * 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.util; + +import java.util.Enumeration; +import java.util.Iterator; + +/** + * TokenIterator, Iterator-based replacement for StringTokenizer. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TokenIterator.java#1 $ + */ +public interface TokenIterator extends Iterator, Enumeration { + boolean hasMoreTokens(); + + /** + * Returns the next element in the iteration as a {@code String}. + * + * @return the next element in the iteration. + * @exception java.util.NoSuchElementException iteration has no more elements. + */ + String nextToken(); + + /** + * Resets this iterator. + * + */ + void reset(); +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/Visitor.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/Visitor.java index e62026c6..cb3311e8 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/Visitor.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/Visitor.java @@ -1,3 +1,33 @@ +/* + * 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.util; /** diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/WeakWeakMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/WeakWeakMap.java index d9737e92..9a1cc517 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/WeakWeakMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/WeakWeakMap.java @@ -4,32 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; -import java.util.*; import java.lang.ref.WeakReference; +import java.util.*; /** * Special-purpose map implementation with weak keys and weak values. This is diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConversionException.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConversionException.java index 06bf09ab..675a6e67 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConversionException.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConversionException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.convert; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java index 106b6164..8f6b09c5 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java @@ -1,194 +1,198 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.convert; - -import com.twelvemonkeys.util.Time; - -import java.util.Date; -import java.util.Hashtable; -import java.util.Map; - -/** - * The converter (singleton). Converts strings to objects and back. - * This is the entry point to the converter framework. - *

- * By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date} - * and {@link Object} - * (the {@link DefaultConverter}) are registered by this class' static - * initializer. You might remove them using the - * {@code unregisterConverter} method. - * - * @see #registerConverter(Class, PropertyConverter) - * @see #unregisterConverter(Class) - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/Converter.java#1 $ - */ -// TODO: Get rid of singleton stuff -// Can probably be a pure static class, but is that a good idea? -// Maybe have BeanUtil act as a "proxy", and hide this class all together? -// TODO: ServiceRegistry for registering 3rd party converters -// TODO: URI scheme, for implicit typing? Is that a good idea? -// TODO: Array converters? -public abstract class Converter implements PropertyConverter { - - /** Our singleton instance */ - protected static final Converter sInstance = new ConverterImpl(); // Thread safe & EASY - - /** The converters Map */ - protected final Map converters = new Hashtable(); - - // Register our predefined converters - static { - PropertyConverter defaultConverter = new DefaultConverter(); - registerConverter(Object.class, defaultConverter); - registerConverter(Boolean.TYPE, defaultConverter); - - PropertyConverter numberConverter = new NumberConverter(); - registerConverter(Number.class, numberConverter); - registerConverter(Byte.TYPE, numberConverter); - registerConverter(Double.TYPE, numberConverter); - registerConverter(Float.TYPE, numberConverter); - registerConverter(Integer.TYPE, numberConverter); - registerConverter(Long.TYPE, numberConverter); - registerConverter(Short.TYPE, numberConverter); - - registerConverter(Date.class, new DateConverter()); - registerConverter(Time.class, new TimeConverter()); - } - - /** - * Creates a Converter. - */ - protected Converter() { - } - - /** - * Gets the Converter instance. - * - * @return the converter instance - */ - public static Converter getInstance() { - return sInstance; - } - - /** - * Registers a converter for a given type. - * This converter will also be used for all subclasses, unless a more - * specific version is registered. - *

- * By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date} - * and {@link Object} - * (the {@link DefaultConverter}) are registered by this class' static - * initializer. You might remove them using the - * {@code unregisterConverter} method. - * - * @param pType the (super) type to register a converter for - * @param pConverter the converter - * - * @see #unregisterConverter(Class) - */ - public static void registerConverter(final Class pType, final PropertyConverter pConverter) { - getInstance().converters.put(pType, pConverter); - } - - /** - * Un-registers a converter for a given type. That is, making it unavailable - * for the converter framework, and making it (potentially) available for - * garbage collection. - * - * @param pType the (super) type to remove converter for - * - * @see #registerConverter(Class,PropertyConverter) - */ - @SuppressWarnings("UnusedDeclaration") - public static void unregisterConverter(final Class pType) { - getInstance().converters.remove(pType); - } - - /** - * Converts the string to an object of the given type. - * - * @param pString the string to convert - * @param pType the type to convert to - * - * @return the object created from the given string. - * - * @throws ConversionException if the string cannot be converted for any - * reason. - */ - public Object toObject(final String pString, final Class pType) throws ConversionException { - return toObject(pString, pType, null); - } - - /** - * Converts the string to an object of the given type, parsing after the - * given format. - * - * @param pString the string to convert - * @param pType the type to convert to - * @param pFormat the (optional) conversion format - * - * @return the object created from the given string. - * - * @throws ConversionException if the string cannot be converted for any - * reason. - */ - public abstract Object toObject(String pString, Class pType, String pFormat) - throws ConversionException; - - /** - * Converts the object to a string, using {@code object.toString()} - * - * @param pObject the object to convert. - * - * @return the string representation of the object, on the correct format. - * - * @throws ConversionException if the object cannot be converted to a - * string for any reason. - */ - public String toString(final Object pObject) throws ConversionException { - return toString(pObject, null); - } - - /** - * Converts the object to a string, using {@code object.toString()} - * - * @param pObject the object to convert. - * @param pFormat the (optional) conversion format - * - * @return the string representation of the object, on the correct format. - * - * @throws ConversionException if the object cannot be converted to a - * string for any reason. - */ - public abstract String toString(Object pObject, String pFormat) - throws ConversionException; -} +/* + * 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.util.convert; + +import com.twelvemonkeys.util.Time; + +import java.util.Date; +import java.util.Hashtable; +import java.util.Map; + +/** + * The converter (singleton). Converts strings to objects and back. + * This is the entry point to the converter framework. + *

+ * By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date} + * and {@link Object} + * (the {@link DefaultConverter}) are registered by this class' static + * initializer. You might remove them using the + * {@code unregisterConverter} method. + *

+ * + * @see #registerConverter(Class, PropertyConverter) + * @see #unregisterConverter(Class) + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/Converter.java#1 $ + */ +// TODO: Get rid of singleton stuff +// Can probably be a pure static class, but is that a good idea? +// Maybe have BeanUtil act as a "proxy", and hide this class all together? +// TODO: ServiceRegistry for registering 3rd party converters +// TODO: URI scheme, for implicit typing? Is that a good idea? +// TODO: Array converters? +public abstract class Converter implements PropertyConverter { + + /** Our singleton instance */ + protected static final Converter sInstance = new ConverterImpl(); // Thread safe & EASY + + /** The converters Map */ + protected final Map converters = new Hashtable(); + + // Register our predefined converters + static { + PropertyConverter defaultConverter = new DefaultConverter(); + registerConverter(Object.class, defaultConverter); + registerConverter(Boolean.TYPE, defaultConverter); + + PropertyConverter numberConverter = new NumberConverter(); + registerConverter(Number.class, numberConverter); + registerConverter(Byte.TYPE, numberConverter); + registerConverter(Double.TYPE, numberConverter); + registerConverter(Float.TYPE, numberConverter); + registerConverter(Integer.TYPE, numberConverter); + registerConverter(Long.TYPE, numberConverter); + registerConverter(Short.TYPE, numberConverter); + + registerConverter(Date.class, new DateConverter()); + registerConverter(Time.class, new TimeConverter()); + } + + /** + * Creates a Converter. + */ + protected Converter() { + } + + /** + * Gets the Converter instance. + * + * @return the converter instance + */ + public static Converter getInstance() { + return sInstance; + } + + /** + * Registers a converter for a given type. + * This converter will also be used for all subclasses, unless a more + * specific version is registered. + *

+ * By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date} + * and {@link Object} + * (the {@link DefaultConverter}) are registered by this class' static + * initializer. You might remove them using the + * {@code unregisterConverter} method. + *

+ * + * @param pType the (super) type to register a converter for + * @param pConverter the converter + * + * @see #unregisterConverter(Class) + */ + public static void registerConverter(final Class pType, final PropertyConverter pConverter) { + getInstance().converters.put(pType, pConverter); + } + + /** + * Un-registers a converter for a given type. That is, making it unavailable + * for the converter framework, and making it (potentially) available for + * garbage collection. + * + * @param pType the (super) type to remove converter for + * + * @see #registerConverter(Class,PropertyConverter) + */ + @SuppressWarnings("UnusedDeclaration") + public static void unregisterConverter(final Class pType) { + getInstance().converters.remove(pType); + } + + /** + * Converts the string to an object of the given type. + * + * @param pString the string to convert + * @param pType the type to convert to + * + * @return the object created from the given string. + * + * @throws ConversionException if the string cannot be converted for any + * reason. + */ + public Object toObject(final String pString, final Class pType) throws ConversionException { + return toObject(pString, pType, null); + } + + /** + * Converts the string to an object of the given type, parsing after the + * given format. + * + * @param pString the string to convert + * @param pType the type to convert to + * @param pFormat the (optional) conversion format + * + * @return the object created from the given string. + * + * @throws ConversionException if the string cannot be converted for any + * reason. + */ + public abstract Object toObject(String pString, Class pType, String pFormat) + throws ConversionException; + + /** + * Converts the object to a string, using {@code object.toString()} + * + * @param pObject the object to convert. + * + * @return the string representation of the object, on the correct format. + * + * @throws ConversionException if the object cannot be converted to a + * string for any reason. + */ + public String toString(final Object pObject) throws ConversionException { + return toString(pObject, null); + } + + /** + * Converts the object to a string, using {@code object.toString()} + * + * @param pObject the object to convert. + * @param pFormat the (optional) conversion format + * + * @return the string representation of the object, on the correct format. + * + * @throws ConversionException if the object cannot be converted to a + * string for any reason. + */ + public abstract String toString(Object pObject, String pFormat) + throws ConversionException; +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConverterImpl.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConverterImpl.java index 321c6ee1..58a0eb6b 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConverterImpl.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/ConverterImpl.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.convert; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java index 9183fbb7..13ca4dcb 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java @@ -1,152 +1,158 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.convert; - -import com.twelvemonkeys.lang.*; - -import java.util.*; -import java.text.*; -import java.lang.reflect.InvocationTargetException; - -/** - * Converts strings to dates and back. - *

- * This class has a static cache of {@code DateFormats}, to avoid - * creation and parsing of date formats every time one is used. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java#2 $ - */ -public class DateConverter extends NumberConverter { - - /** Creates a {@code DateConverter} */ - public DateConverter() { - } - - /** - * Converts the string to a date, using the given format for parsing. - * - * @param pString the string to convert. - * @param pType the type to convert to. {@code java.util.Date} and - * subclasses allowed. - * @param pFormat the format used for parsing. Must be a legal - * {@code SimpleDateFormat} format, or {@code null} which will use the - * default format. - * - * @return the object created from the given string. May safely be typecast - * to {@code java.util.Date} - * - * @see Date - * @see java.text.DateFormat - * - * @throws ConversionException - */ - public Object toObject(String pString, Class pType, String pFormat) throws ConversionException { - if (StringUtil.isEmpty(pString)) - return null; - - try { - DateFormat format; - - if (pFormat == null) { - // Use system default format, using default locale - format = DateFormat.getDateTimeInstance(); - } - else { - // Get format from cache - format = getDateFormat(pFormat); - } - - Date date = StringUtil.toDate(pString, format); - - // Allow for conversion to Date subclasses (ie. java.sql.*) - if (pType != Date.class) { - try { - date = (Date) BeanUtil.createInstance(pType, new Long(date.getTime())); - } - catch (ClassCastException e) { - throw new TypeMismathException(pType); - } - catch (InvocationTargetException e) { - throw new ConversionException(e); - } - } - - return date; - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - /** - * Converts the object to a string, using the given format - * - * @param pObject the object to convert. - * @param pFormat the format used for conversion. Must be a legal - * {@code SimpleDateFormat} format, or {@code null} which will use the - * default format. - * - * @return the string representation of the object, on the correct format. - * - * @throws ConversionException if the object is not a subclass of - * {@code java.util.Date} - * - * @see Date - * @see java.text.DateFormat - */ - public String toString(Object pObject, String pFormat) throws ConversionException { - if (pObject == null) - return null; - - if (!(pObject instanceof Date)) { - throw new TypeMismathException(pObject.getClass()); - } - - try { - // Convert to string, default way - if (StringUtil.isEmpty(pFormat)) { - return DateFormat.getDateTimeInstance().format(pObject); - } - - // Convert to string, using format - DateFormat format = getDateFormat(pFormat); - - return format.format(pObject); - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - private DateFormat getDateFormat(String pFormat) { - return (DateFormat) getFormat(SimpleDateFormat.class, pFormat, Locale.US); - } -} +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.BeanUtil; +import com.twelvemonkeys.lang.StringUtil; + +import java.lang.reflect.InvocationTargetException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * Converts strings to dates and back. + *

+ * This class has a static cache of {@code DateFormats}, to avoid + * creation and parsing of date formats every time one is used. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java#2 $ + */ +public class DateConverter extends NumberConverter { + + /** Creates a {@code DateConverter} */ + public DateConverter() { + } + + /** + * Converts the string to a date, using the given format for parsing. + * + * @param pString the string to convert. + * @param pType the type to convert to. {@code java.util.Date} and + * subclasses allowed. + * @param pFormat the format used for parsing. Must be a legal + * {@code SimpleDateFormat} format, or {@code null} which will use the + * default format. + * + * @return the object created from the given string. May safely be typecast + * to {@code java.util.Date} + * + * @see Date + * @see java.text.DateFormat + * + * @throws ConversionException + */ + public Object toObject(String pString, Class pType, String pFormat) throws ConversionException { + if (StringUtil.isEmpty(pString)) + return null; + + try { + DateFormat format; + + if (pFormat == null) { + // Use system default format, using default locale + format = DateFormat.getDateTimeInstance(); + } + else { + // Get format from cache + format = getDateFormat(pFormat); + } + + Date date = StringUtil.toDate(pString, format); + + // Allow for conversion to Date subclasses (ie. java.sql.*) + if (pType != Date.class) { + try { + date = (Date) BeanUtil.createInstance(pType, new Long(date.getTime())); + } + catch (ClassCastException e) { + throw new TypeMismathException(pType); + } + catch (InvocationTargetException e) { + throw new ConversionException(e); + } + } + + return date; + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + /** + * Converts the object to a string, using the given format + * + * @param pObject the object to convert. + * @param pFormat the format used for conversion. Must be a legal + * {@code SimpleDateFormat} format, or {@code null} which will use the + * default format. + * + * @return the string representation of the object, on the correct format. + * + * @throws ConversionException if the object is not a subclass of + * {@code java.util.Date} + * + * @see Date + * @see java.text.DateFormat + */ + public String toString(Object pObject, String pFormat) throws ConversionException { + if (pObject == null) + return null; + + if (!(pObject instanceof Date)) { + throw new TypeMismathException(pObject.getClass()); + } + + try { + // Convert to string, default way + if (StringUtil.isEmpty(pFormat)) { + return DateFormat.getDateTimeInstance().format(pObject); + } + + // Convert to string, using format + DateFormat format = getDateFormat(pFormat); + + return format.format(pObject); + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + private DateFormat getDateFormat(String pFormat) { + return (DateFormat) getFormat(SimpleDateFormat.class, pFormat, Locale.US); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java index 59236d7c..86f6d838 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java @@ -1,266 +1,269 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.convert; - -import com.twelvemonkeys.lang.BeanUtil; -import com.twelvemonkeys.lang.StringUtil; - -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; - -/** - * Converts strings to objects and back. - *

- * This converter first tries to create an object, using the class' single - * string argument constructor ({@code <type>(String)}) if found, - * otherwise, an attempt to call - * the class' static {@code valueOf(String)} method. If both fails, a - * {@link ConversionException} is thrown. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java#2 $ - * - */ -public final class DefaultConverter implements PropertyConverter { - - /** - * Creates a {@code DefaultConverter}. - */ - public DefaultConverter() { - } - - /** - * Converts the string to an object of the given type. - * - * @param pString the string to convert - * @param pType the type to convert to - * @param pFormat ignored. - * - * @return the object created from the given string. - * - * @throws ConversionException if the type is null, or if the string cannot - * be converted into the given type, using a string constructor or static - * {@code valueOf} method. - */ - public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException { - if (pString == null) { - return null; - } - - if (pType == null) { - throw new MissingTypeException(); - } - - if (pType.isArray()) { - return toArray(pString, pType, pFormat); - } - - // TODO: Separate CollectionConverter? - // should however, be simple to wrap array using Arrays.asList - // But what about generic type?! It's erased... - - // Primitive -> wrapper - Class type = unBoxType(pType); - - try { - // Try to create instance from (String) - Object value = BeanUtil.createInstance(type, pString); - - if (value == null) { - // createInstance failed for some reason - // Try to invoke the static method valueOf(String) - value = BeanUtil.invokeStaticMethod(type, "valueOf", pString); - - if (value == null) { - // If the value is still null, well, then I cannot help... - throw new ConversionException(String.format( - "Could not convert String to %1$s: No constructor %1$s(String) or static %1$s.valueOf(String) method found!", - type.getName() - )); - } - } - - return value; - } - catch (InvocationTargetException ite) { - throw new ConversionException(ite.getTargetException()); - } - catch (ConversionException ce) { - throw ce; - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - private Object toArray(final String pString, final Class pType, final String pFormat) { - String[] strings = StringUtil.toStringArray(pString, pFormat != null ? pFormat : StringUtil.DELIMITER_STRING); - Class type = pType.getComponentType(); - if (type == String.class) { - return strings; - } - - Object array = Array.newInstance(type, strings.length); - try { - for (int i = 0; i < strings.length; i++) { - Array.set(array, i, Converter.getInstance().toObject(strings[i], type)); - } - } - catch (ConversionException e) { - if (pFormat != null) { - throw new ConversionException(String.format("%s for string \"%s\" with format \"%s\"", e.getMessage(), pString, pFormat), e); - } - else { - throw new ConversionException(String.format("%s for string \"%s\"", e.getMessage(), pString), e); - } - } - - return array; - } - - /** - * Converts the object to a string, using {@code pObject.toString()}. - * - * @param pObject the object to convert. - * @param pFormat ignored. - * - * @return the string representation of the object, or {@code null} if {@code pObject == null} - */ - public String toString(final Object pObject, final String pFormat) - throws ConversionException { - - try { - return pObject == null ? null : pObject.getClass().isArray() ? arrayToString(toObjectArray(pObject), pFormat) : pObject.toString(); - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - private String arrayToString(final Object[] pArray, final String pFormat) { - return pFormat == null ? StringUtil.toCSVString(pArray) : StringUtil.toCSVString(pArray, pFormat); - } - - private Object[] toObjectArray(final Object pObject) { - // TODO: Extract util method for wrapping/unwrapping native arrays? - Object[] array; - Class componentType = pObject.getClass().getComponentType(); - if (componentType.isPrimitive()) { - if (int.class == componentType) { - array = new Integer[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (short.class == componentType) { - array = new Short[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (long.class == componentType) { - array = new Long[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (float.class == componentType) { - array = new Float[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (double.class == componentType) { - array = new Double[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (boolean.class == componentType) { - array = new Boolean[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (byte.class == componentType) { - array = new Byte[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else if (char.class == componentType) { - array = new Character[Array.getLength(pObject)]; - for (int i = 0; i < array.length; i++) { - Array.set(array, i, Array.get(pObject, i)); - } - } - else { - throw new IllegalArgumentException("Unknown type " + componentType); - } - } - else { - array = (Object[]) pObject; - } - return array; - } - - private Class unBoxType(final Class pType) { - if (pType.isPrimitive()) { - if (pType == boolean.class) { - return Boolean.class; - } - if (pType == byte.class) { - return Byte.class; - } - if (pType == char.class) { - return Character.class; - } - if (pType == short.class) { - return Short.class; - } - if (pType == int.class) { - return Integer.class; - } - if (pType == float.class) { - return Float.class; - } - if (pType == long.class) { - return Long.class; - } - if (pType == double.class) { - return Double.class; - } - - throw new IllegalArgumentException("Unknown type: " + pType); - } - - return pType; - } -} +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.BeanUtil; +import com.twelvemonkeys.lang.StringUtil; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; + +/** + * Converts strings to objects and back. + *

+ * This converter first tries to create an object, using the class' single + * string argument constructor ({@code <type>(String)}) if found, + * otherwise, an attempt to call + * the class' static {@code valueOf(String)} method. If both fails, a + * {@link ConversionException} is thrown. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java#2 $ + * + */ +public final class DefaultConverter implements PropertyConverter { + + /** + * Creates a {@code DefaultConverter}. + */ + public DefaultConverter() { + } + + /** + * Converts the string to an object of the given type. + * + * @param pString the string to convert + * @param pType the type to convert to + * @param pFormat ignored. + * + * @return the object created from the given string. + * + * @throws ConversionException if the type is null, or if the string cannot + * be converted into the given type, using a string constructor or static + * {@code valueOf} method. + */ + public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException { + if (pString == null) { + return null; + } + + if (pType == null) { + throw new MissingTypeException(); + } + + if (pType.isArray()) { + return toArray(pString, pType, pFormat); + } + + // TODO: Separate CollectionConverter? + // should however, be simple to wrap array using Arrays.asList + // But what about generic type?! It's erased... + + // Primitive -> wrapper + Class type = unBoxType(pType); + + try { + // Try to create instance from (String) + Object value = BeanUtil.createInstance(type, pString); + + if (value == null) { + // createInstance failed for some reason + // Try to invoke the static method valueOf(String) + value = BeanUtil.invokeStaticMethod(type, "valueOf", pString); + + if (value == null) { + // If the value is still null, well, then I cannot help... + throw new ConversionException(String.format( + "Could not convert String to %1$s: No constructor %1$s(String) or static %1$s.valueOf(String) method found!", + type.getName() + )); + } + } + + return value; + } + catch (InvocationTargetException ite) { + throw new ConversionException(ite.getTargetException()); + } + catch (ConversionException ce) { + throw ce; + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + private Object toArray(final String pString, final Class pType, final String pFormat) { + String[] strings = StringUtil.toStringArray(pString, pFormat != null ? pFormat : StringUtil.DELIMITER_STRING); + Class type = pType.getComponentType(); + if (type == String.class) { + return strings; + } + + Object array = Array.newInstance(type, strings.length); + try { + for (int i = 0; i < strings.length; i++) { + Array.set(array, i, Converter.getInstance().toObject(strings[i], type)); + } + } + catch (ConversionException e) { + if (pFormat != null) { + throw new ConversionException(String.format("%s for string \"%s\" with format \"%s\"", e.getMessage(), pString, pFormat), e); + } + else { + throw new ConversionException(String.format("%s for string \"%s\"", e.getMessage(), pString), e); + } + } + + return array; + } + + /** + * Converts the object to a string, using {@code pObject.toString()}. + * + * @param pObject the object to convert. + * @param pFormat ignored. + * + * @return the string representation of the object, or {@code null} if {@code pObject == null} + */ + public String toString(final Object pObject, final String pFormat) + throws ConversionException { + + try { + return pObject == null ? null : pObject.getClass().isArray() ? arrayToString(toObjectArray(pObject), pFormat) : pObject.toString(); + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + private String arrayToString(final Object[] pArray, final String pFormat) { + return pFormat == null ? StringUtil.toCSVString(pArray) : StringUtil.toCSVString(pArray, pFormat); + } + + private Object[] toObjectArray(final Object pObject) { + // TODO: Extract util method for wrapping/unwrapping native arrays? + Object[] array; + Class componentType = pObject.getClass().getComponentType(); + if (componentType.isPrimitive()) { + if (int.class == componentType) { + array = new Integer[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (short.class == componentType) { + array = new Short[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (long.class == componentType) { + array = new Long[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (float.class == componentType) { + array = new Float[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (double.class == componentType) { + array = new Double[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (boolean.class == componentType) { + array = new Boolean[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (byte.class == componentType) { + array = new Byte[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else if (char.class == componentType) { + array = new Character[Array.getLength(pObject)]; + for (int i = 0; i < array.length; i++) { + Array.set(array, i, Array.get(pObject, i)); + } + } + else { + throw new IllegalArgumentException("Unknown type " + componentType); + } + } + else { + array = (Object[]) pObject; + } + return array; + } + + private Class unBoxType(final Class pType) { + if (pType.isPrimitive()) { + if (pType == boolean.class) { + return Boolean.class; + } + if (pType == byte.class) { + return Byte.class; + } + if (pType == char.class) { + return Character.class; + } + if (pType == short.class) { + return Short.class; + } + if (pType == int.class) { + return Integer.class; + } + if (pType == float.class) { + return Float.class; + } + if (pType == long.class) { + return Long.class; + } + if (pType == double.class) { + return Double.class; + } + + throw new IllegalArgumentException("Unknown type: " + pType); + } + + return pType; + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/MissingTypeException.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/MissingTypeException.java index e9b2da3f..cdd10c4c 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/MissingTypeException.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/MissingTypeException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.convert; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NoAvailableConverterException.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NoAvailableConverterException.java index 42631321..ebe2e73e 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NoAvailableConverterException.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NoAvailableConverterException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.convert; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java index 13c7e322..6c2d75fb 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java @@ -1,204 +1,211 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.convert; - -import com.twelvemonkeys.lang.*; -import com.twelvemonkeys.util.LRUHashMap; - -import java.util.*; -import java.math.*; -import java.text.*; - -/** - * Converts strings to numbers and back. - *

- * This class has a static cache of {@code NumberFormats}, to avoid - * creation and parsing of number formats every time one is used. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java#2 $ - */ -public class NumberConverter implements PropertyConverter { - // TODO: Need to either make this non-locale aware, or document that it is... - - private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols(Locale.US); - private static final NumberFormat sDefaultFormat = new DecimalFormat("#0.#", SYMBOLS); - private static final Map sFormats = new LRUHashMap(50); - - public NumberConverter() { - } - - /** - * Converts the string to a number, using the given format for parsing. - * - * @param pString the string to convert. - * @param pType the type to convert to. PropertyConverter - * implementations may choose to ignore this parameter. - * @param pFormat the format used for parsing. PropertyConverter - * implementations may choose to ignore this parameter. Also, - * implementations that require a parser format, should provide a default - * format, and allow {@code null} as the format argument. - * - * @return the object created from the given string. May safely be typecast - * to {@code java.lang.Number} or the class of the {@code type} parameter. - * - * @see Number - * @see java.text.NumberFormat - * - * @throws ConversionException - */ - public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException { - if (StringUtil.isEmpty(pString)) { - return null; - } - - try { - if (pType.equals(BigInteger.class)) { - return new BigInteger(pString); // No format? - } - if (pType.equals(BigDecimal.class)) { - return new BigDecimal(pString); // No format? - } - - NumberFormat format; - - if (pFormat == null) { - // Use system default format, using default locale - format = sDefaultFormat; - } - else { - // Get format from cache - format = getNumberFormat(pFormat); - } - - Number num; - synchronized (format) { - num = format.parse(pString); - } - - if (pType == Integer.TYPE || pType == Integer.class) { - return num.intValue(); - } - else if (pType == Long.TYPE || pType == Long.class) { - return num.longValue(); - } - else if (pType == Double.TYPE || pType == Double.class) { - return num.doubleValue(); - } - else if (pType == Float.TYPE || pType == Float.class) { - return num.floatValue(); - } - else if (pType == Byte.TYPE || pType == Byte.class) { - return num.byteValue(); - } - else if (pType == Short.TYPE || pType == Short.class) { - return num.shortValue(); - } - - return num; - } - catch (ParseException pe) { - throw new ConversionException(pe); - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - /** - * Converts the object to a string, using the given format - * - * @param pObject the object to convert. - * @param pFormat the format used for parsing. PropertyConverter - * implementations may choose to ignore this parameter. Also, - * implementations that require a parser format, should provide a default - * format, and allow {@code null} as the format argument. - * - * @return the string representation of the object, on the correct format. - * - * @throws ConversionException if the object is not a subclass of {@link java.lang.Number} - */ - public String toString(final Object pObject, final String pFormat) - throws ConversionException { - - if (pObject == null) { - return null; - } - - if (!(pObject instanceof Number)) { - throw new TypeMismathException(pObject.getClass()); - } - - try { - // Convert to string, default way - if (StringUtil.isEmpty(pFormat)) { - return sDefaultFormat.format(pObject); - } - - // Convert to string, using format - NumberFormat format = getNumberFormat(pFormat); - - synchronized (format) { - return format.format(pObject); - } - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - private NumberFormat getNumberFormat(String pFormat) { - return (NumberFormat) getFormat(DecimalFormat.class, pFormat, SYMBOLS); - } - - protected final Format getFormat(Class pFormatterClass, Object... pFormat) { - // Try to get format from cache - synchronized (sFormats) { - String key = pFormatterClass.getName() + ":" + Arrays.toString(pFormat); - Format format = sFormats.get(key); - - if (format == null) { - // If not found, create... - try { - format = (Format) BeanUtil.createInstance(pFormatterClass, pFormat); - } - catch (Exception e) { - e.printStackTrace(); - return null; - } - - // ...and store in cache - sFormats.put(key, format); - } - - return format; - } - } -} +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.BeanUtil; +import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.util.LRUHashMap; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.*; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; + +/** + * Converts strings to numbers and back. + *

+ * This class has a static cache of {@code NumberFormats}, to avoid + * creation and parsing of number formats every time one is used. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java#2 $ + */ +public class NumberConverter implements PropertyConverter { + // TODO: Need to either make this non-locale aware, or document that it is... + + private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols(Locale.US); + private static final NumberFormat sDefaultFormat = new DecimalFormat("#0.#", SYMBOLS); + private static final Map sFormats = new LRUHashMap(50); + + public NumberConverter() { + } + + /** + * Converts the string to a number, using the given format for parsing. + * + * @param pString the string to convert. + * @param pType the type to convert to. PropertyConverter + * implementations may choose to ignore this parameter. + * @param pFormat the format used for parsing. PropertyConverter + * implementations may choose to ignore this parameter. Also, + * implementations that require a parser format, should provide a default + * format, and allow {@code null} as the format argument. + * + * @return the object created from the given string. May safely be typecast + * to {@code java.lang.Number} or the class of the {@code type} parameter. + * + * @see Number + * @see java.text.NumberFormat + * + * @throws ConversionException + */ + public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException { + if (StringUtil.isEmpty(pString)) { + return null; + } + + try { + if (pType.equals(BigInteger.class)) { + return new BigInteger(pString); // No format? + } + if (pType.equals(BigDecimal.class)) { + return new BigDecimal(pString); // No format? + } + + NumberFormat format; + + if (pFormat == null) { + // Use system default format, using default locale + format = sDefaultFormat; + } + else { + // Get format from cache + format = getNumberFormat(pFormat); + } + + Number num; + synchronized (format) { + num = format.parse(pString); + } + + if (pType == Integer.TYPE || pType == Integer.class) { + return num.intValue(); + } + else if (pType == Long.TYPE || pType == Long.class) { + return num.longValue(); + } + else if (pType == Double.TYPE || pType == Double.class) { + return num.doubleValue(); + } + else if (pType == Float.TYPE || pType == Float.class) { + return num.floatValue(); + } + else if (pType == Byte.TYPE || pType == Byte.class) { + return num.byteValue(); + } + else if (pType == Short.TYPE || pType == Short.class) { + return num.shortValue(); + } + + return num; + } + catch (ParseException pe) { + throw new ConversionException(pe); + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + /** + * Converts the object to a string, using the given format + * + * @param pObject the object to convert. + * @param pFormat the format used for parsing. PropertyConverter + * implementations may choose to ignore this parameter. Also, + * implementations that require a parser format, should provide a default + * format, and allow {@code null} as the format argument. + * + * @return the string representation of the object, on the correct format. + * + * @throws ConversionException if the object is not a subclass of {@link java.lang.Number} + */ + public String toString(final Object pObject, final String pFormat) + throws ConversionException { + + if (pObject == null) { + return null; + } + + if (!(pObject instanceof Number)) { + throw new TypeMismathException(pObject.getClass()); + } + + try { + // Convert to string, default way + if (StringUtil.isEmpty(pFormat)) { + return sDefaultFormat.format(pObject); + } + + // Convert to string, using format + NumberFormat format = getNumberFormat(pFormat); + + synchronized (format) { + return format.format(pObject); + } + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + private NumberFormat getNumberFormat(String pFormat) { + return (NumberFormat) getFormat(DecimalFormat.class, pFormat, SYMBOLS); + } + + protected final Format getFormat(Class pFormatterClass, Object... pFormat) { + // Try to get format from cache + synchronized (sFormats) { + String key = pFormatterClass.getName() + ":" + Arrays.toString(pFormat); + Format format = sFormats.get(key); + + if (format == null) { + // If not found, create... + try { + format = (Format) BeanUtil.createInstance(pFormatterClass, pFormat); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + + // ...and store in cache + sFormats.put(key, format); + } + + return format; + } + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/PropertyConverter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/PropertyConverter.java index ae1bf51b..1822368b 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/PropertyConverter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/PropertyConverter.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.convert; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java index 0a6fd5a6..0dd69ec6 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java @@ -1,136 +1,139 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.convert; - -import com.twelvemonkeys.lang.*; -import com.twelvemonkeys.util.Time; -import com.twelvemonkeys.util.TimeFormat; - -/** - * Converts strings to times and back. - *

- * This class has a static cache of {@code TimeFormats}, to avoid creation and - * parsing of timeformats every time one is used. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java#1 $ - */ -public class TimeConverter extends NumberConverter { - - public TimeConverter() { - } - - /** - * Converts the string to a time, using the given format for parsing. - * - * @param pString the string to convert. - * @param pType the type to convert to. PropertyConverter - * implementations may choose to ignore this parameter. - * @param pFormat the format used for parsing. PropertyConverter - * implementations may choose to ignore this parameter. Also, - * implementations that require a parser format, should provide a default - * format, and allow {@code null} as the format argument. - * - * @return the object created from the given string. May safely be typecast - * to {@code com.twelvemonkeys.util.Time} - * - * @see com.twelvemonkeys.util.Time - * @see com.twelvemonkeys.util.TimeFormat - * - * @throws ConversionException - */ - public Object toObject(String pString, Class pType, String pFormat) - throws ConversionException { - if (StringUtil.isEmpty(pString)) - return null; - - TimeFormat format; - - try { - if (pFormat == null) { - // Use system default format - format = TimeFormat.getInstance(); - } - else { - // Get format from cache - format = getTimeFormat(pFormat); - } - - return format.parse(pString); - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - - } - - /** - * Converts the object to a string, using the given format - * - * @param pObject the object to convert. - * @param pFormat the format used for parsing. PropertyConverter - * implementations may choose to ignore this parameter. Also, - * implementations that require a parser format, should provide a default - * format, and allow {@code null} as the format argument. - * - * @return the string representation of the object, on the correct format. - * - * @throws ConversionException if the object is not a subclass of - * {@code com.twelvemonkeys.util.Time} - * - * @see com.twelvemonkeys.util.Time - * @see com.twelvemonkeys.util.TimeFormat - */ - public String toString(Object pObject, String pFormat) - throws ConversionException { - if (pObject == null) - return null; - - if (!(pObject instanceof com.twelvemonkeys.util.Time)) - throw new TypeMismathException(pObject.getClass()); - - try { - // Convert to string, default way - if (StringUtil.isEmpty(pFormat)) - return pObject.toString(); - - // Convert to string, using format - TimeFormat format = getTimeFormat(pFormat); - return format.format((Time) pObject); - } - catch (RuntimeException rte) { - throw new ConversionException(rte); - } - } - - private TimeFormat getTimeFormat(String pFormat) { - return (TimeFormat) getFormat(TimeFormat.class, pFormat); - } -} +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.util.Time; +import com.twelvemonkeys.util.TimeFormat; + +/** + * Converts strings to times and back. + *

+ * This class has a static cache of {@code TimeFormats}, to avoid creation and + * parsing of timeformats every time one is used. + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java#1 $ + */ +public class TimeConverter extends NumberConverter { + + public TimeConverter() { + } + + /** + * Converts the string to a time, using the given format for parsing. + * + * @param pString the string to convert. + * @param pType the type to convert to. PropertyConverter + * implementations may choose to ignore this parameter. + * @param pFormat the format used for parsing. PropertyConverter + * implementations may choose to ignore this parameter. Also, + * implementations that require a parser format, should provide a default + * format, and allow {@code null} as the format argument. + * + * @return the object created from the given string. May safely be typecast + * to {@code com.twelvemonkeys.util.Time} + * + * @see com.twelvemonkeys.util.Time + * @see com.twelvemonkeys.util.TimeFormat + * + * @throws ConversionException + */ + public Object toObject(String pString, Class pType, String pFormat) + throws ConversionException { + if (StringUtil.isEmpty(pString)) + return null; + + TimeFormat format; + + try { + if (pFormat == null) { + // Use system default format + format = TimeFormat.getInstance(); + } + else { + // Get format from cache + format = getTimeFormat(pFormat); + } + + return format.parse(pString); + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + + } + + /** + * Converts the object to a string, using the given format + * + * @param pObject the object to convert. + * @param pFormat the format used for parsing. PropertyConverter + * implementations may choose to ignore this parameter. Also, + * implementations that require a parser format, should provide a default + * format, and allow {@code null} as the format argument. + * + * @return the string representation of the object, on the correct format. + * + * @throws ConversionException if the object is not a subclass of + * {@code com.twelvemonkeys.util.Time} + * + * @see com.twelvemonkeys.util.Time + * @see com.twelvemonkeys.util.TimeFormat + */ + public String toString(Object pObject, String pFormat) + throws ConversionException { + if (pObject == null) + return null; + + if (!(pObject instanceof com.twelvemonkeys.util.Time)) + throw new TypeMismathException(pObject.getClass()); + + try { + // Convert to string, default way + if (StringUtil.isEmpty(pFormat)) + return pObject.toString(); + + // Convert to string, using format + TimeFormat format = getTimeFormat(pFormat); + return format.format((Time) pObject); + } + catch (RuntimeException rte) { + throw new ConversionException(rte); + } + } + + private TimeFormat getTimeFormat(String pFormat) { + return (TimeFormat) getFormat(TimeFormat.class, pFormat); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TypeMismathException.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TypeMismathException.java index acc8c0f6..9164a2f5 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TypeMismathException.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/TypeMismathException.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.convert; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/pacakge_info.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/pacakge_info.java index faed2215..26dcb8b8 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/pacakge_info.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/pacakge_info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides a general purpose conversion framework, for conversion of values * between string representations and objects. diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/package_info.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/package_info.java index 9cc218ca..4ede179d 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/package_info.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/package_info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides miscellaneous utility classes. */ diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java index ce1219f9..401b48c8 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java @@ -1,105 +1,107 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.regex; - -import com.twelvemonkeys.util.AbstractTokenIterator; - -import java.util.NoSuchElementException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * {@code StringTokenizer} replacement, that uses regular expressions to split - * strings into tokens. - *

- * @see java.util.regex.Pattern for pattern syntax. - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java#1 $ - */ -public class RegExTokenIterator extends AbstractTokenIterator { - private final Matcher matcher; - private boolean next = false; - - /** - * Creates a {@code RegExTokenIterator}. - * Default pettern is {@code "\S+"}. - * - * @param pString the string to be parsed. - * - * @throws IllegalArgumentException if {@code pString} is {@code null} - */ - public RegExTokenIterator(String pString) { - this(pString, "\\S+"); - } - - /** - * Creates a {@code RegExTokenIterator}. - * - * @see Pattern for pattern syntax. - * - * @param pString the string to be parsed. - * @param pPattern the pattern - * - * @throws PatternSyntaxException if {@code pPattern} is not a valid pattern - * @throws IllegalArgumentException if any of the arguments are {@code null} - */ - public RegExTokenIterator(String pString, String pPattern) { - if (pString == null) { - throw new IllegalArgumentException("string == null"); - } - - if (pPattern == null) { - throw new IllegalArgumentException("pattern == null"); - } - - matcher = Pattern.compile(pPattern).matcher(pString); - } - - /** - * Resets this iterator. - * - */ - public void reset() { - matcher.reset(); - } - - public boolean hasNext() { - return next || (next = matcher.find()); - } - - public String next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - next = false; - return matcher.group(); - } +/* + * 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.util.regex; + +import com.twelvemonkeys.util.AbstractTokenIterator; + +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * {@code StringTokenizer} replacement, that uses regular expressions to split + * strings into tokens. + * + *@see java.util.regex.Pattern for pattern syntax. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java#1 $ + */ +public class RegExTokenIterator extends AbstractTokenIterator { + private final Matcher matcher; + private boolean next = false; + + /** + * Creates a {@code RegExTokenIterator}. + * Default pettern is {@code "\S+"}. + * + * @param pString the string to be parsed. + * + * @throws IllegalArgumentException if {@code pString} is {@code null} + */ + public RegExTokenIterator(String pString) { + this(pString, "\\S+"); + } + + /** + * Creates a {@code RegExTokenIterator}. + * + * @see Pattern for pattern syntax. + * + * @param pString the string to be parsed. + * @param pPattern the pattern + * + * @throws PatternSyntaxException if {@code pPattern} is not a valid pattern + * @throws IllegalArgumentException if any of the arguments are {@code null} + */ + public RegExTokenIterator(String pString, String pPattern) { + if (pString == null) { + throw new IllegalArgumentException("string == null"); + } + + if (pPattern == null) { + throw new IllegalArgumentException("pattern == null"); + } + + matcher = Pattern.compile(pPattern).matcher(pString); + } + + /** + * Resets this iterator. + * + */ + public void reset() { + matcher.reset(); + } + + public boolean hasNext() { + return next || (next = matcher.find()); + } + + public String next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + next = false; + return matcher.group(); + } } \ No newline at end of file diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/WildcardStringParser.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/WildcardStringParser.java index 5a40f96e..c13bf51e 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/WildcardStringParser.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/WildcardStringParser.java @@ -1,783 +1,776 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.regex; - -import java.io.PrintStream; - -/** - * This class parses arbitrary strings against a wildcard string mask provided. - * The wildcard characters are '*' and '?'. - *

- * The string masks provided are treated as case sensitive.
- * Null-valued string masks as well as null valued strings to be parsed, will lead to rejection. - *

- *

- *

- * This class is custom designed for wildcard string parsing and is several times faster than the implementation based on the Jakarta Regexp package. - *

- *


- *

- * This task is performed based on regular expression techniques. - * The possibilities of string generation with the well-known wildcard characters stated above, - * represent a subset of the possibilities of string generation with regular expressions.
- * The '*' corresponds to ([Union of all characters in the alphabet])*
- * The '?' corresponds to ([Union of all characters in the alphabet])
- *       These expressions are not suited for textual representation at all, I must say. Is there any math tags included in HTML? - *

- *

- *

- * The complete meta-language for regular expressions are much larger. - * This fact makes it fairly straightforward to build data structures for parsing because the amount of rules of building these structures are quite limited, as stated below. - *

- *

- *

- * To bring this over to mathematical terms: - * The parser ia a nondeterministic finite automaton (latin) representing the grammar which is stated by the string mask. - * The language accepted by this automaton is the set of all strings accepted by this automaton.
- * The formal automaton quintuple consists of: - *

    - *
  1. A finite set of states, depending on the wildcard string mask. - * For each character in the mask a state representing that character is created. - * The number of states therefore coincides with the length of the mask. - *
  2. An alphabet consisting of all legal filename characters - included the two wildcard characters '*' and '?'. - * This alphabet is hard-coded in this class. It contains {a .. �}, {A .. �}, {0 .. 9}, {.}, {_}, {-}, {*} and {?}. - *
  3. A finite set of initial states, here only consisting of the state corresponding to the first character in the mask. - *
  4. A finite set of final states, here only consisting of the state corresponding to the last character in the mask. - *
  5. A transition relation that is a finite set of transitions satisfying some formal rules.
    - * This implementation on the other hand, only uses ad-hoc rules which start with an initial setup of the states as a sequence according to the string mask.
    - * Additionally, the following rules completes the building of the automaton: - *
      - *
    1. If the next state represents the same character as the next character in the string to test - go to this next state. - *
    2. If the next state represents '*' - go to this next state. - *
    3. If the next state represents '?' - go to this next state. - *
    4. If a '*' is followed by one or more '?', the last of these '?' state counts as a '*' state. Some extra checks regarding the number of characters read must be imposed if this is the case... - *
    5. If the next character in the string to test does not coincide with the next state - go to the last state representing '*'. If there are none - rejection. - *
    6. If there are no subsequent state (final state) and the state represents '*' - acceptance. - *
    7. If there are no subsequent state (final state) and the end of the string to test is reached - acceptance. - *
    - *
    - * - * Disclaimer: This class does not build a finite automaton according to formal mathematical rules. - * The proper way of implementation should be finding the complete set of transition relations, decomposing these into rules accepted by a deterministic finite automaton and finally build this automaton to be used for string parsing. - * Instead, this class is ad-hoc implemented based on the informal transition rules stated above. - * Therefore the correctness cannot be guaranteed before extensive testing has been imposed on this class... anyway, I think I have succeeded. - * Parsing faults must be reported to the author. - * - *
- *

- *


- *

- * Examples of usage:
- * This example will return "Accepted!". - *

- * WildcardStringParser parser = new WildcardStringParser("*_28????.jp*");
- * if (parser.parseString("gupu_280915.jpg")) {
- *     System.out.println("Accepted!");
- * } else {
- *     System.out.println("Not accepted!");
- * }
- * 
- *

- *


- *

- * Theories and concepts are based on the book Elements of the Theory of Computation, by Harry l. Lewis and Christos H. Papadimitriou, (c) 1981 by Prentice Hall. - *

- *

- * - * @author Eirik Torske - * @deprecated Will probably be removed in the near future - */ -public class WildcardStringParser { - // TODO: Get rid of this class - - // Constants - - /** Field ALPHABET */ - public static final char[] ALPHABET = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00e6', - '\u00f8', '\u00e5', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'N', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', - 'Z', '\u00c6', '\u00d8', '\u00c5', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '-' - }; - - /** Field FREE_RANGE_CHARACTER */ - public static final char FREE_RANGE_CHARACTER = '*'; - - /** Field FREE_PASS_CHARACTER */ - public static final char FREE_PASS_CHARACTER = '?'; - - // Members - boolean initialized; - String stringMask; - WildcardStringParserState initialState; - int totalNumberOfStringsParsed; - boolean debugging; - PrintStream out; - - // Properties - // Constructors - - /** - * Creates a wildcard string parser. - *

- * - * @param pStringMask the wildcard string mask. - */ - public WildcardStringParser(final String pStringMask) { - this(pStringMask, false); - } - - /** - * Creates a wildcard string parser. - *

- * - * @param pStringMask the wildcard string mask. - * @param pDebugging {@code true} will cause debug messages to be emitted to {@code System.out}. - */ - public WildcardStringParser(final String pStringMask, final boolean pDebugging) { - this(pStringMask, pDebugging, System.out); - } - - /** - * Creates a wildcard string parser. - *

- * - * @param pStringMask the wildcard string mask. - * @param pDebugging {@code true} will cause debug messages to be emitted. - * @param pDebuggingPrintStream the {@code java.io.PrintStream} to which the debug messages will be emitted. - */ - public WildcardStringParser(final String pStringMask, final boolean pDebugging, final PrintStream pDebuggingPrintStream) { - this.stringMask = pStringMask; - this.debugging = pDebugging; - this.out = pDebuggingPrintStream; - initialized = buildAutomaton(); - } - - // Methods - private boolean checkIfStateInWildcardRange(WildcardStringParserState pState) { - - WildcardStringParserState runnerState = pState; - - while (runnerState.previousState != null) { - runnerState = runnerState.previousState; - if (isFreeRangeCharacter(runnerState.character)) { - return true; - } - if (!isFreePassCharacter(runnerState.character)) { - return false; - } // If free-pass char '?' - move on - } - return false; - } - - private boolean checkIfLastFreeRangeState(WildcardStringParserState pState) { - - if (isFreeRangeCharacter(pState.character)) { - return true; - } - if (isFreePassCharacter(pState.character)) { - if (checkIfStateInWildcardRange(pState)) { - return true; - } - } - return false; - } - - /** @return {@code true} if and only if the string mask only consists of free-range wildcard character(s). */ - private boolean isTrivialAutomaton() { - - for (int i = 0; i < stringMask.length(); i++) { - if (!isFreeRangeCharacter(stringMask.charAt(i))) { - return false; - } - } - return true; - } - - private boolean buildAutomaton() { - - char activeChar; - WildcardStringParserState runnerState = null; - WildcardStringParserState newState = null; - WildcardStringParserState lastFreeRangeState = null; - - // Create the initial state of the automaton - if ((stringMask != null) && (stringMask.length() > 0)) { - newState = new WildcardStringParserState(stringMask.charAt(0)); - newState.automatonStateNumber = 0; - newState.previousState = null; - if (checkIfLastFreeRangeState(newState)) { - lastFreeRangeState = newState; - } - runnerState = newState; - initialState = runnerState; - initialState.automatonStateNumber = 0; - } - else { - System.err.println("string mask provided are null or empty - aborting!"); - return false; - } - - // Create the rest of the automaton - for (int i = 1; i < stringMask.length(); i++) { - activeChar = stringMask.charAt(i); - - // Check if the char is an element in the alphabet or is a wildcard character - if (!((isInAlphabet(activeChar)) || (isWildcardCharacter(activeChar)))) { - System.err.println("one or more characters in string mask are not legal characters - aborting!"); - return false; - } - - // Set last free-range state before creating/checking the next state - runnerState.lastFreeRangeState = lastFreeRangeState; - - // Create next state, check if free-range state, set the state number and preceeding state - newState = new WildcardStringParserState(activeChar); - newState.automatonStateNumber = i; - newState.previousState = runnerState; - - // Special check if the state represents an '*' or '?' with only preceeding states representing '?' and '*' - if (checkIfLastFreeRangeState(newState)) { - lastFreeRangeState = newState; - } - - // Set the succeding state before moving to the next state - runnerState.nextState = newState; - - // Move to the next state - runnerState = newState; - - // Special setting of the last free-range state for the last element - if (runnerState.automatonStateNumber == stringMask.length() - 1) { - runnerState.lastFreeRangeState = lastFreeRangeState; - } - } - - // Initiate some statistics - totalNumberOfStringsParsed = 0; - return true; - } - - /** Tests if a certain character is a valid character in the alphabet that is applying for this automaton. */ - public static boolean isInAlphabet(final char pCharToCheck) { - - for (int i = 0; i < ALPHABET.length; i++) { - if (pCharToCheck == ALPHABET[i]) { - return true; - } - } - return false; - } - - /** Tests if a certain character is the designated "free-range" character ('*'). */ - public static boolean isFreeRangeCharacter(final char pCharToCheck) { - return pCharToCheck == FREE_RANGE_CHARACTER; - } - - /** Tests if a certain character is the designated "free-pass" character ('?'). */ - public static boolean isFreePassCharacter(final char pCharToCheck) { - return pCharToCheck == FREE_PASS_CHARACTER; - } - - /** Tests if a certain character is a wildcard character ('*' or '?'). */ - public static boolean isWildcardCharacter(final char pCharToCheck) { - return ((isFreeRangeCharacter(pCharToCheck)) || (isFreePassCharacter(pCharToCheck))); - } - - /** - * Gets the string mask that was used when building the parser atomaton. - *

- * - * @return the string mask used for building the parser automaton. - */ - public String getStringMask() { - return stringMask; - } - - /** - * Parses a string according to the rules stated above. - *

- * - * @param pStringToParse the string to parse. - * @return {@code true} if and only if the string are accepted by the automaton. - */ - public boolean parseString(final String pStringToParse) { - - if (debugging) { - out.println("parsing \"" + pStringToParse + "\"..."); - } - - // Update statistics - totalNumberOfStringsParsed++; - - // Check string to be parsed for nullness - if (pStringToParse == null) { - if (debugging) { - out.println("string to be parsed is null - rejection!"); - } - return false; - } - - // Create parsable string - ParsableString parsableString = new ParsableString(pStringToParse); - - // Check string to be parsed - if (!parsableString.checkString()) { - if (debugging) { - out.println("one or more characters in string to be parsed are not legal characters - rejection!"); - } - return false; - } - - // Check if automaton is correctly initialized - if (!initialized) { - System.err.println("automaton is not initialized - rejection!"); - return false; - } - - // Check if automaton is trivial (accepts all strings) - if (isTrivialAutomaton()) { - if (debugging) { - out.println("automaton represents a trivial string mask (accepts all strings) - acceptance!"); - } - return true; - } - - // Check if string to be parsed is empty - if (parsableString.isEmpty()) { - if (debugging) { - out.println("string to be parsed is empty and not trivial automaton - rejection!"); - } - return false; - } - - // Flag and more to indicate that state skipping due to sequence of '?' succeeding a '*' has been performed - boolean hasPerformedFreeRangeMovement = false; - int numberOfFreePassCharactersRead_SinceLastFreePassState = 0; - int numberOfParsedCharactersRead_SinceLastFreePassState = 0; - WildcardStringParserState runnerState = null; - - // Accepted by the first state? - if ((parsableString.charArray[0] == initialState.character) || isWildcardCharacter(initialState.character)) { - runnerState = initialState; - parsableString.index = 0; - } - else { - if (debugging) { - out.println("cannot enter first automaton state - rejection!"); - } - return false; - } - - // Initialize the free-pass character state visited count - if (isFreePassCharacter(runnerState.character)) { - numberOfFreePassCharactersRead_SinceLastFreePassState++; - } - - // Perform parsing according to the rules above - for (int i = 0; i < parsableString.length(); i++) { - if (debugging) { - out.println(); - } - if (debugging) { - out.println("parsing - index number " + i + ", active char: '" - + parsableString.getActiveChar() + "' char string index: " + parsableString.index - + " number of chars since last free-range state: " + numberOfParsedCharactersRead_SinceLastFreePassState); - } - if (debugging) { - out.println("parsing - state: " + runnerState.automatonStateNumber + " '" - + runnerState.character + "' - no of free-pass chars read: " + numberOfFreePassCharactersRead_SinceLastFreePassState); - } - if (debugging) { - out.println("parsing - hasPerformedFreeRangeMovement: " + hasPerformedFreeRangeMovement); - } - if (runnerState.nextState == null) { - if (debugging) { - out.println("parsing - runnerState.nextState == null"); - } - - // If there are no subsequent state (final state) and the state represents '*' - acceptance! - if (isFreeRangeCharacter(runnerState.character)) { - - // Special free-range skipping check - if (hasPerformedFreeRangeMovement) { - if (parsableString.reachedEndOfString()) { - if (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState) { - if (debugging) { - out.println( - "no subsequent state (final state) and the state represents '*' - end of parsing string, but not enough characters read - rejection!"); - } - return false; - } - else { - if (debugging) { - out.println( - "no subsequent state (final state) and the state represents '*' - end of parsing string and enough characters read - acceptance!"); - } - return true; - } - } - else { - if (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState) { - if (debugging) { - out.println( - "no subsequent state (final state) and the state represents '*' - not the end of parsing string and not enough characters read - read next character"); - } - parsableString.index++; - numberOfParsedCharactersRead_SinceLastFreePassState++; - } - else { - if (debugging) { - out.println( - "no subsequent state (final state) and the state represents '*' - not the end of parsing string, but enough characters read - acceptance!"); - } - return true; - } - } - } - else { - if (debugging) { - out.println("no subsequent state (final state) and the state represents '*' - no skipping performed - acceptance!"); - } - return true; - } - } - - // If there are no subsequent state (final state) and no skipping has been performed and the end of the string to test is reached - acceptance! - else if (parsableString.reachedEndOfString()) { - - // Special free-range skipping check - if ((hasPerformedFreeRangeMovement) - && (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState)) { - if (debugging) { - out.println( - "no subsequent state (final state) and skipping has been performed and end of parsing string, but not enough characters read - rejection!"); - } - return false; - } - if (debugging) { - out.println("no subsequent state (final state) and the end of the string to test is reached - acceptance!"); - } - return true; - } - else { - if (debugging) { - out.println("parsing - escaping process..."); - } - } - } - else { - if (debugging) { - out.println("parsing - runnerState.nextState != null"); - } - - // Special Case: - // If this state represents '*' - go to the rightmost state representing '?'. - // This state will act as an '*' - except that you only can go to the next state or accept the string, if and only if the number of '?' read are equal or less than the number of character read from the parsing string. - if (isFreeRangeCharacter(runnerState.character)) { - numberOfFreePassCharactersRead_SinceLastFreePassState = 0; - numberOfParsedCharactersRead_SinceLastFreePassState = 0; - WildcardStringParserState freeRangeRunnerState = runnerState.nextState; - - while ((freeRangeRunnerState != null) && (isFreePassCharacter(freeRangeRunnerState.character))) { - runnerState = freeRangeRunnerState; - hasPerformedFreeRangeMovement = true; - numberOfFreePassCharactersRead_SinceLastFreePassState++; - freeRangeRunnerState = freeRangeRunnerState.nextState; - } - - // Special Case: if the mask is at the end - if (runnerState.nextState == null) { - if (debugging) { - out.println(); - } - if (debugging) { - out.println("parsing - index number " + i + ", active char: '" - + parsableString.getActiveChar() + "' char string index: " + parsableString.index - + " number of chars since last free-range state: " + numberOfParsedCharactersRead_SinceLastFreePassState); - } - if (debugging) { - out.println("parsing - state: " + runnerState.automatonStateNumber + " '" - + runnerState.character + "' - no of free-pass chars read: " + numberOfFreePassCharactersRead_SinceLastFreePassState); - } - if (debugging) { - out.println("parsing - hasPerformedFreeRangeMovement: " - + hasPerformedFreeRangeMovement); - } - if ((hasPerformedFreeRangeMovement) - && (numberOfFreePassCharactersRead_SinceLastFreePassState >= numberOfParsedCharactersRead_SinceLastFreePassState)) { - return true; - } - else { - return false; - } - } - } - - // If the next state represents '*' - go to this next state - if (isFreeRangeCharacter(runnerState.nextState.character)) { - runnerState = runnerState.nextState; - parsableString.index++; - numberOfParsedCharactersRead_SinceLastFreePassState++; - } - - // If the next state represents '?' - go to this next state - else if (isFreePassCharacter(runnerState.nextState.character)) { - runnerState = runnerState.nextState; - parsableString.index++; - numberOfFreePassCharactersRead_SinceLastFreePassState++; - numberOfParsedCharactersRead_SinceLastFreePassState++; - } - - // If the next state represents the same character as the next character in the string to test - go to this next state - else if ((!parsableString.reachedEndOfString()) && (runnerState.nextState.character == parsableString.getSubsequentChar())) { - runnerState = runnerState.nextState; - parsableString.index++; - numberOfParsedCharactersRead_SinceLastFreePassState++; - } - - // If the next character in the string to test does not coincide with the next state - go to the last state representing '*'. If there are none - rejection! - else if (runnerState.lastFreeRangeState != null) { - runnerState = runnerState.lastFreeRangeState; - parsableString.index++; - numberOfParsedCharactersRead_SinceLastFreePassState++; - } - else { - if (debugging) { - out.println("the next state does not represent the same character as the next character in the string to test, and there are no last-free-range-state - rejection!"); - } - return false; - } - } - } - if (debugging) { - out.println("finished reading parsing string and not at any final state - rejection!"); - } - return false; - } - - /* - * Overriding mandatory methods from EntityObject's. - */ - - /** - * Method toString - * - * @return - */ - public String toString() { - - StringBuilder buffer = new StringBuilder(); - - if (!initialized) { - buffer.append(getClass().getName()); - buffer.append(": Not initialized properly!"); - buffer.append("\n"); - buffer.append("\n"); - } - else { - WildcardStringParserState runnerState = initialState; - - buffer.append(getClass().getName()); - buffer.append(": String mask "); - buffer.append(stringMask); - buffer.append("\n"); - buffer.append("\n"); - buffer.append(" Automaton: "); - while (runnerState != null) { - buffer.append(runnerState.automatonStateNumber); - buffer.append(": "); - buffer.append(runnerState.character); - buffer.append(" ("); - if (runnerState.lastFreeRangeState != null) { - buffer.append(runnerState.lastFreeRangeState.automatonStateNumber); - } - else { - buffer.append("-"); - } - buffer.append(")"); - if (runnerState.nextState != null) { - buffer.append(" --> "); - } - runnerState = runnerState.nextState; - } - buffer.append("\n"); - buffer.append(" Format: : ()"); - buffer.append("\n"); - buffer.append(" Number of strings parsed: " + totalNumberOfStringsParsed); - buffer.append("\n"); - } - return buffer.toString(); - } - - /** - * Method equals - * - * @param pObject - * @return - */ - public boolean equals(Object pObject) { - - if (pObject instanceof WildcardStringParser) { - WildcardStringParser externalParser = (WildcardStringParser) pObject; - - return ((externalParser.initialized == this.initialized) && (externalParser.stringMask == this.stringMask)); - } - return super.equals(pObject); - } - - // Just taking the lazy, easy and dangerous way out - - /** - * Method hashCode - * - * @return - */ - public int hashCode() { - return super.hashCode(); - } - - protected Object clone() throws CloneNotSupportedException { - - if (initialized) { - return new WildcardStringParser(stringMask); - } - return null; - } - - // Just taking the lazy, easy and dangerous way out - protected void finalize() throws Throwable { - } - - /** A simple holder class for an automaton state. */ - class WildcardStringParserState { - - // Constants - // Members - int automatonStateNumber; - char character; - WildcardStringParserState previousState; - WildcardStringParserState nextState; - WildcardStringParserState lastFreeRangeState; - - // Constructors - - /** - * Constructor WildcardStringParserState - * - * @param pChar - */ - public WildcardStringParserState(final char pChar) { - this.character = pChar; - } - - // Methods - // Debug - } - - /** A simple holder class for a string to be parsed. */ - class ParsableString { - - // Constants - // Members - char[] charArray; - int index; - - // Constructors - ParsableString(final String pStringToParse) { - - if (pStringToParse != null) { - charArray = pStringToParse.toCharArray(); - } - index = -1; - } - - // Methods - boolean reachedEndOfString() { - - //System.out.println(DebugUtil.DEBUG + DebugUtil.getClassName(this) + ": index :" + index); - //System.out.println(DebugUtil.DEBUG + DebugUtil.getClassName(this) + ": charArray.length :" + charArray.length); - return index == charArray.length - 1; - } - - int length() { - return charArray.length; - } - - char getActiveChar() { - - if ((index > -1) && (index < charArray.length)) { - return charArray[index]; - } - System.err.println(getClass().getName() + ": trying to access character outside character array!"); - return ' '; - } - - char getSubsequentChar() { - - if ((index > -1) && (index + 1 < charArray.length)) { - return charArray[index + 1]; - } - System.err.println(getClass().getName() + ": trying to access character outside character array!"); - return ' '; - } - - boolean checkString() { - - if (!isEmpty()) { - - // Check if the string only contains chars that are elements in the alphabet - for (int i = 0; i < charArray.length; i++) { - if (!WildcardStringParser.isInAlphabet(charArray[i])) { - return false; - } - } - } - return true; - } - - boolean isEmpty() { - return ((charArray == null) || (charArray.length == 0)); - } - - /** - * Method toString - * - * @return - */ - public String toString() { - return new String(charArray); - } - } -} - - -/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/ - - -/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/ +/* + * 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.util.regex; + +import java.io.PrintStream; + +/** + * This class parses arbitrary strings against a wildcard string mask provided. + * The wildcard characters are '*' and '?'. + *

+ * The string masks provided are treated as case sensitive.
+ * Null-valued string masks as well as null valued strings to be parsed, will lead to rejection. + *

+ *

+ * This class is custom designed for wildcard string parsing and is several times faster than the implementation based on the Jakarta Regexp package. + *

+ *
+ *

+ * This task is performed based on regular expression techniques. + * The possibilities of string generation with the well-known wildcard characters stated above, + * represent a subset of the possibilities of string generation with regular expressions.
+ * The '*' corresponds to ([Union of all characters in the alphabet])*
+ * The '?' corresponds to ([Union of all characters in the alphabet])
+ *       These expressions are not suited for textual representation at all, I must say. Is there any math tags included in HTML? + *

+ *

+ * The complete meta-language for regular expressions are much larger. + * This fact makes it fairly straightforward to build data structures for parsing because the amount of rules of building these structures are quite limited, as stated below. + *

+ *

+ * To bring this over to mathematical terms: + * The parser ia a nondeterministic finite automaton (latin) representing the grammar which is stated by the string mask. + * The language accepted by this automaton is the set of all strings accepted by this automaton.
+ * The formal automaton quintuple consists of: + *

+ *
    + *
  1. A finite set of states, depending on the wildcard string mask. + * For each character in the mask a state representing that character is created. + * The number of states therefore coincides with the length of the mask. + *
  2. An alphabet consisting of all legal filename characters - included the two wildcard characters '*' and '?'. + * This alphabet is hard-coded in this class. It contains {a .. �}, {A .. �}, {0 .. 9}, {.}, {_}, {-}, {*} and {?}. + *
  3. A finite set of initial states, here only consisting of the state corresponding to the first character in the mask. + *
  4. A finite set of final states, here only consisting of the state corresponding to the last character in the mask. + *
  5. A transition relation that is a finite set of transitions satisfying some formal rules.
    + * This implementation on the other hand, only uses ad-hoc rules which start with an initial setup of the states as a sequence according to the string mask.
    + * Additionally, the following rules completes the building of the automaton: + *
      + *
    1. If the next state represents the same character as the next character in the string to test - go to this next state. + *
    2. If the next state represents '*' - go to this next state. + *
    3. If the next state represents '?' - go to this next state. + *
    4. If a '*' is followed by one or more '?', the last of these '?' state counts as a '*' state. Some extra checks regarding the number of characters read must be imposed if this is the case... + *
    5. If the next character in the string to test does not coincide with the next state - go to the last state representing '*'. If there are none - rejection. + *
    6. If there are no subsequent state (final state) and the state represents '*' - acceptance. + *
    7. If there are no subsequent state (final state) and the end of the string to test is reached - acceptance. + *
    + *
    + * + * Disclaimer: This class does not build a finite automaton according to formal mathematical rules. + * The proper way of implementation should be finding the complete set of transition relations, decomposing these into rules accepted by a deterministic finite automaton and finally build this automaton to be used for string parsing. + * Instead, this class is ad-hoc implemented based on the informal transition rules stated above. + * Therefore the correctness cannot be guaranteed before extensive testing has been imposed on this class... anyway, I think I have succeeded. + * Parsing faults must be reported to the author. + * + *
+ *
+ *

+ * Examples of usage:
+ * This example will return "Accepted!". + *

+ *
+ * WildcardStringParser parser = new WildcardStringParser("*_28????.jp*");
+ * if (parser.parseString("gupu_280915.jpg")) {
+ *     System.out.println("Accepted!");
+ * } else {
+ *     System.out.println("Not accepted!");
+ * }
+ * 
+ *
+ *

+ * Theories and concepts are based on the book Elements of the Theory of Computation, by Harry l. Lewis and Christos H. Papadimitriou, (c) 1981 by Prentice Hall. + *

+ * + * @author Eirik Torske + * @deprecated Will probably be removed in the near future + */ +public class WildcardStringParser { + // TODO: Get rid of this class + + // Constants + + /** Field ALPHABET */ + public static final char[] ALPHABET = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00e6', + '\u00f8', '\u00e5', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'N', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '\u00c6', '\u00d8', '\u00c5', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '-' + }; + + /** Field FREE_RANGE_CHARACTER */ + public static final char FREE_RANGE_CHARACTER = '*'; + + /** Field FREE_PASS_CHARACTER */ + public static final char FREE_PASS_CHARACTER = '?'; + + // Members + boolean initialized; + String stringMask; + WildcardStringParserState initialState; + int totalNumberOfStringsParsed; + boolean debugging; + PrintStream out; + + // Properties + // Constructors + + /** + * Creates a wildcard string parser. + * + * @param pStringMask the wildcard string mask. + */ + public WildcardStringParser(final String pStringMask) { + this(pStringMask, false); + } + + /** + * Creates a wildcard string parser. + * + * @param pStringMask the wildcard string mask. + * @param pDebugging {@code true} will cause debug messages to be emitted to {@code System.out}. + */ + public WildcardStringParser(final String pStringMask, final boolean pDebugging) { + this(pStringMask, pDebugging, System.out); + } + + /** + * Creates a wildcard string parser. + * + * @param pStringMask the wildcard string mask. + * @param pDebugging {@code true} will cause debug messages to be emitted. + * @param pDebuggingPrintStream the {@code java.io.PrintStream} to which the debug messages will be emitted. + */ + public WildcardStringParser(final String pStringMask, final boolean pDebugging, final PrintStream pDebuggingPrintStream) { + this.stringMask = pStringMask; + this.debugging = pDebugging; + this.out = pDebuggingPrintStream; + initialized = buildAutomaton(); + } + + // Methods + private boolean checkIfStateInWildcardRange(WildcardStringParserState pState) { + + WildcardStringParserState runnerState = pState; + + while (runnerState.previousState != null) { + runnerState = runnerState.previousState; + if (isFreeRangeCharacter(runnerState.character)) { + return true; + } + if (!isFreePassCharacter(runnerState.character)) { + return false; + } // If free-pass char '?' - move on + } + return false; + } + + private boolean checkIfLastFreeRangeState(WildcardStringParserState pState) { + + if (isFreeRangeCharacter(pState.character)) { + return true; + } + if (isFreePassCharacter(pState.character)) { + if (checkIfStateInWildcardRange(pState)) { + return true; + } + } + return false; + } + + /** @return {@code true} if and only if the string mask only consists of free-range wildcard character(s). */ + private boolean isTrivialAutomaton() { + + for (int i = 0; i < stringMask.length(); i++) { + if (!isFreeRangeCharacter(stringMask.charAt(i))) { + return false; + } + } + return true; + } + + private boolean buildAutomaton() { + + char activeChar; + WildcardStringParserState runnerState = null; + WildcardStringParserState newState = null; + WildcardStringParserState lastFreeRangeState = null; + + // Create the initial state of the automaton + if ((stringMask != null) && (stringMask.length() > 0)) { + newState = new WildcardStringParserState(stringMask.charAt(0)); + newState.automatonStateNumber = 0; + newState.previousState = null; + if (checkIfLastFreeRangeState(newState)) { + lastFreeRangeState = newState; + } + runnerState = newState; + initialState = runnerState; + initialState.automatonStateNumber = 0; + } + else { + System.err.println("string mask provided are null or empty - aborting!"); + return false; + } + + // Create the rest of the automaton + for (int i = 1; i < stringMask.length(); i++) { + activeChar = stringMask.charAt(i); + + // Check if the char is an element in the alphabet or is a wildcard character + if (!((isInAlphabet(activeChar)) || (isWildcardCharacter(activeChar)))) { + System.err.println("one or more characters in string mask are not legal characters - aborting!"); + return false; + } + + // Set last free-range state before creating/checking the next state + runnerState.lastFreeRangeState = lastFreeRangeState; + + // Create next state, check if free-range state, set the state number and preceeding state + newState = new WildcardStringParserState(activeChar); + newState.automatonStateNumber = i; + newState.previousState = runnerState; + + // Special check if the state represents an '*' or '?' with only preceeding states representing '?' and '*' + if (checkIfLastFreeRangeState(newState)) { + lastFreeRangeState = newState; + } + + // Set the succeding state before moving to the next state + runnerState.nextState = newState; + + // Move to the next state + runnerState = newState; + + // Special setting of the last free-range state for the last element + if (runnerState.automatonStateNumber == stringMask.length() - 1) { + runnerState.lastFreeRangeState = lastFreeRangeState; + } + } + + // Initiate some statistics + totalNumberOfStringsParsed = 0; + return true; + } + + /** Tests if a certain character is a valid character in the alphabet that is applying for this automaton. */ + public static boolean isInAlphabet(final char pCharToCheck) { + + for (int i = 0; i < ALPHABET.length; i++) { + if (pCharToCheck == ALPHABET[i]) { + return true; + } + } + return false; + } + + /** Tests if a certain character is the designated "free-range" character ('*'). */ + public static boolean isFreeRangeCharacter(final char pCharToCheck) { + return pCharToCheck == FREE_RANGE_CHARACTER; + } + + /** Tests if a certain character is the designated "free-pass" character ('?'). */ + public static boolean isFreePassCharacter(final char pCharToCheck) { + return pCharToCheck == FREE_PASS_CHARACTER; + } + + /** Tests if a certain character is a wildcard character ('*' or '?'). */ + public static boolean isWildcardCharacter(final char pCharToCheck) { + return ((isFreeRangeCharacter(pCharToCheck)) || (isFreePassCharacter(pCharToCheck))); + } + + /** + * Gets the string mask that was used when building the parser atomaton. + * + * @return the string mask used for building the parser automaton. + */ + public String getStringMask() { + return stringMask; + } + + /** + * Parses a string according to the rules stated above. + * + * @param pStringToParse the string to parse. + * @return {@code true} if and only if the string are accepted by the automaton. + */ + public boolean parseString(final String pStringToParse) { + + if (debugging) { + out.println("parsing \"" + pStringToParse + "\"..."); + } + + // Update statistics + totalNumberOfStringsParsed++; + + // Check string to be parsed for nullness + if (pStringToParse == null) { + if (debugging) { + out.println("string to be parsed is null - rejection!"); + } + return false; + } + + // Create parsable string + ParsableString parsableString = new ParsableString(pStringToParse); + + // Check string to be parsed + if (!parsableString.checkString()) { + if (debugging) { + out.println("one or more characters in string to be parsed are not legal characters - rejection!"); + } + return false; + } + + // Check if automaton is correctly initialized + if (!initialized) { + System.err.println("automaton is not initialized - rejection!"); + return false; + } + + // Check if automaton is trivial (accepts all strings) + if (isTrivialAutomaton()) { + if (debugging) { + out.println("automaton represents a trivial string mask (accepts all strings) - acceptance!"); + } + return true; + } + + // Check if string to be parsed is empty + if (parsableString.isEmpty()) { + if (debugging) { + out.println("string to be parsed is empty and not trivial automaton - rejection!"); + } + return false; + } + + // Flag and more to indicate that state skipping due to sequence of '?' succeeding a '*' has been performed + boolean hasPerformedFreeRangeMovement = false; + int numberOfFreePassCharactersRead_SinceLastFreePassState = 0; + int numberOfParsedCharactersRead_SinceLastFreePassState = 0; + WildcardStringParserState runnerState = null; + + // Accepted by the first state? + if ((parsableString.charArray[0] == initialState.character) || isWildcardCharacter(initialState.character)) { + runnerState = initialState; + parsableString.index = 0; + } + else { + if (debugging) { + out.println("cannot enter first automaton state - rejection!"); + } + return false; + } + + // Initialize the free-pass character state visited count + if (isFreePassCharacter(runnerState.character)) { + numberOfFreePassCharactersRead_SinceLastFreePassState++; + } + + // Perform parsing according to the rules above + for (int i = 0; i < parsableString.length(); i++) { + if (debugging) { + out.println(); + } + if (debugging) { + out.println("parsing - index number " + i + ", active char: '" + + parsableString.getActiveChar() + "' char string index: " + parsableString.index + + " number of chars since last free-range state: " + numberOfParsedCharactersRead_SinceLastFreePassState); + } + if (debugging) { + out.println("parsing - state: " + runnerState.automatonStateNumber + " '" + + runnerState.character + "' - no of free-pass chars read: " + numberOfFreePassCharactersRead_SinceLastFreePassState); + } + if (debugging) { + out.println("parsing - hasPerformedFreeRangeMovement: " + hasPerformedFreeRangeMovement); + } + if (runnerState.nextState == null) { + if (debugging) { + out.println("parsing - runnerState.nextState == null"); + } + + // If there are no subsequent state (final state) and the state represents '*' - acceptance! + if (isFreeRangeCharacter(runnerState.character)) { + + // Special free-range skipping check + if (hasPerformedFreeRangeMovement) { + if (parsableString.reachedEndOfString()) { + if (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState) { + if (debugging) { + out.println( + "no subsequent state (final state) and the state represents '*' - end of parsing string, but not enough characters read - rejection!"); + } + return false; + } + else { + if (debugging) { + out.println( + "no subsequent state (final state) and the state represents '*' - end of parsing string and enough characters read - acceptance!"); + } + return true; + } + } + else { + if (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState) { + if (debugging) { + out.println( + "no subsequent state (final state) and the state represents '*' - not the end of parsing string and not enough characters read - read next character"); + } + parsableString.index++; + numberOfParsedCharactersRead_SinceLastFreePassState++; + } + else { + if (debugging) { + out.println( + "no subsequent state (final state) and the state represents '*' - not the end of parsing string, but enough characters read - acceptance!"); + } + return true; + } + } + } + else { + if (debugging) { + out.println("no subsequent state (final state) and the state represents '*' - no skipping performed - acceptance!"); + } + return true; + } + } + + // If there are no subsequent state (final state) and no skipping has been performed and the end of the string to test is reached - acceptance! + else if (parsableString.reachedEndOfString()) { + + // Special free-range skipping check + if ((hasPerformedFreeRangeMovement) + && (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState)) { + if (debugging) { + out.println( + "no subsequent state (final state) and skipping has been performed and end of parsing string, but not enough characters read - rejection!"); + } + return false; + } + if (debugging) { + out.println("no subsequent state (final state) and the end of the string to test is reached - acceptance!"); + } + return true; + } + else { + if (debugging) { + out.println("parsing - escaping process..."); + } + } + } + else { + if (debugging) { + out.println("parsing - runnerState.nextState != null"); + } + + // Special Case: + // If this state represents '*' - go to the rightmost state representing '?'. + // This state will act as an '*' - except that you only can go to the next state or accept the string, if and only if the number of '?' read are equal or less than the number of character read from the parsing string. + if (isFreeRangeCharacter(runnerState.character)) { + numberOfFreePassCharactersRead_SinceLastFreePassState = 0; + numberOfParsedCharactersRead_SinceLastFreePassState = 0; + WildcardStringParserState freeRangeRunnerState = runnerState.nextState; + + while ((freeRangeRunnerState != null) && (isFreePassCharacter(freeRangeRunnerState.character))) { + runnerState = freeRangeRunnerState; + hasPerformedFreeRangeMovement = true; + numberOfFreePassCharactersRead_SinceLastFreePassState++; + freeRangeRunnerState = freeRangeRunnerState.nextState; + } + + // Special Case: if the mask is at the end + if (runnerState.nextState == null) { + if (debugging) { + out.println(); + } + if (debugging) { + out.println("parsing - index number " + i + ", active char: '" + + parsableString.getActiveChar() + "' char string index: " + parsableString.index + + " number of chars since last free-range state: " + numberOfParsedCharactersRead_SinceLastFreePassState); + } + if (debugging) { + out.println("parsing - state: " + runnerState.automatonStateNumber + " '" + + runnerState.character + "' - no of free-pass chars read: " + numberOfFreePassCharactersRead_SinceLastFreePassState); + } + if (debugging) { + out.println("parsing - hasPerformedFreeRangeMovement: " + + hasPerformedFreeRangeMovement); + } + if ((hasPerformedFreeRangeMovement) + && (numberOfFreePassCharactersRead_SinceLastFreePassState >= numberOfParsedCharactersRead_SinceLastFreePassState)) { + return true; + } + else { + return false; + } + } + } + + // If the next state represents '*' - go to this next state + if (isFreeRangeCharacter(runnerState.nextState.character)) { + runnerState = runnerState.nextState; + parsableString.index++; + numberOfParsedCharactersRead_SinceLastFreePassState++; + } + + // If the next state represents '?' - go to this next state + else if (isFreePassCharacter(runnerState.nextState.character)) { + runnerState = runnerState.nextState; + parsableString.index++; + numberOfFreePassCharactersRead_SinceLastFreePassState++; + numberOfParsedCharactersRead_SinceLastFreePassState++; + } + + // If the next state represents the same character as the next character in the string to test - go to this next state + else if ((!parsableString.reachedEndOfString()) && (runnerState.nextState.character == parsableString.getSubsequentChar())) { + runnerState = runnerState.nextState; + parsableString.index++; + numberOfParsedCharactersRead_SinceLastFreePassState++; + } + + // If the next character in the string to test does not coincide with the next state - go to the last state representing '*'. If there are none - rejection! + else if (runnerState.lastFreeRangeState != null) { + runnerState = runnerState.lastFreeRangeState; + parsableString.index++; + numberOfParsedCharactersRead_SinceLastFreePassState++; + } + else { + if (debugging) { + out.println("the next state does not represent the same character as the next character in the string to test, and there are no last-free-range-state - rejection!"); + } + return false; + } + } + } + if (debugging) { + out.println("finished reading parsing string and not at any final state - rejection!"); + } + return false; + } + + /* + * Overriding mandatory methods from EntityObject's. + */ + + /** + * Method toString + * + * @return + */ + public String toString() { + + StringBuilder buffer = new StringBuilder(); + + if (!initialized) { + buffer.append(getClass().getName()); + buffer.append(": Not initialized properly!"); + buffer.append("\n"); + buffer.append("\n"); + } + else { + WildcardStringParserState runnerState = initialState; + + buffer.append(getClass().getName()); + buffer.append(": String mask "); + buffer.append(stringMask); + buffer.append("\n"); + buffer.append("\n"); + buffer.append(" Automaton: "); + while (runnerState != null) { + buffer.append(runnerState.automatonStateNumber); + buffer.append(": "); + buffer.append(runnerState.character); + buffer.append(" ("); + if (runnerState.lastFreeRangeState != null) { + buffer.append(runnerState.lastFreeRangeState.automatonStateNumber); + } + else { + buffer.append("-"); + } + buffer.append(")"); + if (runnerState.nextState != null) { + buffer.append(" --> "); + } + runnerState = runnerState.nextState; + } + buffer.append("\n"); + buffer.append(" Format: : ()"); + buffer.append("\n"); + buffer.append(" Number of strings parsed: ").append(totalNumberOfStringsParsed); + buffer.append("\n"); + } + return buffer.toString(); + } + + /** + * Method equals + * + * @param pObject + * @return + */ + public boolean equals(Object pObject) { + + if (pObject instanceof WildcardStringParser) { + WildcardStringParser externalParser = (WildcardStringParser) pObject; + + return ((externalParser.initialized == this.initialized) && (externalParser.stringMask == this.stringMask)); + } + return super.equals(pObject); + } + + // Just taking the lazy, easy and dangerous way out + + /** + * Method hashCode + * + * @return + */ + public int hashCode() { + return super.hashCode(); + } + + protected Object clone() throws CloneNotSupportedException { + + if (initialized) { + return new WildcardStringParser(stringMask); + } + return null; + } + + // Just taking the lazy, easy and dangerous way out + protected void finalize() throws Throwable { + } + + /** A simple holder class for an automaton state. */ + class WildcardStringParserState { + + // Constants + // Members + int automatonStateNumber; + char character; + WildcardStringParserState previousState; + WildcardStringParserState nextState; + WildcardStringParserState lastFreeRangeState; + + // Constructors + + /** + * Constructor WildcardStringParserState + * + * @param pChar + */ + public WildcardStringParserState(final char pChar) { + this.character = pChar; + } + + // Methods + // Debug + } + + /** A simple holder class for a string to be parsed. */ + class ParsableString { + + // Constants + // Members + char[] charArray; + int index; + + // Constructors + ParsableString(final String pStringToParse) { + + if (pStringToParse != null) { + charArray = pStringToParse.toCharArray(); + } + index = -1; + } + + // Methods + boolean reachedEndOfString() { + + //System.out.println(DebugUtil.DEBUG + DebugUtil.getClassName(this) + ": index :" + index); + //System.out.println(DebugUtil.DEBUG + DebugUtil.getClassName(this) + ": charArray.length :" + charArray.length); + return index == charArray.length - 1; + } + + int length() { + return charArray.length; + } + + char getActiveChar() { + + if ((index > -1) && (index < charArray.length)) { + return charArray[index]; + } + System.err.println(getClass().getName() + ": trying to access character outside character array!"); + return ' '; + } + + char getSubsequentChar() { + + if ((index > -1) && (index + 1 < charArray.length)) { + return charArray[index + 1]; + } + System.err.println(getClass().getName() + ": trying to access character outside character array!"); + return ' '; + } + + boolean checkString() { + + if (!isEmpty()) { + + // Check if the string only contains chars that are elements in the alphabet + for (int i = 0; i < charArray.length; i++) { + if (!WildcardStringParser.isInAlphabet(charArray[i])) { + return false; + } + } + } + return true; + } + + boolean isEmpty() { + return ((charArray == null) || (charArray.length == 0)); + } + + /** + * Method toString + * + * @return + */ + public String toString() { + return new String(charArray); + } + } +} + + +/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/ + + +/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/ diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/package_info.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/package_info.java index 10302814..ca928070 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/package_info.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/regex/package_info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides functionality for regular expressions. */ diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java index 1c666ef2..102b8989 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java @@ -1,61 +1,64 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.service; - -/** - * An optional interface that may be implemented by service provider objects. - *

- * If this interface is implemented, the service provider objects will receive - * notification of registration and deregistration from the - * {@code ServiceRegistry}. - * - * @see ServiceRegistry - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java#1 $ - */ -public interface RegisterableService { - /** - * Called right after this service provider object is added to - * the given category of the given {@code ServiceRegistry}. - * - * @param pRegistry the {@code ServiceRegistry} {@code this} was added to - * @param pCategory the category {@code this} was added to - */ - void onRegistration(ServiceRegistry pRegistry, Class pCategory); - - /** - * Called right after this service provider object is removed - * from the given category of the given {@code ServiceRegistry}. - * - * @param pRegistry the {@code ServiceRegistry} {@code this} was added to - * @param pCategory the category {@code this} was added to - */ - void onDeregistration(ServiceRegistry pRegistry, Class pCategory); -} +/* + * 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.util.service; + +/** + * An optional interface that may be implemented by service provider objects. + *

+ * If this interface is implemented, the service provider objects will receive + * notification of registration and deregistration from the + * {@code ServiceRegistry}. + *

+ * + * @see ServiceRegistry + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java#1 $ + */ +public interface RegisterableService { + /** + * Called right after this service provider object is added to + * the given category of the given {@code ServiceRegistry}. + * + * @param pRegistry the {@code ServiceRegistry} {@code this} was added to + * @param pCategory the category {@code this} was added to + */ + void onRegistration(ServiceRegistry pRegistry, Class pCategory); + + /** + * Called right after this service provider object is removed + * from the given category of the given {@code ServiceRegistry}. + * + * @param pRegistry the {@code ServiceRegistry} {@code this} was added to + * @param pCategory the category {@code this} was added to + */ + void onDeregistration(ServiceRegistry pRegistry, Class pCategory); +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java index 0aa281f0..9d5f8c1c 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java @@ -1,52 +1,54 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.service; - -/** - * Error thrown by the {@code ServiceRegistry} in case of a configuration - * error. - *

- * @see ServiceRegistry - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java#1 $ - */ -public class ServiceConfigurationError extends Error { - ServiceConfigurationError(Throwable pCause) { - super(pCause); - } - - ServiceConfigurationError(String pMessage) { - super(pMessage); - } - - ServiceConfigurationError(String pMessage, Throwable pCause) { - super(pMessage, pCause); - } -} +/* + * 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.util.service; + +/** + * Error thrown by the {@code ServiceRegistry} in case of a configuration + * error. + * + * @see ServiceRegistry + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java#1 $ + */ +public class ServiceConfigurationError extends Error { + ServiceConfigurationError(Throwable pCause) { + super(pCause); + } + + ServiceConfigurationError(String pMessage) { + super(pMessage); + } + + ServiceConfigurationError(String pMessage, Throwable pCause) { + super(pMessage, pCause); + } +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceRegistry.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceRegistry.java index c375d955..18fdc3a0 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceRegistry.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/ServiceRegistry.java @@ -1,546 +1,561 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util.service; - -import com.twelvemonkeys.lang.Validate; -import com.twelvemonkeys.util.FilterIterator; - -import java.io.IOException; -import java.net.URL; -import java.util.*; - -/** - * A registry for service provider objects. - *

- * Service providers are looked up from the classpath, under the path - * {@code META-INF/services/}<full-class-name>. - *

- * For example:
- * {@code META-INF/services/com.company.package.spi.MyService}. - *

- * The file should contain a list of fully-qualified concrete class names, - * one per line. - *

- * The full-class-name represents an interface or (typically) an - * abstract class, and is the same class used as the category for this registry. - * Note that only one instance of a concrete subclass may be registered with a - * specific category at a time. - *

- * Implementation detail: This class is a clean room implementation of - * a service registry and does not use the proprietary {@code sun.misc.Service} - * class that is referred to in the JAR File specification. - * This class should work on any Java platform. - * - * - * @author Harald Kuhr - * @version $Id: com/twelvemonkeys/util/service/ServiceRegistry.java#2 $ - * @see RegisterableService - * @see JAR File Specification - */ -public class ServiceRegistry { - // TODO: Security issues? - // TODO: Application contexts? Probably use instance per thread group.. - - /** - * "META-INF/services/" - */ - public static final String SERVICES = "META-INF/services/"; - - // Class to CategoryRegistry mapping - private final Map, CategoryRegistry> categoryMap; - - /** - * Creates a {@code ServiceRegistry} instance with a set of categories - * taken from the {@code pCategories} argument. - *

- * The categories are constant during the lifetime of the registry, and may - * not be changed after initial creation. - * - * @param pCategories an {@code Iterator} containing - * {@code Class} objects that defines this registry's categories. - * @throws IllegalArgumentException if {@code pCategories} is {@code null}. - * @throws ClassCastException if {@code pCategories} contains anything - * but {@code Class} objects. - */ - public ServiceRegistry(final Iterator> pCategories) { - Validate.notNull(pCategories, "categories"); - - Map, CategoryRegistry> map = new LinkedHashMap, CategoryRegistry>(); - - while (pCategories.hasNext()) { - putCategory(map, pCategories.next()); - } - - // NOTE: Categories are constant for the lifetime of a registry - categoryMap = Collections.unmodifiableMap(map); - } - - private void putCategory(Map, CategoryRegistry> pMap, Class pCategory) { - CategoryRegistry registry = new CategoryRegistry(pCategory); - pMap.put(pCategory, registry); - } - - /** - * Registers all provider implementations for this {@code ServiceRegistry} - * found in the application classpath. - * - * @throws ServiceConfigurationError if an error occurred during registration - */ - public void registerApplicationClasspathSPIs() { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - Iterator> categories = categories(); - - while (categories.hasNext()) { - Class category = categories.next(); - - try { - // Find all META-INF/services/ + name on class path - String name = SERVICES + category.getName(); - Enumeration spiResources = loader.getResources(name); - - while (spiResources.hasMoreElements()) { - URL resource = spiResources.nextElement(); - registerSPIs(resource, category, loader); - } - } - catch (IOException e) { - throw new ServiceConfigurationError(e); - } - } - } - - /** - * Registers all SPIs listed in the given resource. - * - * @param pResource the resource to load SPIs from - * @param pCategory the category class - * @param pLoader the class loader to use - */ - void registerSPIs(final URL pResource, final Class pCategory, final ClassLoader pLoader) { - Properties classNames = new Properties(); - - try { - classNames.load(pResource.openStream()); - } - catch (IOException e) { - throw new ServiceConfigurationError(e); - } - - if (!classNames.isEmpty()) { - @SuppressWarnings({"unchecked"}) - CategoryRegistry registry = categoryMap.get(pCategory); - - Set providerClassNames = classNames.keySet(); - - for (Object providerClassName : providerClassNames) { - String className = (String) providerClassName; - try { - @SuppressWarnings({"unchecked"}) - Class providerClass = (Class) Class.forName(className, true, pLoader); - T provider = providerClass.newInstance(); - registry.register(provider); - } - catch (ClassNotFoundException e) { - throw new ServiceConfigurationError(e); - } - catch (IllegalAccessException e) { - throw new ServiceConfigurationError(e); - } - catch (InstantiationException e) { - throw new ServiceConfigurationError(e); - } - catch (IllegalArgumentException e) { - throw new ServiceConfigurationError(e); - } - } - } - } - - /** - * Returns an {@code Iterator} containing all providers in the given - * category. - *

- * The iterator supports removal. - *

- * - * NOTE: Removing a provider from the iterator, deregisters the current - * provider (as returned by the last invocation of {@code next()}) from - * {@code pCategory}, it does not remove the provider - * from other categories in the registry. - * - * - * @param pCategory the category class - * @return an {@code Iterator} containing all providers in the given - * category. - * @throws IllegalArgumentException if {@code pCategory} is not a valid - * category in this registry - */ - protected Iterator providers(Class pCategory) { - return getRegistry(pCategory).providers(); - } - - /** - * Returns an {@code Iterator} containing all categories in this registry. - *

- * The iterator does not support removal. - * - * @return an {@code Iterator} containing all categories in this registry. - */ - protected Iterator> categories() { - return categoryMap.keySet().iterator(); - } - - /** - * Returns an {@code Iterator} containing all categories in this registry - * the given {@code pProvider} may be registered with. - *

- * The iterator does not support removal. - * - * @param pProvider the provider instance - * @return an {@code Iterator} containing all categories in this registry - * the given {@code pProvider} may be registered with - */ - protected Iterator> compatibleCategories(final Object pProvider) { - return new FilterIterator>(categories(), - new FilterIterator.Filter>() { - public boolean accept(Class pElement) { - return pElement.isInstance(pProvider); - } - }); - } - - /** - * Returns an {@code Iterator} containing all categories in this registry - * the given {@code pProvider} is currently registered with. - *

- * The iterator supports removal. - *

- * - * NOTE: Removing a category from the iterator, de-registers - * {@code pProvider} from the current category (as returned by the last - * invocation of {@code next()}), it does not remove the category - * itself from the registry. - * - * - * @param pProvider the provider instance - * @return an {@code Iterator} containing all categories in this registry - * the given {@code pProvider} may be registered with - */ - protected Iterator> containingCategories(final Object pProvider) { - // TODO: Is removal using the iterator really a good idea? - return new FilterIterator>(categories(), - new FilterIterator.Filter>() { - public boolean accept(Class pElement) { - return getRegistry(pElement).contains(pProvider); - } - }) { - Class current; - - public Class next() { - return (current = super.next()); - } - - public void remove() { - if (current == null) { - throw new IllegalStateException("No current element"); - } - - getRegistry(current).deregister(pProvider); - current = null; - } - }; - } - - /** - * Gets the category registry for the given category. - * - * @param pCategory the category class - * @return the {@code CategoryRegistry} for the given category - */ - private CategoryRegistry getRegistry(final Class pCategory) { - @SuppressWarnings({"unchecked"}) - CategoryRegistry registry = categoryMap.get(pCategory); - if (registry == null) { - throw new IllegalArgumentException("No such category: " + pCategory.getName()); - } - return registry; - } - - /** - * Registers the given provider for all categories it matches. - * - * @param pProvider the provider instance - * @return {@code true} if {@code pProvider} is now registered in - * one or more categories it was not registered in before. - * @see #compatibleCategories(Object) - */ - public boolean register(final Object pProvider) { - Iterator> categories = compatibleCategories(pProvider); - boolean registered = false; - while (categories.hasNext()) { - Class category = categories.next(); - if (registerImpl(pProvider, category) && !registered) { - registered = true; - } - } - return registered; - } - - private boolean registerImpl(final Object pProvider, final Class pCategory) { - return getRegistry(pCategory).register(pCategory.cast(pProvider)); - } - - /** - * Registers the given provider for the given category. - * - * @param pProvider the provider instance - * @param pCategory the category class - * @return {@code true} if {@code pProvider} is now registered in - * the given category - */ - public boolean register(final T pProvider, final Class pCategory) { - return registerImpl(pProvider, pCategory); - } - - /** - * De-registers the given provider from all categories it's currently - * registered in. - * - * @param pProvider the provider instance - * @return {@code true} if {@code pProvider} was previously registered in - * any category and is now de-registered. - * @see #containingCategories(Object) - */ - public boolean deregister(final Object pProvider) { - Iterator> categories = containingCategories(pProvider); - - boolean deregistered = false; - while (categories.hasNext()) { - Class category = categories.next(); - if (deregister(pProvider, category) && !deregistered) { - deregistered = true; - } - } - - return deregistered; - } - - /** - * Deregisters the given provider from the given category. - * - * @param pProvider the provider instance - * @param pCategory the category class - * @return {@code true} if {@code pProvider} was previously registered in - * the given category - */ - public boolean deregister(final Object pProvider, final Class pCategory) { - return getRegistry(pCategory).deregister(pProvider); - } - - /** - * Keeps track of each individual category. - */ - class CategoryRegistry { - private final Class category; - private final Map providers = new LinkedHashMap(); - - CategoryRegistry(Class pCategory) { - Validate.notNull(pCategory, "category"); - category = pCategory; - } - - private void checkCategory(final Object pProvider) { - if (!category.isInstance(pProvider)) { - throw new IllegalArgumentException(pProvider + " not instance of category " + category.getName()); - } - } - - public boolean register(final T pProvider) { - checkCategory(pProvider); - - // NOTE: We only register the new instance, if we don't already have an instance of pProvider's class. - if (!contains(pProvider)) { - providers.put(pProvider.getClass(), pProvider); - processRegistration(pProvider); - return true; - } - - return false; - } - - void processRegistration(final T pProvider) { - if (pProvider instanceof RegisterableService) { - RegisterableService service = (RegisterableService) pProvider; - service.onRegistration(ServiceRegistry.this, category); - } - } - - public boolean deregister(final Object pProvider) { - checkCategory(pProvider); - - // NOTE: We remove any provider of the same class, this may or may - // not be the same instance as pProvider. - T oldProvider = providers.remove(pProvider.getClass()); - - if (oldProvider != null) { - processDeregistration(oldProvider); - return true; - } - - return false; - } - - void processDeregistration(final T pOldProvider) { - if (pOldProvider instanceof RegisterableService) { - RegisterableService service = (RegisterableService) pOldProvider; - service.onDeregistration(ServiceRegistry.this, category); - } - } - - public boolean contains(final Object pProvider) { - return providers.containsKey(pProvider != null ? pProvider.getClass() : null); - } - - public Iterator providers() { - // NOTE: The iterator must support removal because deregistering - // using the deregister method will result in - // ConcurrentModificationException in the iterator.. - // We wrap the iterator to track deregistration right. - final Iterator iterator = providers.values().iterator(); - return new Iterator() { - T current; - - public boolean hasNext() { - return iterator.hasNext(); - - } - - public T next() { - return (current = iterator.next()); - } - - public void remove() { - iterator.remove(); - processDeregistration(current); - } - }; - } - } - - @SuppressWarnings({"UnnecessaryFullyQualifiedName"}) - public static void main(String[] pArgs) { - abstract class Spi {} - class One extends Spi {} - class Two extends Spi {} - - ServiceRegistry testRegistry = new ServiceRegistry( - Arrays.>asList( - java.nio.charset.spi.CharsetProvider.class, - java.nio.channels.spi.SelectorProvider.class, - javax.imageio.spi.ImageReaderSpi.class, - javax.imageio.spi.ImageWriterSpi.class, - Spi.class - ).iterator() - ); - - testRegistry.registerApplicationClasspathSPIs(); - - One one = new One(); - Two two = new Two(); - testRegistry.register(one, Spi.class); - testRegistry.register(two, Spi.class); - testRegistry.deregister(one); - testRegistry.deregister(one, Spi.class); - testRegistry.deregister(two, Spi.class); - testRegistry.deregister(two); - - Iterator> categories = testRegistry.categories(); - System.out.println("Categories: "); - while (categories.hasNext()) { - Class category = categories.next(); - System.out.println(" " + category.getName() + ":"); - - Iterator providers = testRegistry.providers(category); - Object provider = null; - while (providers.hasNext()) { - provider = providers.next(); - System.out.println(" " + provider); - if (provider instanceof javax.imageio.spi.ImageReaderWriterSpi) { - System.out.println(" - " + ((javax.imageio.spi.ImageReaderWriterSpi) provider).getDescription(null)); - } - // javax.imageio.spi.ImageReaderWriterSpi provider = (javax.imageio.spi.ImageReaderWriterSpi) providers.next(); - // System.out.println(" " + provider); - // System.out.println(" " + provider.getVendorName()); - // System.out.println(" Formats:"); - // - // System.out.print(" "); - // String[] formatNames = provider.getFormatNames(); - // for (int i = 0; i < formatNames.length; i++) { - // if (i != 0) { - // System.out.print(", "); - // } - // System.out.print(formatNames[i]); - // } - // System.out.println(); - - // Don't remove last one, it's removed later to exercise more code :-) - if (providers.hasNext()) { - providers.remove(); - } - } - - // Remove the last item from all categories - if (provider != null) { - Iterator containers = testRegistry.containingCategories(provider); - int count = 0; - while (containers.hasNext()) { - if (category == containers.next()) { - containers.remove(); - count++; - } - } - - if (count != 1) { - System.err.println("Removed " + provider + " from " + count + " categories"); - } - } - - // Remove all using providers iterator - providers = testRegistry.providers(category); - if (!providers.hasNext()) { - System.out.println("All providers successfully deregistered"); - } - while (providers.hasNext()) { - System.err.println("Not removed: " + providers.next()); - } - } - } - - //*/ -} +/* + * 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.util.service; + +import com.twelvemonkeys.lang.Validate; +import com.twelvemonkeys.util.FilterIterator; + +import java.io.IOException; +import java.net.URL; +import java.util.*; + +/** + * A registry for service provider objects. + *

+ * Service providers are looked up from the classpath, under the path + * {@code META-INF/services/}<full-class-name>. + *

+ *

+ * For example: + *
+ * {@code META-INF/services/com.company.package.spi.MyService}. + *

+ *

+ * The file should contain a list of fully-qualified concrete class names, + * one per line. + *

+ *

+ * The full-class-name represents an interface or (typically) an + * abstract class, and is the same class used as the category for this registry. + * Note that only one instance of a concrete subclass may be registered with a + * specific category at a time. + *

+ *

+ * Implementation detail: This class is a clean room implementation of + * a service registry and does not use the proprietary {@code sun.misc.Service} + * class that is referred to in the JAR File specification. + * This class should work on any Java platform. + * + *

+ * + * @author Harald Kuhr + * @version $Id: com/twelvemonkeys/util/service/ServiceRegistry.java#2 $ + * @see RegisterableService + * @see JAR File Specification + */ +public class ServiceRegistry { + // TODO: Security issues? + // TODO: Application contexts? Probably use instance per thread group.. + + /** + * "META-INF/services/" + */ + public static final String SERVICES = "META-INF/services/"; + + // Class to CategoryRegistry mapping + private final Map, CategoryRegistry> categoryMap; + + /** + * Creates a {@code ServiceRegistry} instance with a set of categories + * taken from the {@code pCategories} argument. + *

+ * The categories are constant during the lifetime of the registry, and may + * not be changed after initial creation. + *

+ * + * @param pCategories an {@code Iterator} containing + * {@code Class} objects that defines this registry's categories. + * @throws IllegalArgumentException if {@code pCategories} is {@code null}. + * @throws ClassCastException if {@code pCategories} contains anything + * but {@code Class} objects. + */ + public ServiceRegistry(final Iterator> pCategories) { + Validate.notNull(pCategories, "categories"); + + Map, CategoryRegistry> map = new LinkedHashMap, CategoryRegistry>(); + + while (pCategories.hasNext()) { + putCategory(map, pCategories.next()); + } + + // NOTE: Categories are constant for the lifetime of a registry + categoryMap = Collections.unmodifiableMap(map); + } + + private void putCategory(Map, CategoryRegistry> pMap, Class pCategory) { + CategoryRegistry registry = new CategoryRegistry(pCategory); + pMap.put(pCategory, registry); + } + + /** + * Registers all provider implementations for this {@code ServiceRegistry} + * found in the application classpath. + * + * @throws ServiceConfigurationError if an error occurred during registration + */ + public void registerApplicationClasspathSPIs() { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Iterator> categories = categories(); + + while (categories.hasNext()) { + Class category = categories.next(); + + try { + // Find all META-INF/services/ + name on class path + String name = SERVICES + category.getName(); + Enumeration spiResources = loader.getResources(name); + + while (spiResources.hasMoreElements()) { + URL resource = spiResources.nextElement(); + registerSPIs(resource, category, loader); + } + } + catch (IOException e) { + throw new ServiceConfigurationError(e); + } + } + } + + /** + * Registers all SPIs listed in the given resource. + * + * @param pResource the resource to load SPIs from + * @param pCategory the category class + * @param pLoader the class loader to use + */ + void registerSPIs(final URL pResource, final Class pCategory, final ClassLoader pLoader) { + Properties classNames = new Properties(); + + try { + classNames.load(pResource.openStream()); + } + catch (IOException e) { + throw new ServiceConfigurationError(e); + } + + if (!classNames.isEmpty()) { + @SuppressWarnings({"unchecked"}) + CategoryRegistry registry = categoryMap.get(pCategory); + + Set providerClassNames = classNames.keySet(); + + for (Object providerClassName : providerClassNames) { + String className = (String) providerClassName; + try { + @SuppressWarnings({"unchecked"}) + Class providerClass = (Class) Class.forName(className, true, pLoader); + T provider = providerClass.newInstance(); + registry.register(provider); + } + catch (ClassNotFoundException e) { + throw new ServiceConfigurationError(e); + } + catch (IllegalAccessException e) { + throw new ServiceConfigurationError(e); + } + catch (InstantiationException e) { + throw new ServiceConfigurationError(e); + } + catch (IllegalArgumentException e) { + throw new ServiceConfigurationError(e); + } + } + } + } + + /** + * Returns an {@code Iterator} containing all providers in the given + * category. + *

+ * The iterator supports removal. + *

+ *

+ * + * NOTE: Removing a provider from the iterator, deregisters the current + * provider (as returned by the last invocation of {@code next()}) from + * {@code pCategory}, it does not remove the provider + * from other categories in the registry. + * + *

+ * + * @param pCategory the category class + * @return an {@code Iterator} containing all providers in the given + * category. + * @throws IllegalArgumentException if {@code pCategory} is not a valid + * category in this registry + */ + protected Iterator providers(Class pCategory) { + return getRegistry(pCategory).providers(); + } + + /** + * Returns an {@code Iterator} containing all categories in this registry. + *

+ * The iterator does not support removal. + *

+ * + * @return an {@code Iterator} containing all categories in this registry. + */ + protected Iterator> categories() { + return categoryMap.keySet().iterator(); + } + + /** + * Returns an {@code Iterator} containing all categories in this registry + * the given {@code pProvider} may be registered with. + *

+ * The iterator does not support removal. + *

+ * + * @param pProvider the provider instance + * @return an {@code Iterator} containing all categories in this registry + * the given {@code pProvider} may be registered with + */ + protected Iterator> compatibleCategories(final Object pProvider) { + return new FilterIterator>(categories(), + new FilterIterator.Filter>() { + public boolean accept(Class pElement) { + return pElement.isInstance(pProvider); + } + }); + } + + /** + * Returns an {@code Iterator} containing all categories in this registry + * the given {@code pProvider} is currently registered with. + *

+ * The iterator supports removal. + *

+ *

+ * + * NOTE: Removing a category from the iterator, de-registers + * {@code pProvider} from the current category (as returned by the last + * invocation of {@code next()}), it does not remove the category + * itself from the registry. + * + *

+ * + * @param pProvider the provider instance + * @return an {@code Iterator} containing all categories in this registry + * the given {@code pProvider} may be registered with + */ + protected Iterator> containingCategories(final Object pProvider) { + // TODO: Is removal using the iterator really a good idea? + return new FilterIterator>(categories(), + new FilterIterator.Filter>() { + public boolean accept(Class pElement) { + return getRegistry(pElement).contains(pProvider); + } + }) { + Class current; + + public Class next() { + return (current = super.next()); + } + + public void remove() { + if (current == null) { + throw new IllegalStateException("No current element"); + } + + getRegistry(current).deregister(pProvider); + current = null; + } + }; + } + + /** + * Gets the category registry for the given category. + * + * @param pCategory the category class + * @return the {@code CategoryRegistry} for the given category + */ + private CategoryRegistry getRegistry(final Class pCategory) { + @SuppressWarnings({"unchecked"}) + CategoryRegistry registry = categoryMap.get(pCategory); + if (registry == null) { + throw new IllegalArgumentException("No such category: " + pCategory.getName()); + } + return registry; + } + + /** + * Registers the given provider for all categories it matches. + * + * @param pProvider the provider instance + * @return {@code true} if {@code pProvider} is now registered in + * one or more categories it was not registered in before. + * @see #compatibleCategories(Object) + */ + public boolean register(final Object pProvider) { + Iterator> categories = compatibleCategories(pProvider); + boolean registered = false; + while (categories.hasNext()) { + Class category = categories.next(); + if (registerImpl(pProvider, category) && !registered) { + registered = true; + } + } + return registered; + } + + private boolean registerImpl(final Object pProvider, final Class pCategory) { + return getRegistry(pCategory).register(pCategory.cast(pProvider)); + } + + /** + * Registers the given provider for the given category. + * + * @param pProvider the provider instance + * @param pCategory the category class + * @return {@code true} if {@code pProvider} is now registered in + * the given category + */ + public boolean register(final T pProvider, final Class pCategory) { + return registerImpl(pProvider, pCategory); + } + + /** + * De-registers the given provider from all categories it's currently + * registered in. + * + * @param pProvider the provider instance + * @return {@code true} if {@code pProvider} was previously registered in + * any category and is now de-registered. + * @see #containingCategories(Object) + */ + public boolean deregister(final Object pProvider) { + Iterator> categories = containingCategories(pProvider); + + boolean deregistered = false; + while (categories.hasNext()) { + Class category = categories.next(); + if (deregister(pProvider, category) && !deregistered) { + deregistered = true; + } + } + + return deregistered; + } + + /** + * Deregisters the given provider from the given category. + * + * @param pProvider the provider instance + * @param pCategory the category class + * @return {@code true} if {@code pProvider} was previously registered in + * the given category + */ + public boolean deregister(final Object pProvider, final Class pCategory) { + return getRegistry(pCategory).deregister(pProvider); + } + + /** + * Keeps track of each individual category. + */ + class CategoryRegistry { + private final Class category; + private final Map providers = new LinkedHashMap(); + + CategoryRegistry(Class pCategory) { + Validate.notNull(pCategory, "category"); + category = pCategory; + } + + private void checkCategory(final Object pProvider) { + if (!category.isInstance(pProvider)) { + throw new IllegalArgumentException(pProvider + " not instance of category " + category.getName()); + } + } + + public boolean register(final T pProvider) { + checkCategory(pProvider); + + // NOTE: We only register the new instance, if we don't already have an instance of pProvider's class. + if (!contains(pProvider)) { + providers.put(pProvider.getClass(), pProvider); + processRegistration(pProvider); + return true; + } + + return false; + } + + void processRegistration(final T pProvider) { + if (pProvider instanceof RegisterableService) { + RegisterableService service = (RegisterableService) pProvider; + service.onRegistration(ServiceRegistry.this, category); + } + } + + public boolean deregister(final Object pProvider) { + checkCategory(pProvider); + + // NOTE: We remove any provider of the same class, this may or may + // not be the same instance as pProvider. + T oldProvider = providers.remove(pProvider.getClass()); + + if (oldProvider != null) { + processDeregistration(oldProvider); + return true; + } + + return false; + } + + void processDeregistration(final T pOldProvider) { + if (pOldProvider instanceof RegisterableService) { + RegisterableService service = (RegisterableService) pOldProvider; + service.onDeregistration(ServiceRegistry.this, category); + } + } + + public boolean contains(final Object pProvider) { + return providers.containsKey(pProvider != null ? pProvider.getClass() : null); + } + + public Iterator providers() { + // NOTE: The iterator must support removal because deregistering + // using the deregister method will result in + // ConcurrentModificationException in the iterator.. + // We wrap the iterator to track deregistration right. + final Iterator iterator = providers.values().iterator(); + return new Iterator() { + T current; + + public boolean hasNext() { + return iterator.hasNext(); + + } + + public T next() { + return (current = iterator.next()); + } + + public void remove() { + iterator.remove(); + processDeregistration(current); + } + }; + } + } + + @SuppressWarnings({"UnnecessaryFullyQualifiedName"}) + public static void main(String[] pArgs) { + abstract class Spi {} + class One extends Spi {} + class Two extends Spi {} + + ServiceRegistry testRegistry = new ServiceRegistry( + Arrays.>asList( + java.nio.charset.spi.CharsetProvider.class, + java.nio.channels.spi.SelectorProvider.class, + javax.imageio.spi.ImageReaderSpi.class, + javax.imageio.spi.ImageWriterSpi.class, + Spi.class + ).iterator() + ); + + testRegistry.registerApplicationClasspathSPIs(); + + One one = new One(); + Two two = new Two(); + testRegistry.register(one, Spi.class); + testRegistry.register(two, Spi.class); + testRegistry.deregister(one); + testRegistry.deregister(one, Spi.class); + testRegistry.deregister(two, Spi.class); + testRegistry.deregister(two); + + Iterator> categories = testRegistry.categories(); + System.out.println("Categories: "); + while (categories.hasNext()) { + Class category = categories.next(); + System.out.println(" " + category.getName() + ":"); + + Iterator providers = testRegistry.providers(category); + Object provider = null; + while (providers.hasNext()) { + provider = providers.next(); + System.out.println(" " + provider); + if (provider instanceof javax.imageio.spi.ImageReaderWriterSpi) { + System.out.println(" - " + ((javax.imageio.spi.ImageReaderWriterSpi) provider).getDescription(null)); + } + // javax.imageio.spi.ImageReaderWriterSpi provider = (javax.imageio.spi.ImageReaderWriterSpi) providers.next(); + // System.out.println(" " + provider); + // System.out.println(" " + provider.getVendorName()); + // System.out.println(" Formats:"); + // + // System.out.print(" "); + // String[] formatNames = provider.getFormatNames(); + // for (int i = 0; i < formatNames.length; i++) { + // if (i != 0) { + // System.out.print(", "); + // } + // System.out.print(formatNames[i]); + // } + // System.out.println(); + + // Don't remove last one, it's removed later to exercise more code :-) + if (providers.hasNext()) { + providers.remove(); + } + } + + // Remove the last item from all categories + if (provider != null) { + Iterator containers = testRegistry.containingCategories(provider); + int count = 0; + while (containers.hasNext()) { + if (category == containers.next()) { + containers.remove(); + count++; + } + } + + if (count != 1) { + System.err.println("Removed " + provider + " from " + count + " categories"); + } + } + + // Remove all using providers iterator + providers = testRegistry.providers(category); + if (!providers.hasNext()) { + System.out.println("All providers successfully deregistered"); + } + while (providers.hasNext()) { + System.err.println("Not removed: " + providers.next()); + } + } + } + + //*/ +} diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/package_info.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/package_info.java index ca790a2c..78925b54 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/service/package_info.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/service/package_info.java @@ -1,9 +1,40 @@ -/** - * Provides a service provider registry. - *

- * This package contains a service provider registry, as specified in the - * JAR File Specification. - * - * @see JAR File Specification - */ -package com.twelvemonkeys.util.service; +/* + * 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. + */ + +/** + * Provides a service provider registry. + *

+ * This package contains a service provider registry, as specified in the + * JAR File Specification. + *

+ * + * @see JAR File Specification + */ +package com.twelvemonkeys.util.service; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTest.java similarity index 77% rename from common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTest.java index 59e69b97..b256a68d 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTest.java @@ -1,235 +1,274 @@ -package com.twelvemonkeys.lang; - -import junit.framework.TestCase; - -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; - -/** - * BeanUtilTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java#1 $ - */ -public class BeanUtilTestCase extends TestCase { - - public void testConfigureNoMehtod() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - map.put("noSuchMethod", "jaffa"); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - } - - public void testConfigureNoMethodArgs() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - map.put("doubleValue", new Object()); // Should not be able to convert this - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertNull(bean.getDoubleValue()); - - } - - public void testConfigureNullValue() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - map.put("stringValue", null); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertNull(bean.getStringValue()); - } - - public void testConfigureSimple() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - map.put("stringValue", "one"); - map.put("intValue", 2); - map.put("doubleValue", .3); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertEquals("one", bean.getStringValue()); - assertEquals(2, bean.getIntValue()); - assertEquals(.3, bean.getDoubleValue()); - } - - public void testConfigureConvert() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - map.put("stringValue", 1); - map.put("intValue", "2"); - map.put("doubleValue", ".3"); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertEquals("1", bean.getStringValue()); - assertEquals(2, bean.getIntValue()); - assertEquals(0.3, bean.getDoubleValue()); - } - - public void testConfigureAmbiguous1() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - String value = "one"; - map.put("ambiguous", value); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertNotNull(bean.getAmbiguous()); - assertEquals("String converted rather than invoking setAmbiguous(String), ordering not predictable", - "one", bean.getAmbiguous()); - assertSame("String converted rather than invoking setAmbiguous(String), ordering not predictable", - value, bean.getAmbiguous()); - } - - public void testConfigureAmbiguous2() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - Integer value = 2; - map.put("ambiguous", value); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertNotNull(bean.getAmbiguous()); - assertEquals("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable", - 2, bean.getAmbiguous()); - assertSame("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable", - value, bean.getAmbiguous()); - } - - public void testConfigureAmbiguous3() { - TestBean bean = new TestBean(); - - Map map = new HashMap(); - - Double value = .3; - map.put("ambiguous", value); - - try { - BeanUtil.configure(bean, map); - } - catch (InvocationTargetException e) { - fail(e.getMessage()); - } - - assertNotNull(bean.getAmbiguous()); - assertEquals("Object converted rather than invoking setAmbiguous(Object), ordering not predictable", - value.getClass(), bean.getAmbiguous().getClass()); - assertSame("Object converted rather than invoking setAmbiguous(Object), ordering not predictable", - value, bean.getAmbiguous()); - } - - static class TestBean { - private String stringVal; - private int intVal; - private Double doubleVal; - - private Object ambiguous; - - public Double getDoubleValue() { - return doubleVal; - } - - public int getIntValue() { - return intVal; - } - - public String getStringValue() { - return stringVal; - } - - @SuppressWarnings("UnusedDeclaration") - public void setStringValue(String pString) { - stringVal = pString; - } - - @SuppressWarnings("UnusedDeclaration") - public void setIntValue(int pInt) { - intVal = pInt; - } - - @SuppressWarnings("UnusedDeclaration") - public void setDoubleValue(Double pDouble) { - doubleVal = pDouble; - } - - @SuppressWarnings("UnusedDeclaration") - public void setAmbiguous(String pString) { - ambiguous = pString; - } - - @SuppressWarnings("UnusedDeclaration") - public void setAmbiguous(Object pObject) { - ambiguous = pObject; - } - - @SuppressWarnings("UnusedDeclaration") - public void setAmbiguous(Integer pInteger) { - ambiguous = pInteger; - } - - @SuppressWarnings("UnusedDeclaration") - public void setAmbiguous(int pInt) { - ambiguous = (long) pInt; // Just to differentiate... - } - - public Object getAmbiguous() { - return ambiguous; - } - } -} +/* + * 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.lang; + +import org.junit.Test; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * BeanUtilTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java#1 $ + */ +public class BeanUtilTest { + + @Test + public void testConfigureNoMehtod() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + map.put("noSuchMethod", "jaffa"); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + } + + @Test + public void testConfigureNoMethodArgs() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + map.put("doubleValue", new Object()); // Should not be able to convert this + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertNull(bean.getDoubleValue()); + + } + + @Test + public void testConfigureNullValue() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + map.put("stringValue", null); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertNull(bean.getStringValue()); + } + + public void testConfigureSimple() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + map.put("stringValue", "one"); + map.put("intValue", 2); + map.put("doubleValue", .3); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertEquals("one", bean.getStringValue()); + assertEquals(2, bean.getIntValue()); + assertEquals(.3, bean.getDoubleValue(), 0); + } + + @Test + public void testConfigureConvert() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + map.put("stringValue", 1); + map.put("intValue", "2"); + map.put("doubleValue", ".3"); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertEquals("1", bean.getStringValue()); + assertEquals(2, bean.getIntValue()); + assertEquals(0.3, bean.getDoubleValue(), 0); + } + + @Test + public void testConfigureAmbiguous1() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + String value = "one"; + map.put("ambiguous", value); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertNotNull(bean.getAmbiguous()); + assertEquals("String converted rather than invoking setAmbiguous(String), ordering not predictable", + "one", bean.getAmbiguous()); + assertSame("String converted rather than invoking setAmbiguous(String), ordering not predictable", + value, bean.getAmbiguous()); + } + + @Test + public void testConfigureAmbiguous2() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + Integer value = 2; + map.put("ambiguous", value); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertNotNull(bean.getAmbiguous()); + assertEquals("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable", + 2, bean.getAmbiguous()); + assertSame("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable", + value, bean.getAmbiguous()); + } + + @Test + public void testConfigureAmbiguous3() { + TestBean bean = new TestBean(); + + Map map = new HashMap(); + + Double value = .3; + map.put("ambiguous", value); + + try { + BeanUtil.configure(bean, map); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + + assertNotNull(bean.getAmbiguous()); + assertEquals("Object converted rather than invoking setAmbiguous(Object), ordering not predictable", + value.getClass(), bean.getAmbiguous().getClass()); + assertSame("Object converted rather than invoking setAmbiguous(Object), ordering not predictable", + value, bean.getAmbiguous()); + } + + static class TestBean { + private String stringVal; + private int intVal; + private Double doubleVal; + + private Object ambiguous; + + public Double getDoubleValue() { + return doubleVal; + } + + public int getIntValue() { + return intVal; + } + + public String getStringValue() { + return stringVal; + } + + @SuppressWarnings("UnusedDeclaration") + public void setStringValue(String pString) { + stringVal = pString; + } + + @SuppressWarnings("UnusedDeclaration") + public void setIntValue(int pInt) { + intVal = pInt; + } + + @SuppressWarnings("UnusedDeclaration") + public void setDoubleValue(Double pDouble) { + doubleVal = pDouble; + } + + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(String pString) { + ambiguous = pString; + } + + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(Object pObject) { + ambiguous = pObject; + } + + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(Integer pInteger) { + ambiguous = pInteger; + } + + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(int pInt) { + ambiguous = (long) pInt; // Just to differentiate... + } + + public Object getAmbiguous() { + return ambiguous; + } + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/DateUtilTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/DateUtilTest.java index 6f4959e1..0dde4b48 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/DateUtilTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/DateUtilTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.lang; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTest.java similarity index 86% rename from common/common-lang/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTest.java index 6ed3f622..4b5397c7 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTest.java @@ -1,309 +1,339 @@ -package com.twelvemonkeys.lang; - -import org.junit.Test; - -import java.io.*; -import java.lang.reflect.Method; - -import static org.junit.Assert.*; - -/** - * AbstractObjectTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTestCase.java#1 $ - */ -public abstract class ObjectAbstractTestCase { - // TODO: See com.tm.util.ObjectAbstractTestCase - // TODO: The idea is that this should be some generic base-class that - // implements the basic object tests - - // TODO: Create Serializable test similar way - // TODO: Create Comparable test similar way - - /** - * Returns an instance of the class we are testing. - * Implement this method to return the object to test. - * - * @return the object to test - */ - protected abstract Object makeObject(); - - // TODO: Can we really do serious testing with just one object? - // TODO: How can we make sure we create equal or different objects?! - //protected abstract Object makeDifferentObject(Object pObject); - //protected abstract Object makeEqualObject(Object pObject); - - - @Test - public void testToString() { - assertNotNull(makeObject().toString()); - // TODO: What more can we test? - } - - // TODO: assert that either BOTH or NONE of equals/hashcode is overridden - @Test - public void testEqualsHashCode(){ - Object obj = makeObject(); - - Class cl = obj.getClass(); - if (isEqualsOverriden(cl)) { - assertTrue("Class " + cl.getName() + " implements equals but not hashCode", isHashCodeOverriden(cl)); - } - else if (isHashCodeOverriden(cl)) { - assertTrue("Class " + cl.getName() + " implements hashCode but not equals", isEqualsOverriden(cl)); - } - - } - - protected static boolean isEqualsOverriden(Class pClass) { - return getDeclaredMethod(pClass, "equals", new Class[]{Object.class}) != null; - } - - protected static boolean isHashCodeOverriden(Class pClass) { - return getDeclaredMethod(pClass, "hashCode", null) != null; - } - - private static Method getDeclaredMethod(Class pClass, String pName, Class[] pArameters) { - try { - return pClass.getDeclaredMethod(pName, pArameters); - } - catch (NoSuchMethodException ignore) { - return null; - } - } - - @Test - public void testObjectEqualsSelf() { - Object obj = makeObject(); - assertEquals("An Object should equal itself", obj, obj); - } - - @Test - public void testEqualsNull() { - Object obj = makeObject(); - // NOTE: Makes sure this doesn't throw NPE either - //noinspection ObjectEqualsNull - assertFalse("An object should never equal null", obj.equals(null)); - } - - @Test - public void testObjectHashCodeEqualsSelfHashCode() { - Object obj = makeObject(); - assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode()); - } - - @Test - public void testObjectHashCodeEqualsContract() { - Object obj1 = makeObject(); - if (obj1.equals(obj1)) { - assertEquals( - "[1] When two objects are equal, their hashCodes should be also.", - obj1.hashCode(), obj1.hashCode()); - } - // TODO: Make sure we create at least one equal object, and one different object - Object obj2 = makeObject(); - if (obj1.equals(obj2)) { - assertEquals( - "[2] When two objects are equal, their hashCodes should be also.", - obj1.hashCode(), obj2.hashCode()); - assertTrue( - "When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true", - obj2.equals(obj1)); - } - } - - /* - public void testFinalize() { - // TODO: Implement - } - */ - - //////////////////////////////////////////////////////////////////////////// - // Cloneable interface - @Test - public void testClone() throws Exception { - Object obj = makeObject(); - if (obj instanceof Cloneable) { - Class cl = obj.getClass(); - - Method clone = findMethod(cl, "clone"); - - // Disregard protected modifier - // NOTE: This will throw a SecurityException if a SecurityManager - // disallows access, but should not happen in a test context - if (!clone.isAccessible()) { - clone.setAccessible(true); - } - - Object cloned = clone.invoke(obj); - - assertNotNull("Cloned object should never be null", cloned); - - // TODO: This can only be asserted if equals() test is based on - // value equality, not reference (identity) equality - // Maybe it's possible to do a reflective introspection of - // the objects fields? - if (isHashCodeOverriden(cl)) { - assertEquals("Cloned object not equal", obj, cloned); - } - } - } - - private static Method findMethod(Class pClass, String pName) throws NoSuchMethodException { - if (pClass == null) { - throw new IllegalArgumentException("class == null"); - } - if (pName == null) { - throw new IllegalArgumentException("name == null"); - } - - Class cl = pClass; - - while (cl != null) { - try { - return cl.getDeclaredMethod(pName, new Class[0]); - } - catch (NoSuchMethodException e) { - } - catch (SecurityException e) { - } - - cl = cl.getSuperclass(); - } - - throw new NoSuchMethodException(pName + " in class " + pClass.getName()); - } - - /////////////////////////////////////////////////////////////////////////// - // Serializable interface - @Test - public void testSerializeDeserializeThenCompare() throws Exception { - Object obj = makeObject(); - if (obj instanceof Serializable) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(buffer); - try { - out.writeObject(obj); - } - finally { - out.close(); - } - - Object dest; - ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); - try { - dest = in.readObject(); - } - finally { - in.close(); - } - - // TODO: This can only be asserted if equals() test is based on - // value equality, not reference (identity) equality - // Maybe it's possible to do a reflective introspection of - // the objects fields? - if (isEqualsOverriden(obj.getClass())) { - assertEquals("obj != deserialize(serialize(obj))", obj, dest); - } - } - } - - /** - * Sanity check method, makes sure that any {@code Serializable} - * class can be serialized and de-serialized in memory, - * using the handy makeObject() method - * - * @throws java.io.IOException - * @throws ClassNotFoundException - */ - @Test - public void testSimpleSerialization() throws Exception { - Object o = makeObject(); - if (o instanceof Serializable) { - byte[] object = writeExternalFormToBytes((Serializable) o); - readExternalFormFromBytes(object); - } - } - - /** - * Write a Serializable or Externalizable object as - * a file at the given path. - * NOT USEFUL as part - * of a unit test; this is just a utility method - * for creating disk-based objects in CVS that can become - * the basis for compatibility tests using - * readExternalFormFromDisk(String path) - * - * @param o Object to serialize - * @param path path to write the serialized Object - * @exception java.io.IOException - */ - protected void writeExternalFormToDisk(Serializable o, String path) throws IOException { - FileOutputStream fileStream = new FileOutputStream(path); - writeExternalFormToStream(o, fileStream); - } - - /** - * Converts a Serializable or Externalizable object to - * bytes. Useful for in-memory tests of serialization - * - * @param o Object to convert to bytes - * @return serialized form of the Object - * @exception java.io.IOException - */ - protected byte[] writeExternalFormToBytes(Serializable o) throws IOException { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - writeExternalFormToStream(o, byteStream); - return byteStream.toByteArray(); - } - - /** - * Reads a Serialized or Externalized Object from disk. - * Useful for creating compatibility tests between - * different CVS versions of the same class - * - * @param path path to the serialized Object - * @return the Object at the given path - * @exception java.io.IOException - * @exception ClassNotFoundException - */ - protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException { - FileInputStream stream = new FileInputStream(path); - return readExternalFormFromStream(stream); - } - - /** - * Read a Serialized or Externalized Object from bytes. - * Useful for verifying serialization in memory. - * - * @param b byte array containing a serialized Object - * @return Object contained in the bytes - * @exception java.io.IOException - * @exception ClassNotFoundException - */ - protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException { - ByteArrayInputStream stream = new ByteArrayInputStream(b); - return readExternalFormFromStream(stream); - } - - // private implementation - //----------------------------------------------------------------------- - private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException { - ObjectInputStream oStream = new ObjectInputStream(stream); - return oStream.readObject(); - } - - private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException { - ObjectOutputStream oStream = new ObjectOutputStream(stream); - oStream.writeObject(o); - } - - public static final class SanityTestTestCase extends ObjectAbstractTestCase { - protected Object makeObject() { - return new Cloneable() {}; - } - } - -} +/* + * 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.lang; + +import org.junit.Test; + +import java.io.*; +import java.lang.reflect.Method; + +import static org.junit.Assert.*; + +/** + * AbstractObjectTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTestCase.java#1 $ + */ +public abstract class ObjectAbstractTest { + // TODO: See com.tm.util.ObjectAbstractTestCase + // TODO: The idea is that this should be some generic base-class that + // implements the basic object tests + + // TODO: Create Serializable test similar way + // TODO: Create Comparable test similar way + + /** + * Returns an instance of the class we are testing. + * Implement this method to return the object to test. + * + * @return the object to test + */ + protected abstract Object makeObject(); + + // TODO: Can we really do serious testing with just one object? + // TODO: How can we make sure we create equal or different objects?! + //protected abstract Object makeDifferentObject(Object pObject); + //protected abstract Object makeEqualObject(Object pObject); + + + @Test + public void testToString() { + assertNotNull(makeObject().toString()); + // TODO: What more can we test? + } + + // TODO: assert that either BOTH or NONE of equals/hashcode is overridden + @Test + public void testEqualsHashCode(){ + Object obj = makeObject(); + + Class cl = obj.getClass(); + if (isEqualsOverriden(cl)) { + assertTrue("Class " + cl.getName() + " implements equals but not hashCode", isHashCodeOverriden(cl)); + } + else if (isHashCodeOverriden(cl)) { + assertTrue("Class " + cl.getName() + " implements hashCode but not equals", isEqualsOverriden(cl)); + } + + } + + protected static boolean isEqualsOverriden(Class pClass) { + return getDeclaredMethod(pClass, "equals", new Class[]{Object.class}) != null; + } + + protected static boolean isHashCodeOverriden(Class pClass) { + return getDeclaredMethod(pClass, "hashCode", null) != null; + } + + private static Method getDeclaredMethod(Class pClass, String pName, Class[] pArameters) { + try { + return pClass.getDeclaredMethod(pName, pArameters); + } + catch (NoSuchMethodException ignore) { + return null; + } + } + + @Test + public void testObjectEqualsSelf() { + Object obj = makeObject(); + assertEquals("An Object should equal itself", obj, obj); + } + + @Test + public void testEqualsNull() { + Object obj = makeObject(); + // NOTE: Makes sure this doesn't throw NPE either + //noinspection ObjectEqualsNull + assertFalse("An object should never equal null", obj.equals(null)); + } + + @Test + public void testObjectHashCodeEqualsSelfHashCode() { + Object obj = makeObject(); + assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode()); + } + + @Test + public void testObjectHashCodeEqualsContract() { + Object obj1 = makeObject(); + if (obj1.equals(obj1)) { + assertEquals( + "[1] When two objects are equal, their hashCodes should be also.", + obj1.hashCode(), obj1.hashCode()); + } + // TODO: Make sure we create at least one equal object, and one different object + Object obj2 = makeObject(); + if (obj1.equals(obj2)) { + assertEquals( + "[2] When two objects are equal, their hashCodes should be also.", + obj1.hashCode(), obj2.hashCode()); + assertTrue( + "When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true", + obj2.equals(obj1)); + } + } + + /* + public void testFinalize() { + // TODO: Implement + } + */ + + //////////////////////////////////////////////////////////////////////////// + // Cloneable interface + @Test + public void testClone() throws Exception { + Object obj = makeObject(); + if (obj instanceof Cloneable) { + Class cl = obj.getClass(); + + Method clone = findMethod(cl, "clone"); + + // Disregard protected modifier + // NOTE: This will throw a SecurityException if a SecurityManager + // disallows access, but should not happen in a test context + if (!clone.isAccessible()) { + clone.setAccessible(true); + } + + Object cloned = clone.invoke(obj); + + assertNotNull("Cloned object should never be null", cloned); + + // TODO: This can only be asserted if equals() test is based on + // value equality, not reference (identity) equality + // Maybe it's possible to do a reflective introspection of + // the objects fields? + if (isHashCodeOverriden(cl)) { + assertEquals("Cloned object not equal", obj, cloned); + } + } + } + + private static Method findMethod(Class pClass, String pName) throws NoSuchMethodException { + if (pClass == null) { + throw new IllegalArgumentException("class == null"); + } + if (pName == null) { + throw new IllegalArgumentException("name == null"); + } + + Class cl = pClass; + + while (cl != null) { + try { + return cl.getDeclaredMethod(pName, new Class[0]); + } + catch (NoSuchMethodException e) { + } + catch (SecurityException e) { + } + + cl = cl.getSuperclass(); + } + + throw new NoSuchMethodException(pName + " in class " + pClass.getName()); + } + + /////////////////////////////////////////////////////////////////////////// + // Serializable interface + @Test + public void testSerializeDeserializeThenCompare() throws Exception { + Object obj = makeObject(); + if (obj instanceof Serializable) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(buffer); + try { + out.writeObject(obj); + } + finally { + out.close(); + } + + Object dest; + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); + try { + dest = in.readObject(); + } + finally { + in.close(); + } + + // TODO: This can only be asserted if equals() test is based on + // value equality, not reference (identity) equality + // Maybe it's possible to do a reflective introspection of + // the objects fields? + if (isEqualsOverriden(obj.getClass())) { + assertEquals("obj != deserialize(serialize(obj))", obj, dest); + } + } + } + + /** + * Sanity check method, makes sure that any {@code Serializable} + * class can be serialized and de-serialized in memory, + * using the handy makeObject() method + * + * @throws java.io.IOException + * @throws ClassNotFoundException + */ + @Test + public void testSimpleSerialization() throws Exception { + Object o = makeObject(); + if (o instanceof Serializable) { + byte[] object = writeExternalFormToBytes((Serializable) o); + readExternalFormFromBytes(object); + } + } + + /** + * Write a Serializable or Externalizable object as + * a file at the given path. + * NOT USEFUL as part + * of a unit test; this is just a utility method + * for creating disk-based objects in CVS that can become + * the basis for compatibility tests using + * readExternalFormFromDisk(String path) + * + * @param o Object to serialize + * @param path path to write the serialized Object + * @exception java.io.IOException + */ + protected void writeExternalFormToDisk(Serializable o, String path) throws IOException { + FileOutputStream fileStream = new FileOutputStream(path); + writeExternalFormToStream(o, fileStream); + } + + /** + * Converts a Serializable or Externalizable object to + * bytes. Useful for in-memory tests of serialization + * + * @param o Object to convert to bytes + * @return serialized form of the Object + * @exception java.io.IOException + */ + protected byte[] writeExternalFormToBytes(Serializable o) throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + writeExternalFormToStream(o, byteStream); + return byteStream.toByteArray(); + } + + /** + * Reads a Serialized or Externalized Object from disk. + * Useful for creating compatibility tests between + * different CVS versions of the same class + * + * @param path path to the serialized Object + * @return the Object at the given path + * @exception java.io.IOException + * @exception ClassNotFoundException + */ + protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException { + FileInputStream stream = new FileInputStream(path); + return readExternalFormFromStream(stream); + } + + /** + * Read a Serialized or Externalized Object from bytes. + * Useful for verifying serialization in memory. + * + * @param b byte array containing a serialized Object + * @return Object contained in the bytes + * @exception java.io.IOException + * @exception ClassNotFoundException + */ + protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException { + ByteArrayInputStream stream = new ByteArrayInputStream(b); + return readExternalFormFromStream(stream); + } + + // private implementation + //----------------------------------------------------------------------- + private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException { + ObjectInputStream oStream = new ObjectInputStream(stream); + return oStream.readObject(); + } + + private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException { + ObjectOutputStream oStream = new ObjectOutputStream(stream); + oStream.writeObject(o); + } + + public static final class SanityTestTest extends ObjectAbstractTest { + protected Object makeObject() { + return new Cloneable() {}; + } + } + +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/PlatformTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/PlatformTest.java index ed1aa5e7..724cef40 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/PlatformTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/PlatformTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.lang; @@ -33,7 +35,8 @@ import org.junit.Test; import java.util.Properties; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; /** * PlatformTest diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/StringUtilTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/StringUtilTest.java similarity index 92% rename from common/common-lang/src/test/java/com/twelvemonkeys/lang/StringUtilTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/lang/StringUtilTest.java index c889f559..923cffd0 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/StringUtilTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/StringUtilTest.java @@ -1,858 +1,948 @@ - -package com.twelvemonkeys.lang; - -import junit.framework.TestCase; - -import java.awt.*; -import java.util.Date; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.sql.Timestamp; - -/** - * StringUtilTestCase - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/StringUtilTestCase.java#1 $ - * - */ -public class StringUtilTestCase extends TestCase { - final static Object TEST_OBJECT = new Object(); - final static Integer TEST_INTEGER = 42; - final static String TEST_STRING = "TheQuickBrownFox"; // No WS! - final static String TEST_SUB_STRING = TEST_STRING.substring(2, 5); - final static String TEST_DELIM_STRING = "one,two, three\n four\tfive six"; - final static String[] STRING_ARRAY = {"one", "two", "three", "four", "five", "six"}; - final static String TEST_INT_DELIM_STRING = "1,2, 3\n 4\t5 6"; - final static int[] INT_ARRAY = {1, 2, 3, 4, 5, 6}; - final static String TEST_DOUBLE_DELIM_STRING = "1.4,2.1, 3\n .4\t-5 6e5"; - final static double[] DOUBLE_ARRAY = {1.4, 2.1, 3, .4, -5, 6e5}; - final static String EMPTY_STRING = ""; - final static String WHITESPACE_STRING = " \t \r \n "; - - public void testValueOfObject() { - - assertNotNull(StringUtil.valueOf(TEST_OBJECT)); - assertEquals(StringUtil.valueOf(TEST_OBJECT), TEST_OBJECT.toString()); - assertEquals(StringUtil.valueOf(TEST_INTEGER), TEST_INTEGER.toString()); - assertEquals(StringUtil.valueOf(TEST_STRING), TEST_STRING); - assertSame(StringUtil.valueOf(TEST_STRING), TEST_STRING); - - assertNull(StringUtil.valueOf(null)); - } - - public void testToUpperCase() { - String str = StringUtil.toUpperCase(TEST_STRING); - assertNotNull(str); - assertEquals(TEST_STRING.toUpperCase(), str); - - str = StringUtil.toUpperCase(null); - assertNull(str); - } - - public void testToLowerCase() { - String str = StringUtil.toLowerCase(TEST_STRING); - assertNotNull(str); - assertEquals(TEST_STRING.toLowerCase(), str); - - str = StringUtil.toLowerCase(null); - assertNull(str); - } - - public void testIsEmpty() { - assertTrue(StringUtil.isEmpty((String) null)); - assertTrue(StringUtil.isEmpty(EMPTY_STRING)); - assertTrue(StringUtil.isEmpty(WHITESPACE_STRING)); - assertFalse(StringUtil.isEmpty(TEST_STRING)); - } - - public void testIsEmptyArray() { - assertTrue(StringUtil.isEmpty((String[]) null)); - assertTrue(StringUtil.isEmpty(new String[]{EMPTY_STRING})); - assertTrue(StringUtil.isEmpty(new String[]{EMPTY_STRING, WHITESPACE_STRING})); - assertFalse(StringUtil.isEmpty(new String[]{EMPTY_STRING, TEST_STRING})); - assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING})); - } - - public void testContains() { - assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING)); - assertTrue(StringUtil.contains(TEST_STRING, TEST_SUB_STRING)); - assertTrue(StringUtil.contains(TEST_STRING, EMPTY_STRING)); - assertFalse(StringUtil.contains(TEST_STRING, WHITESPACE_STRING)); - assertFalse(StringUtil.contains(TEST_SUB_STRING, TEST_STRING)); - assertFalse(StringUtil.contains(EMPTY_STRING, TEST_STRING)); - assertFalse(StringUtil.contains(WHITESPACE_STRING, TEST_STRING)); - assertFalse(StringUtil.contains(null, TEST_STRING)); - assertFalse(StringUtil.contains(null, null)); - } - - public void testContainsIgnoreCase() { - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING)); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING)); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING)); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase())); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING)); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING)); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING)); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase())); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, EMPTY_STRING)); - assertFalse(StringUtil.containsIgnoreCase(TEST_STRING, WHITESPACE_STRING)); - assertFalse(StringUtil.containsIgnoreCase(TEST_SUB_STRING, TEST_STRING)); - assertFalse(StringUtil.containsIgnoreCase(EMPTY_STRING, TEST_STRING)); - assertFalse(StringUtil.containsIgnoreCase(WHITESPACE_STRING, TEST_STRING)); - assertFalse(StringUtil.containsIgnoreCase(null, TEST_STRING)); - assertFalse(StringUtil.containsIgnoreCase(null, null)); - } - - public void testContainsChar() { - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING.charAt(i))); - assertFalse(StringUtil.contains(EMPTY_STRING, TEST_STRING.charAt(i))); - assertFalse(StringUtil.contains(WHITESPACE_STRING, TEST_STRING.charAt(i))); - assertFalse(StringUtil.contains(null, TEST_STRING.charAt(i))); - } - for (int i = 0; i < TEST_SUB_STRING.length(); i++) { - assertTrue(StringUtil.contains(TEST_STRING, TEST_SUB_STRING.charAt(i))); - } - for (int i = 0; i < WHITESPACE_STRING.length(); i++) { - assertFalse(StringUtil.contains(TEST_STRING, WHITESPACE_STRING.charAt(i))); - } - - // Test all alpha-chars - for (int i = 'a'; i < 'z'; i++) { - if (TEST_STRING.indexOf(i) < 0) { - assertFalse(TEST_STRING + " seems to contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.contains(TEST_STRING, i)); - } - else { - assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.contains(TEST_STRING, i)); - } - } - } - - public void testContainsIgnoreCaseChar() { - // Must contain all chars in string - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING.charAt(i))); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i))); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i))); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)))); - assertFalse(StringUtil.containsIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i))); - assertFalse(StringUtil.containsIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i))); - assertFalse(StringUtil.containsIgnoreCase(null, TEST_STRING.charAt(i))); - } - for (int i = 0; i < TEST_SUB_STRING.length(); i++) { - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i))); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i))); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i))); - assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i))); - } - - for (int i = 0; i < WHITESPACE_STRING.length(); i++) { - assertFalse(StringUtil.containsIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i))); - } - - // Test all alpha-chars - for (int i = 'a'; i < 'z'; i++) { - if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { - assertFalse(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), StringUtil.containsIgnoreCase(TEST_STRING, i)); - } - else { - assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.containsIgnoreCase(TEST_STRING, i)); - } - } - } - - public void testIndexOfIgnoreCase() { - assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING)); - assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING)); - assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING)); - assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase())); - assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase())); - - for (int i = 1; i < TEST_STRING.length(); i++) { - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i))); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i))); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i))); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i))); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i))); - } - - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase())); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase())); - - for (int i = 1; i < TEST_STRING.length(); i++) { - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); - } - - assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(null, null)); - } - - public void testIndexOfIgnoreCasePos() { - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING, 1)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING, 2)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING, 3)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), 4)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase(), 5)); - - for (int i = 1; i < TEST_STRING.length(); i++) { - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i), i - 1)); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i), i - 1)); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i), i - 1)); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i), i - 1)); - assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i), i - 1)); - } - - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING, 1)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING, 1)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING, 2)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase(), 1)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase(), 2)); - - assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING, 234)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(null, null, -45)); - } - - public void testLastIndexOfIgnoreCase() { - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase())); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase())); - - for (int i = 1; i < TEST_STRING.length(); i++) { - assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i))); - assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i))); - assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i))); - assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i))); - assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i))); - } - - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase())); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase())); - - for (int i = 1; i < TEST_STRING.length(); i++) { - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); - } - - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING)); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, null)); - - } - - public void testLastIndexOfIgnoreCasePos() { - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING, 1)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING, 2)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING, 3)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), 4)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase(), 5)); - - for (int i = 1; i < TEST_STRING.length(); i++) { - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(0, i), i - 1)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(0, i), i - 1)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(0, i), i - 1)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(0, i), i - 1)); - assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(0, i), i - 1)); - } - - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING, TEST_SUB_STRING.length() + 3)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING, TEST_SUB_STRING.length() + 3)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING, TEST_SUB_STRING.length() + 4)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase(), TEST_SUB_STRING.length() + 3)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase(), TEST_SUB_STRING.length() + 4)); - - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING, 234)); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, null, -45)); - - } - - public void testIndexOfIgnoreCaseChar() { - // Must contain all chars in string - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i))); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i))); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i))); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)))); - assertEquals(-1, StringUtil.indexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i))); - assertEquals(-1, StringUtil.indexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i))); - assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING.charAt(i))); - } - - for (int i = 0; i < TEST_SUB_STRING.length(); i++) { - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i))); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i))); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i))); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i))); - } - - for (int i = 0; i < WHITESPACE_STRING.length(); i++) { - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i))); - } - - // Test all alpha-chars - for (int i = 'a'; i < 'z'; i++) { - if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { - assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.indexOfIgnoreCase(TEST_STRING, i)); - } - else { - assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, i)); - } - } - } - - public void testIndexOfIgnoreCaseCharPos() { - // Must contain all chars in string - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)), i)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i), i)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i), i)); - assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING.charAt(i), i)); - } - - for (int i = 0; i < TEST_SUB_STRING.length(); i++) { - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i), i)); - } - - for (int i = 0; i < WHITESPACE_STRING.length(); i++) { - assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i), i)); - } - - // Test all alpha-chars - for (int i = 'a'; i < 'z'; i++) { - if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { - assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.indexOfIgnoreCase(TEST_STRING, i, 0)); - } - else { - assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, i, 0)); - } - } - } - - public void testLastIndexOfIgnoreCaseChar() { - // Must contain all chars in string - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i))); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i))); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i))); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)))); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i))); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i))); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING.charAt(i))); - } - - for (int i = 0; i < TEST_SUB_STRING.length(); i++) { - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i))); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i))); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i))); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i))); - } - - for (int i = 0; i < WHITESPACE_STRING.length(); i++) { - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i))); - } - - // Test all alpha-chars - for (int i = 'a'; i < 'z'; i++) { - if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { - assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i)); - } - else { - assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i)); - } - } - } - - public void testLastIndexOfIgnoreCaseCharPos() { - // Must contain all chars in string - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i), i)); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)), i)); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i), i)); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i), i)); - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING.charAt(i), i)); - } - - for (int i = 0; i < TEST_SUB_STRING.length(); i++) { - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i), TEST_STRING.length())); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i), TEST_STRING.length())); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i), TEST_STRING.length())); - assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i), TEST_STRING.length())); - } - - for (int i = 0; i < WHITESPACE_STRING.length(); i++) { - assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i), TEST_STRING.length())); - } - - // Test all alpha-chars - for (int i = 'a'; i < 'z'; i++) { - if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { - assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i, TEST_STRING.length())); - } - else { - assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i, TEST_STRING.length())); - } - } - } - - public void testLtrim() { - assertEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING)); - assertEquals(TEST_STRING, StringUtil.ltrim(" " + TEST_STRING)); - assertEquals(TEST_STRING, StringUtil.ltrim(WHITESPACE_STRING + TEST_STRING)); - assertFalse(TEST_STRING.equals(StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING))); - // TODO: Test is not complete - } - - public void testRtrim() { - assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING)); - assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + " ")); - assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + WHITESPACE_STRING)); - assertFalse(TEST_STRING.equals(StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING))); - // TODO: Test is not complete - } - - public void testReplace() { - assertEquals("", StringUtil.replace(TEST_STRING, TEST_STRING, "")); - assertEquals("", StringUtil.replace("", "", "")); - assertEquals("", StringUtil.replace("", "xyzzy", "xyzzy")); - assertEquals(TEST_STRING, StringUtil.replace(TEST_STRING, "", "xyzzy")); - assertEquals("aabbdd", StringUtil.replace("aabbccdd", "c", "")); - assertEquals("aabbccdd", StringUtil.replace("aabbdd", "bd", "bccd")); - // TODO: Test is not complete - } - - public void testReplaceIgnoreCase() { - assertEquals("", StringUtil.replaceIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), "")); - assertEquals("", StringUtil.replaceIgnoreCase("", "", "")); - assertEquals("", StringUtil.replaceIgnoreCase("", "xyzzy", "xyzzy")); - assertEquals(TEST_STRING, StringUtil.replaceIgnoreCase(TEST_STRING, "", "xyzzy")); - assertEquals("aabbdd", StringUtil.replaceIgnoreCase("aabbCCdd", "c", "")); - assertEquals("aabbdd", StringUtil.replaceIgnoreCase("aabbccdd", "C", "")); - assertEquals("aabbccdd", StringUtil.replaceIgnoreCase("aabbdd", "BD", "bccd")); - assertEquals("aabbccdd", StringUtil.replaceIgnoreCase("aabBDd", "bd", "bccd")); - // TODO: Test is not complete - } - - public void testCut() { - assertEquals(TEST_STRING, StringUtil.cut(TEST_STRING, TEST_STRING.length(), "..")); - assertEquals("This is a test..", StringUtil.cut("This is a test of how this works", 16, "..")); - assertEquals("This is a test", StringUtil.cut("This is a test of how this works", 16, null)); - assertEquals("This is a test", StringUtil.cut("This is a test of how this works", 16, "")); - // TODO: Test is not complete - } - - public void testCaptialize() { - assertNull(StringUtil.capitalize(null)); - assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase())); - assertTrue(StringUtil.capitalize("abc").charAt(0) == 'A'); - } - - public void testCaptializePos() { - assertNull(StringUtil.capitalize(null, 45)); - - // TODO: Should this throw IllegalArgument or StringIndexOutOfBonds? - assertEquals(TEST_STRING, StringUtil.capitalize(TEST_STRING, TEST_STRING.length() + 45)); - - for (int i = 0; i < TEST_STRING.length(); i++) { - assertTrue(Character.isUpperCase(StringUtil.capitalize(TEST_STRING, i).charAt(i))); - } - } - - public void testPad() { - assertEquals(TEST_STRING + "...", StringUtil.pad(TEST_STRING, TEST_STRING.length() + 3, "..", false)); - assertEquals(TEST_STRING, StringUtil.pad(TEST_STRING, 4, ".", false)); - assertEquals(TEST_STRING, StringUtil.pad(TEST_STRING, 4, ".", true)); - assertEquals("..." + TEST_STRING, StringUtil.pad(TEST_STRING, TEST_STRING.length() + 3, "..", true)); - } - - public void testToDate() { - long time = System.currentTimeMillis(); - Date now = new Date(time - time % 60000); // Default format seems to have no seconds.. - Date date = StringUtil.toDate(DateFormat.getInstance().format(now)); - assertNotNull(date); - assertEquals(now, date); - } - - public void testToDateWithFormatString() { - Calendar cal = new GregorianCalendar(); - cal.clear(); - cal.set(1976, 2, 16); // Month is 0-based - Date date = StringUtil.toDate("16.03.1976", "dd.MM.yyyy"); - assertNotNull(date); - assertEquals(cal.getTime(), date); - - cal.clear(); - cal.set(2004, 4, 13, 23, 51, 3); - date = StringUtil.toDate("2004-5-13 23:51 (03)", "yyyy-MM-dd hh:mm (ss)"); - assertNotNull(date); - assertEquals(cal.getTime(), date); - - cal.clear(); - cal.set(Calendar.HOUR, 1); - cal.set(Calendar.MINUTE, 2); - cal.set(Calendar.SECOND, 3); - date = StringUtil.toDate("123", "hms"); - assertNotNull(date); - assertEquals(cal.getTime(), date); - } - - public void testToDateWithFormat() { - Calendar cal = new GregorianCalendar(); - cal.clear(); - cal.set(1976, 2, 16); // Month is 0-based - Date date = StringUtil.toDate("16.03.1976", new SimpleDateFormat("dd.MM.yyyy")); - assertNotNull(date); - assertEquals(cal.getTime(), date); - - cal.clear(); - cal.set(2004, 4, 13, 23, 51); - date = StringUtil.toDate("13.5.04 23:51", - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO"))); - assertNotNull(date); - assertEquals(cal.getTime(), date); - - cal.clear(); - cal.set(Calendar.HOUR, 1); - cal.set(Calendar.MINUTE, 2); - date = StringUtil.toDate("1:02 am", - DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US)); - assertNotNull(date); - assertEquals(cal.getTime(), date); - } - - public void testToTimestamp() { - Calendar cal = new GregorianCalendar(); - cal.clear(); - cal.set(1976, 2, 16, 21, 28, 4); // Month is 0-based - Date date = StringUtil.toTimestamp("1976-03-16 21:28:04"); - assertNotNull(date); - assertTrue(date instanceof Timestamp); - assertEquals(cal.getTime(), date); - } - - public void testToStringArray() { - String[] arr = StringUtil.toStringArray(TEST_DELIM_STRING); - assertNotNull(arr); - assertEquals(STRING_ARRAY.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(STRING_ARRAY[i], arr[i]); - } - } - - public void testToStringArrayDelim() { - String[] arr = StringUtil.toStringArray("-1---2-3--4-5", "---"); - String[] arr2 = {"1", "2", "3", "4", "5"}; - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - - arr = StringUtil.toStringArray("1, 2, 3; 4 5", ",; "); - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - } - - public void testToIntArray() { - int[] arr = StringUtil.toIntArray(TEST_INT_DELIM_STRING); - assertNotNull(arr); - assertEquals(INT_ARRAY.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(INT_ARRAY[i], arr[i]); - } - } - - public void testToIntArrayDelim() { - int[] arr = StringUtil.toIntArray("-1---2-3--4-5", "---"); - int[] arr2 = {1, 2, 3, 4, 5}; - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - - arr = StringUtil.toIntArray("1, 2, 3; 4 5", ",; "); - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - } - - public void testToIntArrayDelimBase() { - int[] arr = StringUtil.toIntArray("-1___2_3__F_a", "___", 16); - int[] arr2 = {-1, 2, 3, 0xf, 0xa}; - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - - arr = StringUtil.toIntArray("-1, 2, 3; 17 12", ",; ", 8); - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - } - - public void testToLongArray() { - long[] arr = StringUtil.toLongArray(TEST_INT_DELIM_STRING); - assertNotNull(arr); - assertEquals(INT_ARRAY.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(INT_ARRAY[i], arr[i]); - } - } - - public void testToLongArrayDelim() { - long[] arr = StringUtil.toLongArray("-12854928752983___2_3__4_5", "___"); - long[] arr2 = {-12854928752983L, 2, 3, 4, 5}; - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - - arr = StringUtil.toLongArray("-12854928752983, 2, 3; 4 5", ",; "); - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i]); - } - } - - public void testToDoubleArray() { - double[] arr = StringUtil.toDoubleArray(TEST_DOUBLE_DELIM_STRING); - assertNotNull(arr); - assertEquals(DOUBLE_ARRAY.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(DOUBLE_ARRAY[i], arr[i], 0d); - } - } - - public void testToDoubleArrayDelim() { - double[] arr = StringUtil.toDoubleArray("-12854928752983___.2_3__4_5e4", "___"); - double[] arr2 = {-12854928752983L, .2, 3, 4, 5e4}; - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i], 0d); - } - - arr = StringUtil.toDoubleArray("-12854928752983, .2, 3; 4 5E4", ",; "); - assertNotNull(arr); - assertEquals(arr2.length, arr.length); - for (int i = 0; i < arr.length; i++) { - assertEquals(arr2[i], arr[i], 0d); - } - } - - public void testTestToColor() { - // Test all constants - assertEquals(Color.black, StringUtil.toColor("black")); - assertEquals(Color.black, StringUtil.toColor("BLACK")); - assertEquals(Color.blue, StringUtil.toColor("blue")); - assertEquals(Color.blue, StringUtil.toColor("BLUE")); - assertEquals(Color.cyan, StringUtil.toColor("cyan")); - assertEquals(Color.cyan, StringUtil.toColor("CYAN")); - assertEquals(Color.darkGray, StringUtil.toColor("darkGray")); - assertEquals(Color.darkGray, StringUtil.toColor("DARK_GRAY")); - assertEquals(Color.gray, StringUtil.toColor("gray")); - assertEquals(Color.gray, StringUtil.toColor("GRAY")); - assertEquals(Color.green, StringUtil.toColor("green")); - assertEquals(Color.green, StringUtil.toColor("GREEN")); - assertEquals(Color.lightGray, StringUtil.toColor("lightGray")); - assertEquals(Color.lightGray, StringUtil.toColor("LIGHT_GRAY")); - assertEquals(Color.magenta, StringUtil.toColor("magenta")); - assertEquals(Color.magenta, StringUtil.toColor("MAGENTA")); - assertEquals(Color.orange, StringUtil.toColor("orange")); - assertEquals(Color.orange, StringUtil.toColor("ORANGE")); - assertEquals(Color.pink, StringUtil.toColor("pink")); - assertEquals(Color.pink, StringUtil.toColor("PINK")); - assertEquals(Color.red, StringUtil.toColor("red")); - assertEquals(Color.red, StringUtil.toColor("RED")); - assertEquals(Color.white, StringUtil.toColor("white")); - assertEquals(Color.white, StringUtil.toColor("WHITE")); - assertEquals(Color.yellow, StringUtil.toColor("yellow")); - assertEquals(Color.yellow, StringUtil.toColor("YELLOW")); - -// System.out.println(StringUtil.deepToString(Color.yellow)); -// System.out.println(StringUtil.deepToString(Color.pink, true, -1)); - - // Test HTML/CSS style color - for (int i = 0; i < 256; i++) { - int c = i; - if (i < 0x10) { - c = i * 16; - } - String colorStr = "#" + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i); - String colorStrAlpha = "#" + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i); - assertEquals(new Color(c, c, c), StringUtil.toColor(colorStr)); - assertEquals(new Color(c, c, c, c), StringUtil.toColor(colorStrAlpha)); - - } - - // Test null - // TODO: Hmmm.. Maybe reconsider this.. - assertNull(StringUtil.toColor(null)); - - // Test - try { - StringUtil.toColor("illegal-color-value"); - fail("toColor with illegal color value should throw IllegalArgumentException."); - } - catch (IllegalArgumentException e) { - assertNotNull(e.getMessage()); - } - } - - public void testToColorString() { - assertEquals("#ff0000", StringUtil.toColorString(Color.red)); - assertEquals("#00ff00", StringUtil.toColorString(Color.green)); - assertEquals("#0000ff", StringUtil.toColorString(Color.blue)); - assertEquals("#101010", StringUtil.toColorString(new Color(0x10, 0x10, 0x10))); - - for (int i = 0; i < 256; i++) { - String str = (i < 0x10 ? "0" : "") + Integer.toHexString(i); - assertEquals("#" + str + str + str, StringUtil.toColorString(new Color(i, i, i))); - } - - // Test null - // TODO: Hmmm.. Maybe reconsider this.. - assertNull(StringUtil.toColorString(null)); - } - - public void testIsNumber() { - assertTrue(StringUtil.isNumber("0")); - assertTrue(StringUtil.isNumber("12345")); - assertTrue(StringUtil.isNumber(TEST_INTEGER.toString())); - assertTrue(StringUtil.isNumber("1234567890123456789012345678901234567890")); - assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE))); - assertFalse(StringUtil.isNumber("abc")); - assertFalse(StringUtil.isNumber(TEST_STRING)); - } - - public void testIsNumberNegative() { - assertTrue(StringUtil.isNumber("-12345")); - assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString())); - assertTrue(StringUtil.isNumber("-1234567890123456789012345678901234567890")); - assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE))); - assertFalse(StringUtil.isNumber("-abc")); - assertFalse(StringUtil.isNumber('-' + TEST_STRING)); - } - - public void testCamelToLispNull() { - try { - StringUtil.camelToLisp(null); - fail("should not accept null"); - } - catch (IllegalArgumentException iae) { - assertNotNull(iae.getMessage()); - } - } - public void testCamelToLispNoConversion() { - assertEquals("", StringUtil.camelToLisp("")); - assertEquals("equal", StringUtil.camelToLisp("equal")); - assertEquals("allready-lisp", StringUtil.camelToLisp("allready-lisp")); - } - - public void testCamelToLispSimple() { - // Simple tests - assertEquals("foo-bar", StringUtil.camelToLisp("fooBar")); - } - - public void testCamelToLispCase() { - // Casing - assertEquals("my-url", StringUtil.camelToLisp("myURL")); - assertEquals("another-url", StringUtil.camelToLisp("AnotherURL")); - } - - public void testCamelToLispMulti() { - // Several words - assertEquals("http-request-wrapper", StringUtil.camelToLisp("HttpRequestWrapper")); - String s = StringUtil.camelToLisp("HttpURLConnection"); - assertEquals("http-url-connection", s); - // Long and short abbre in upper case - assertEquals("welcome-to-my-world", StringUtil.camelToLisp("WELCOMEToMYWorld")); - } - - public void testCamelToLispLeaveUntouched() { - // Leave others untouched - assertEquals("a-slightly-longer-and-more-bumpy-string?.,[]()", StringUtil.camelToLisp("ASlightlyLongerANDMoreBumpyString?.,[]()")); - } - public void testCamelToLispNumbers() { - // Numbers - // TODO: FixMe - String s = StringUtil.camelToLisp("my45Caliber"); - assertEquals("my-45-caliber", s); - assertEquals("hello-12345-world-67890", StringUtil.camelToLisp("Hello12345world67890")); - assertEquals("hello-12345-my-world-67890-this-time", StringUtil.camelToLisp("HELLO12345MyWorld67890thisTime")); - assertEquals("hello-12345-world-67890-too", StringUtil.camelToLisp("Hello12345WORLD67890too")); - } - - public void testLispToCamelNull() { - try { - StringUtil.lispToCamel(null); - fail("should not accept null"); - } - catch (IllegalArgumentException iae) { - assertNotNull(iae.getMessage()); - } - } - public void testLispToCamelNoConversion() { - assertEquals("", StringUtil.lispToCamel("")); - assertEquals("equal", StringUtil.lispToCamel("equal")); - assertEquals("allreadyCamel", StringUtil.lispToCamel("allreadyCamel")); - } - - public void testLispToCamelSimple() { - // Simple tests - assertEquals("fooBar", StringUtil.lispToCamel("foo-bar")); - assertEquals("myUrl", StringUtil.lispToCamel("my-URL")); - assertEquals("anotherUrl", StringUtil.lispToCamel("ANOTHER-URL")); - } - - public void testLispToCamelCase() { - // Casing - assertEquals("Object", StringUtil.lispToCamel("object", true)); - assertEquals("object", StringUtil.lispToCamel("Object", false)); - } - - public void testLispToCamelMulti() { - // Several words - assertEquals("HttpRequestWrapper", StringUtil.lispToCamel("http-request-wrapper", true)); - } - - public void testLispToCamelLeaveUntouched() { - // Leave others untouched - assertEquals("ASlightlyLongerAndMoreBumpyString?.,[]()", StringUtil.lispToCamel("a-slightly-longer-and-more-bumpy-string?.,[]()", true)); - } - - public void testLispToCamelNumber() { - // Numbers - assertEquals("my45Caliber", StringUtil.lispToCamel("my-45-caliber")); - } -} +/* + * 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.lang; + +import static org.junit.Assert.*; + +import java.awt.*; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; + +import org.junit.Test; + +/** + * StringUtilTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/StringUtilTestCase.java#1 $ + * + */ +public class StringUtilTest { + final static Object TEST_OBJECT = new Object(); + final static Integer TEST_INTEGER = 42; + final static String TEST_STRING = "TheQuickBrownFox"; // No WS! + final static String TEST_SUB_STRING = TEST_STRING.substring(2, 5); + final static String TEST_DELIM_STRING = "one,two, three\n four\tfive six"; + final static String[] STRING_ARRAY = {"one", "two", "three", "four", "five", "six"}; + final static String TEST_INT_DELIM_STRING = "1,2, 3\n 4\t5 6"; + final static int[] INT_ARRAY = {1, 2, 3, 4, 5, 6}; + final static String TEST_DOUBLE_DELIM_STRING = "1.4,2.1, 3\n .4\t-5 6e5"; + final static double[] DOUBLE_ARRAY = {1.4, 2.1, 3, .4, -5, 6e5}; + final static String EMPTY_STRING = ""; + final static String WHITESPACE_STRING = " \t \r \n "; + + @Test + public void testValueOfObject() { + assertNotNull(StringUtil.valueOf(TEST_OBJECT)); + assertEquals(StringUtil.valueOf(TEST_OBJECT), TEST_OBJECT.toString()); + assertEquals(StringUtil.valueOf(TEST_INTEGER), TEST_INTEGER.toString()); + assertEquals(StringUtil.valueOf(TEST_STRING), TEST_STRING); + assertSame(StringUtil.valueOf(TEST_STRING), TEST_STRING); + + assertNull(StringUtil.valueOf(null)); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void testToUpperCase() { + String str = StringUtil.toUpperCase(TEST_STRING); + assertNotNull(str); + assertEquals(TEST_STRING.toUpperCase(), str); + + assertNull(StringUtil.toUpperCase(null)); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void testToLowerCase() { + String str = StringUtil.toLowerCase(TEST_STRING); + assertNotNull(str); + assertEquals(TEST_STRING.toLowerCase(), str); + + assertNull(StringUtil.toLowerCase(null)); + } + + @Test + public void testIsEmpty() { + assertTrue(StringUtil.isEmpty((String) null)); + assertTrue(StringUtil.isEmpty(EMPTY_STRING)); + assertTrue(StringUtil.isEmpty(WHITESPACE_STRING)); + assertFalse(StringUtil.isEmpty(TEST_STRING)); + } + + @Test + public void testIsEmptyArray() { + assertTrue(StringUtil.isEmpty((String[]) null)); + assertTrue(StringUtil.isEmpty(new String[]{EMPTY_STRING})); + assertTrue(StringUtil.isEmpty(new String[]{EMPTY_STRING, WHITESPACE_STRING})); + assertFalse(StringUtil.isEmpty(new String[]{EMPTY_STRING, TEST_STRING})); + assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING})); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void testContains() { + assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING)); + assertTrue(StringUtil.contains(TEST_STRING, TEST_SUB_STRING)); + assertTrue(StringUtil.contains(TEST_STRING, EMPTY_STRING)); + assertFalse(StringUtil.contains(TEST_STRING, WHITESPACE_STRING)); + assertFalse(StringUtil.contains(TEST_SUB_STRING, TEST_STRING)); + assertFalse(StringUtil.contains(EMPTY_STRING, TEST_STRING)); + assertFalse(StringUtil.contains(WHITESPACE_STRING, TEST_STRING)); + assertFalse(StringUtil.contains(null, TEST_STRING)); + assertFalse(StringUtil.contains(null, null)); + } + + @Test + public void testContainsIgnoreCase() { + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING)); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING)); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING)); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase())); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING)); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING)); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING)); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase())); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, EMPTY_STRING)); + assertFalse(StringUtil.containsIgnoreCase(TEST_STRING, WHITESPACE_STRING)); + assertFalse(StringUtil.containsIgnoreCase(TEST_SUB_STRING, TEST_STRING)); + assertFalse(StringUtil.containsIgnoreCase(EMPTY_STRING, TEST_STRING)); + assertFalse(StringUtil.containsIgnoreCase(WHITESPACE_STRING, TEST_STRING)); + assertFalse(StringUtil.containsIgnoreCase(null, TEST_STRING)); + assertFalse(StringUtil.containsIgnoreCase(null, null)); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void testContainsChar() { + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING.charAt(i))); + assertFalse(StringUtil.contains(EMPTY_STRING, TEST_STRING.charAt(i))); + assertFalse(StringUtil.contains(WHITESPACE_STRING, TEST_STRING.charAt(i))); + assertFalse(StringUtil.contains(null, TEST_STRING.charAt(i))); + } + for (int i = 0; i < TEST_SUB_STRING.length(); i++) { + assertTrue(StringUtil.contains(TEST_STRING, TEST_SUB_STRING.charAt(i))); + } + for (int i = 0; i < WHITESPACE_STRING.length(); i++) { + assertFalse(StringUtil.contains(TEST_STRING, WHITESPACE_STRING.charAt(i))); + } + + // Test all alpha-chars + for (int i = 'a'; i < 'z'; i++) { + if (TEST_STRING.indexOf(i) < 0) { + assertFalse(TEST_STRING + " seems to contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.contains(TEST_STRING, i)); + } + else { + assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.contains(TEST_STRING, i)); + } + } + } + + @Test + public void testContainsIgnoreCaseChar() { + // Must contain all chars in string + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING.charAt(i))); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i))); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i))); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)))); + assertFalse(StringUtil.containsIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i))); + assertFalse(StringUtil.containsIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i))); + assertFalse(StringUtil.containsIgnoreCase(null, TEST_STRING.charAt(i))); + } + for (int i = 0; i < TEST_SUB_STRING.length(); i++) { + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i))); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i))); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i))); + assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i))); + } + + for (int i = 0; i < WHITESPACE_STRING.length(); i++) { + assertFalse(StringUtil.containsIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i))); + } + + // Test all alpha-chars + for (int i = 'a'; i < 'z'; i++) { + if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { + assertFalse(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), StringUtil.containsIgnoreCase(TEST_STRING, i)); + } + else { + assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.containsIgnoreCase(TEST_STRING, i)); + } + } + } + + @Test + public void testIndexOfIgnoreCase() { + assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING)); + assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING)); + assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING)); + assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase())); + assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase())); + + for (int i = 1; i < TEST_STRING.length(); i++) { + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i))); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i))); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i))); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i))); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i))); + } + + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase())); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase())); + + for (int i = 1; i < TEST_STRING.length(); i++) { + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); + } + + assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(null, null)); + } + + @Test + public void testIndexOfIgnoreCasePos() { + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING, 1)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING, 2)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING, 3)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), 4)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase(), 5)); + + for (int i = 1; i < TEST_STRING.length(); i++) { + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i), i - 1)); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i), i - 1)); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i), i - 1)); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i), i - 1)); + assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i), i - 1)); + } + + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING, 1)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING, 1)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING, 2)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase(), 1)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase(), 2)); + + assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING, 234)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(null, null, -45)); + } + + @Test + public void testLastIndexOfIgnoreCase() { + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase())); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase())); + + for (int i = 1; i < TEST_STRING.length(); i++) { + assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i))); + assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i))); + assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i))); + assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i))); + assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i))); + } + + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase())); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase())); + + for (int i = 1; i < TEST_STRING.length(); i++) { + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING)); + } + + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING)); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, null)); + + } + + @Test + public void testLastIndexOfIgnoreCasePos() { + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING, 1)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING, 2)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING, 3)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), 4)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase(), 5)); + + for (int i = 1; i < TEST_STRING.length(); i++) { + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(0, i), i - 1)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(0, i), i - 1)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(0, i), i - 1)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(0, i), i - 1)); + assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(0, i), i - 1)); + } + + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING, TEST_SUB_STRING.length() + 3)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING, TEST_SUB_STRING.length() + 3)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING, TEST_SUB_STRING.length() + 4)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase(), TEST_SUB_STRING.length() + 3)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase(), TEST_SUB_STRING.length() + 4)); + + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING, 234)); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, null, -45)); + + } + + @Test + public void testIndexOfIgnoreCaseChar() { + // Must contain all chars in string + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i))); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i))); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i))); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)))); + assertEquals(-1, StringUtil.indexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i))); + assertEquals(-1, StringUtil.indexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i))); + assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING.charAt(i))); + } + + for (int i = 0; i < TEST_SUB_STRING.length(); i++) { + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i))); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i))); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i))); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i))); + } + + for (int i = 0; i < WHITESPACE_STRING.length(); i++) { + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i))); + } + + // Test all alpha-chars + for (int i = 'a'; i < 'z'; i++) { + if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { + assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.indexOfIgnoreCase(TEST_STRING, i)); + } + else { + assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, i)); + } + } + } + + @Test + public void testIndexOfIgnoreCaseCharPos() { + // Must contain all chars in string + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)), i)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i), i)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i), i)); + assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING.charAt(i), i)); + } + + for (int i = 0; i < TEST_SUB_STRING.length(); i++) { + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i), i)); + } + + for (int i = 0; i < WHITESPACE_STRING.length(); i++) { + assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i), i)); + } + + // Test all alpha-chars + for (int i = 'a'; i < 'z'; i++) { + if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { + assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.indexOfIgnoreCase(TEST_STRING, i, 0)); + } + else { + assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, i, 0)); + } + } + } + + @Test + public void testLastIndexOfIgnoreCaseChar() { + // Must contain all chars in string + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i))); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i))); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i))); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)))); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i))); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i))); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING.charAt(i))); + } + + for (int i = 0; i < TEST_SUB_STRING.length(); i++) { + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i))); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i))); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i))); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i))); + } + + for (int i = 0; i < WHITESPACE_STRING.length(); i++) { + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i))); + } + + // Test all alpha-chars + for (int i = 'a'; i < 'z'; i++) { + if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { + assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i)); + } + else { + assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i)); + } + } + } + + @Test + public void testLastIndexOfIgnoreCaseCharPos() { + // Must contain all chars in string + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i), i)); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)), i)); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i), i)); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i), i)); + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING.charAt(i), i)); + } + + for (int i = 0; i < TEST_SUB_STRING.length(); i++) { + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i), TEST_STRING.length())); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i), TEST_STRING.length())); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i), TEST_STRING.length())); + assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i), TEST_STRING.length())); + } + + for (int i = 0; i < WHITESPACE_STRING.length(); i++) { + assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i), TEST_STRING.length())); + } + + // Test all alpha-chars + for (int i = 'a'; i < 'z'; i++) { + if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) { + assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i, TEST_STRING.length())); + } + else { + assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i, TEST_STRING.length())); + } + } + } + + @Test + public void testLtrim() { + assertEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING)); + assertEquals(TEST_STRING, StringUtil.ltrim(" " + TEST_STRING)); + assertEquals(TEST_STRING, StringUtil.ltrim(WHITESPACE_STRING + TEST_STRING)); + assertNotEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING)); + // TODO: Test is not complete + } + + @Test + public void testRtrim() { + assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING)); + assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + " ")); + assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + WHITESPACE_STRING)); + assertNotEquals(TEST_STRING, StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING)); + // TODO: Test is not complete + } + + @Test + public void testReplace() { + assertEquals("", StringUtil.replace(TEST_STRING, TEST_STRING, "")); + assertEquals("", StringUtil.replace("", "", "")); + assertEquals("", StringUtil.replace("", "xyzzy", "xyzzy")); + assertEquals(TEST_STRING, StringUtil.replace(TEST_STRING, "", "xyzzy")); + assertEquals("aabbdd", StringUtil.replace("aabbccdd", "c", "")); + assertEquals("aabbccdd", StringUtil.replace("aabbdd", "bd", "bccd")); + // TODO: Test is not complete + } + + @Test + public void testReplaceIgnoreCase() { + assertEquals("", StringUtil.replaceIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), "")); + assertEquals("", StringUtil.replaceIgnoreCase("", "", "")); + assertEquals("", StringUtil.replaceIgnoreCase("", "xyzzy", "xyzzy")); + assertEquals(TEST_STRING, StringUtil.replaceIgnoreCase(TEST_STRING, "", "xyzzy")); + assertEquals("aabbdd", StringUtil.replaceIgnoreCase("aabbCCdd", "c", "")); + assertEquals("aabbdd", StringUtil.replaceIgnoreCase("aabbccdd", "C", "")); + assertEquals("aabbccdd", StringUtil.replaceIgnoreCase("aabbdd", "BD", "bccd")); + assertEquals("aabbccdd", StringUtil.replaceIgnoreCase("aabBDd", "bd", "bccd")); + // TODO: Test is not complete + } + + @Test + public void testCut() { + assertEquals(TEST_STRING, StringUtil.cut(TEST_STRING, TEST_STRING.length(), "..")); + assertEquals("This is a test..", StringUtil.cut("This is a test of how this works", 16, "..")); + assertEquals("This is a test", StringUtil.cut("This is a test of how this works", 16, null)); + assertEquals("This is a test", StringUtil.cut("This is a test of how this works", 16, "")); + // TODO: Test is not complete + } + + @Test + public void testCaptialize() { + assertNull(StringUtil.capitalize(null)); + assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase())); + assertEquals('A', StringUtil.capitalize("abc").charAt(0)); + } + + @Test + public void testCaptializePos() { + assertNull(StringUtil.capitalize(null, 45)); + + // TODO: Should this throw IllegalArgument or StringIndexOutOfBonds? + assertEquals(TEST_STRING, StringUtil.capitalize(TEST_STRING, TEST_STRING.length() + 45)); + + for (int i = 0; i < TEST_STRING.length(); i++) { + assertTrue(Character.isUpperCase(StringUtil.capitalize(TEST_STRING, i).charAt(i))); + } + } + + @Test + public void testPad() { + assertEquals(TEST_STRING + "...", StringUtil.pad(TEST_STRING, TEST_STRING.length() + 3, "..", false)); + assertEquals(TEST_STRING, StringUtil.pad(TEST_STRING, 4, ".", false)); + assertEquals(TEST_STRING, StringUtil.pad(TEST_STRING, 4, ".", true)); + assertEquals("..." + TEST_STRING, StringUtil.pad(TEST_STRING, TEST_STRING.length() + 3, "..", true)); + } + + @Test + public void testToDate() { + long time = System.currentTimeMillis(); + Date now = new Date(time - time % 60000); // Default format seems to have no seconds.. + Date date = StringUtil.toDate(DateFormat.getInstance().format(now)); + assertNotNull(date); + assertEquals(now, date); + } + + @Test + public void testToDateWithFormatString() { + Calendar cal = new GregorianCalendar(); + cal.clear(); + cal.set(1976, Calendar.MARCH, 16); // Month is 0-based + Date date = StringUtil.toDate("16.03.1976", "dd.MM.yyyy"); + assertNotNull(date); + assertEquals(cal.getTime(), date); + + cal.clear(); + cal.set(2004, Calendar.MAY, 13, 23, 51, 3); + date = StringUtil.toDate("2004-5-13 23:51 (03)", "yyyy-MM-dd hh:mm (ss)"); + assertNotNull(date); + assertEquals(cal.getTime(), date); + + cal.clear(); + cal.set(Calendar.HOUR, 1); + cal.set(Calendar.MINUTE, 2); + cal.set(Calendar.SECOND, 3); + date = StringUtil.toDate("123", "hms"); + assertNotNull(date); + assertEquals(cal.getTime(), date); + } + + @Test + public void testToDateWithFormat() { + Calendar cal = new GregorianCalendar(); + cal.clear(); + cal.set(1976, Calendar.MARCH, 16); // Month is 0-based + Date date = StringUtil.toDate("16.03.1976", new SimpleDateFormat("dd.MM.yyyy")); + assertNotNull(date); + assertEquals(cal.getTime(), date); + + cal.clear(); + cal.set(2004, Calendar.MAY, 13, 23, 51); + DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO")); + date = StringUtil.toDate(format.format(cal.getTime()), format); + assertNotNull(date); + assertEquals(cal.getTime(), date); + + cal.clear(); + cal.set(Calendar.HOUR, 1); + cal.set(Calendar.MINUTE, 2); + date = StringUtil.toDate("1:02 am", + DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US)); + assertNotNull(date); + assertEquals(cal.getTime(), date); + } + + @Test + public void testToTimestamp() { + Calendar cal = new GregorianCalendar(); + cal.clear(); + cal.set(1976, Calendar.MARCH, 16, 21, 28, 4); // Month is 0-based + Timestamp date = StringUtil.toTimestamp("1976-03-16 21:28:04"); + assertNotNull(date); + assertEquals(cal.getTime(), date); + } + + @Test + public void testToStringArray() { + String[] arr = StringUtil.toStringArray(TEST_DELIM_STRING); + assertNotNull(arr); + assertEquals(STRING_ARRAY.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(STRING_ARRAY[i], arr[i]); + } + } + + @Test + public void testToStringArrayDelim() { + String[] arr = StringUtil.toStringArray("-1---2-3--4-5", "---"); + String[] arr2 = {"1", "2", "3", "4", "5"}; + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + + arr = StringUtil.toStringArray("1, 2, 3; 4 5", ",; "); + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + } + + @Test + public void testToIntArray() { + int[] arr = StringUtil.toIntArray(TEST_INT_DELIM_STRING); + assertNotNull(arr); + assertEquals(INT_ARRAY.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(INT_ARRAY[i], arr[i]); + } + } + + @Test + public void testToIntArrayDelim() { + int[] arr = StringUtil.toIntArray("-1---2-3--4-5", "---"); + int[] arr2 = {1, 2, 3, 4, 5}; + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + + arr = StringUtil.toIntArray("1, 2, 3; 4 5", ",; "); + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + } + + @Test + public void testToIntArrayDelimBase() { + int[] arr = StringUtil.toIntArray("-1___2_3__F_a", "___", 16); + int[] arr2 = {-1, 2, 3, 0xf, 0xa}; + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + + arr = StringUtil.toIntArray("-1, 2, 3; 17 12", ",; ", 8); + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + } + + @Test + public void testToLongArray() { + long[] arr = StringUtil.toLongArray(TEST_INT_DELIM_STRING); + assertNotNull(arr); + assertEquals(INT_ARRAY.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(INT_ARRAY[i], arr[i]); + } + } + + @Test + public void testToLongArrayDelim() { + long[] arr = StringUtil.toLongArray("-12854928752983___2_3__4_5", "___"); + long[] arr2 = {-12854928752983L, 2, 3, 4, 5}; + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + + arr = StringUtil.toLongArray("-12854928752983, 2, 3; 4 5", ",; "); + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i]); + } + } + + @Test + public void testToDoubleArray() { + double[] arr = StringUtil.toDoubleArray(TEST_DOUBLE_DELIM_STRING); + assertNotNull(arr); + assertEquals(DOUBLE_ARRAY.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(DOUBLE_ARRAY[i], arr[i], 0d); + } + } + + @Test + public void testToDoubleArrayDelim() { + double[] arr = StringUtil.toDoubleArray("-12854928752983___.2_3__4_5e4", "___"); + double[] arr2 = {-12854928752983L, .2, 3, 4, 5e4}; + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i], 0d); + } + + arr = StringUtil.toDoubleArray("-12854928752983, .2, 3; 4 5E4", ",; "); + assertNotNull(arr); + assertEquals(arr2.length, arr.length); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr2[i], arr[i], 0d); + } + } + + @Test + public void testTestToColor() { + // Test all constants + assertEquals(Color.black, StringUtil.toColor("black")); + assertEquals(Color.black, StringUtil.toColor("BLACK")); + assertEquals(Color.blue, StringUtil.toColor("blue")); + assertEquals(Color.blue, StringUtil.toColor("BLUE")); + assertEquals(Color.cyan, StringUtil.toColor("cyan")); + assertEquals(Color.cyan, StringUtil.toColor("CYAN")); + assertEquals(Color.darkGray, StringUtil.toColor("darkGray")); + assertEquals(Color.darkGray, StringUtil.toColor("DARK_GRAY")); + assertEquals(Color.gray, StringUtil.toColor("gray")); + assertEquals(Color.gray, StringUtil.toColor("GRAY")); + assertEquals(Color.green, StringUtil.toColor("green")); + assertEquals(Color.green, StringUtil.toColor("GREEN")); + assertEquals(Color.lightGray, StringUtil.toColor("lightGray")); + assertEquals(Color.lightGray, StringUtil.toColor("LIGHT_GRAY")); + assertEquals(Color.magenta, StringUtil.toColor("magenta")); + assertEquals(Color.magenta, StringUtil.toColor("MAGENTA")); + assertEquals(Color.orange, StringUtil.toColor("orange")); + assertEquals(Color.orange, StringUtil.toColor("ORANGE")); + assertEquals(Color.pink, StringUtil.toColor("pink")); + assertEquals(Color.pink, StringUtil.toColor("PINK")); + assertEquals(Color.red, StringUtil.toColor("red")); + assertEquals(Color.red, StringUtil.toColor("RED")); + assertEquals(Color.white, StringUtil.toColor("white")); + assertEquals(Color.white, StringUtil.toColor("WHITE")); + assertEquals(Color.yellow, StringUtil.toColor("yellow")); + assertEquals(Color.yellow, StringUtil.toColor("YELLOW")); + +// System.out.println(StringUtil.deepToString(Color.yellow)); +// System.out.println(StringUtil.deepToString(Color.pink, true, -1)); + + // Test HTML/CSS style color + for (int i = 0; i < 256; i++) { + int c = i; + if (i < 0x10) { + c = i * 16; + } + String colorStr = "#" + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i); + String colorStrAlpha = "#" + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i); + assertEquals(new Color(c, c, c), StringUtil.toColor(colorStr)); + assertEquals(new Color(c, c, c, c), StringUtil.toColor(colorStrAlpha)); + + } + + // Test null + // TODO: Hmmm.. Maybe reconsider this.. + assertNull(StringUtil.toColor(null)); + + // Test + try { + StringUtil.toColor("illegal-color-value"); + fail("toColor with illegal color value should throw IllegalArgumentException."); + } + catch (IllegalArgumentException e) { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testToColorString() { + assertEquals("#ff0000", StringUtil.toColorString(Color.red)); + assertEquals("#00ff00", StringUtil.toColorString(Color.green)); + assertEquals("#0000ff", StringUtil.toColorString(Color.blue)); + assertEquals("#101010", StringUtil.toColorString(new Color(0x10, 0x10, 0x10))); + + for (int i = 0; i < 256; i++) { + String str = (i < 0x10 ? "0" : "") + Integer.toHexString(i); + assertEquals("#" + str + str + str, StringUtil.toColorString(new Color(i, i, i))); + } + + // Test null + // TODO: Hmmm.. Maybe reconsider this.. + assertNull(StringUtil.toColorString(null)); + } + + @Test + public void testIsNumber() { + assertTrue(StringUtil.isNumber("0")); + assertTrue(StringUtil.isNumber("12345")); + assertTrue(StringUtil.isNumber(TEST_INTEGER.toString())); + assertTrue(StringUtil.isNumber("1234567890123456789012345678901234567890")); + assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + Long.MAX_VALUE)); + assertFalse(StringUtil.isNumber("abc")); + assertFalse(StringUtil.isNumber(TEST_STRING)); + } + + @Test + public void testIsNumberNegative() { + assertTrue(StringUtil.isNumber("-12345")); + assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString())); + assertTrue(StringUtil.isNumber("-1234567890123456789012345678901234567890")); + assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + Long.MAX_VALUE)); + assertFalse(StringUtil.isNumber("-abc")); + assertFalse(StringUtil.isNumber('-' + TEST_STRING)); + } + + @Test + public void testCamelToLispNull() { + try { + StringUtil.camelToLisp(null); + fail("should not accept null"); + } + catch (IllegalArgumentException iae) { + assertNotNull(iae.getMessage()); + } + } + + @Test + public void testCamelToLispNoConversion() { + assertEquals("", StringUtil.camelToLisp("")); + assertEquals("equal", StringUtil.camelToLisp("equal")); + assertEquals("allready-lisp", StringUtil.camelToLisp("allready-lisp")); + } + + @Test + public void testCamelToLispSimple() { + // Simple tests + assertEquals("foo-bar", StringUtil.camelToLisp("fooBar")); + } + + @Test + public void testCamelToLispCase() { + // Casing + assertEquals("my-url", StringUtil.camelToLisp("myURL")); + assertEquals("another-url", StringUtil.camelToLisp("AnotherURL")); + } + + @Test + public void testCamelToLispMulti() { + // Several words + assertEquals("http-request-wrapper", StringUtil.camelToLisp("HttpRequestWrapper")); + String s = StringUtil.camelToLisp("HttpURLConnection"); + assertEquals("http-url-connection", s); + // Long and short abbre in upper case + assertEquals("welcome-to-my-world", StringUtil.camelToLisp("WELCOMEToMYWorld")); + } + + @Test + public void testCamelToLispLeaveUntouched() { + // Leave others untouched + assertEquals("a-slightly-longer-and-more-bumpy-string?.,[]()", StringUtil.camelToLisp("ASlightlyLongerANDMoreBumpyString?.,[]()")); + } + + @Test + public void testCamelToLispNumbers() { + // Numbers + // TODO: FixMe + String s = StringUtil.camelToLisp("my45Caliber"); + assertEquals("my-45-caliber", s); + assertEquals("hello-12345-world-67890", StringUtil.camelToLisp("Hello12345world67890")); + assertEquals("hello-12345-my-world-67890-this-time", StringUtil.camelToLisp("HELLO12345MyWorld67890thisTime")); + assertEquals("hello-12345-world-67890-too", StringUtil.camelToLisp("Hello12345WORLD67890too")); + } + + @Test + public void testLispToCamelNull() { + try { + StringUtil.lispToCamel(null); + fail("should not accept null"); + } + catch (IllegalArgumentException iae) { + assertNotNull(iae.getMessage()); + } + } + + @Test + public void testLispToCamelNoConversion() { + assertEquals("", StringUtil.lispToCamel("")); + assertEquals("equal", StringUtil.lispToCamel("equal")); + assertEquals("alreadyCamel", StringUtil.lispToCamel("alreadyCamel")); + } + + @Test + public void testLispToCamelSimple() { + // Simple tests + assertEquals("fooBar", StringUtil.lispToCamel("foo-bar")); + assertEquals("myUrl", StringUtil.lispToCamel("my-URL")); + assertEquals("anotherUrl", StringUtil.lispToCamel("ANOTHER-URL")); + } + + @Test + public void testLispToCamelCase() { + // Casing + assertEquals("Object", StringUtil.lispToCamel("object", true)); + assertEquals("object", StringUtil.lispToCamel("Object", false)); + } + + @Test + public void testLispToCamelMulti() { + // Several words + assertEquals("HttpRequestWrapper", StringUtil.lispToCamel("http-request-wrapper", true)); + } + + @Test + public void testLispToCamelLeaveUntouched() { + // Leave others untouched + assertEquals("ASlightlyLongerAndMoreBumpyString?.,[]()", StringUtil.lispToCamel("a-slightly-longer-and-more-bumpy-string?.,[]()", true)); + } + + @Test + public void testLispToCamelNumber() { + // Numbers + assertEquals("my45Caliber", StringUtil.lispToCamel("my-45-caliber")); + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/SystemUtilTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/SystemUtilTest.java index 4405fd9e..5b73f5af 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/SystemUtilTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/SystemUtilTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.lang; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/ValidateTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/ValidateTest.java index 760d747b..76fee60c 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/ValidateTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/ValidateTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.lang; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/BeanMapTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/BeanMapTest.java similarity index 65% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/BeanMapTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/BeanMapTest.java index c33cde2e..68de2b41 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/BeanMapTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/BeanMapTest.java @@ -1,146 +1,175 @@ -package com.twelvemonkeys.util; - -import java.util.Map; -import java.beans.IntrospectionException; -import java.io.Serializable; - -/** - * BeanMapTestCase - *

- * @todo Extend with BeanMap specific tests - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/BeanMapTestCase.java#2 $ - */ -public class BeanMapTestCase extends MapAbstractTestCase { - - public boolean isPutAddSupported() { - return false; - } - - public boolean isRemoveSupported() { - return false; - } - - public boolean isSetValueSupported() { - return true; - } - - public boolean isAllowNullKey() { - return false; - } - - public Object[] getSampleKeys() { - return new Object[] { - "blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee" - }; - } - - public Object[] getSampleValues() { - return new Object[] { - "blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev" - }; - } - - public Object[] getNewSampleValues() { - return new Object[] { - "newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv", "newgollyv", "newgeev" - }; - } - - public Map makeEmptyMap() { - try { - return new BeanMap(new NullBean()); - } - catch (IntrospectionException e) { - throw new RuntimeException(e); - } - } - - public Map makeFullMap() { - try { - return new BeanMap(new MyBean()); - } - catch (IntrospectionException e) { - throw new RuntimeException(e); - } - } - - public static class MyBean implements Serializable { - Object blah = "blahv"; - Object foo = "foov"; - Object bar = "barv"; - Object baz = "bazv"; - Object tmp = "tmpv"; - Object gosh = "goshv"; - Object golly = "gollyv"; - Object gee = "geev"; - - public Object getBar() { - return bar; - } - - public void setBar(Object pBar) { - bar = pBar; - } - - public Object getBaz() { - return baz; - } - - public void setBaz(Object pBaz) { - baz = pBaz; - } - - public Object getBlah() { - return blah; - } - - public void setBlah(Object pBlah) { - blah = pBlah; - } - - public Object getFoo() { - return foo; - } - - public void setFoo(Object pFoo) { - foo = pFoo; - } - - public Object getGee() { - return gee; - } - - public void setGee(Object pGee) { - gee = pGee; - } - - public Object getGolly() { - return golly; - } - - public void setGolly(Object pGolly) { - golly = pGolly; - } - - public Object getGosh() { - return gosh; - } - - public void setGosh(Object pGosh) { - gosh = pGosh; - } - - public Object getTmp() { - return tmp; - } - - public void setTmp(Object pTmp) { - tmp = pTmp; - } - } - - static class NullBean implements Serializable { } -} +/* + * 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.util; + +import java.beans.IntrospectionException; +import java.io.Serializable; +import java.util.Map; + +/** + * BeanMapTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/BeanMapTestCase.java#2 $ + */ +// TODO: Extend with BeanMap specific tests +public class BeanMapTest extends MapAbstractTest { + + public boolean isPutAddSupported() { + return false; + } + + public boolean isRemoveSupported() { + return false; + } + + public boolean isSetValueSupported() { + return true; + } + + public boolean isAllowNullKey() { + return false; + } + + public Object[] getSampleKeys() { + return new Object[] { + "blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee" + }; + } + + public Object[] getSampleValues() { + return new Object[] { + "blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev" + }; + } + + public Object[] getNewSampleValues() { + return new Object[] { + "newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv", "newgollyv", "newgeev" + }; + } + + public Map makeEmptyMap() { + try { + return new BeanMap(new NullBean()); + } + catch (IntrospectionException e) { + throw new RuntimeException(e); + } + } + + public Map makeFullMap() { + try { + return new BeanMap(new MyBean()); + } + catch (IntrospectionException e) { + throw new RuntimeException(e); + } + } + + public static class MyBean implements Serializable { + Object blah = "blahv"; + Object foo = "foov"; + Object bar = "barv"; + Object baz = "bazv"; + Object tmp = "tmpv"; + Object gosh = "goshv"; + Object golly = "gollyv"; + Object gee = "geev"; + + public Object getBar() { + return bar; + } + + public void setBar(Object pBar) { + bar = pBar; + } + + public Object getBaz() { + return baz; + } + + public void setBaz(Object pBaz) { + baz = pBaz; + } + + public Object getBlah() { + return blah; + } + + public void setBlah(Object pBlah) { + blah = pBlah; + } + + public Object getFoo() { + return foo; + } + + public void setFoo(Object pFoo) { + foo = pFoo; + } + + public Object getGee() { + return gee; + } + + public void setGee(Object pGee) { + gee = pGee; + } + + public Object getGolly() { + return golly; + } + + public void setGolly(Object pGolly) { + golly = pGolly; + } + + public Object getGosh() { + return gosh; + } + + public void setGosh(Object pGosh) { + gosh = pGosh; + } + + public Object getTmp() { + return tmp; + } + + public void setTmp(Object pTmp) { + tmp = pTmp; + } + } + + static class NullBean implements Serializable { } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionAbstractTest.java similarity index 95% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionAbstractTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionAbstractTest.java index 4bb26738..fa4dd5ff 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionAbstractTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionAbstractTest.java @@ -1,1308 +1,1360 @@ -/* - * Copyright 2001-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.twelvemonkeys.util; - -import java.lang.reflect.Array; -import java.util.*; - -/** - * Abstract test class for {@link java.util.Collection} methods and contracts. - *

- * You should create a concrete subclass of this class to test any custom - * {@link Collection} implementation. At minimum, you'll have to - * implement the {@link #makeCollection()} method. You might want to - * override some of the additional public methods as well: - *

- * Element Population Methods - *

- * Override these if your collection restricts what kind of elements are - * allowed (for instance, if {@code null} is not permitted): - *

    - *
  • {@link #getFullElements()} - *
  • {@link #getOtherElements()} - *
- *

- * Supported Operation Methods - *

- * Override these if your collection doesn't support certain operations: - *

    - *
  • {@link #isAddSupported()} - *
  • {@link #isRemoveSupported()} - *
  • {@link #areEqualElementsDistinguishable()} - *
  • {@link #isNullSupported()} - *
  • {@link #isFailFastSupported()} - *
- *

- * Fixture Methods - *

- * Fixtures are used to verify that the the operation results in correct state - * for the collection. Basically, the operation is performed against your - * collection implementation, and an identical operation is performed against a - * confirmed collection implementation. A confirmed collection - * implementation is something like {@code java.util.ArrayList}, which is - * known to conform exactly to its collection interface's contract. After the - * operation takes place on both your collection implementation and the - * confirmed collection implementation, the two collections are compared to see - * if their state is identical. The comparison is usually much more involved - * than a simple {@code equals} test. This verification is used to ensure - * proper modifications are made along with ensuring that the collection does - * not change when read-only modifications are made. - *

- * The {@link #collection} field holds an instance of your collection - * implementation; the {@link #confirmed} field holds an instance of the - * confirmed collection implementation. The {@link #resetEmpty()} and - * {@link #resetFull()} methods set these fields to empty or full collections, - * so that tests can proceed from a known state. - *

- * After a modification operation to both {@link #collection} and - * {@link #confirmed}, the {@link #verifyAll()} method is invoked to compare - * the results. You may want to override {@link # verifyAll ()} to perform - * additional verifications. For instance, when testing the collection - * views of a map, {@link AbstractTestMap} would override {@link # verifyAll ()} to make - * sure the map is changed after the collection view is changed. - *

- * If you're extending this class directly, you will have to provide - * implementations for the following: - *

    - *
  • {@link #makeConfirmedCollection()} - *
  • {@link #makeConfirmedFullCollection()} - *
- *

- * Those methods should provide a confirmed collection implementation - * that's compatible with your collection implementation. - *

- * If you're extending {@link AbstractTestList}, {@link SetAbstractTestCase}, - * or {@link AbstractTestBag}, you probably don't have to worry about the - * above methods, because those three classes already override the methods - * to provide standard JDK confirmed collections.

- *

- * Other notes - *

- * If your {@link Collection} fails one of these tests by design, - * you may still use this base set of cases. Simply override the - * test case (method) your {@link Collection} fails. - * - * @version $Revision: #2 $ $Date: 2008/07/15 $ - * - * @author Rodney Waldhoff - * @author Paul Jack - * @author Michael A. Smith - * @author Neil O'Toole - * @author Stephen Colebourne - */ -public abstract class CollectionAbstractTestCase extends ObjectAbstractTestCase { - - // - // NOTE: - // - // Collection doesn't define any semantics for equals, and recommends you - // use reference-based default behavior of Object.equals. (And a test for - // that already exists in ObjectAbstractTestCase). Tests for equality of lists, sets - // and bags will have to be written in test subclasses. Thus, there is no - // tests on Collection.equals nor any for Collection.hashCode. - // - - - // These fields are used by reset() and verifyAll(), and any test - // method that tests a modification. - - /** - * A collection instance that will be used for testing. - */ - public Collection collection; - - /** - * Confirmed collection. This is an instance of a collection that is - * confirmed to conform exactly to the java.util.Collection contract. - * Modification operations are tested by performing a mod on your - * collection, performing the exact same mod on an equivalent confirmed - * collection, and then calling verifyAll() to make sure your collection - * still matches the confirmed collection. - */ - public Collection confirmed; - - //----------------------------------------------------------------------- - /** - * Specifies whether equal elements in the collection are, in fact, - * distinguishable with information not readily available. That is, if a - * particular value is to be removed from the collection, then there is - * one and only one value that can be removed, even if there are other - * elements which are equal to it. - * - *

In most collection cases, elements are not distinguishable (equal is - * equal), thus this method defaults to return false. In some cases, - * however, they are. For example, the collection returned from the map's - * values() collection view are backed by the map, so while there may be - * two values that are equal, their associated keys are not. Since the - * keys are distinguishable, the values are. - * - *

This flag is used to skip some verifications for iterator.remove() - * where it is impossible to perform an equivalent modification on the - * confirmed collection because it is not possible to determine which - * value in the confirmed collection to actually remove. Tests that - * override the default (i.e. where equal elements are distinguishable), - * should provide additional tests on iterator.remove() to make sure the - * proper elements are removed when remove() is called on the iterator. - **/ - public boolean areEqualElementsDistinguishable() { - return false; - } - - /** - * Returns true if the collections produced by - * {@link #makeCollection()} and {@link #makeFullCollection()} - * support the {@code add} and {@code addAll} - * operations.

- * Default implementation returns true. Override if your collection - * class does not support add or addAll. - */ - public boolean isAddSupported() { - return true; - } - - /** - * Returns true if the collections produced by - * {@link #makeCollection()} and {@link #makeFullCollection()} - * support the {@code remove}, {@code removeAll}, - * {@code retainAll}, {@code clear} and - * {@code iterator().remove()} methods. - * Default implementation returns true. Override if your collection - * class does not support removal operations. - */ - public boolean isRemoveSupported() { - return true; - } - - /** - * Returns true to indicate that the collection supports holding null. - * The default implementation returns true; - */ - public boolean isNullSupported() { - return true; - } - - /** - * Returns true to indicate that the collection supports fail fast iterators. - * The default implementation returns true; - */ - public boolean isFailFastSupported() { - return false; - } - - /** - * Returns true to indicate that the collection supports equals() comparisons. - * This implementation returns false; - */ - public boolean isEqualsCheckable() { - return false; - } - - //----------------------------------------------------------------------- - /** - * Verifies that {@link #collection} and {@link #confirmed} have - * identical state. - */ - public void verifyAll() { - int confirmedSize = confirmed.size(); - assertEquals("Collection size should match confirmed collection's", - confirmedSize, collection.size()); - assertEquals("Collection isEmpty() result should match confirmed " + - " collection's", - confirmed.isEmpty(), collection.isEmpty()); - - // verify the collections are the same by attempting to match each - // object in the collection and confirmed collection. To account for - // duplicates and differing orders, each confirmed element is copied - // into an array and a flag is maintained for each element to determine - // whether it has been matched once and only once. If all elements in - // the confirmed collection are matched once and only once and there - // aren't any elements left to be matched in the collection, - // verification is a success. - - // copy each collection value into an array - Object[] confirmedValues = new Object[confirmedSize]; - - Iterator iter; - - iter = confirmed.iterator(); - int pos = 0; - while(iter.hasNext()) { - confirmedValues[pos++] = iter.next(); - } - - // allocate an array of boolean flags for tracking values that have - // been matched once and only once. - boolean[] matched = new boolean[confirmedSize]; - - // now iterate through the values of the collection and try to match - // the value with one in the confirmed array. - iter = collection.iterator(); - while(iter.hasNext()) { - Object o = iter.next(); - boolean match = false; - for(int i = 0; i < confirmedSize; i++) { - if(matched[i]) { - // skip values already matched - continue; - } - if(o == confirmedValues[i] || - (o != null && o.equals(confirmedValues[i]))) { - // values matched - matched[i] = true; - match = true; - break; - } - } - // no match found! - if(!match) { - fail("Collection should not contain a value that the " + - "confirmed collection does not have: " + o + - "\nTest: " + collection + "\nReal: " + confirmed); - } - } - - // make sure there aren't any unmatched values - for(int i = 0; i < confirmedSize; i++) { - if(!matched[i]) { - // the collection didn't match all the confirmed values - fail("Collection should contain all values that are in the confirmed collection" + - "\nTest: " + collection + "\nReal: " + confirmed); - } - } - } - - //----------------------------------------------------------------------- - /** - * Resets the {@link #collection} and {@link #confirmed} fields to empty - * collections. Invoke this method before performing a modification - * test. - */ - public void resetEmpty() { - this.collection = makeCollection(); - this.confirmed = makeConfirmedCollection(); - } - - /** - * Resets the {@link #collection} and {@link #confirmed} fields to full - * collections. Invoke this method before performing a modification - * test. - */ - public void resetFull() { - this.collection = makeFullCollection(); - this.confirmed = makeConfirmedFullCollection(); - } - - //----------------------------------------------------------------------- - /** - * Returns a confirmed empty collection. - * For instance, an {@link java.util.ArrayList} for lists or a - * {@link java.util.HashSet} for sets. - * - * @return a confirmed empty collection - */ - public abstract Collection makeConfirmedCollection(); - - /** - * Returns a confirmed full collection. - * For instance, an {@link java.util.ArrayList} for lists or a - * {@link java.util.HashSet} for sets. The returned collection - * should contain the elements returned by {@link #getFullElements()}. - * - * @return a confirmed full collection - */ - public abstract Collection makeConfirmedFullCollection(); - - /** - * Return a new, empty {@link Collection} to be used for testing. - */ - public abstract Collection makeCollection(); - - /** - * Returns a full collection to be used for testing. The collection - * returned by this method should contain every element returned by - * {@link #getFullElements()}. The default implementation, in fact, - * simply invokes {@code addAll} on an empty collection with - * the results of {@link #getFullElements()}. Override this default - * if your collection doesn't support addAll. - */ - public Collection makeFullCollection() { - Collection c = makeCollection(); - c.addAll(Arrays.asList(getFullElements())); - return c; - } - - /** - * Returns an empty collection for Object tests. - */ - public Object makeObject() { - return makeCollection(); - } - - /** - * Creates a new Map Entry that is independent of the first and the map. - */ - public Map.Entry cloneMapEntry(Map.Entry entry) { - HashMap map = new HashMap(); - map.put(entry.getKey(), entry.getValue()); - return (Map.Entry) map.entrySet().iterator().next(); - } - - //----------------------------------------------------------------------- - /** - * Returns an array of objects that are contained in a collection - * produced by {@link #makeFullCollection()}. Every element in the - * returned array must be an element in a full collection.

- * The default implementation returns a heterogenous array of - * objects with some duplicates. null is added if allowed. - * Override if you require specific testing elements. Note that if you - * override {@link #makeFullCollection()}, you must override - * this method to reflect the contents of a full collection. - */ - public Object[] getFullElements() { - if (isNullSupported()) { - ArrayList list = new ArrayList(); - list.addAll(Arrays.asList(getFullNonNullElements())); - list.add(4, null); - return list.toArray(); - } else { - return (Object[]) getFullNonNullElements().clone(); - } - } - - /** - * Returns an array of elements that are not contained in a - * full collection. Every element in the returned array must - * not exist in a collection returned by {@link #makeFullCollection()}. - * The default implementation returns a heterogenous array of elements - * without null. Note that some of the tests add these elements - * to an empty or full collection, so if your collection restricts - * certain kinds of elements, you should override this method. - */ - public Object[] getOtherElements() { - return getOtherNonNullElements(); - } - - //----------------------------------------------------------------------- - /** - * Returns a list of elements suitable for return by - * {@link #getFullElements()}. The array returned by this method - * does not include null, but does include a variety of objects - * of different types. Override getFullElements to return - * the results of this method if your collection does not support - * the null element. - */ - public Object[] getFullNonNullElements() { - return new Object[] { - new String(""), - new String("One"), - new Integer(2), - "Three", - new Integer(4), - "One", - new Double(5), - new Float(6), - "Seven", - "Eight", - new String("Nine"), - new Integer(10), - new Short((short)11), - new Long(12), - "Thirteen", - "14", - "15", - new Byte((byte)16) - }; - } - - /** - * Returns the default list of objects returned by - * {@link #getOtherElements()}. Includes many objects - * of different types. - */ - public Object[] getOtherNonNullElements() { - return new Object[] { - new Integer(0), - new Float(0), - new Double(0), - "Zero", - new Short((short)0), - new Byte((byte)0), - new Long(0), - new Character('\u0000'), - "0" - }; - } - - /** - * Returns a list of string elements suitable for return by - * {@link #getFullElements()}. Override getFullElements to return - * the results of this method if your collection does not support - * heterogenous elements or the null element. - */ - public Object[] getFullNonNullStringElements() { - return new Object[] { - "If","the","dull","substance","of","my","flesh","were","thought", - "Injurious","distance","could","not","stop","my","way", - }; - } - - /** - * Returns a list of string elements suitable for return by - * {@link #getOtherElements()}. Override getOtherElements to return - * the results of this method if your collection does not support - * heterogenous elements or the null element. - */ - public Object[] getOtherNonNullStringElements() { - return new Object[] { - "For","then","despite",/* of */"space","I","would","be","brought", - "From","limits","far","remote","where","thou","dost","stay" - }; - } - - // Tests - //----------------------------------------------------------------------- - /** - * Tests {@link Collection#add(Object)}. - */ - public void testCollectionAdd() { - if (!isAddSupported()) return; - - Object[] elements = getFullElements(); - for (int i = 0; i < elements.length; i++) { - resetEmpty(); - boolean r = collection.add(elements[i]); - confirmed.add(elements[i]); - verifyAll(); - assertTrue("Empty collection changed after add", r); - assertEquals("Collection size is 1 after first add", 1, collection.size()); - } - - resetEmpty(); - int size = 0; - for (int i = 0; i < elements.length; i++) { - boolean r = collection.add(elements[i]); - confirmed.add(elements[i]); - verifyAll(); - if (r) size++; - assertEquals("Collection size should grow after add", - size, collection.size()); - assertTrue("Collection should contain added element", - collection.contains(elements[i])); - } - } - - - /** - * Tests {@link Collection#addAll(Collection)}. - */ - public void testCollectionAddAll() { - if (!isAddSupported()) return; - - resetEmpty(); - Object[] elements = getFullElements(); - boolean r = collection.addAll(Arrays.asList(elements)); - confirmed.addAll(Arrays.asList(elements)); - verifyAll(); - assertTrue("Empty collection should change after addAll", r); - for (int i = 0; i < elements.length; i++) { - assertTrue("Collection should contain added element", - collection.contains(elements[i])); - } - - resetFull(); - int size = collection.size(); - elements = getOtherElements(); - r = collection.addAll(Arrays.asList(elements)); - confirmed.addAll(Arrays.asList(elements)); - verifyAll(); - assertTrue("Full collection should change after addAll", r); - for (int i = 0; i < elements.length; i++) { - assertTrue("Full collection should contain added element", - collection.contains(elements[i])); - } - assertEquals("Size should increase after addAll", - size + elements.length, collection.size()); - - resetFull(); - size = collection.size(); - r = collection.addAll(Arrays.asList(getFullElements())); - confirmed.addAll(Arrays.asList(getFullElements())); - verifyAll(); - if (r) { - assertTrue("Size should increase if addAll returns true", - size < collection.size()); - } else { - assertEquals("Size should not change if addAll returns false", - size, collection.size()); - } - } - - - /** - * If {@link #isAddSupported()} returns false, tests that add operations - * raise UnsupportedOperationException. - */ - public void testUnsupportedAdd() { - if (isAddSupported()) return; - - resetEmpty(); - try { - collection.add(new Object()); - fail("Emtpy collection should not support add."); - } catch (UnsupportedOperationException e) { - // expected - } - // make sure things didn't change even if the expected exception was - // thrown. - verifyAll(); - - try { - collection.addAll(Arrays.asList(getFullElements())); - fail("Emtpy collection should not support addAll."); - } catch (UnsupportedOperationException e) { - // expected - } - // make sure things didn't change even if the expected exception was - // thrown. - verifyAll(); - - resetFull(); - try { - collection.add(new Object()); - fail("Full collection should not support add."); - } catch (UnsupportedOperationException e) { - // expected - } - // make sure things didn't change even if the expected exception was - // thrown. - verifyAll(); - - try { - collection.addAll(Arrays.asList(getOtherElements())); - fail("Full collection should not support addAll."); - } catch (UnsupportedOperationException e) { - // expected - } - // make sure things didn't change even if the expected exception was - // thrown. - verifyAll(); - } - - - /** - * Test {@link Collection#clear()}. - */ - public void testCollectionClear() { - if (!isRemoveSupported()) return; - - resetEmpty(); - collection.clear(); // just to make sure it doesn't raise anything - verifyAll(); - - resetFull(); - collection.clear(); - confirmed.clear(); - verifyAll(); - } - - - /** - * Tests {@link Collection#contains(Object)}. - */ - public void testCollectionContains() { - Object[] elements; - - resetEmpty(); - elements = getFullElements(); - for(int i = 0; i < elements.length; i++) { - assertTrue("Empty collection shouldn't contain element[" + i + "]", - !collection.contains(elements[i])); - } - // make sure calls to "contains" don't change anything - verifyAll(); - - elements = getOtherElements(); - for(int i = 0; i < elements.length; i++) { - assertTrue("Empty collection shouldn't contain element[" + i + "]", - !collection.contains(elements[i])); - } - // make sure calls to "contains" don't change anything - verifyAll(); - - resetFull(); - elements = getFullElements(); - for(int i = 0; i < elements.length; i++) { - assertTrue("Full collection should contain element[" + i + "]", - collection.contains(elements[i])); - } - // make sure calls to "contains" don't change anything - verifyAll(); - - resetFull(); - elements = getOtherElements(); - for(int i = 0; i < elements.length; i++) { - assertTrue("Full collection shouldn't contain element", - !collection.contains(elements[i])); - } - } - - - /** - * Tests {@link Collection#containsAll(Collection)}. - */ - public void testCollectionContainsAll() { - resetEmpty(); - Collection col = new HashSet(); - assertTrue("Every Collection should contain all elements of an " + - "empty Collection.", collection.containsAll(col)); - col.addAll(Arrays.asList(getOtherElements())); - assertTrue("Empty Collection shouldn't contain all elements of " + - "a non-empty Collection.", !collection.containsAll(col)); - // make sure calls to "containsAll" don't change anything - verifyAll(); - - resetFull(); - assertTrue("Full collection shouldn't contain other elements", - !collection.containsAll(col)); - - col.clear(); - col.addAll(Arrays.asList(getFullElements())); - assertTrue("Full collection should containAll full elements", - collection.containsAll(col)); - // make sure calls to "containsAll" don't change anything - verifyAll(); - - int min = (getFullElements().length < 2 ? 0 : 2); - int max = (getFullElements().length == 1 ? 1 : - (getFullElements().length <= 5 ? getFullElements().length - 1 : 5)); - col = Arrays.asList(getFullElements()).subList(min, max); - assertTrue("Full collection should containAll partial full " + - "elements", collection.containsAll(col)); - assertTrue("Full collection should containAll itself", - collection.containsAll(collection)); - // make sure calls to "containsAll" don't change anything - verifyAll(); - - col = new ArrayList(); - col.addAll(Arrays.asList(getFullElements())); - col.addAll(Arrays.asList(getFullElements())); - assertTrue("Full collection should containAll duplicate full " + - "elements", collection.containsAll(col)); - - // make sure calls to "containsAll" don't change anything - verifyAll(); - } - - /** - * Tests {@link Collection#isEmpty()}. - */ - public void testCollectionIsEmpty() { - resetEmpty(); - assertEquals("New Collection should be empty.", - true, collection.isEmpty()); - // make sure calls to "isEmpty() don't change anything - verifyAll(); - - resetFull(); - assertEquals("Full collection shouldn't be empty", - false, collection.isEmpty()); - // make sure calls to "isEmpty() don't change anything - verifyAll(); - } - - - /** - * Tests the read-only functionality of {@link Collection#iterator()}. - */ - public void testCollectionIterator() { - resetEmpty(); - Iterator it1 = collection.iterator(); - assertEquals("Iterator for empty Collection shouldn't have next.", - false, it1.hasNext()); - try { - it1.next(); - fail("Iterator at end of Collection should throw " + - "NoSuchElementException when next is called."); - } catch(NoSuchElementException e) { - // expected - } - // make sure nothing has changed after non-modification - verifyAll(); - - resetFull(); - it1 = collection.iterator(); - for (int i = 0; i < collection.size(); i++) { - assertTrue("Iterator for full collection should haveNext", - it1.hasNext()); - it1.next(); - } - assertTrue("Iterator should be finished", !it1.hasNext()); - - ArrayList list = new ArrayList(); - it1 = collection.iterator(); - for (int i = 0; i < collection.size(); i++) { - Object next = it1.next(); - assertTrue("Collection should contain element returned by " + - "its iterator", collection.contains(next)); - list.add(next); - } - try { - it1.next(); - fail("iterator.next() should raise NoSuchElementException " + - "after it finishes"); - } catch (NoSuchElementException e) { - // expected - } - // make sure nothing has changed after non-modification - verifyAll(); - } - - - /** - * Tests removals from {@link Collection#iterator()}. - */ - public void testCollectionIteratorRemove() { - if (!isRemoveSupported()) return; - - resetEmpty(); - try { - collection.iterator().remove(); - fail("New iterator.remove should raise IllegalState"); - } catch (IllegalStateException e) { - // expected - } - verifyAll(); - - try { - Iterator iter = collection.iterator(); - iter.hasNext(); - iter.remove(); - fail("New iterator.remove should raise IllegalState " + - "even after hasNext"); - } catch (IllegalStateException e) { - // expected - } - verifyAll(); - - resetFull(); - int size = collection.size(); - Iterator iter = collection.iterator(); - while (iter.hasNext()) { - Object o = iter.next(); - // TreeMap reuses the Map Entry, so the verify below fails - // Clone it here if necessary - if (o instanceof Map.Entry) { - o = cloneMapEntry((Map.Entry) o); - } - iter.remove(); - - // if the elements aren't distinguishable, we can just remove a - // matching element from the confirmed collection and verify - // contents are still the same. Otherwise, we don't have the - // ability to distinguish the elements and determine which to - // remove from the confirmed collection (in which case, we don't - // verify because we don't know how). - // - // see areEqualElementsDistinguishable() - if(!areEqualElementsDistinguishable()) { - confirmed.remove(o); - verifyAll(); - } - - size--; - assertEquals("Collection should shrink by one after " + - "iterator.remove", size, collection.size()); - } - assertTrue("Collection should be empty after iterator purge", - collection.isEmpty()); - - resetFull(); - iter = collection.iterator(); - iter.next(); - iter.remove(); - try { - iter.remove(); - fail("Second iter.remove should raise IllegalState"); - } catch (IllegalStateException e) { - // expected - } - } - - - /** - * Tests {@link Collection#remove(Object)}. - */ - public void testCollectionRemove() { - if (!isRemoveSupported()) return; - - resetEmpty(); - Object[] elements = getFullElements(); - for (int i = 0; i < elements.length; i++) { - assertTrue("Shouldn't remove nonexistent element", - !collection.remove(elements[i])); - verifyAll(); - } - - Object[] other = getOtherElements(); - - resetFull(); - for (int i = 0; i < other.length; i++) { - assertTrue("Shouldn't remove nonexistent other element", - !collection.remove(other[i])); - verifyAll(); - } - - int size = collection.size(); - for (int i = 0; i < elements.length; i++) { - resetFull(); - assertTrue("Collection should remove extant element: " + elements[i], - collection.remove(elements[i])); - - // if the elements aren't distinguishable, we can just remove a - // matching element from the confirmed collection and verify - // contents are still the same. Otherwise, we don't have the - // ability to distinguish the elements and determine which to - // remove from the confirmed collection (in which case, we don't - // verify because we don't know how). - // - // see areEqualElementsDistinguishable() - if(!areEqualElementsDistinguishable()) { - confirmed.remove(elements[i]); - verifyAll(); - } - - assertEquals("Collection should shrink after remove", - size - 1, collection.size()); - } - } - - - /** - * Tests {@link Collection#removeAll(Collection)}. - */ - public void testCollectionRemoveAll() { - if (!isRemoveSupported()) return; - - resetEmpty(); - assertTrue("Emtpy collection removeAll should return false for " + - "empty input", - !collection.removeAll(Collections.EMPTY_SET)); - verifyAll(); - - assertTrue("Emtpy collection removeAll should return false for " + - "nonempty input", - !collection.removeAll(new ArrayList(collection))); - verifyAll(); - - resetFull(); - assertTrue("Full collection removeAll should return false for " + - "empty input", - !collection.removeAll(Collections.EMPTY_SET)); - verifyAll(); - - assertTrue("Full collection removeAll should return false for other elements", - !collection.removeAll(Arrays.asList(getOtherElements()))); - verifyAll(); - - assertTrue("Full collection removeAll should return true for full elements", - collection.removeAll(new HashSet(collection))); - confirmed.removeAll(new HashSet(confirmed)); - verifyAll(); - - resetFull(); - int size = collection.size(); - int min = (getFullElements().length < 2 ? 0 : 2); - int max = (getFullElements().length == 1 ? 1 : - (getFullElements().length <= 5 ? getFullElements().length - 1 : 5)); - Collection all = Arrays.asList(getFullElements()).subList(min, max); - assertTrue("Full collection removeAll should work", - collection.removeAll(all)); - confirmed.removeAll(all); - verifyAll(); - - assertTrue("Collection should shrink after removeAll", - collection.size() < size); - Iterator iter = all.iterator(); - while (iter.hasNext()) { - assertTrue("Collection shouldn't contain removed element", - !collection.contains(iter.next())); - } - } - - - /** - * Tests {@link Collection#retainAll(Collection)}. - */ - public void testCollectionRetainAll() { - if (!isRemoveSupported()) return; - - resetEmpty(); - List elements = Arrays.asList(getFullElements()); - List other = Arrays.asList(getOtherElements()); - - assertTrue("Empty retainAll() should return false", - !collection.retainAll(Collections.EMPTY_SET)); - verifyAll(); - - assertTrue("Empty retainAll() should return false", - !collection.retainAll(elements)); - verifyAll(); - - resetFull(); - assertTrue("Collection should change from retainAll empty", - collection.retainAll(Collections.EMPTY_SET)); - confirmed.retainAll(Collections.EMPTY_SET); - verifyAll(); - - resetFull(); - assertTrue("Collection changed from retainAll other", - collection.retainAll(other)); - confirmed.retainAll(other); - verifyAll(); - - resetFull(); - int size = collection.size(); - assertTrue("Collection shouldn't change from retainAll elements", - !collection.retainAll(elements)); - verifyAll(); - assertEquals("Collection size shouldn't change", size, - collection.size()); - - if (getFullElements().length > 1) { - resetFull(); - size = collection.size(); - int min = (getFullElements().length < 2 ? 0 : 2); - int max = (getFullElements().length <= 5 ? getFullElements().length - 1 : 5); - assertTrue("Collection should changed by partial retainAll", - collection.retainAll(elements.subList(min, max))); - confirmed.retainAll(elements.subList(min, max)); - verifyAll(); - - Iterator iter = collection.iterator(); - while (iter.hasNext()) { - assertTrue("Collection only contains retained element", - elements.subList(min, max).contains(iter.next())); - } - } - - resetFull(); - HashSet set = new HashSet(elements); - size = collection.size(); - assertTrue("Collection shouldn't change from retainAll without " + - "duplicate elements", !collection.retainAll(set)); - verifyAll(); - assertEquals("Collection size didn't change from nonduplicate " + - "retainAll", size, collection.size()); - } - - - /** - * Tests {@link Collection#size()}. - */ - public void testCollectionSize() { - resetEmpty(); - assertEquals("Size of new Collection is 0.", 0, collection.size()); - - resetFull(); - assertTrue("Size of full collection should be greater than zero", - collection.size() > 0); - } - - - /** - * Tests {@link Collection#toArray()}. - */ - public void testCollectionToArray() { - resetEmpty(); - assertEquals("Empty Collection should return empty array for toArray", - 0, collection.toArray().length); - - resetFull(); - Object[] array = collection.toArray(); - assertEquals("Full collection toArray should be same size as " + - "collection", array.length, collection.size()); - Object[] confirmedArray = confirmed.toArray(); - assertEquals("length of array from confirmed collection should " + - "match the length of the collection's array", - confirmedArray.length, array.length); - boolean[] matched = new boolean[array.length]; - - for (int i = 0; i < array.length; i++) { - assertTrue("Collection should contain element in toArray", - collection.contains(array[i])); - - boolean match = false; - // find a match in the confirmed array - for(int j = 0; j < array.length; j++) { - // skip already matched - if(matched[j]) continue; - if(array[i] == confirmedArray[j] || - (array[i] != null && array[i].equals(confirmedArray[j]))) { - matched[j] = true; - match = true; - break; - } - } - if(!match) { - fail("element " + i + " in returned array should be found " + - "in the confirmed collection's array"); - } - } - for(int i = 0; i < matched.length; i++) { - assertEquals("Collection should return all its elements in " + - "toArray", true, matched[i]); - } - } - - - /** - * Tests {@link Collection#toArray(Object[])}. - */ - public void testCollectionToArray2() { - resetEmpty(); - Object[] a = new Object[] { new Object(), null, null }; - Object[] array = collection.toArray(a); - assertEquals("Given array shouldn't shrink", array, a); - assertEquals("Last element should be set to null", a[0], null); - verifyAll(); - - resetFull(); - try { - array = collection.toArray(new Void[0]); - fail("toArray(new Void[0]) should raise ArrayStore"); - } catch (ArrayStoreException e) { - // expected - } - verifyAll(); - - try { - array = collection.toArray(null); - fail("toArray(null) should raise NPE"); - } catch (NullPointerException e) { - // expected - } - verifyAll(); - - array = collection.toArray(new Object[0]); - a = collection.toArray(); - assertEquals("toArrays should be equal", - Arrays.asList(array), Arrays.asList(a)); - - // Figure out if they're all the same class - // TODO: It'd be nicer to detect a common superclass - HashSet classes = new HashSet(); - for (int i = 0; i < array.length; i++) { - classes.add((array[i] == null) ? null : array[i].getClass()); - } - if (classes.size() > 1) return; - - Class cl = (Class)classes.iterator().next(); - if (Map.Entry.class.isAssignableFrom(cl)) { // check needed for protective cases like Predicated/Unmod map entrySet - cl = Map.Entry.class; - } - a = (Object[])Array.newInstance(cl, 0); - array = collection.toArray(a); - assertEquals("toArray(Object[]) should return correct array type", - a.getClass(), array.getClass()); - assertEquals("type-specific toArrays should be equal", - Arrays.asList(array), - Arrays.asList(collection.toArray())); - verifyAll(); - } - - - /** - * Tests {@code toString} on a collection. - */ - public void testCollectionToString() { - resetEmpty(); - assertTrue("toString shouldn't return null", - collection.toString() != null); - - resetFull(); - assertTrue("toString shouldn't return null", - collection.toString() != null); - } - - - /** - * If isRemoveSupported() returns false, tests to see that remove - * operations raise an UnsupportedOperationException. - */ - public void testUnsupportedRemove() { - if (isRemoveSupported()) return; - - resetEmpty(); - try { - collection.clear(); - fail("clear should raise UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // expected - } - verifyAll(); - - try { - collection.remove(null); - fail("remove should raise UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // expected - } - verifyAll(); - - try { - collection.removeAll(null); - fail("removeAll should raise UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // expected - } - verifyAll(); - - try { - collection.retainAll(null); - fail("removeAll should raise UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // expected - } - verifyAll(); - - resetFull(); - try { - Iterator iterator = collection.iterator(); - iterator.next(); - iterator.remove(); - fail("iterator.remove should raise UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // expected - } - verifyAll(); - - } - - - /** - * Tests that the collection's iterator is fail-fast. - */ - public void testCollectionIteratorFailFast() { - if (!isFailFastSupported()) return; - - if (isAddSupported()) { - resetFull(); - try { - Iterator iter = collection.iterator(); - Object o = getOtherElements()[0]; - collection.add(o); - confirmed.add(o); - iter.next(); - fail("next after add should raise ConcurrentModification"); - } catch (ConcurrentModificationException e) { - // expected - } - verifyAll(); - - resetFull(); - try { - Iterator iter = collection.iterator(); - collection.addAll(Arrays.asList(getOtherElements())); - confirmed.addAll(Arrays.asList(getOtherElements())); - iter.next(); - fail("next after addAll should raise ConcurrentModification"); - } catch (ConcurrentModificationException e) { - // expected - } - verifyAll(); - } - - if (!isRemoveSupported()) return; - - resetFull(); - try { - Iterator iter = collection.iterator(); - collection.clear(); - iter.next(); - fail("next after clear should raise ConcurrentModification"); - } catch (ConcurrentModificationException e) { - // expected - } catch (NoSuchElementException e) { - // (also legal given spec) - } - - resetFull(); - try { - Iterator iter = collection.iterator(); - collection.remove(getFullElements()[0]); - iter.next(); - fail("next after remove should raise ConcurrentModification"); - } catch (ConcurrentModificationException e) { - // expected - } - - resetFull(); - try { - Iterator iter = collection.iterator(); - List sublist = Arrays.asList(getFullElements()).subList(2,5); - collection.removeAll(sublist); - iter.next(); - fail("next after removeAll should raise ConcurrentModification"); - } catch (ConcurrentModificationException e) { - // expected - } - - resetFull(); - try { - Iterator iter = collection.iterator(); - List sublist = Arrays.asList(getFullElements()).subList(2,5); - collection.retainAll(sublist); - iter.next(); - fail("next after retainAll should raise ConcurrentModification"); - } catch (ConcurrentModificationException e) { - // expected - } - } - - /* - public void testSerializeDeserializeThenCompare() throws Exception { - Object obj = makeCollection(); - if (obj instanceof Serializable && isTestSerialization()) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(buffer); - out.writeObject(obj); - out.close(); - - ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); - Object dest = in.readObject(); - in.close(); - if (isEqualsCheckable()) { - assertEquals("obj != deserialize(serialize(obj)) - EMPTY Collection", obj, dest); - } - } - obj = makeFullCollection(); - if (obj instanceof Serializable && isTestSerialization()) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(buffer); - out.writeObject(obj); - out.close(); - - ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); - Object dest = in.readObject(); - in.close(); - if (isEqualsCheckable()) { - assertEquals("obj != deserialize(serialize(obj)) - FULL Collection", obj, dest); - } - } - } - */ - -} +/* + * 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.twelvemonkeys.util; + +import org.junit.Test; + +import java.lang.reflect.Array; +import java.util.*; + +import static org.junit.Assert.*; + +/** + * Abstract test class for {@link java.util.Collection} methods and contracts. + *

+ * You should create a concrete subclass of this class to test any custom + * {@link Collection} implementation. At minimum, you'll have to + * implement the {@link #makeCollection()} method. You might want to + * override some of the additional public methods as well: + *

+ * Element Population Methods + *

+ * Override these if your collection restricts what kind of elements are + * allowed (for instance, if {@code null} is not permitted): + *

    + *
  • {@link #getFullElements()} + *
  • {@link #getOtherElements()} + *
+ *

+ * Supported Operation Methods + *

+ * Override these if your collection doesn't support certain operations: + *

    + *
  • {@link #isAddSupported()} + *
  • {@link #isRemoveSupported()} + *
  • {@link #areEqualElementsDistinguishable()} + *
  • {@link #isNullSupported()} + *
  • {@link #isFailFastSupported()} + *
+ *

+ * Fixture Methods + *

+ * Fixtures are used to verify that the the operation results in correct state + * for the collection. Basically, the operation is performed against your + * collection implementation, and an identical operation is performed against a + * confirmed collection implementation. A confirmed collection + * implementation is something like {@code java.util.ArrayList}, which is + * known to conform exactly to its collection interface's contract. After the + * operation takes place on both your collection implementation and the + * confirmed collection implementation, the two collections are compared to see + * if their state is identical. The comparison is usually much more involved + * than a simple {@code equals} test. This verification is used to ensure + * proper modifications are made along with ensuring that the collection does + * not change when read-only modifications are made. + *

+ * The {@link #collection} field holds an instance of your collection + * implementation; the {@link #confirmed} field holds an instance of the + * confirmed collection implementation. The {@link #resetEmpty()} and + * {@link #resetFull()} methods set these fields to empty or full collections, + * so that tests can proceed from a known state. + *

+ * After a modification operation to both {@link #collection} and + * {@link #confirmed}, the {@link #verifyAll()} method is invoked to compare + * the results. You may want to override {@link # verifyAll ()} to perform + * additional verifications. For instance, when testing the collection + * views of a map, {@link AbstractTestMap} would override {@link # verifyAll ()} to make + * sure the map is changed after the collection view is changed. + *

+ * If you're extending this class directly, you will have to provide + * implementations for the following: + *

    + *
  • {@link #makeConfirmedCollection()} + *
  • {@link #makeConfirmedFullCollection()} + *
+ *

+ * Those methods should provide a confirmed collection implementation + * that's compatible with your collection implementation. + *

+ * If you're extending {@link AbstractTestList}, {@link SetAbstractTest}, + * or {@link AbstractTestBag}, you probably don't have to worry about the + * above methods, because those three classes already override the methods + * to provide standard JDK confirmed collections.

+ *

+ * Other notes + *

+ * If your {@link Collection} fails one of these tests by design, + * you may still use this base set of cases. Simply override the + * test case (method) your {@link Collection} fails. + * + * @version $Revision: #2 $ $Date: 2008/07/15 $ + * + * @author Rodney Waldhoff + * @author Paul Jack + * @author Michael A. Smith + * @author Neil O'Toole + * @author Stephen Colebourne + */ +public abstract class CollectionAbstractTest extends ObjectAbstractTest { + + // + // NOTE: + // + // Collection doesn't define any semantics for equals, and recommends you + // use reference-based default behavior of Object.equals. (And a test for + // that already exists in ObjectAbstractTestCase). Tests for equality of lists, sets + // and bags will have to be written in test subclasses. Thus, there is no + // tests on Collection.equals nor any for Collection.hashCode. + // + + + // These fields are used by reset() and verifyAll(), and any test + // method that tests a modification. + + /** + * A collection instance that will be used for testing. + */ + public Collection collection; + + /** + * Confirmed collection. This is an instance of a collection that is + * confirmed to conform exactly to the java.util.Collection contract. + * Modification operations are tested by performing a mod on your + * collection, performing the exact same mod on an equivalent confirmed + * collection, and then calling verifyAll() to make sure your collection + * still matches the confirmed collection. + */ + public Collection confirmed; + + //----------------------------------------------------------------------- + /** + * Specifies whether equal elements in the collection are, in fact, + * distinguishable with information not readily available. That is, if a + * particular value is to be removed from the collection, then there is + * one and only one value that can be removed, even if there are other + * elements which are equal to it. + * + *

In most collection cases, elements are not distinguishable (equal is + * equal), thus this method defaults to return false. In some cases, + * however, they are. For example, the collection returned from the map's + * values() collection view are backed by the map, so while there may be + * two values that are equal, their associated keys are not. Since the + * keys are distinguishable, the values are. + * + *

This flag is used to skip some verifications for iterator.remove() + * where it is impossible to perform an equivalent modification on the + * confirmed collection because it is not possible to determine which + * value in the confirmed collection to actually remove. Tests that + * override the default (i.e. where equal elements are distinguishable), + * should provide additional tests on iterator.remove() to make sure the + * proper elements are removed when remove() is called on the iterator. + **/ + public boolean areEqualElementsDistinguishable() { + return false; + } + + /** + * Returns true if the collections produced by + * {@link #makeCollection()} and {@link #makeFullCollection()} + * support the {@code add} and {@code addAll} + * operations.

+ * Default implementation returns true. Override if your collection + * class does not support add or addAll. + */ + public boolean isAddSupported() { + return true; + } + + /** + * Returns true if the collections produced by + * {@link #makeCollection()} and {@link #makeFullCollection()} + * support the {@code remove}, {@code removeAll}, + * {@code retainAll}, {@code clear} and + * {@code iterator().remove()} methods. + * Default implementation returns true. Override if your collection + * class does not support removal operations. + */ + public boolean isRemoveSupported() { + return true; + } + + /** + * Returns true to indicate that the collection supports holding null. + * The default implementation returns true; + */ + public boolean isNullSupported() { + return true; + } + + /** + * Returns true to indicate that the collection supports fail fast iterators. + * The default implementation returns true; + */ + public boolean isFailFastSupported() { + return false; + } + + /** + * Returns true to indicate that the collection supports equals() comparisons. + * This implementation returns false; + */ + public boolean isEqualsCheckable() { + return false; + } + + //----------------------------------------------------------------------- + /** + * Verifies that {@link #collection} and {@link #confirmed} have + * identical state. + */ + public void verifyAll() { + int confirmedSize = confirmed.size(); + assertEquals("Collection size should match confirmed collection's", + confirmedSize, collection.size()); + assertEquals("Collection isEmpty() result should match confirmed " + + " collection's", + confirmed.isEmpty(), collection.isEmpty()); + + // verify the collections are the same by attempting to match each + // object in the collection and confirmed collection. To account for + // duplicates and differing orders, each confirmed element is copied + // into an array and a flag is maintained for each element to determine + // whether it has been matched once and only once. If all elements in + // the confirmed collection are matched once and only once and there + // aren't any elements left to be matched in the collection, + // verification is a success. + + // copy each collection value into an array + Object[] confirmedValues = new Object[confirmedSize]; + + Iterator iter; + + iter = confirmed.iterator(); + int pos = 0; + while(iter.hasNext()) { + confirmedValues[pos++] = iter.next(); + } + + // allocate an array of boolean flags for tracking values that have + // been matched once and only once. + boolean[] matched = new boolean[confirmedSize]; + + // now iterate through the values of the collection and try to match + // the value with one in the confirmed array. + iter = collection.iterator(); + while(iter.hasNext()) { + Object o = iter.next(); + boolean match = false; + for(int i = 0; i < confirmedSize; i++) { + if(matched[i]) { + // skip values already matched + continue; + } + if(o == confirmedValues[i] || + (o != null && o.equals(confirmedValues[i]))) { + // values matched + matched[i] = true; + match = true; + break; + } + } + // no match found! + if(!match) { + fail("Collection should not contain a value that the " + + "confirmed collection does not have: " + o + + "\nTest: " + collection + "\nReal: " + confirmed); + } + } + + // make sure there aren't any unmatched values + for(int i = 0; i < confirmedSize; i++) { + if(!matched[i]) { + // the collection didn't match all the confirmed values + fail("Collection should contain all values that are in the confirmed collection" + + "\nTest: " + collection + "\nReal: " + confirmed); + } + } + } + + //----------------------------------------------------------------------- + /** + * Resets the {@link #collection} and {@link #confirmed} fields to empty + * collections. Invoke this method before performing a modification + * test. + */ + public void resetEmpty() { + this.collection = makeCollection(); + this.confirmed = makeConfirmedCollection(); + } + + /** + * Resets the {@link #collection} and {@link #confirmed} fields to full + * collections. Invoke this method before performing a modification + * test. + */ + public void resetFull() { + this.collection = makeFullCollection(); + this.confirmed = makeConfirmedFullCollection(); + } + + //----------------------------------------------------------------------- + /** + * Returns a confirmed empty collection. + * For instance, an {@link java.util.ArrayList} for lists or a + * {@link java.util.HashSet} for sets. + * + * @return a confirmed empty collection + */ + public abstract Collection makeConfirmedCollection(); + + /** + * Returns a confirmed full collection. + * For instance, an {@link java.util.ArrayList} for lists or a + * {@link java.util.HashSet} for sets. The returned collection + * should contain the elements returned by {@link #getFullElements()}. + * + * @return a confirmed full collection + */ + public abstract Collection makeConfirmedFullCollection(); + + /** + * Return a new, empty {@link Collection} to be used for testing. + */ + public abstract Collection makeCollection(); + + /** + * Returns a full collection to be used for testing. The collection + * returned by this method should contain every element returned by + * {@link #getFullElements()}. The default implementation, in fact, + * simply invokes {@code addAll} on an empty collection with + * the results of {@link #getFullElements()}. Override this default + * if your collection doesn't support addAll. + */ + public Collection makeFullCollection() { + Collection c = makeCollection(); + c.addAll(Arrays.asList(getFullElements())); + return c; + } + + /** + * Returns an empty collection for Object tests. + */ + public Object makeObject() { + return makeCollection(); + } + + /** + * Creates a new Map Entry that is independent of the first and the map. + */ + public Map.Entry cloneMapEntry(Map.Entry entry) { + HashMap map = new HashMap(); + map.put(entry.getKey(), entry.getValue()); + return (Map.Entry) map.entrySet().iterator().next(); + } + + //----------------------------------------------------------------------- + /** + * Returns an array of objects that are contained in a collection + * produced by {@link #makeFullCollection()}. Every element in the + * returned array must be an element in a full collection.

+ * The default implementation returns a heterogenous array of + * objects with some duplicates. null is added if allowed. + * Override if you require specific testing elements. Note that if you + * override {@link #makeFullCollection()}, you must override + * this method to reflect the contents of a full collection. + */ + public Object[] getFullElements() { + if (isNullSupported()) { + ArrayList list = new ArrayList(); + list.addAll(Arrays.asList(getFullNonNullElements())); + list.add(4, null); + return list.toArray(); + } else { + return (Object[]) getFullNonNullElements().clone(); + } + } + + /** + * Returns an array of elements that are not contained in a + * full collection. Every element in the returned array must + * not exist in a collection returned by {@link #makeFullCollection()}. + * The default implementation returns a heterogenous array of elements + * without null. Note that some of the tests add these elements + * to an empty or full collection, so if your collection restricts + * certain kinds of elements, you should override this method. + */ + public Object[] getOtherElements() { + return getOtherNonNullElements(); + } + + //----------------------------------------------------------------------- + /** + * Returns a list of elements suitable for return by + * {@link #getFullElements()}. The array returned by this method + * does not include null, but does include a variety of objects + * of different types. Override getFullElements to return + * the results of this method if your collection does not support + * the null element. + */ + public Object[] getFullNonNullElements() { + return new Object[] { + new String(""), + new String("One"), + new Integer(2), + "Three", + new Integer(4), + "One", + new Double(5), + new Float(6), + "Seven", + "Eight", + new String("Nine"), + new Integer(10), + new Short((short)11), + new Long(12), + "Thirteen", + "14", + "15", + new Byte((byte)16) + }; + } + + /** + * Returns the default list of objects returned by + * {@link #getOtherElements()}. Includes many objects + * of different types. + */ + public Object[] getOtherNonNullElements() { + return new Object[] { + new Integer(0), + new Float(0), + new Double(0), + "Zero", + new Short((short)0), + new Byte((byte)0), + new Long(0), + new Character('\u0000'), + "0" + }; + } + + /** + * Returns a list of string elements suitable for return by + * {@link #getFullElements()}. Override getFullElements to return + * the results of this method if your collection does not support + * heterogenous elements or the null element. + */ + public Object[] getFullNonNullStringElements() { + return new Object[] { + "If","the","dull","substance","of","my","flesh","were","thought", + "Injurious","distance","could","not","stop","my","way", + }; + } + + /** + * Returns a list of string elements suitable for return by + * {@link #getOtherElements()}. Override getOtherElements to return + * the results of this method if your collection does not support + * heterogenous elements or the null element. + */ + public Object[] getOtherNonNullStringElements() { + return new Object[] { + "For","then","despite",/* of */"space","I","would","be","brought", + "From","limits","far","remote","where","thou","dost","stay" + }; + } + + // Tests + //----------------------------------------------------------------------- + /** + * Tests {@link Collection#add(Object)}. + */ + @Test + public void testCollectionAdd() { + if (!isAddSupported()) return; + + Object[] elements = getFullElements(); + for (int i = 0; i < elements.length; i++) { + resetEmpty(); + boolean r = collection.add(elements[i]); + confirmed.add(elements[i]); + verifyAll(); + assertTrue("Empty collection changed after add", r); + assertEquals("Collection size is 1 after first add", 1, collection.size()); + } + + resetEmpty(); + int size = 0; + for (int i = 0; i < elements.length; i++) { + boolean r = collection.add(elements[i]); + confirmed.add(elements[i]); + verifyAll(); + if (r) size++; + assertEquals("Collection size should grow after add", + size, collection.size()); + assertTrue("Collection should contain added element", + collection.contains(elements[i])); + } + } + + + /** + * Tests {@link Collection#addAll(Collection)}. + */ + @Test + public void testCollectionAddAll() { + if (!isAddSupported()) return; + + resetEmpty(); + Object[] elements = getFullElements(); + boolean r = collection.addAll(Arrays.asList(elements)); + confirmed.addAll(Arrays.asList(elements)); + verifyAll(); + assertTrue("Empty collection should change after addAll", r); + for (int i = 0; i < elements.length; i++) { + assertTrue("Collection should contain added element", + collection.contains(elements[i])); + } + + resetFull(); + int size = collection.size(); + elements = getOtherElements(); + r = collection.addAll(Arrays.asList(elements)); + confirmed.addAll(Arrays.asList(elements)); + verifyAll(); + assertTrue("Full collection should change after addAll", r); + for (int i = 0; i < elements.length; i++) { + assertTrue("Full collection should contain added element", + collection.contains(elements[i])); + } + assertEquals("Size should increase after addAll", + size + elements.length, collection.size()); + + resetFull(); + size = collection.size(); + r = collection.addAll(Arrays.asList(getFullElements())); + confirmed.addAll(Arrays.asList(getFullElements())); + verifyAll(); + if (r) { + assertTrue("Size should increase if addAll returns true", + size < collection.size()); + } else { + assertEquals("Size should not change if addAll returns false", + size, collection.size()); + } + } + + + /** + * If {@link #isAddSupported()} returns false, tests that add operations + * raise UnsupportedOperationException. + */ + @Test + public void testUnsupportedAdd() { + if (isAddSupported()) return; + + resetEmpty(); + try { + collection.add(new Object()); + fail("Emtpy collection should not support add."); + } catch (UnsupportedOperationException e) { + // expected + } + // make sure things didn't change even if the expected exception was + // thrown. + verifyAll(); + + try { + collection.addAll(Arrays.asList(getFullElements())); + fail("Emtpy collection should not support addAll."); + } catch (UnsupportedOperationException e) { + // expected + } + // make sure things didn't change even if the expected exception was + // thrown. + verifyAll(); + + resetFull(); + try { + collection.add(new Object()); + fail("Full collection should not support add."); + } catch (UnsupportedOperationException e) { + // expected + } + // make sure things didn't change even if the expected exception was + // thrown. + verifyAll(); + + try { + collection.addAll(Arrays.asList(getOtherElements())); + fail("Full collection should not support addAll."); + } catch (UnsupportedOperationException e) { + // expected + } + // make sure things didn't change even if the expected exception was + // thrown. + verifyAll(); + } + + + /** + * Test {@link Collection#clear()}. + */ + @Test + public void testCollectionClear() { + if (!isRemoveSupported()) return; + + resetEmpty(); + collection.clear(); // just to make sure it doesn't raise anything + verifyAll(); + + resetFull(); + collection.clear(); + confirmed.clear(); + verifyAll(); + } + + + /** + * Tests {@link Collection#contains(Object)}. + */ + public void testCollectionContains() { + Object[] elements; + + resetEmpty(); + elements = getFullElements(); + for(int i = 0; i < elements.length; i++) { + assertTrue("Empty collection shouldn't contain element[" + i + "]", + !collection.contains(elements[i])); + } + // make sure calls to "contains" don't change anything + verifyAll(); + + elements = getOtherElements(); + for(int i = 0; i < elements.length; i++) { + assertTrue("Empty collection shouldn't contain element[" + i + "]", + !collection.contains(elements[i])); + } + // make sure calls to "contains" don't change anything + verifyAll(); + + resetFull(); + elements = getFullElements(); + for(int i = 0; i < elements.length; i++) { + assertTrue("Full collection should contain element[" + i + "]", + collection.contains(elements[i])); + } + // make sure calls to "contains" don't change anything + verifyAll(); + + resetFull(); + elements = getOtherElements(); + for(int i = 0; i < elements.length; i++) { + assertTrue("Full collection shouldn't contain element", + !collection.contains(elements[i])); + } + } + + + /** + * Tests {@link Collection#containsAll(Collection)}. + */ + @Test + public void testCollectionContainsAll() { + resetEmpty(); + Collection col = new HashSet(); + assertTrue("Every Collection should contain all elements of an " + + "empty Collection.", collection.containsAll(col)); + col.addAll(Arrays.asList(getOtherElements())); + assertTrue("Empty Collection shouldn't contain all elements of " + + "a non-empty Collection.", !collection.containsAll(col)); + // make sure calls to "containsAll" don't change anything + verifyAll(); + + resetFull(); + assertTrue("Full collection shouldn't contain other elements", + !collection.containsAll(col)); + + col.clear(); + col.addAll(Arrays.asList(getFullElements())); + assertTrue("Full collection should containAll full elements", + collection.containsAll(col)); + // make sure calls to "containsAll" don't change anything + verifyAll(); + + int min = (getFullElements().length < 2 ? 0 : 2); + int max = (getFullElements().length == 1 ? 1 : + (getFullElements().length <= 5 ? getFullElements().length - 1 : 5)); + col = Arrays.asList(getFullElements()).subList(min, max); + assertTrue("Full collection should containAll partial full " + + "elements", collection.containsAll(col)); + assertTrue("Full collection should containAll itself", + collection.containsAll(collection)); + // make sure calls to "containsAll" don't change anything + verifyAll(); + + col = new ArrayList(); + col.addAll(Arrays.asList(getFullElements())); + col.addAll(Arrays.asList(getFullElements())); + assertTrue("Full collection should containAll duplicate full " + + "elements", collection.containsAll(col)); + + // make sure calls to "containsAll" don't change anything + verifyAll(); + } + + /** + * Tests {@link Collection#isEmpty()}. + */ + @Test + public void testCollectionIsEmpty() { + resetEmpty(); + assertEquals("New Collection should be empty.", + true, collection.isEmpty()); + // make sure calls to "isEmpty() don't change anything + verifyAll(); + + resetFull(); + assertEquals("Full collection shouldn't be empty", + false, collection.isEmpty()); + // make sure calls to "isEmpty() don't change anything + verifyAll(); + } + + + /** + * Tests the read-only functionality of {@link Collection#iterator()}. + */ + @Test + public void testCollectionIterator() { + resetEmpty(); + Iterator it1 = collection.iterator(); + assertEquals("Iterator for empty Collection shouldn't have next.", + false, it1.hasNext()); + try { + it1.next(); + fail("Iterator at end of Collection should throw " + + "NoSuchElementException when next is called."); + } catch(NoSuchElementException e) { + // expected + } + // make sure nothing has changed after non-modification + verifyAll(); + + resetFull(); + it1 = collection.iterator(); + for (int i = 0; i < collection.size(); i++) { + assertTrue("Iterator for full collection should haveNext", + it1.hasNext()); + it1.next(); + } + assertTrue("Iterator should be finished", !it1.hasNext()); + + ArrayList list = new ArrayList(); + it1 = collection.iterator(); + for (int i = 0; i < collection.size(); i++) { + Object next = it1.next(); + assertTrue("Collection should contain element returned by " + + "its iterator", collection.contains(next)); + list.add(next); + } + try { + it1.next(); + fail("iterator.next() should raise NoSuchElementException " + + "after it finishes"); + } catch (NoSuchElementException e) { + // expected + } + // make sure nothing has changed after non-modification + verifyAll(); + } + + + /** + * Tests removals from {@link Collection#iterator()}. + */ + @Test + public void testCollectionIteratorRemove() { + if (!isRemoveSupported()) return; + + resetEmpty(); + try { + collection.iterator().remove(); + fail("New iterator.remove should raise IllegalState"); + } catch (IllegalStateException e) { + // expected + } + verifyAll(); + + try { + Iterator iter = collection.iterator(); + iter.hasNext(); + iter.remove(); + fail("New iterator.remove should raise IllegalState " + + "even after hasNext"); + } catch (IllegalStateException e) { + // expected + } + verifyAll(); + + resetFull(); + int size = collection.size(); + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + // TreeMap reuses the Map Entry, so the verify below fails + // Clone it here if necessary + if (o instanceof Map.Entry) { + o = cloneMapEntry((Map.Entry) o); + } + iter.remove(); + + // if the elements aren't distinguishable, we can just remove a + // matching element from the confirmed collection and verify + // contents are still the same. Otherwise, we don't have the + // ability to distinguish the elements and determine which to + // remove from the confirmed collection (in which case, we don't + // verify because we don't know how). + // + // see areEqualElementsDistinguishable() + if(!areEqualElementsDistinguishable()) { + confirmed.remove(o); + verifyAll(); + } + + size--; + assertEquals("Collection should shrink by one after " + + "iterator.remove", size, collection.size()); + } + assertTrue("Collection should be empty after iterator purge", + collection.isEmpty()); + + resetFull(); + iter = collection.iterator(); + iter.next(); + iter.remove(); + try { + iter.remove(); + fail("Second iter.remove should raise IllegalState"); + } catch (IllegalStateException e) { + // expected + } + } + + + /** + * Tests {@link Collection#remove(Object)}. + */ + @Test + public void testCollectionRemove() { + if (!isRemoveSupported()) return; + + resetEmpty(); + Object[] elements = getFullElements(); + for (int i = 0; i < elements.length; i++) { + assertTrue("Shouldn't remove nonexistent element", + !collection.remove(elements[i])); + verifyAll(); + } + + Object[] other = getOtherElements(); + + resetFull(); + for (int i = 0; i < other.length; i++) { + assertTrue("Shouldn't remove nonexistent other element", + !collection.remove(other[i])); + verifyAll(); + } + + int size = collection.size(); + for (int i = 0; i < elements.length; i++) { + resetFull(); + assertTrue("Collection should remove extant element: " + elements[i], + collection.remove(elements[i])); + + // if the elements aren't distinguishable, we can just remove a + // matching element from the confirmed collection and verify + // contents are still the same. Otherwise, we don't have the + // ability to distinguish the elements and determine which to + // remove from the confirmed collection (in which case, we don't + // verify because we don't know how). + // + // see areEqualElementsDistinguishable() + if(!areEqualElementsDistinguishable()) { + confirmed.remove(elements[i]); + verifyAll(); + } + + assertEquals("Collection should shrink after remove", + size - 1, collection.size()); + } + } + + + /** + * Tests {@link Collection#removeAll(Collection)}. + */ + @Test + public void testCollectionRemoveAll() { + if (!isRemoveSupported()) return; + + resetEmpty(); + assertTrue("Emtpy collection removeAll should return false for " + + "empty input", + !collection.removeAll(Collections.EMPTY_SET)); + verifyAll(); + + assertTrue("Emtpy collection removeAll should return false for " + + "nonempty input", + !collection.removeAll(new ArrayList(collection))); + verifyAll(); + + resetFull(); + assertTrue("Full collection removeAll should return false for " + + "empty input", + !collection.removeAll(Collections.EMPTY_SET)); + verifyAll(); + + assertTrue("Full collection removeAll should return false for other elements", + !collection.removeAll(Arrays.asList(getOtherElements()))); + verifyAll(); + + assertTrue("Full collection removeAll should return true for full elements", + collection.removeAll(new HashSet(collection))); + confirmed.removeAll(new HashSet(confirmed)); + verifyAll(); + + resetFull(); + int size = collection.size(); + int min = (getFullElements().length < 2 ? 0 : 2); + int max = (getFullElements().length == 1 ? 1 : + (getFullElements().length <= 5 ? getFullElements().length - 1 : 5)); + Collection all = Arrays.asList(getFullElements()).subList(min, max); + assertTrue("Full collection removeAll should work", + collection.removeAll(all)); + confirmed.removeAll(all); + verifyAll(); + + assertTrue("Collection should shrink after removeAll", + collection.size() < size); + Iterator iter = all.iterator(); + while (iter.hasNext()) { + assertTrue("Collection shouldn't contain removed element", + !collection.contains(iter.next())); + } + } + + + /** + * Tests {@link Collection#retainAll(Collection)}. + */ + @Test + public void testCollectionRetainAll() { + if (!isRemoveSupported()) return; + + resetEmpty(); + List elements = Arrays.asList(getFullElements()); + List other = Arrays.asList(getOtherElements()); + + assertTrue("Empty retainAll() should return false", + !collection.retainAll(Collections.EMPTY_SET)); + verifyAll(); + + assertTrue("Empty retainAll() should return false", + !collection.retainAll(elements)); + verifyAll(); + + resetFull(); + assertTrue("Collection should change from retainAll empty", + collection.retainAll(Collections.EMPTY_SET)); + confirmed.retainAll(Collections.EMPTY_SET); + verifyAll(); + + resetFull(); + assertTrue("Collection changed from retainAll other", + collection.retainAll(other)); + confirmed.retainAll(other); + verifyAll(); + + resetFull(); + int size = collection.size(); + assertTrue("Collection shouldn't change from retainAll elements", + !collection.retainAll(elements)); + verifyAll(); + assertEquals("Collection size shouldn't change", size, + collection.size()); + + if (getFullElements().length > 1) { + resetFull(); + size = collection.size(); + int min = (getFullElements().length < 2 ? 0 : 2); + int max = (getFullElements().length <= 5 ? getFullElements().length - 1 : 5); + assertTrue("Collection should changed by partial retainAll", + collection.retainAll(elements.subList(min, max))); + confirmed.retainAll(elements.subList(min, max)); + verifyAll(); + + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + assertTrue("Collection only contains retained element", + elements.subList(min, max).contains(iter.next())); + } + } + + resetFull(); + HashSet set = new HashSet(elements); + size = collection.size(); + assertTrue("Collection shouldn't change from retainAll without " + + "duplicate elements", !collection.retainAll(set)); + verifyAll(); + assertEquals("Collection size didn't change from nonduplicate " + + "retainAll", size, collection.size()); + } + + + /** + * Tests {@link Collection#size()}. + */ + @Test + public void testCollectionSize() { + resetEmpty(); + assertEquals("Size of new Collection is 0.", 0, collection.size()); + + resetFull(); + assertTrue("Size of full collection should be greater than zero", + collection.size() > 0); + } + + + /** + * Tests {@link Collection#toArray()}. + */ + public void testCollectionToArray() { + resetEmpty(); + assertEquals("Empty Collection should return empty array for toArray", + 0, collection.toArray().length); + + resetFull(); + Object[] array = collection.toArray(); + assertEquals("Full collection toArray should be same size as " + + "collection", array.length, collection.size()); + Object[] confirmedArray = confirmed.toArray(); + assertEquals("length of array from confirmed collection should " + + "match the length of the collection's array", + confirmedArray.length, array.length); + boolean[] matched = new boolean[array.length]; + + for (int i = 0; i < array.length; i++) { + assertTrue("Collection should contain element in toArray", + collection.contains(array[i])); + + boolean match = false; + // find a match in the confirmed array + for(int j = 0; j < array.length; j++) { + // skip already matched + if(matched[j]) continue; + if(array[i] == confirmedArray[j] || + (array[i] != null && array[i].equals(confirmedArray[j]))) { + matched[j] = true; + match = true; + break; + } + } + if(!match) { + fail("element " + i + " in returned array should be found " + + "in the confirmed collection's array"); + } + } + for(int i = 0; i < matched.length; i++) { + assertEquals("Collection should return all its elements in " + + "toArray", true, matched[i]); + } + } + + + /** + * Tests {@link Collection#toArray(Object[])}. + */ + @SuppressWarnings({"SuspiciousToArrayCall", "RedundantCast"}) + @Test + public void testCollectionToArray2() { + resetEmpty(); + Object[] a = new Object[] { new Object(), null, null }; + Object[] array = collection.toArray(a); + assertArrayEquals("Given array shouldn't shrink", array, a); + assertNull("Last element should be set to null", a[0]); + verifyAll(); + + resetFull(); + try { + collection.toArray(new Void[0]); + fail("toArray(new Void[0]) should raise ArrayStore"); + } catch (ArrayStoreException e) { + // expected + } + verifyAll(); + + try { + collection.toArray((Object[]) null); + fail("toArray(null) should raise NPE"); + } catch (NullPointerException e) { + // expected + } + verifyAll(); + + array = collection.toArray(new Object[0]); + a = collection.toArray(); + assertEquals("toArrays should be equal", + Arrays.asList(array), Arrays.asList(a)); + + // Figure out if they're all the same class + // TODO: It'd be nicer to detect a common superclass + HashSet> classes = new HashSet<>(); + for (int i = 0; i < array.length; i++) { + classes.add((array[i] == null) ? null : array[i].getClass()); + } + if (classes.size() > 1) return; + + Class cl = (Class)classes.iterator().next(); + if (Map.Entry.class.isAssignableFrom(cl)) { // check needed for protective cases like Predicated/Unmod map entrySet + cl = Map.Entry.class; + } + a = (Object[])Array.newInstance(cl, 0); + array = collection.toArray(a); + assertEquals("toArray(Object[]) should return correct array type", + a.getClass(), array.getClass()); + assertEquals("type-specific toArrays should be equal", + Arrays.asList(array), + Arrays.asList(collection.toArray())); + verifyAll(); + } + + + /** + * Tests {@code toString} on a collection. + */ + @Test + public void testCollectionToString() { + resetEmpty(); + assertTrue("toString shouldn't return null", + collection.toString() != null); + + resetFull(); + assertTrue("toString shouldn't return null", + collection.toString() != null); + } + + + /** + * If isRemoveSupported() returns false, tests to see that remove + * operations raise an UnsupportedOperationException. + */ + @Test + public void testUnsupportedRemove() { + if (isRemoveSupported()) return; + + resetEmpty(); + try { + collection.clear(); + fail("clear should raise UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected + } + verifyAll(); + + try { + collection.remove(null); + fail("remove should raise UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected + } + verifyAll(); + + try { + collection.removeAll(null); + fail("removeAll should raise UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected + } + verifyAll(); + + try { + collection.retainAll(null); + fail("removeAll should raise UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected + } + verifyAll(); + + resetFull(); + try { + Iterator iterator = collection.iterator(); + iterator.next(); + iterator.remove(); + fail("iterator.remove should raise UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected + } + verifyAll(); + + } + + + /** + * Tests that the collection's iterator is fail-fast. + */ + @Test + public void testCollectionIteratorFailFast() { + if (!isFailFastSupported()) return; + + if (isAddSupported()) { + resetFull(); + try { + Iterator iter = collection.iterator(); + Object o = getOtherElements()[0]; + collection.add(o); + confirmed.add(o); + iter.next(); + fail("next after add should raise ConcurrentModification"); + } catch (ConcurrentModificationException e) { + // expected + } + verifyAll(); + + resetFull(); + try { + Iterator iter = collection.iterator(); + collection.addAll(Arrays.asList(getOtherElements())); + confirmed.addAll(Arrays.asList(getOtherElements())); + iter.next(); + fail("next after addAll should raise ConcurrentModification"); + } catch (ConcurrentModificationException e) { + // expected + } + verifyAll(); + } + + if (!isRemoveSupported()) return; + + resetFull(); + try { + Iterator iter = collection.iterator(); + collection.clear(); + iter.next(); + fail("next after clear should raise ConcurrentModification"); + } catch (ConcurrentModificationException e) { + // expected + } catch (NoSuchElementException e) { + // (also legal given spec) + } + + resetFull(); + try { + Iterator iter = collection.iterator(); + collection.remove(getFullElements()[0]); + iter.next(); + fail("next after remove should raise ConcurrentModification"); + } catch (ConcurrentModificationException e) { + // expected + } + + resetFull(); + try { + Iterator iter = collection.iterator(); + List sublist = Arrays.asList(getFullElements()).subList(2,5); + collection.removeAll(sublist); + iter.next(); + fail("next after removeAll should raise ConcurrentModification"); + } catch (ConcurrentModificationException e) { + // expected + } + + resetFull(); + try { + Iterator iter = collection.iterator(); + List sublist = Arrays.asList(getFullElements()).subList(2,5); + collection.retainAll(sublist); + iter.next(); + fail("next after retainAll should raise ConcurrentModification"); + } catch (ConcurrentModificationException e) { + // expected + } + } + + /* + @Test + public void testSerializeDeserializeThenCompare() throws Exception { + Object obj = makeCollection(); + if (obj instanceof Serializable && isTestSerialization()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(buffer); + out.writeObject(obj); + out.close(); + + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); + Object dest = in.readObject(); + in.close(); + if (isEqualsCheckable()) { + assertEquals("obj != deserialize(serialize(obj)) - EMPTY Collection", obj, dest); + } + } + obj = makeFullCollection(); + if (obj instanceof Serializable && isTestSerialization()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(buffer); + out.writeObject(obj); + out.close(); + + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); + Object dest = in.readObject(); + in.close(); + if (isEqualsCheckable()) { + assertEquals("obj != deserialize(serialize(obj)) - FULL Collection", obj, dest); + } + } + } + */ + +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionUtilTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionUtilTest.java index 948cdbc2..687833a9 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionUtilTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/CollectionUtilTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; @@ -34,7 +36,6 @@ import org.junit.Test; import java.util.*; import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; /** * CollectionUtilTest @@ -249,12 +250,12 @@ public class CollectionUtilTest { assertCorrectListIterator(new ArrayList(Arrays.asList(new String[] {"foo", "bar", "baz", "boo"})).subList(1, 3).listIterator(0), new String[] {"bar", "baz"}, true, true); } - private void assertCorrectListIterator(ListIterator iterator, final Object[] elements) { + private static void assertCorrectListIterator(ListIterator iterator, final Object[] elements) { assertCorrectListIterator(iterator, elements, false, false); } // NOTE: The test is can only test list iterators with a starting index == 0 - private void assertCorrectListIterator(ListIterator iterator, final Object[] elements, boolean skipRemove, boolean skipAdd) { + private static void assertCorrectListIterator(ListIterator iterator, final Object[] elements, boolean skipRemove, boolean skipAdd) { // Index is now "before 0" assertEquals(-1, iterator.previousIndex()); assertEquals(0, iterator.nextIndex()); diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/LRUMapTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/LRUMapTest.java similarity index 76% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/LRUMapTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/LRUMapTest.java index 91d0f564..bfe47d1c 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/LRUMapTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/LRUMapTest.java @@ -1,181 +1,221 @@ -/* - * Copyright 2001-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.twelvemonkeys.util; - -import java.util.*; - -/** - * Tests LRUMap. - * - * @version $Revision: #2 $ $Date: 2008/07/15 $ - * - * @author James Strachan - * @author Morgan Delagrange - * @author Stephen Colebourne - */ -public class LRUMapTestCase extends LinkedMapTestCase { - - public boolean isGetStructuralModify() { - return true; - } - - //----------------------------------------------------------------------- - public Map makeEmptyMap() { - return new LRUMap(); - } - - //----------------------------------------------------------------------- - public void testRemoveLRU() { - LRUMap map2 = new LRUMap(3); - map2.put(1,"foo"); - map2.put(2,"foo"); - map2.put(3,"foo"); - map2.put(4,"foo"); // removes 1 since max size exceeded - map2.removeLRU(); // should be Integer(2) - - assertTrue("Second to last value should exist",map2.get(new Integer(3)).equals("foo")); - assertTrue("First value inserted should not exist", map2.get(new Integer(1)) == null); - } - - public void testMultiplePuts() { - LRUMap map2 = new LRUMap(2); - map2.put(1,"foo"); - map2.put(2,"bar"); - map2.put(3,"foo"); - map2.put(4,"bar"); - - assertTrue("last value should exist",map2.get(new Integer(4)).equals("bar")); - assertTrue("LRU should not exist", map2.get(new Integer(1)) == null); - } - - /** - * Confirm that putAll(Map) does not cause the LRUMap - * to exceed its maxiumum size. - */ - public void testPutAll() { - LRUMap map2 = new LRUMap(3); - map2.put(1,"foo"); - map2.put(2,"foo"); - map2.put(3,"foo"); - - HashMap hashMap = new HashMap(); - hashMap.put(4,"foo"); - - map2.putAll(hashMap); - - assertTrue("max size is 3, but actual size is " + map2.size(), - map2.size() == 3); - assertTrue("map should contain the Integer(4) object", - map2.containsKey(new Integer(4))); - } - - /** - * Test that the size of the map is reduced immediately - * when setMaximumSize(int) is called - */ - public void testSetMaximumSize() { - LRUMap map = new LRUMap(6); - map.put("1","1"); - map.put("2","2"); - map.put("3","3"); - map.put("4","4"); - map.put("5","5"); - map.put("6","6"); - map.setMaxSize(3); - - assertTrue("map should have size = 3, but actually = " + map.size(), - map.size() == 3); - } - - public void testGetPromotion() { - LRUMap map = new LRUMap(3); - map.put("1","1"); - map.put("2","2"); - map.put("3","3"); - // LRU is now 1 (then 2 then 3) - - // promote 1 to top - // eviction order is now 2,3,1 - map.get("1"); - - // add another value, forcing a remove - // 2 should be evicted (then 3,1,4) - map.put("4","4"); - - Iterator keyIterator = map.keySet().iterator(); - Object[] keys = new Object[3]; - for (int i = 0; keyIterator.hasNext() ; ++i) { - keys[i] = keyIterator.next(); - } - - assertTrue("first evicted should be 3, was " + keys[0], keys[0].equals("3")); - assertTrue("second evicted should be 1, was " + keys[1], keys[1].equals("1")); - assertTrue("third evicted should be 4, was " + keys[2], keys[2].equals("4")); - - } - - /** - * You should be able to subclass LRUMap and perform a - * custom action when items are removed automatically - * by the LRU algorithm (the removeLRU() method). - */ - public void testLRUSubclass() { - LRUCounter counter = new LRUCounter(3); - // oldest <--> newest - // 1 - counter.put("1","foo"); - // 1 2 - counter.put("2","foo"); - // 1 2 3 - counter.put("3","foo"); - // 2 3 1 - counter.put("1","foo"); - // 3 1 4 (2 goes out) - counter.put("4","foo"); - // 1 4 5 (3 goes out) - counter.put("5","foo"); - // 4 5 2 (1 goes out) - counter.put("2","foo"); - // 4 2 - counter.remove("5"); - - assertTrue("size should be 2, but was " + counter.size(), counter.size() == 2); - assertTrue("removedCount should be 3 but was " + counter.removedCount, - counter.removedCount == 3); - - assertTrue("first removed was '2'",counter.list.get(0).equals("2")); - assertTrue("second removed was '3'",counter.list.get(1).equals("3")); - assertTrue("third removed was '1'",counter.list.get(2).equals("1")); - - //assertTrue("oldest key is '4'",counter.get(0).equals("4")); - //assertTrue("newest key is '2'",counter.get(1).equals("2")); - } - - private class LRUCounter extends LRUMap { - int removedCount = 0; - List list = new ArrayList(3); - - LRUCounter(int i) { - super(i); - } - - public void processRemoved(Entry pEntry) { - ++removedCount; - list.add(pEntry.getKey()); - } - } -} - +/* + * 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.twelvemonkeys.util; + +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.assertTrue; + +/** + * Tests LRUMap. + * + * @version $Revision: #2 $ $Date: 2008/07/15 $ + * + * @author James Strachan + * @author Morgan Delagrange + * @author Stephen Colebourne + */ +public class LRUMapTest extends LinkedMapTest { + + public boolean isGetStructuralModify() { + return true; + } + + //----------------------------------------------------------------------- + public Map makeEmptyMap() { + return new LRUMap(); + } + + //----------------------------------------------------------------------- + @Test + public void testRemoveLRU() { + LRUMap map2 = new LRUMap(3); + map2.put(1,"foo"); + map2.put(2,"foo"); + map2.put(3,"foo"); + map2.put(4,"foo"); // removes 1 since max size exceeded + map2.removeLRU(); // should be Integer(2) + + assertTrue("Second to last value should exist",map2.get(new Integer(3)).equals("foo")); + assertTrue("First value inserted should not exist", map2.get(new Integer(1)) == null); + } + + @Test + public void testMultiplePuts() { + LRUMap map2 = new LRUMap(2); + map2.put(1,"foo"); + map2.put(2,"bar"); + map2.put(3,"foo"); + map2.put(4,"bar"); + + assertTrue("last value should exist",map2.get(new Integer(4)).equals("bar")); + assertTrue("LRU should not exist", map2.get(new Integer(1)) == null); + } + + /** + * Confirm that putAll(Map) does not cause the LRUMap + * to exceed its maxiumum size. + */ + @Test + public void testPutAll() { + LRUMap map2 = new LRUMap(3); + map2.put(1,"foo"); + map2.put(2,"foo"); + map2.put(3,"foo"); + + HashMap hashMap = new HashMap(); + hashMap.put(4,"foo"); + + map2.putAll(hashMap); + + assertTrue("max size is 3, but actual size is " + map2.size(), + map2.size() == 3); + assertTrue("map should contain the Integer(4) object", + map2.containsKey(new Integer(4))); + } + + /** + * Test that the size of the map is reduced immediately + * when setMaximumSize(int) is called + */ + @Test + public void testSetMaximumSize() { + LRUMap map = new LRUMap(6); + map.put("1","1"); + map.put("2","2"); + map.put("3","3"); + map.put("4","4"); + map.put("5","5"); + map.put("6","6"); + map.setMaxSize(3); + + assertTrue("map should have size = 3, but actually = " + map.size(), + map.size() == 3); + } + + @Test + public void testGetPromotion() { + LRUMap map = new LRUMap(3); + map.put("1","1"); + map.put("2","2"); + map.put("3","3"); + // LRU is now 1 (then 2 then 3) + + // promote 1 to top + // eviction order is now 2,3,1 + map.get("1"); + + // add another value, forcing a remove + // 2 should be evicted (then 3,1,4) + map.put("4","4"); + + Iterator keyIterator = map.keySet().iterator(); + Object[] keys = new Object[3]; + for (int i = 0; keyIterator.hasNext() ; ++i) { + keys[i] = keyIterator.next(); + } + + assertTrue("first evicted should be 3, was " + keys[0], keys[0].equals("3")); + assertTrue("second evicted should be 1, was " + keys[1], keys[1].equals("1")); + assertTrue("third evicted should be 4, was " + keys[2], keys[2].equals("4")); + + } + + /** + * You should be able to subclass LRUMap and perform a + * custom action when items are removed automatically + * by the LRU algorithm (the removeLRU() method). + */ + @Test + public void testLRUSubclass() { + LRUCounter counter = new LRUCounter(3); + // oldest <--> newest + // 1 + counter.put("1","foo"); + // 1 2 + counter.put("2","foo"); + // 1 2 3 + counter.put("3","foo"); + // 2 3 1 + counter.put("1","foo"); + // 3 1 4 (2 goes out) + counter.put("4","foo"); + // 1 4 5 (3 goes out) + counter.put("5","foo"); + // 4 5 2 (1 goes out) + counter.put("2","foo"); + // 4 2 + counter.remove("5"); + + assertTrue("size should be 2, but was " + counter.size(), counter.size() == 2); + assertTrue("removedCount should be 3 but was " + counter.removedCount, + counter.removedCount == 3); + + assertTrue("first removed was '2'",counter.list.get(0).equals("2")); + assertTrue("second removed was '3'",counter.list.get(1).equals("3")); + assertTrue("third removed was '1'",counter.list.get(2).equals("1")); + + //assertTrue("oldest key is '4'",counter.get(0).equals("4")); + //assertTrue("newest key is '2'",counter.get(1).equals("2")); + } + + private class LRUCounter extends LRUMap { + int removedCount = 0; + List list = new ArrayList(3); + + LRUCounter(int i) { + super(i); + } + + public void processRemoved(Entry pEntry) { + ++removedCount; + list.add(pEntry.getKey()); + } + } +} + diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/LinkedMapTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/LinkedMapTest.java similarity index 77% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/LinkedMapTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/LinkedMapTest.java index 7cdaefe1..79c6a1eb 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/LinkedMapTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/LinkedMapTest.java @@ -1,175 +1,214 @@ - -/* - * Copyright 2001-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.twelvemonkeys.util; - -import java.util.Iterator; -import java.util.Map; - -/** - * Unit tests - * {@link org.apache.commons.collections.SequencedHashMap}. - * Be sure to use the "labRat" instance whenever possible, - * so that subclasses will be tested correctly. - * - * @version $Revision: #2 $ $Date: 2008/07/15 $ - * - * @author Morgan Delagrange - * @author Daniel Rall - * @author Henning P. Schmiedehausen - * @author James Strachan - */ -public class LinkedMapTestCase extends MapAbstractTestCase { - /** - * The instance to experiment on. - */ - protected LinkedMap labRat; - - public void setUp() throws Exception { - super.setUp(); - // use makeMap and cast the result to a SeqHashMap - // so that subclasses of SeqHashMap can share these tests - labRat = (LinkedMap) makeEmptyMap(); - } - - public Map makeEmptyMap() { - return new LinkedMap(); - } - - protected Object[] getKeys() { - return new Object[] { "foo", "baz", "eek" }; - } - - protected Object[] getValues() { - return new Object[] { "bar", "frob", new Object() }; - } - - public void testSequenceMap() throws Throwable { - Object[] keys = getKeys(); - int expectedSize = keys.length; - Object[] values = getValues(); - for (int i = 0; i < expectedSize; i++) { - labRat.put(keys[i], values[i]); - } - - // Test size(). - assertEquals("size() does not match expected size", - expectedSize, labRat.size()); - - // Test clone(), iterator(), and get(Object). - LinkedMap clone = (LinkedMap) labRat.clone(); - assertEquals("Size of clone does not match original", - labRat.size(), clone.size()); - Iterator origEntries = labRat.entrySet().iterator(); - Iterator copiedEntries = clone.entrySet().iterator(); - while (origEntries.hasNext()) { - Map.Entry origEntry = (Map.Entry)origEntries.next(); - Map.Entry copiedEntry = (Map.Entry)copiedEntries.next(); - assertEquals("Cloned key does not match original", - origEntry.getKey(), copiedEntry.getKey()); - assertEquals("Cloned value does not match original", - origEntry.getValue(), copiedEntry.getValue()); - assertEquals("Cloned entry does not match original", - origEntry, copiedEntry); - } - assertTrue("iterator() returned different number of elements than keys()", - !copiedEntries.hasNext()); - - // Test sequence() - /* - List seq = labRat.sequence(); - assertEquals("sequence() returns more keys than in the Map", - expectedSize, seq.size()); - - for (int i = 0; i < seq.size(); i++) { - assertEquals("Key " + i + " is not the same as the key in the Map", - keys[i], seq.get(i)); - } - */ - } - - /* - public void testYoungest() { - labRat.put(new Integer(1),"foo"); - labRat.put(new Integer(2),"bar"); - assertTrue("first key is correct",labRat.get(0).equals(new Integer(1))); - labRat.put(new Integer(1),"boo"); - assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2))); - } - - public void testYoungestReplaceNullWithValue() { - labRat.put(new Integer(1),null); - labRat.put(new Integer(2),"foo"); - assertTrue("first key is correct",labRat.get(0).equals(new Integer(1))); - labRat.put(new Integer(1),"bar"); - assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2))); - } - - public void testYoungestReplaceValueWithNull() { - labRat.put(new Integer(1),"bar"); - labRat.put(new Integer(2),"foo"); - assertTrue("first key is correct",labRat.get(0).equals(new Integer(1))); - labRat.put(new Integer(1),null); - assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2))); - } - */ - - // override TestMap method with more specific tests - /* - public void testFullMapSerialization() - throws IOException, ClassNotFoundException { - LinkedMap map = (LinkedMap) makeFullMap(); - - if (!(map instanceof Serializable)) return; - - byte[] objekt = writeExternalFormToBytes((Serializable) map); - LinkedMap map2 = (LinkedMap) readExternalFormFromBytes(objekt); - - assertEquals("Both maps are same size",map.size(), getSampleKeys().length); - assertEquals("Both maps are same size",map2.size(),getSampleKeys().length); - - assertEquals("Both maps have the same first key", - map.getFirstKey(),getSampleKeys()[0]); - assertEquals("Both maps have the same first key", - map2.getFirstKey(),getSampleKeys()[0]); - assertEquals("Both maps have the same last key", - map.getLastKey(),getSampleKeys()[getSampleKeys().length - 1]); - assertEquals("Both maps have the same last key", - map2.getLastKey(),getSampleKeys()[getSampleKeys().length - 1]); - } - */ - - /* - public void testIndexOf() throws Exception { - Object[] keys = getKeys(); - int expectedSize = keys.length; - Object[] values = getValues(); - for (int i = 0; i < expectedSize; i++) { - labRat.put(keys[i], values[i]); - } - // test that the index returned are in the same order that they were - // placed in the map - for (int i = 0; i < keys.length; i++) { - assertEquals("indexOf with existing key failed", i, labRat.indexOf(keys[i])); - } - // test non existing key.. - assertEquals("test with non-existing key failed", -1, labRat.indexOf("NonExistingKey")); - } - */ - public void tearDown() throws Exception { - labRat = null; - } -} +/* + * 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.twelvemonkeys.util; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Iterator; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests + * {@link org.apache.commons.collections.SequencedHashMap}. + * Be sure to use the "labRat" instance whenever possible, + * so that subclasses will be tested correctly. + * + * @version $Revision: #2 $ $Date: 2008/07/15 $ + * + * @author Morgan Delagrange + * @author Daniel Rall + * @author Henning P. Schmiedehausen + * @author James Strachan + */ +public class LinkedMapTest extends MapAbstractTest { + /** + * The instance to experiment on. + */ + protected LinkedMap labRat; + + @Before + public void setUp() throws Exception { + // use makeMap and cast the result to a SeqHashMap + // so that subclasses of SeqHashMap can share these tests + labRat = (LinkedMap) makeEmptyMap(); + } + + public Map makeEmptyMap() { + return new LinkedMap(); + } + + protected Object[] getKeys() { + return new Object[] { "foo", "baz", "eek" }; + } + + protected Object[] getValues() { + return new Object[] { "bar", "frob", new Object() }; + } + + @Test + public void testSequenceMap() throws Throwable { + Object[] keys = getKeys(); + int expectedSize = keys.length; + Object[] values = getValues(); + for (int i = 0; i < expectedSize; i++) { + labRat.put(keys[i], values[i]); + } + + // Test size(). + assertEquals("size() does not match expected size", + expectedSize, labRat.size()); + + // Test clone(), iterator(), and get(Object). + LinkedMap clone = (LinkedMap) labRat.clone(); + assertEquals("Size of clone does not match original", + labRat.size(), clone.size()); + Iterator origEntries = labRat.entrySet().iterator(); + Iterator copiedEntries = clone.entrySet().iterator(); + while (origEntries.hasNext()) { + Map.Entry origEntry = (Map.Entry)origEntries.next(); + Map.Entry copiedEntry = (Map.Entry)copiedEntries.next(); + assertEquals("Cloned key does not match original", + origEntry.getKey(), copiedEntry.getKey()); + assertEquals("Cloned value does not match original", + origEntry.getValue(), copiedEntry.getValue()); + assertEquals("Cloned entry does not match original", + origEntry, copiedEntry); + } + assertTrue("iterator() returned different number of elements than keys()", + !copiedEntries.hasNext()); + + // Test sequence() + /* + List seq = labRat.sequence(); + assertEquals("sequence() returns more keys than in the Map", + expectedSize, seq.size()); + + for (int i = 0; i < seq.size(); i++) { + assertEquals("Key " + i + " is not the same as the key in the Map", + keys[i], seq.get(i)); + } + */ + } + + /* + public void testYoungest() { + labRat.put(new Integer(1),"foo"); + labRat.put(new Integer(2),"bar"); + assertTrue("first key is correct",labRat.get(0).equals(new Integer(1))); + labRat.put(new Integer(1),"boo"); + assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2))); + } + + public void testYoungestReplaceNullWithValue() { + labRat.put(new Integer(1),null); + labRat.put(new Integer(2),"foo"); + assertTrue("first key is correct",labRat.get(0).equals(new Integer(1))); + labRat.put(new Integer(1),"bar"); + assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2))); + } + + public void testYoungestReplaceValueWithNull() { + labRat.put(new Integer(1),"bar"); + labRat.put(new Integer(2),"foo"); + assertTrue("first key is correct",labRat.get(0).equals(new Integer(1))); + labRat.put(new Integer(1),null); + assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2))); + } + */ + + // override TestMap method with more specific tests + /* + public void testFullMapSerialization() + throws IOException, ClassNotFoundException { + LinkedMap map = (LinkedMap) makeFullMap(); + + if (!(map instanceof Serializable)) return; + + byte[] objekt = writeExternalFormToBytes((Serializable) map); + LinkedMap map2 = (LinkedMap) readExternalFormFromBytes(objekt); + + assertEquals("Both maps are same size",map.size(), getSampleKeys().length); + assertEquals("Both maps are same size",map2.size(),getSampleKeys().length); + + assertEquals("Both maps have the same first key", + map.getFirstKey(),getSampleKeys()[0]); + assertEquals("Both maps have the same first key", + map2.getFirstKey(),getSampleKeys()[0]); + assertEquals("Both maps have the same last key", + map.getLastKey(),getSampleKeys()[getSampleKeys().length - 1]); + assertEquals("Both maps have the same last key", + map2.getLastKey(),getSampleKeys()[getSampleKeys().length - 1]); + } + */ + + /* + public void testIndexOf() throws Exception { + Object[] keys = getKeys(); + int expectedSize = keys.length; + Object[] values = getValues(); + for (int i = 0; i < expectedSize; i++) { + labRat.put(keys[i], values[i]); + } + // test that the index returned are in the same order that they were + // placed in the map + for (int i = 0; i < keys.length; i++) { + assertEquals("indexOf with existing key failed", i, labRat.indexOf(keys[i])); + } + // test non existing key.. + assertEquals("test with non-existing key failed", -1, labRat.indexOf("NonExistingKey")); + } + */ + + @After + public void tearDown() throws Exception { + labRat = null; + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/MapAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/MapAbstractTest.java similarity index 91% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/MapAbstractTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/MapAbstractTest.java index 3b54c3cb..f4f35d1b 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/MapAbstractTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/MapAbstractTest.java @@ -1,1648 +1,1714 @@ -/* - * Copyright 2001-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.twelvemonkeys.util; - -import java.util.*; - -/** - * Abstract test class for {@link java.util.Map} methods and contracts. - *

- * The forces at work here are similar to those in {@link CollectionAbstractTestCase}. - * If your class implements the full Map interface, including optional - * operations, simply extend this class, and implement the - * {@link #makeEmptyMap()} method. - *

- * On the other hand, if your map implementation is weird, you may have to - * override one or more of the other protected methods. They're described - * below. - *

- * Entry Population Methods - *

- * Override these methods if your map requires special entries: - *

- *

    - *
  • {@link #getSampleKeys()} - *
  • {@link #getSampleValues()} - *
  • {@link #getNewSampleValues()} - *
  • {@link #getOtherKeys()} - *
  • {@link #getOtherValues()} - *
- *

- * Supported Operation Methods - *

- * Override these methods if your map doesn't support certain operations: - *

- *

    - *
  • {@link #isPutAddSupported()} - *
  • {@link #isPutChangeSupported()} - *
  • {@link #isSetValueSupported()} - *
  • {@link #isRemoveSupported()} - *
  • {@link #isGetStructuralModify()} - *
  • {@link #isAllowDuplicateValues()} - *
  • {@link #isAllowNullKey()} - *
  • {@link #isAllowNullValue()} - *
- *

- * Fixture Methods - *

- * For tests on modification operations (puts and removes), fixtures are used - * to verify that that operation results in correct state for the map and its - * collection views. Basically, the modification is performed against your - * map implementation, and an identical modification is performed against - * a confirmed map implementation. A confirmed map implementation is - * something like {@code java.util.HashMap}, which is known to conform - * exactly to the {@link Map} contract. After the modification takes place - * on both your map implementation and the confirmed map implementation, the - * two maps are compared to see if their state is identical. The comparison - * also compares the collection views to make sure they're still the same.

- *

- * The upshot of all that is that any test that modifies the map in - * any way will verify that all of the map's state is still - * correct, including the state of its collection views. So for instance - * if a key is removed by the map's key set's iterator, then the entry set - * is checked to make sure the key/value pair no longer appears.

- *

- * The {@link #map} field holds an instance of your collection implementation. - * The {@link #entrySet}, {@link #keySet} and {@link #values} fields hold - * that map's collection views. And the {@link #confirmed} field holds - * an instance of the confirmed collection implementation. The - * {@link #resetEmpty()} and {@link #resetFull()} methods set these fields to - * empty or full maps, so that tests can proceed from a known state.

- *

- * After a modification operation to both {@link #map} and {@link #confirmed}, - * the {@link #verifyAll()} method is invoked to compare the results. The - * {@link # verify0} method calls separate methods to verify the map and its three - * collection views ({@link #verifyMap}, {@link #verifyEntrySet}, - * {@link #verifyKeySet}, and {@link #verifyValues}). You may want to override - * one of the verification methodsto perform additional verifications. For - * instance, TestDoubleOrderedMap would want override its - * {@link #verifyValues()} method to verify that the values are unique and in - * ascending order.

- *

- * Other Notes - *

- * If your {@link Map} fails one of these tests by design, you may still use - * this base set of cases. Simply override the test case (method) your map - * fails and/or the methods that define the assumptions used by the test - * cases. For example, if your map does not allow duplicate values, override - * {@link #isAllowDuplicateValues()} and have it return {@code false} - * - * @author Michael Smith - * @author Rodney Waldhoff - * @author Paul Jack - * @author Stephen Colebourne - * @version $Revision: #2 $ $Date: 2008/07/15 $ - */ -public abstract class MapAbstractTestCase extends ObjectAbstractTestCase { - - /** - * JDK1.2 has bugs in null handling of Maps, especially HashMap.Entry.toString - * This avoids nulls for JDK1.2 - */ - private static final boolean JDK12; - static { - String str = System.getProperty("java.version"); - JDK12 = str.startsWith("1.2"); - } - - // These instance variables are initialized with the reset method. - // Tests for map methods that alter the map (put, putAll, remove) - // first call reset() to create the map and its views; then perform - // the modification on the map; perform the same modification on the - // confirmed; and then call verifyAll() to ensure that the map is equal - // to the confirmed, that the already-constructed collection views - // are still equal to the confirmed's collection views. - - - /** Map created by reset(). */ - protected Map map; - - /** Entry set of map created by reset(). */ - protected Set entrySet; - - /** Key set of map created by reset(). */ - protected Set keySet; - - /** Values collection of map created by reset(). */ - protected Collection values; - - /** HashMap created by reset(). */ - protected Map confirmed; - - // TODO: Figure out if we need these tests... - protected boolean skipSerializedCanonicalTests() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * support the {@code put} and {@code putAll} operations - * adding new mappings. - *

- * Default implementation returns true. - * Override if your collection class does not support put adding. - */ - public boolean isPutAddSupported() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * support the {@code put} and {@code putAll} operations - * changing existing mappings. - *

- * Default implementation returns true. - * Override if your collection class does not support put changing. - */ - public boolean isPutChangeSupported() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * support the {@code setValue} operation on entrySet entries. - *

- * Default implementation returns isPutChangeSupported(). - * Override if your collection class does not support setValue but does - * support put changing. - */ - public boolean isSetValueSupported() { - return isPutChangeSupported(); - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * support the {@code remove} and {@code clear} operations. - *

- * Default implementation returns true. - * Override if your collection class does not support removal operations. - */ - public boolean isRemoveSupported() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * can cause structural modification on a get(). The example is LRUMap. - *

- * Default implementation returns false. - * Override if your map class structurally modifies on get. - */ - public boolean isGetStructuralModify() { - return false; - } - - /** - * Returns whether the sub map views of SortedMap are serializable. - * If the class being tested is based around a TreeMap then you should - * override and return false as TreeMap has a bug in deserialization. - * - * @return false - */ - public boolean isSubMapViewsSerializable() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * supports null keys. - *

- * Default implementation returns true. - * Override if your collection class does not support null keys. - */ - public boolean isAllowNullKey() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * supports null values. - *

- * Default implementation returns true. - * Override if your collection class does not support null values. - */ - public boolean isAllowNullValue() { - return true; - } - - /** - * Returns true if the maps produced by - * {@link #makeEmptyMap()} and {@link #makeFullMap()} - * supports duplicate values. - *

- * Default implementation returns true. - * Override if your collection class does not support duplicate values. - */ - public boolean isAllowDuplicateValues() { - return true; - } - - /** - * Returns the set of keys in the mappings used to test the map. This - * method must return an array with the same length as {@link - * #getSampleValues()} and all array elements must be different. The - * default implementation constructs a set of String keys, and includes a - * single null key if {@link #isAllowNullKey()} returns {@code true}. - */ - public Object[] getSampleKeys() { - Object[] result = new Object[] { - "blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee", - "hello", "goodbye", "we'll", "see", "you", "all", "again", - "key", - "key2", - (isAllowNullKey() && !JDK12) ? null : "nonnullkey" - }; - return result; - } - - public Object[] getOtherKeys() { - return getOtherNonNullStringElements(); - } - - public Object[] getOtherValues() { - return getOtherNonNullStringElements(); - } - - /** - * Returns a list of string elements suitable for return by - * {@link #getOtherKeys()} or {@link #getOtherValues}. - *

- *

Override getOtherElements to returnthe results of this method if your - * collection does not support heterogenous elements or the null element. - *

- */ - public Object[] getOtherNonNullStringElements() { - return new Object[] { - "For", "then", "despite",/* of */"space", "I", "would", "be", "brought", - "From", "limits", "far", "remote", "where", "thou", "dost", "stay" - }; - } - - /** - * Returns the set of values in the mappings used to test the map. This - * method must return an array with the same length as - * {@link #getSampleKeys()}. The default implementation constructs a set of - * String values and includes a single null value if - * {@link #isAllowNullValue()} returns {@code true}, and includes - * two values that are the same if {@link #isAllowDuplicateValues()} returns - * {@code true}. - */ - public Object[] getSampleValues() { - Object[] result = new Object[] { - "blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev", - "hellov", "goodbyev", "we'llv", "seev", "youv", "allv", "againv", - (isAllowNullValue() && !JDK12) ? null : "nonnullvalue", - "value", - (isAllowDuplicateValues()) ? "value" : "value2", - }; - return result; - } - - /** - * Returns a the set of values that can be used to replace the values - * returned from {@link #getSampleValues()}. This method must return an - * array with the same length as {@link #getSampleValues()}. The values - * returned from this method should not be the same as those returned from - * {@link #getSampleValues()}. The default implementation constructs a - * set of String values and includes a single null value if - * {@link #isAllowNullValue()} returns {@code true}, and includes two values - * that are the same if {@link #isAllowDuplicateValues()} returns - * {@code true}. - */ - public Object[] getNewSampleValues() { - Object[] result = new Object[] { - (isAllowNullValue() && !JDK12 && isAllowDuplicateValues()) ? null : "newnonnullvalue", - "newvalue", - (isAllowDuplicateValues()) ? "newvalue" : "newvalue2", - "newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv", - "newgollyv", "newgeev", "newhellov", "newgoodbyev", "newwe'llv", - "newseev", "newyouv", "newallv", "newagainv", - }; - return result; - } - - /** - * Helper method to add all the mappings described by - * {@link #getSampleKeys()} and {@link #getSampleValues()}. - */ - public void addSampleMappings(Map m) { - - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - - for (int i = 0; i < keys.length; i++) { - try { - m.put(keys[i], values[i]); - } - catch (NullPointerException exception) { - assertTrue("NullPointerException only allowed to be thrown if either the key or value is null.", - keys[i] == null || values[i] == null); - - assertTrue("NullPointerException on null key, but isAllowNullKey is not overridden to return false.", - keys[i] == null || !isAllowNullKey()); - - assertTrue("NullPointerException on null value, but isAllowNullValue is not overridden to return false.", - values[i] == null || !isAllowNullValue()); - - assertTrue("Unknown reason for NullPointer.", false); - } - } - assertEquals("size must reflect number of mappings added.", keys.length, m.size()); - } - - //----------------------------------------------------------------------- - - /** - * Return a new, empty {@link Map} to be used for testing. - * - * @return the map to be tested - */ - public abstract Map makeEmptyMap(); - - /** - * Return a new, populated map. The mappings in the map should match the - * keys and values returned from {@link #getSampleKeys()} and - * {@link #getSampleValues()}. The default implementation uses makeEmptyMap() - * and calls {@link #addSampleMappings} to add all the mappings to the - * map. - * - * @return the map to be tested - */ - public Map makeFullMap() { - Map m = makeEmptyMap(); - addSampleMappings(m); - return m; - } - - /** - * Implements the superclass method to return the map to be tested. - * - * @return the map to be tested - */ - public Object makeObject() { - return makeEmptyMap(); - } - - /** - * Override to return a map other than HashMap as the confirmed map. - * - * @return a map that is known to be valid - */ - public Map makeConfirmedMap() { - return new HashMap(); - } - - /** - * Creates a new Map Entry that is independent of the first and the map. - */ - public Map.Entry cloneMapEntry(Map.Entry entry) { - HashMap map = new HashMap(); - map.put(entry.getKey(), entry.getValue()); - return (Map.Entry) map.entrySet().iterator().next(); - } - - /** - * Gets the compatability version, needed for package access. - */ - public String getCompatibilityVersion() { - return super.getCompatibilityVersion(); - } - - //----------------------------------------------------------------------- - - /** - * Test to ensure the test setup is working properly. This method checks - * to ensure that the getSampleKeys and getSampleValues methods are - * returning results that look appropriate. That is, they both return a - * non-null array of equal length. The keys array must not have any - * duplicate values, and may only contain a (single) null key if - * isNullKeySupported() returns true. The values array must only have a null - * value if useNullValue() is true and may only have duplicate values if - * isAllowDuplicateValues() returns true. - */ - public void testSampleMappings() { - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - Object[] newValues = getNewSampleValues(); - - assertTrue("failure in test: Must have keys returned from getSampleKeys.", keys != null); - assertTrue("failure in test: Must have values returned from getSampleValues.", values != null); - - // verify keys and values have equivalent lengths (in case getSampleX are - // overridden) - assertEquals("failure in test: not the same number of sample keys and values.", keys.length, values.length); - assertEquals("failure in test: not the same number of values and new values.", values.length, newValues.length); - - // verify there aren't duplicate keys, and check values - for (int i = 0; i < keys.length - 1; i++) { - for (int j = i + 1; j < keys.length; j++) { - assertTrue("failure in test: duplicate null keys.", (keys[i] != null || keys[j] != null)); - assertTrue("failure in test: duplicate non-null key.", - (keys[i] == null || keys[j] == null || (!keys[i].equals(keys[j]) && !keys[j].equals(keys[i])))); - } - - assertTrue("failure in test: found null key, but isNullKeySupported is false.", keys[i] != null || isAllowNullKey()); - assertTrue("failure in test: found null value, but isNullValueSupported is false.", values[i] != null || isAllowNullValue()); - assertTrue("failure in test: found null new value, but isNullValueSupported is false.", newValues[i] != null || isAllowNullValue()); - assertTrue("failure in test: values should not be the same as new value", - values[i] != newValues[i] && (values[i] == null || !values[i].equals(newValues[i]))); - } - } - - // tests begin here. Each test adds a little bit of tested functionality. - // Many methods assume previous methods passed. That is, they do not - // exhaustively recheck things that have already been checked in a previous - // test methods. - - /** - * Test to ensure that makeEmptyMap and makeFull returns a new non-null - * map with each invocation. - */ - public void testMakeMap() { - Map em = makeEmptyMap(); - assertTrue("failure in test: makeEmptyMap must return a non-null map.", em != null); - - Map em2 = makeEmptyMap(); - assertTrue("failure in test: makeEmptyMap must return a non-null map.", em2 != null); - assertTrue("failure in test: makeEmptyMap must return a new map with each invocation.", em != em2); - - Map fm = makeFullMap(); - assertTrue("failure in test: makeFullMap must return a non-null map.", fm != null); - - Map fm2 = makeFullMap(); - assertTrue("failure in test: makeFullMap must return a non-null map.", fm2 != null); - assertTrue("failure in test: makeFullMap must return a new map with each invocation.", fm != fm2); - } - - /** - * Tests Map.isEmpty() - */ - public void testMapIsEmpty() { - resetEmpty(); - assertEquals("Map.isEmpty() should return true with an empty map", true, map.isEmpty()); - verifyAll(); - - resetFull(); - assertEquals("Map.isEmpty() should return false with a non-empty map", false, map.isEmpty()); - verifyAll(); - } - - /** - * Tests Map.size() - */ - public void testMapSize() { - resetEmpty(); - assertEquals("Map.size() should be 0 with an empty map", 0, map.size()); - verifyAll(); - - resetFull(); - assertEquals("Map.size() should equal the number of entries in the map", getSampleKeys().length, map.size()); - verifyAll(); - } - - /** - * Tests {@link Map#clear()}. If the map {@link #isRemoveSupported()} - * can add and remove elements}, then {@link Map#size()} and - * {@link Map#isEmpty()} are used to ensure that map has no elements after - * a call to clear. If the map does not support adding and removing - * elements, this method checks to ensure clear throws an - * UnsupportedOperationException. - */ - public void testMapClear() { - if (!isRemoveSupported()) { - try { - resetFull(); - map.clear(); - fail("Expected UnsupportedOperationException on clear"); - } - catch (UnsupportedOperationException ex) { - } - return; - } - - resetEmpty(); - map.clear(); - confirmed.clear(); - verifyAll(); - - resetFull(); - map.clear(); - confirmed.clear(); - verifyAll(); - } - - /** - * Tests Map.containsKey(Object) by verifying it returns false for all - * sample keys on a map created using an empty map and returns true for - * all sample keys returned on a full map. - */ - public void testMapContainsKey() { - Object[] keys = getSampleKeys(); - - resetEmpty(); - for (Object key : keys) { - assertTrue("Map must not contain key when map is empty", !map.containsKey(key)); - } - verifyAll(); - - resetFull(); - for (Object key : keys) { - assertTrue("Map must contain key for a mapping in the map. Missing: " + key, map.containsKey(key)); - } - verifyAll(); - } - - /** - * Tests Map.containsValue(Object) by verifying it returns false for all - * sample values on an empty map and returns true for all sample values on - * a full map. - */ - public void testMapContainsValue() { - Object[] values = getSampleValues(); - - resetEmpty(); - for (Object value : values) { - assertTrue("Empty map must not contain value", !map.containsValue(value)); - } - verifyAll(); - - resetFull(); - for (Object value : values) { - assertTrue("Map must contain value for a mapping in the map.", map.containsValue(value)); - } - verifyAll(); - } - - /** - * Tests Map.equals(Object) - */ - public void testMapEquals() { - resetEmpty(); - assertTrue("Empty maps unequal.", map.equals(confirmed)); - verifyAll(); - - resetFull(); - assertTrue("Full maps unequal.", map.equals(confirmed)); - verifyAll(); - - resetFull(); - // modify the HashMap created from the full map and make sure this - // change results in map.equals() to return false. - Iterator iter = confirmed.keySet().iterator(); - iter.next(); - iter.remove(); - assertTrue("Different maps equal.", !map.equals(confirmed)); - - resetFull(); - assertTrue("equals(null) returned true.", !map.equals(null)); - assertTrue("equals(new Object()) returned true.", !map.equals(new Object())); - verifyAll(); - } - - /** - * Tests Map.get(Object) - */ - public void testMapGet() { - resetEmpty(); - - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - - for (Object key : keys) { - assertTrue("Empty map.get() should return null.", map.get(key) == null); - } - - verifyAll(); - - resetFull(); - for (int i = 0; i < keys.length; i++) { - assertEquals("Full map.get() should return value from mapping.", values[i], map.get(keys[i])); - } - } - - /** - * Tests Map.hashCode() - */ - public void testMapHashCode() { - resetEmpty(); - assertTrue("Empty maps have different hashCodes.", map.hashCode() == confirmed.hashCode()); - - resetFull(); - assertTrue("Equal maps have different hashCodes.", map.hashCode() == confirmed.hashCode()); - } - - /** - * Tests Map.toString(). Since the format of the string returned by the - * toString() method is not defined in the Map interface, there is no - * common way to test the results of the toString() method. Thereforce, - * it is encouraged that Map implementations override this test with one - * that checks the format matches any format defined in its API. This - * default implementation just verifies that the toString() method does - * not return null. - */ - public void testMapToString() { - resetEmpty(); - assertTrue("Empty map toString() should not return null", map.toString() != null); - verifyAll(); - - resetFull(); - assertTrue("Empty map toString() should not return null", map.toString() != null); - verifyAll(); - } - - /** - * Compare the current serialized form of the Map - * against the canonical version in CVS. - */ - /* - public void testEmptyMapCompatibility() throws Exception { - /* - * Create canonical objects with this code - Map map = makeEmptyMap(); - if (!(map instanceof Serializable)) return; - - writeExternalFormToDisk((Serializable) map, getCanonicalEmptyCollectionName(map)); - // - - // test to make sure the canonical form has been preserved - Map map = makeEmptyMap(); - if (map instanceof Serializable && !skipSerializedCanonicalTests() && isTestSerialization()) { - Map map2 = (Map) readExternalFormFromDisk(getCanonicalEmptyCollectionName(map)); - assertEquals("Map is empty", 0, map2.size()); - } - } - */ - - /** - * Compare the current serialized form of the Map - * against the canonical version in CVS. - */ - //public void testFullMapCompatibility() throws Exception { - /** - * Create canonical objects with this code - Map map = makeFullMap(); - if (!(map instanceof Serializable)) return; - - writeExternalFormToDisk((Serializable) map, getCanonicalFullCollectionName(map)); - */ -/* - // test to make sure the canonical form has been preserved - Map map = makeFullMap(); - if (map instanceof Serializable && !skipSerializedCanonicalTests() && isTestSerialization()) { - Map map2 = (Map) readExternalFormFromDisk(getCanonicalFullCollectionName(map)); - assertEquals("Map is the right size", getSampleKeys().length, map2.size()); - } - }*/ - - /** - * Tests Map.put(Object, Object) - */ - public void testMapPut() { - resetEmpty(); - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - Object[] newValues = getNewSampleValues(); - - if (isPutAddSupported()) { - for (int i = 0; i < keys.length; i++) { - Object o = map.put(keys[i], values[i]); - confirmed.put(keys[i], values[i]); - verifyAll(); - assertTrue("First map.put should return null", o == null); - assertTrue("Map should contain key after put", - map.containsKey(keys[i])); - assertTrue("Map should contain value after put", - map.containsValue(values[i])); - } - if (isPutChangeSupported()) { - for (int i = 0; i < keys.length; i++) { - Object o = map.put(keys[i], newValues[i]); - confirmed.put(keys[i], newValues[i]); - verifyAll(); - assertEquals("Map.put should return previous value when changed", - values[i], o); - assertTrue("Map should still contain key after put when changed", - map.containsKey(keys[i])); - assertTrue("Map should contain new value after put when changed", - map.containsValue(newValues[i])); - - // if duplicates are allowed, we're not guaranteed that the value - // no longer exists, so don't try checking that. - if (!isAllowDuplicateValues()) { - assertTrue("Map should not contain old value after put when changed", - !map.containsValue(values[i])); - } - } - } - else { - try { - // two possible exception here, either valid - map.put(keys[0], newValues[0]); - fail("Expected IllegalArgumentException or UnsupportedOperationException on put (change)"); - } - catch (IllegalArgumentException ex) { - } - catch (UnsupportedOperationException ex) { - } - } - } - else if (isPutChangeSupported()) { - resetEmpty(); - try { - map.put(keys[0], values[0]); - fail("Expected UnsupportedOperationException or IllegalArgumentException on put (add) when fixed size"); - } - catch (IllegalArgumentException ex) { - } - catch (UnsupportedOperationException ex) { - } - - resetFull(); - int i = 0; - for (Iterator it = map.keySet().iterator(); it.hasNext() && i < newValues.length; i++) { - Object key = it.next(); - Object o = map.put(key, newValues[i]); - Object value = confirmed.put(key, newValues[i]); - verifyAll(); - assertEquals("Map.put should return previous value when changed", - value, o); - assertTrue("Map should still contain key after put when changed", - map.containsKey(key)); - assertTrue("Map should contain new value after put when changed", - map.containsValue(newValues[i])); - - // if duplicates are allowed, we're not guaranteed that the value - // no longer exists, so don't try checking that. - if (!isAllowDuplicateValues()) { - assertTrue("Map should not contain old value after put when changed", - !map.containsValue(values[i])); - } - } - } - else { - try { - map.put(keys[0], values[0]); - fail("Expected UnsupportedOperationException on put (add)"); - } - catch (UnsupportedOperationException ex) { - } - } - } - - /** - * Tests Map.put(null, value) - */ - public void testMapPutNullKey() { - resetFull(); - Object[] values = getSampleValues(); - - if (isPutAddSupported()) { - if (isAllowNullKey()) { - map.put(null, values[0]); - } - else { - try { - map.put(null, values[0]); - fail("put(null, value) should throw NPE/IAE"); - } - catch (NullPointerException ex) { - } - catch (IllegalArgumentException ex) { - } - } - } - } - - /** - * Tests Map.put(null, value) - */ - public void testMapPutNullValue() { - resetFull(); - Object[] keys = getSampleKeys(); - - if (isPutAddSupported()) { - if (isAllowNullValue()) { - map.put(keys[0], null); - } - else { - try { - map.put(keys[0], null); - fail("put(key, null) should throw NPE/IAE"); - } - catch (NullPointerException ex) { - } - catch (IllegalArgumentException ex) { - } - } - } - } - - /** - * Tests Map.putAll(map) - */ - public void testMapPutAll() { - if (!isPutAddSupported()) { - if (!isPutChangeSupported()) { - Map temp = makeFullMap(); - resetEmpty(); - try { - map.putAll(temp); - fail("Expected UnsupportedOperationException on putAll"); - } - catch (UnsupportedOperationException ex) { - } - } - return; - } - - resetEmpty(); - - Map m2 = makeFullMap(); - - map.putAll(m2); - confirmed.putAll(m2); - verifyAll(); - - resetEmpty(); - - m2 = makeConfirmedMap(); - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - for (int i = 0; i < keys.length; i++) { - m2.put(keys[i], values[i]); - } - - map.putAll(m2); - confirmed.putAll(m2); - verifyAll(); - } - - /** - * Tests Map.remove(Object) - */ - public void testMapRemove() { - if (!isRemoveSupported()) { - try { - resetFull(); - map.remove(map.keySet().iterator().next()); - fail("Expected UnsupportedOperationException on remove"); - } - catch (UnsupportedOperationException ex) { - } - return; - } - - resetEmpty(); - - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - for (int i = 0; i < keys.length; i++) { - Object o = map.remove(keys[i]); - assertTrue("First map.remove should return null", o == null); - } - verifyAll(); - - resetFull(); - - for (int i = 0; i < keys.length; i++) { - Object o = map.remove(keys[i]); - confirmed.remove(keys[i]); - verifyAll(); - - assertEquals("map.remove with valid key should return value", - values[i], o); - } - - Object[] other = getOtherKeys(); - - resetFull(); - int size = map.size(); - for (int i = 0; i < other.length; i++) { - Object o = map.remove(other[i]); - assertEquals("map.remove for nonexistent key should return null", - o, null); - assertEquals("map.remove for nonexistent key should not " + - "shrink map", size, map.size()); - } - verifyAll(); - } - - //----------------------------------------------------------------------- - - /** - * Tests that the {@link Map#values} collection is backed by - * the underlying map for clear(). - */ - public void testValuesClearChangesMap() { - if (!isRemoveSupported()) { - return; - } - - // clear values, reflected in map - resetFull(); - Collection values = map.values(); - assertTrue(map.size() > 0); - assertTrue(values.size() > 0); - values.clear(); - assertTrue(map.size() == 0); - assertTrue(values.size() == 0); - - // clear map, reflected in values - resetFull(); - values = map.values(); - assertTrue(map.size() > 0); - assertTrue(values.size() > 0); - map.clear(); - assertTrue(map.size() == 0); - assertTrue(values.size() == 0); - } - - /** - * Tests that the {@link Map#keySet} collection is backed by - * the underlying map for clear(). - */ - public void testKeySetClearChangesMap() { - if (!isRemoveSupported()) { - return; - } - - // clear values, reflected in map - resetFull(); - Set keySet = map.keySet(); - assertTrue(map.size() > 0); - assertTrue(keySet.size() > 0); - keySet.clear(); - assertTrue(map.size() == 0); - assertTrue(keySet.size() == 0); - - // clear map, reflected in values - resetFull(); - keySet = map.keySet(); - assertTrue(map.size() > 0); - assertTrue(keySet.size() > 0); - map.clear(); - assertTrue(map.size() == 0); - assertTrue(keySet.size() == 0); - } - - /** - * Tests that the {@link Map#entrySet()} collection is backed by - * the underlying map for clear(). - */ - public void testEntrySetClearChangesMap() { - if (!isRemoveSupported()) { - return; - } - - // clear values, reflected in map - resetFull(); - Set entrySet = map.entrySet(); - assertTrue(map.size() > 0); - assertTrue(entrySet.size() > 0); - entrySet.clear(); - assertTrue(map.size() == 0); - assertTrue(entrySet.size() == 0); - - // clear map, reflected in values - resetFull(); - entrySet = map.entrySet(); - assertTrue(map.size() > 0); - assertTrue(entrySet.size() > 0); - map.clear(); - assertTrue(map.size() == 0); - assertTrue(entrySet.size() == 0); - } - - //----------------------------------------------------------------------- - public void testEntrySetContains1() { - resetFull(); - Set entrySet = map.entrySet(); - Map.Entry entry = (Map.Entry) entrySet.iterator().next(); - assertEquals(true, entrySet.contains(entry)); - } - - public void testEntrySetContains2() { - resetFull(); - Set entrySet = map.entrySet(); - Map.Entry entry = (Map.Entry) entrySet.iterator().next(); - Map.Entry test = cloneMapEntry(entry); - assertEquals(true, entrySet.contains(test)); - } - - public void testEntrySetContains3() { - resetFull(); - Set entrySet = map.entrySet(); - Map.Entry entry = (Map.Entry) entrySet.iterator().next(); - HashMap temp = new HashMap(); - temp.put(entry.getKey(), "A VERY DIFFERENT VALUE"); - Map.Entry test = (Map.Entry) temp.entrySet().iterator().next(); - assertEquals(false, entrySet.contains(test)); - } - - public void testEntrySetRemove1() { - if (!isRemoveSupported()) { - return; - } - resetFull(); - int size = map.size(); - Set entrySet = map.entrySet(); - Map.Entry entry = (Map.Entry) entrySet.iterator().next(); - Object key = entry.getKey(); - - assertEquals(true, entrySet.remove(entry)); - assertEquals(false, map.containsKey(key)); - assertEquals(size - 1, map.size()); - } - - public void testEntrySetRemove2() { - if (!isRemoveSupported()) { - return; - } - resetFull(); - int size = map.size(); - Set entrySet = map.entrySet(); - Map.Entry entry = (Map.Entry) entrySet.iterator().next(); - Object key = entry.getKey(); - Map.Entry test = cloneMapEntry(entry); - - assertEquals(true, entrySet.remove(test)); - assertEquals(false, map.containsKey(key)); - assertEquals(size - 1, map.size()); - } - - public void testEntrySetRemove3() { - if (!isRemoveSupported()) { - return; - } - resetFull(); - int size = map.size(); - Set entrySet = map.entrySet(); - Map.Entry entry = (Map.Entry) entrySet.iterator().next(); - Object key = entry.getKey(); - HashMap temp = new HashMap(); - temp.put(entry.getKey(), "A VERY DIFFERENT VALUE"); - Map.Entry test = (Map.Entry) temp.entrySet().iterator().next(); - - assertEquals(false, entrySet.remove(test)); - assertEquals(true, map.containsKey(key)); - assertEquals(size, map.size()); - } - - //----------------------------------------------------------------------- - - /** - * Tests that the {@link Map#values} collection is backed by - * the underlying map by removing from the values collection - * and testing if the value was removed from the map. - *

- * We should really test the "vice versa" case--that values removed - * from the map are removed from the values collection--also, - * but that's a more difficult test to construct (lacking a - * "removeValue" method.) - *

- *

- * See bug - * 9573. - *

- */ - public void testValuesRemoveChangesMap() { - resetFull(); - Object[] sampleValues = getSampleValues(); - Collection values = map.values(); - for (int i = 0; i < sampleValues.length; i++) { - if (map.containsValue(sampleValues[i])) { - int j = 0; // loop counter prevents infinite loops when remove is broken - while (values.contains(sampleValues[i]) && j < 10000) { - try { - values.remove(sampleValues[i]); - } - catch (UnsupportedOperationException e) { - // if values.remove is unsupported, just skip this test - return; - } - j++; - } - assertTrue("values().remove(obj) is broken", j < 10000); - assertTrue( - "Value should have been removed from the underlying map.", - !map.containsValue(sampleValues[i])); - } - } - } - - /** - * Tests that the {@link Map#keySet} set is backed by - * the underlying map by removing from the keySet set - * and testing if the key was removed from the map. - */ - public void testKeySetRemoveChangesMap() { - resetFull(); - Object[] sampleKeys = getSampleKeys(); - Set keys = map.keySet(); - for (int i = 0; i < sampleKeys.length; i++) { - try { - keys.remove(sampleKeys[i]); - } - catch (UnsupportedOperationException e) { - // if key.remove is unsupported, just skip this test - return; - } - assertTrue( - "Key should have been removed from the underlying map.", - !map.containsKey(sampleKeys[i])); - } - } - - // TODO: Need: - // testValuesRemovedFromEntrySetAreRemovedFromMap - // same for EntrySet/KeySet/values's - // Iterator.remove, removeAll, retainAll - - /** - * Utility methods to create an array of Map.Entry objects - * out of the given key and value arrays.

- * - * @param keys the array of keys - * @param values the array of values - * @return an array of Map.Entry of those keys to those values - */ - private Map.Entry[] makeEntryArray(Object[] keys, Object[] values) { - Map.Entry[] result = new Map.Entry[keys.length]; - for (int i = 0; i < keys.length; i++) { - Map map = makeConfirmedMap(); - map.put(keys[i], values[i]); - result[i] = (Map.Entry) map.entrySet().iterator().next(); - } - return result; - } - - /** - * Bulk test {@link Map#entrySet()}. This method runs through all of - * the tests in {@link SetAbstractTestCase}. - * After modification operations, {@link #verifyAll()} is invoked to ensure - * that the map and the other collection views are still valid. - * - * @return a {@link SetAbstractTestCase} instance for testing the map's entry set - */ - /* - public BulkTest bulkTestMapEntrySet() { - return new TestMapEntrySet(); - } - */ - - public class TestMapEntrySet extends SetAbstractTestCase { - - // Have to implement manually; entrySet doesn't support addAll - public Object[] getFullElements() { - Object[] k = getSampleKeys(); - Object[] v = getSampleValues(); - return makeEntryArray(k, v); - } - - // Have to implement manually; entrySet doesn't support addAll - public Object[] getOtherElements() { - Object[] k = getOtherKeys(); - Object[] v = getOtherValues(); - return makeEntryArray(k, v); - } - - public Set makeEmptySet() { - return makeEmptyMap().entrySet(); - } - - public Set makeFullSet() { - return makeFullMap().entrySet(); - } - - public boolean isAddSupported() { - // Collection views don't support add operations. - return false; - } - - public boolean isRemoveSupported() { - // Entry set should only support remove if map does - return MapAbstractTestCase.this.isRemoveSupported(); - } - - public boolean isGetStructuralModify() { - return MapAbstractTestCase.this.isGetStructuralModify(); - } - - public boolean isTestSerialization() { - return false; - } - - public void resetFull() { - MapAbstractTestCase.this.resetFull(); - collection = map.entrySet(); - TestMapEntrySet.this.confirmed = MapAbstractTestCase.this.confirmed.entrySet(); - } - - public void resetEmpty() { - MapAbstractTestCase.this.resetEmpty(); - collection = map.entrySet(); - TestMapEntrySet.this.confirmed = MapAbstractTestCase.this.confirmed.entrySet(); - } - - public void testMapEntrySetIteratorEntry() { - resetFull(); - Iterator it = collection.iterator(); - int count = 0; - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - assertEquals(true, MapAbstractTestCase.this.map.containsKey(entry.getKey())); - assertEquals(true, MapAbstractTestCase.this.map.containsValue(entry.getValue())); - if (isGetStructuralModify() == false) { - assertEquals(MapAbstractTestCase.this.map.get(entry.getKey()), entry.getValue()); - } - count++; - } - assertEquals(collection.size(), count); - } - - public void testMapEntrySetIteratorEntrySetValue() { - Object key1 = getSampleKeys()[0]; - Object key2 = (getSampleKeys().length == 1 ? getSampleKeys()[0] : getSampleKeys()[1]); - Object newValue1 = getNewSampleValues()[0]; - Object newValue2 = (getNewSampleValues().length == 1 ? getNewSampleValues()[0] : getNewSampleValues()[1]); - - resetFull(); - // explicitly get entries as sample values/keys are connected for some maps - // such as BeanMap - Iterator it = TestMapEntrySet.this.collection.iterator(); - Map.Entry entry1 = getEntry(it, key1); - it = TestMapEntrySet.this.collection.iterator(); - Map.Entry entry2 = getEntry(it, key2); - Iterator itConfirmed = TestMapEntrySet.this.confirmed.iterator(); - Map.Entry entryConfirmed1 = getEntry(itConfirmed, key1); - itConfirmed = TestMapEntrySet.this.confirmed.iterator(); - Map.Entry entryConfirmed2 = getEntry(itConfirmed, key2); - verifyAll(); - - if (isSetValueSupported() == false) { - try { - entry1.setValue(newValue1); - } - catch (UnsupportedOperationException ex) { - } - return; - } - - entry1.setValue(newValue1); - entryConfirmed1.setValue(newValue1); - assertEquals(newValue1, entry1.getValue()); - assertEquals(true, MapAbstractTestCase.this.map.containsKey(entry1.getKey())); - assertEquals(true, MapAbstractTestCase.this.map.containsValue(newValue1)); - assertEquals(newValue1, MapAbstractTestCase.this.map.get(entry1.getKey())); - verifyAll(); - - entry1.setValue(newValue1); - entryConfirmed1.setValue(newValue1); - assertEquals(newValue1, entry1.getValue()); - assertEquals(true, MapAbstractTestCase.this.map.containsKey(entry1.getKey())); - assertEquals(true, MapAbstractTestCase.this.map.containsValue(newValue1)); - assertEquals(newValue1, MapAbstractTestCase.this.map.get(entry1.getKey())); - verifyAll(); - - entry2.setValue(newValue2); - entryConfirmed2.setValue(newValue2); - assertEquals(newValue2, entry2.getValue()); - assertEquals(true, MapAbstractTestCase.this.map.containsKey(entry2.getKey())); - assertEquals(true, MapAbstractTestCase.this.map.containsValue(newValue2)); - assertEquals(newValue2, MapAbstractTestCase.this.map.get(entry2.getKey())); - verifyAll(); - } - - public Map.Entry getEntry(Iterator itConfirmed, Object key) { - Map.Entry entry = null; - while (itConfirmed.hasNext()) { - Map.Entry temp = (Map.Entry) itConfirmed.next(); - if (temp.getKey() == null) { - if (key == null) { - entry = temp; - break; - } - } - else if (temp.getKey().equals(key)) { - entry = temp; - break; - } - } - assertNotNull("No matching entry in map for key '" + key + "'", entry); - return entry; - } - - public void testMapEntrySetRemoveNonMapEntry() { - if (isRemoveSupported() == false) { - return; - } - resetFull(); - assertEquals(false, getSet().remove(null)); - assertEquals(false, getSet().remove(new Object())); - } - - public void verifyAll() { - super.verifyAll(); - MapAbstractTestCase.this.verifyAll(); - } - } - - /** - * Bulk test {@link Map#keySet()}. This method runs through all of - * the tests in {@link SetAbstractTestCase}. - * After modification operations, {@link #verifyAll()} is invoked to ensure - * that the map and the other collection views are still valid. - * - * @return a {@link SetAbstractTestCase} instance for testing the map's key set - */ - /* - public BulkTest bulkTestMapKeySet() { - return new TestMapKeySet(); - } - */ - - public class TestMapKeySet extends SetAbstractTestCase { - public Object[] getFullElements() { - return getSampleKeys(); - } - - public Object[] getOtherElements() { - return getOtherKeys(); - } - - public Set makeEmptySet() { - return makeEmptyMap().keySet(); - } - - public Set makeFullSet() { - return makeFullMap().keySet(); - } - - public boolean isNullSupported() { - return MapAbstractTestCase.this.isAllowNullKey(); - } - - public boolean isAddSupported() { - return false; - } - - public boolean isRemoveSupported() { - return MapAbstractTestCase.this.isRemoveSupported(); - } - - public boolean isTestSerialization() { - return false; - } - - public void resetEmpty() { - MapAbstractTestCase.this.resetEmpty(); - collection = map.keySet(); - TestMapKeySet.this.confirmed = MapAbstractTestCase.this.confirmed.keySet(); - } - - public void resetFull() { - MapAbstractTestCase.this.resetFull(); - collection = map.keySet(); - TestMapKeySet.this.confirmed = MapAbstractTestCase.this.confirmed.keySet(); - } - - public void verifyAll() { - super.verifyAll(); - MapAbstractTestCase.this.verifyAll(); - } - } - - /** - * Bulk test {@link Map#values()}. This method runs through all of - * the tests in {@link CollectionAbstractTestCase}. - * After modification operations, {@link #verifyAll()} is invoked to ensure - * that the map and the other collection views are still valid. - * - * @return a {@link CollectionAbstractTestCase} instance for testing the map's - * values collection - */ - /* - public BulkTest bulkTestMapValues() { - return new TestMapValues(); - } - */ - - public class TestMapValues extends CollectionAbstractTestCase { - public Object[] getFullElements() { - return getSampleValues(); - } - - public Object[] getOtherElements() { - return getOtherValues(); - } - - public Collection makeCollection() { - return makeEmptyMap().values(); - } - - public Collection makeFullCollection() { - return makeFullMap().values(); - } - - public boolean isNullSupported() { - return MapAbstractTestCase.this.isAllowNullKey(); - } - - public boolean isAddSupported() { - return false; - } - - public boolean isRemoveSupported() { - return MapAbstractTestCase.this.isRemoveSupported(); - } - - public boolean isTestSerialization() { - return false; - } - - public boolean areEqualElementsDistinguishable() { - // equal values are associated with different keys, so they are - // distinguishable. - return true; - } - - public Collection makeConfirmedCollection() { - // never gets called, reset methods are overridden - return null; - } - - public Collection makeConfirmedFullCollection() { - // never gets called, reset methods are overridden - return null; - } - - public void resetFull() { - MapAbstractTestCase.this.resetFull(); - collection = map.values(); - TestMapValues.this.confirmed = MapAbstractTestCase.this.confirmed.values(); - } - - public void resetEmpty() { - MapAbstractTestCase.this.resetEmpty(); - collection = map.values(); - TestMapValues.this.confirmed = MapAbstractTestCase.this.confirmed.values(); - } - - public void verifyAll() { - super.verifyAll(); - MapAbstractTestCase.this.verifyAll(); - } - - // TODO: should test that a remove on the values collection view - // removes the proper mapping and not just any mapping that may have - // the value equal to the value returned from the values iterator. - } - - /** - * Resets the {@link #map}, {@link #entrySet}, {@link #keySet}, - * {@link #values} and {@link #confirmed} fields to empty. - */ - public void resetEmpty() { - this.map = makeEmptyMap(); - views(); - this.confirmed = makeConfirmedMap(); - } - - /** - * Resets the {@link #map}, {@link #entrySet}, {@link #keySet}, - * {@link #values} and {@link #confirmed} fields to full. - */ - public void resetFull() { - this.map = makeFullMap(); - views(); - this.confirmed = makeConfirmedMap(); - Object[] k = getSampleKeys(); - Object[] v = getSampleValues(); - for (int i = 0; i < k.length; i++) { - confirmed.put(k[i], v[i]); - } - } - - /** - * Resets the collection view fields. - */ - private void views() { - this.keySet = map.keySet(); - this.values = map.values(); - this.entrySet = map.entrySet(); - } - - /** - * Verifies that {@link #map} is still equal to {@link #confirmed}. - * This method checks that the map is equal to the HashMap, - * and that the map's collection views are still equal to - * the HashMap's collection views. An {@code equals} test - * is done on the maps and their collection views; their size and - * {@code isEmpty} results are compared; their hashCodes are - * compared; and {@code containsAll} tests are run on the - * collection views. - */ - public void verifyAll() { - verifyMap(); - verifyEntrySet(); - verifyKeySet(); - verifyValues(); - } - - public void verifyMap() { - int size = confirmed.size(); - boolean empty = confirmed.isEmpty(); - assertEquals("Map should be same size as HashMap", size, map.size()); - assertEquals("Map should be empty if HashMap is", empty, map.isEmpty()); - assertEquals("hashCodes should be the same", confirmed.hashCode(), map.hashCode()); - // this fails for LRUMap because confirmed.equals() somehow modifies - // map, causing concurrent modification exceptions. - //assertEquals("Map should still equal HashMap", confirmed, map); - // this works though and performs the same verification: - assertTrue("Map should still equal HashMap", map.equals(confirmed)); - // TODO: this should really be reexamined to figure out why LRU map - // behaves like it does (the equals shouldn't modify since all accesses - // by the confirmed collection should be through an iterator, thus not - // causing LRUMap to change). - } - - public void verifyEntrySet() { - int size = confirmed.size(); - boolean empty = confirmed.isEmpty(); - assertEquals("entrySet should be same size as HashMap's\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), - size, entrySet.size()); - assertEquals("entrySet should be empty if HashMap is\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), - empty, entrySet.isEmpty()); - assertTrue("entrySet should contain all HashMap's elements\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), - entrySet.containsAll(confirmed.entrySet())); - assertEquals("entrySet hashCodes should be the same\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), - confirmed.entrySet().hashCode(), entrySet.hashCode()); - assertEquals("Map's entry set should still equal HashMap's", confirmed.entrySet(), entrySet); - } - - public void verifyKeySet() { - int size = confirmed.size(); - boolean empty = confirmed.isEmpty(); - assertEquals("keySet should be same size as HashMap's\nTest: " + keySet + "\nReal: " + confirmed.keySet(), - size, keySet.size()); - assertEquals("keySet should be empty if HashMap is\nTest: " + keySet + "\nReal: " + confirmed.keySet(), - empty, keySet.isEmpty()); - assertTrue("keySet should contain all HashMap's elements\nTest: " + keySet + "\nReal: " + confirmed.keySet(), - keySet.containsAll(confirmed.keySet())); - assertEquals("keySet hashCodes should be the same\nTest: " + keySet + "\nReal: " + confirmed.keySet(), - confirmed.keySet().hashCode(), keySet.hashCode()); - assertEquals("Map's key set should still equal HashMap's", confirmed.keySet(), keySet); - } - - public void verifyValues() { - List known = new ArrayList(confirmed.values()); - List test = new ArrayList(values); - - int size = confirmed.size(); - boolean empty = confirmed.isEmpty(); - - assertEquals("values should be same size as HashMap's\nTest: " + test + "\nReal: " + known, size, values.size()); - assertEquals("values should be empty if HashMap is\nTest: " + test + "\nReal: " + known, empty, values.isEmpty()); - assertTrue("values should contain all HashMap's elements\nTest: " + test + "\nReal: " + known, test.containsAll(known)); - assertTrue("values should contain all HashMap's elements\nTest: " + test + "\nReal: " + known, known.containsAll(test)); - - for (Object aKnown : known) { - boolean removed = test.remove(aKnown); - assertTrue("Map's values should still equal HashMap's", removed); - } - - assertTrue("Map's values should still equal HashMap's", test.isEmpty()); - } - - /** - * Erases any leftover instance variables by setting them to null. - */ - public void tearDown() throws Exception { - map = null; - keySet = null; - entrySet = null; - values = null; - confirmed = null; - } -} +/* + * 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.twelvemonkeys.util; + +import org.junit.After; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; + +/** + * Abstract test class for {@link java.util.Map} methods and contracts. + *

+ * The forces at work here are similar to those in {@link CollectionAbstractTest}. + * If your class implements the full Map interface, including optional + * operations, simply extend this class, and implement the + * {@link #makeEmptyMap()} method. + *

+ * On the other hand, if your map implementation is weird, you may have to + * override one or more of the other protected methods. They're described + * below. + *

+ * Entry Population Methods + *

+ * Override these methods if your map requires special entries: + *

+ *

    + *
  • {@link #getSampleKeys()} + *
  • {@link #getSampleValues()} + *
  • {@link #getNewSampleValues()} + *
  • {@link #getOtherKeys()} + *
  • {@link #getOtherValues()} + *
+ *

+ * Supported Operation Methods + *

+ * Override these methods if your map doesn't support certain operations: + *

+ *

    + *
  • {@link #isPutAddSupported()} + *
  • {@link #isPutChangeSupported()} + *
  • {@link #isSetValueSupported()} + *
  • {@link #isRemoveSupported()} + *
  • {@link #isGetStructuralModify()} + *
  • {@link #isAllowDuplicateValues()} + *
  • {@link #isAllowNullKey()} + *
  • {@link #isAllowNullValue()} + *
+ *

+ * Fixture Methods + *

+ * For tests on modification operations (puts and removes), fixtures are used + * to verify that that operation results in correct state for the map and its + * collection views. Basically, the modification is performed against your + * map implementation, and an identical modification is performed against + * a confirmed map implementation. A confirmed map implementation is + * something like {@code java.util.HashMap}, which is known to conform + * exactly to the {@link Map} contract. After the modification takes place + * on both your map implementation and the confirmed map implementation, the + * two maps are compared to see if their state is identical. The comparison + * also compares the collection views to make sure they're still the same.

+ *

+ * The upshot of all that is that any test that modifies the map in + * any way will verify that all of the map's state is still + * correct, including the state of its collection views. So for instance + * if a key is removed by the map's key set's iterator, then the entry set + * is checked to make sure the key/value pair no longer appears.

+ *

+ * The {@link #map} field holds an instance of your collection implementation. + * The {@link #entrySet}, {@link #keySet} and {@link #values} fields hold + * that map's collection views. And the {@link #confirmed} field holds + * an instance of the confirmed collection implementation. The + * {@link #resetEmpty()} and {@link #resetFull()} methods set these fields to + * empty or full maps, so that tests can proceed from a known state.

+ *

+ * After a modification operation to both {@link #map} and {@link #confirmed}, + * the {@link #verifyAll()} method is invoked to compare the results. The + * {@link # verify0} method calls separate methods to verify the map and its three + * collection views ({@link #verifyMap}, {@link #verifyEntrySet}, + * {@link #verifyKeySet}, and {@link #verifyValues}). You may want to override + * one of the verification methodsto perform additional verifications. For + * instance, TestDoubleOrderedMap would want override its + * {@link #verifyValues()} method to verify that the values are unique and in + * ascending order.

+ *

+ * Other Notes + *

+ * If your {@link Map} fails one of these tests by design, you may still use + * this base set of cases. Simply override the test case (method) your map + * fails and/or the methods that define the assumptions used by the test + * cases. For example, if your map does not allow duplicate values, override + * {@link #isAllowDuplicateValues()} and have it return {@code false} + * + * @author Michael Smith + * @author Rodney Waldhoff + * @author Paul Jack + * @author Stephen Colebourne + * @version $Revision: #2 $ $Date: 2008/07/15 $ + */ +public abstract class MapAbstractTest extends ObjectAbstractTest { + + /** + * JDK1.2 has bugs in null handling of Maps, especially HashMap.Entry.toString + * This avoids nulls for JDK1.2 + */ + private static final boolean JDK12; + static { + String str = System.getProperty("java.version"); + JDK12 = str.startsWith("1.2"); + } + + // These instance variables are initialized with the reset method. + // Tests for map methods that alter the map (put, putAll, remove) + // first call reset() to create the map and its views; then perform + // the modification on the map; perform the same modification on the + // confirmed; and then call verifyAll() to ensure that the map is equal + // to the confirmed, that the already-constructed collection views + // are still equal to the confirmed's collection views. + + + /** Map created by reset(). */ + protected Map map; + + /** Entry set of map created by reset(). */ + protected Set entrySet; + + /** Key set of map created by reset(). */ + protected Set keySet; + + /** Values collection of map created by reset(). */ + protected Collection values; + + /** HashMap created by reset(). */ + protected Map confirmed; + + // TODO: Figure out if we need these tests... + protected boolean skipSerializedCanonicalTests() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * support the {@code put} and {@code putAll} operations + * adding new mappings. + *

+ * Default implementation returns true. + * Override if your collection class does not support put adding. + */ + public boolean isPutAddSupported() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * support the {@code put} and {@code putAll} operations + * changing existing mappings. + *

+ * Default implementation returns true. + * Override if your collection class does not support put changing. + */ + public boolean isPutChangeSupported() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * support the {@code setValue} operation on entrySet entries. + *

+ * Default implementation returns isPutChangeSupported(). + * Override if your collection class does not support setValue but does + * support put changing. + */ + public boolean isSetValueSupported() { + return isPutChangeSupported(); + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * support the {@code remove} and {@code clear} operations. + *

+ * Default implementation returns true. + * Override if your collection class does not support removal operations. + */ + public boolean isRemoveSupported() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * can cause structural modification on a get(). The example is LRUMap. + *

+ * Default implementation returns false. + * Override if your map class structurally modifies on get. + */ + public boolean isGetStructuralModify() { + return false; + } + + /** + * Returns whether the sub map views of SortedMap are serializable. + * If the class being tested is based around a TreeMap then you should + * override and return false as TreeMap has a bug in deserialization. + * + * @return false + */ + public boolean isSubMapViewsSerializable() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * supports null keys. + *

+ * Default implementation returns true. + * Override if your collection class does not support null keys. + */ + public boolean isAllowNullKey() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * supports null values. + *

+ * Default implementation returns true. + * Override if your collection class does not support null values. + */ + public boolean isAllowNullValue() { + return true; + } + + /** + * Returns true if the maps produced by + * {@link #makeEmptyMap()} and {@link #makeFullMap()} + * supports duplicate values. + *

+ * Default implementation returns true. + * Override if your collection class does not support duplicate values. + */ + public boolean isAllowDuplicateValues() { + return true; + } + + /** + * Returns the set of keys in the mappings used to test the map. This + * method must return an array with the same length as {@link + * #getSampleValues()} and all array elements must be different. The + * default implementation constructs a set of String keys, and includes a + * single null key if {@link #isAllowNullKey()} returns {@code true}. + */ + public Object[] getSampleKeys() { + Object[] result = new Object[] { + "blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee", + "hello", "goodbye", "we'll", "see", "you", "all", "again", + "key", + "key2", + (isAllowNullKey() && !JDK12) ? null : "nonnullkey" + }; + return result; + } + + public Object[] getOtherKeys() { + return getOtherNonNullStringElements(); + } + + public Object[] getOtherValues() { + return getOtherNonNullStringElements(); + } + + /** + * Returns a list of string elements suitable for return by + * {@link #getOtherKeys()} or {@link #getOtherValues}. + *

+ *

Override getOtherElements to returnthe results of this method if your + * collection does not support heterogenous elements or the null element. + *

+ */ + public Object[] getOtherNonNullStringElements() { + return new Object[] { + "For", "then", "despite",/* of */"space", "I", "would", "be", "brought", + "From", "limits", "far", "remote", "where", "thou", "dost", "stay" + }; + } + + /** + * Returns the set of values in the mappings used to test the map. This + * method must return an array with the same length as + * {@link #getSampleKeys()}. The default implementation constructs a set of + * String values and includes a single null value if + * {@link #isAllowNullValue()} returns {@code true}, and includes + * two values that are the same if {@link #isAllowDuplicateValues()} returns + * {@code true}. + */ + public Object[] getSampleValues() { + Object[] result = new Object[] { + "blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev", + "hellov", "goodbyev", "we'llv", "seev", "youv", "allv", "againv", + (isAllowNullValue() && !JDK12) ? null : "nonnullvalue", + "value", + (isAllowDuplicateValues()) ? "value" : "value2", + }; + return result; + } + + /** + * Returns a the set of values that can be used to replace the values + * returned from {@link #getSampleValues()}. This method must return an + * array with the same length as {@link #getSampleValues()}. The values + * returned from this method should not be the same as those returned from + * {@link #getSampleValues()}. The default implementation constructs a + * set of String values and includes a single null value if + * {@link #isAllowNullValue()} returns {@code true}, and includes two values + * that are the same if {@link #isAllowDuplicateValues()} returns + * {@code true}. + */ + public Object[] getNewSampleValues() { + Object[] result = new Object[] { + (isAllowNullValue() && !JDK12 && isAllowDuplicateValues()) ? null : "newnonnullvalue", + "newvalue", + (isAllowDuplicateValues()) ? "newvalue" : "newvalue2", + "newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv", + "newgollyv", "newgeev", "newhellov", "newgoodbyev", "newwe'llv", + "newseev", "newyouv", "newallv", "newagainv", + }; + return result; + } + + /** + * Helper method to add all the mappings described by + * {@link #getSampleKeys()} and {@link #getSampleValues()}. + */ + public void addSampleMappings(Map m) { + + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + + for (int i = 0; i < keys.length; i++) { + try { + m.put(keys[i], values[i]); + } + catch (NullPointerException exception) { + assertTrue("NullPointerException only allowed to be thrown if either the key or value is null.", + keys[i] == null || values[i] == null); + + assertTrue("NullPointerException on null key, but isAllowNullKey is not overridden to return false.", + keys[i] == null || !isAllowNullKey()); + + assertTrue("NullPointerException on null value, but isAllowNullValue is not overridden to return false.", + values[i] == null || !isAllowNullValue()); + + assertTrue("Unknown reason for NullPointer.", false); + } + } + assertEquals("size must reflect number of mappings added.", keys.length, m.size()); + } + + //----------------------------------------------------------------------- + + /** + * Return a new, empty {@link Map} to be used for testing. + * + * @return the map to be tested + */ + public abstract Map makeEmptyMap(); + + /** + * Return a new, populated map. The mappings in the map should match the + * keys and values returned from {@link #getSampleKeys()} and + * {@link #getSampleValues()}. The default implementation uses makeEmptyMap() + * and calls {@link #addSampleMappings} to add all the mappings to the + * map. + * + * @return the map to be tested + */ + public Map makeFullMap() { + Map m = makeEmptyMap(); + addSampleMappings(m); + return m; + } + + /** + * Implements the superclass method to return the map to be tested. + * + * @return the map to be tested + */ + public Object makeObject() { + return makeEmptyMap(); + } + + /** + * Override to return a map other than HashMap as the confirmed map. + * + * @return a map that is known to be valid + */ + public Map makeConfirmedMap() { + return new HashMap(); + } + + /** + * Creates a new Map Entry that is independent of the first and the map. + */ + public Map.Entry cloneMapEntry(Map.Entry entry) { + HashMap map = new HashMap(); + map.put(entry.getKey(), entry.getValue()); + return (Map.Entry) map.entrySet().iterator().next(); + } + + /** + * Gets the compatability version, needed for package access. + */ + public String getCompatibilityVersion() { + return super.getCompatibilityVersion(); + } + + //----------------------------------------------------------------------- + + /** + * Test to ensure the test setup is working properly. This method checks + * to ensure that the getSampleKeys and getSampleValues methods are + * returning results that look appropriate. That is, they both return a + * non-null array of equal length. The keys array must not have any + * duplicate values, and may only contain a (single) null key if + * isNullKeySupported() returns true. The values array must only have a null + * value if useNullValue() is true and may only have duplicate values if + * isAllowDuplicateValues() returns true. + */ + @Test + public void testSampleMappings() { + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + Object[] newValues = getNewSampleValues(); + + assertTrue("failure in test: Must have keys returned from getSampleKeys.", keys != null); + assertTrue("failure in test: Must have values returned from getSampleValues.", values != null); + + // verify keys and values have equivalent lengths (in case getSampleX are + // overridden) + assertEquals("failure in test: not the same number of sample keys and values.", keys.length, values.length); + assertEquals("failure in test: not the same number of values and new values.", values.length, newValues.length); + + // verify there aren't duplicate keys, and check values + for (int i = 0; i < keys.length - 1; i++) { + for (int j = i + 1; j < keys.length; j++) { + assertTrue("failure in test: duplicate null keys.", (keys[i] != null || keys[j] != null)); + assertTrue("failure in test: duplicate non-null key.", + (keys[i] == null || keys[j] == null || (!keys[i].equals(keys[j]) && !keys[j].equals(keys[i])))); + } + + assertTrue("failure in test: found null key, but isNullKeySupported is false.", keys[i] != null || isAllowNullKey()); + assertTrue("failure in test: found null value, but isNullValueSupported is false.", values[i] != null || isAllowNullValue()); + assertTrue("failure in test: found null new value, but isNullValueSupported is false.", newValues[i] != null || isAllowNullValue()); + assertTrue("failure in test: values should not be the same as new value", + values[i] != newValues[i] && (values[i] == null || !values[i].equals(newValues[i]))); + } + } + + // tests begin here. Each test adds a little bit of tested functionality. + // Many methods assume previous methods passed. That is, they do not + // exhaustively recheck things that have already been checked in a previous + // test methods. + + /** + * Test to ensure that makeEmptyMap and makeFull returns a new non-null + * map with each invocation. + */ + @Test + public void testMakeMap() { + Map em = makeEmptyMap(); + assertTrue("failure in test: makeEmptyMap must return a non-null map.", em != null); + + Map em2 = makeEmptyMap(); + assertTrue("failure in test: makeEmptyMap must return a non-null map.", em2 != null); + assertTrue("failure in test: makeEmptyMap must return a new map with each invocation.", em != em2); + + Map fm = makeFullMap(); + assertTrue("failure in test: makeFullMap must return a non-null map.", fm != null); + + Map fm2 = makeFullMap(); + assertTrue("failure in test: makeFullMap must return a non-null map.", fm2 != null); + assertTrue("failure in test: makeFullMap must return a new map with each invocation.", fm != fm2); + } + + /** + * Tests Map.isEmpty() + */ + @Test + public void testMapIsEmpty() { + resetEmpty(); + assertEquals("Map.isEmpty() should return true with an empty map", true, map.isEmpty()); + verifyAll(); + + resetFull(); + assertEquals("Map.isEmpty() should return false with a non-empty map", false, map.isEmpty()); + verifyAll(); + } + + /** + * Tests Map.size() + */ + @Test + public void testMapSize() { + resetEmpty(); + assertEquals("Map.size() should be 0 with an empty map", 0, map.size()); + verifyAll(); + + resetFull(); + assertEquals("Map.size() should equal the number of entries in the map", getSampleKeys().length, map.size()); + verifyAll(); + } + + /** + * Tests {@link Map#clear()}. If the map {@link #isRemoveSupported()} + * can add and remove elements}, then {@link Map#size()} and + * {@link Map#isEmpty()} are used to ensure that map has no elements after + * a call to clear. If the map does not support adding and removing + * elements, this method checks to ensure clear throws an + * UnsupportedOperationException. + */ + @Test + public void testMapClear() { + if (!isRemoveSupported()) { + try { + resetFull(); + map.clear(); + fail("Expected UnsupportedOperationException on clear"); + } + catch (UnsupportedOperationException ex) { + } + return; + } + + resetEmpty(); + map.clear(); + confirmed.clear(); + verifyAll(); + + resetFull(); + map.clear(); + confirmed.clear(); + verifyAll(); + } + + /** + * Tests Map.containsKey(Object) by verifying it returns false for all + * sample keys on a map created using an empty map and returns true for + * all sample keys returned on a full map. + */ + @Test + public void testMapContainsKey() { + Object[] keys = getSampleKeys(); + + resetEmpty(); + for (Object key : keys) { + assertTrue("Map must not contain key when map is empty", !map.containsKey(key)); + } + verifyAll(); + + resetFull(); + for (Object key : keys) { + assertTrue("Map must contain key for a mapping in the map. Missing: " + key, map.containsKey(key)); + } + verifyAll(); + } + + /** + * Tests Map.containsValue(Object) by verifying it returns false for all + * sample values on an empty map and returns true for all sample values on + * a full map. + */ + @Test + public void testMapContainsValue() { + Object[] values = getSampleValues(); + + resetEmpty(); + for (Object value : values) { + assertTrue("Empty map must not contain value", !map.containsValue(value)); + } + verifyAll(); + + resetFull(); + for (Object value : values) { + assertTrue("Map must contain value for a mapping in the map.", map.containsValue(value)); + } + verifyAll(); + } + + /** + * Tests Map.equals(Object) + */ + @Test + public void testMapEquals() { + resetEmpty(); + assertTrue("Empty maps unequal.", map.equals(confirmed)); + verifyAll(); + + resetFull(); + assertTrue("Full maps unequal.", map.equals(confirmed)); + verifyAll(); + + resetFull(); + // modify the HashMap created from the full map and make sure this + // change results in map.equals() to return false. + Iterator iter = confirmed.keySet().iterator(); + iter.next(); + iter.remove(); + assertTrue("Different maps equal.", !map.equals(confirmed)); + + resetFull(); + assertTrue("equals(null) returned true.", !map.equals(null)); + assertTrue("equals(new Object()) returned true.", !map.equals(new Object())); + verifyAll(); + } + + /** + * Tests Map.get(Object) + */ + @Test + public void testMapGet() { + resetEmpty(); + + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + + for (Object key : keys) { + assertTrue("Empty map.get() should return null.", map.get(key) == null); + } + + verifyAll(); + + resetFull(); + for (int i = 0; i < keys.length; i++) { + assertEquals("Full map.get() should return value from mapping.", values[i], map.get(keys[i])); + } + } + + /** + * Tests Map.hashCode() + */ + @Test + public void testMapHashCode() { + resetEmpty(); + assertTrue("Empty maps have different hashCodes.", map.hashCode() == confirmed.hashCode()); + + resetFull(); + assertTrue("Equal maps have different hashCodes.", map.hashCode() == confirmed.hashCode()); + } + + /** + * Tests Map.toString(). Since the format of the string returned by the + * toString() method is not defined in the Map interface, there is no + * common way to test the results of the toString() method. Thereforce, + * it is encouraged that Map implementations override this test with one + * that checks the format matches any format defined in its API. This + * default implementation just verifies that the toString() method does + * not return null. + */ + @Test + public void testMapToString() { + resetEmpty(); + assertTrue("Empty map toString() should not return null", map.toString() != null); + verifyAll(); + + resetFull(); + assertTrue("Empty map toString() should not return null", map.toString() != null); + verifyAll(); + } + + /** + * Compare the current serialized form of the Map + * against the canonical version in CVS. + */ + /* + @Test + public void testEmptyMapCompatibility() throws Exception { + /* + * Create canonical objects with this code + Map map = makeEmptyMap(); + if (!(map instanceof Serializable)) return; + + writeExternalFormToDisk((Serializable) map, getCanonicalEmptyCollectionName(map)); + // + + // test to make sure the canonical form has been preserved + Map map = makeEmptyMap(); + if (map instanceof Serializable && !skipSerializedCanonicalTests() && isTestSerialization()) { + Map map2 = (Map) readExternalFormFromDisk(getCanonicalEmptyCollectionName(map)); + assertEquals("Map is empty", 0, map2.size()); + } + } + */ + + /** + * Compare the current serialized form of the Map + * against the canonical version in CVS. + */ + //public void testFullMapCompatibility() throws Exception { + /** + * Create canonical objects with this code + Map map = makeFullMap(); + if (!(map instanceof Serializable)) return; + + writeExternalFormToDisk((Serializable) map, getCanonicalFullCollectionName(map)); + */ +/* + // test to make sure the canonical form has been preserved + Map map = makeFullMap(); + if (map instanceof Serializable && !skipSerializedCanonicalTests() && isTestSerialization()) { + Map map2 = (Map) readExternalFormFromDisk(getCanonicalFullCollectionName(map)); + assertEquals("Map is the right size", getSampleKeys().length, map2.size()); + } + }*/ + + /** + * Tests Map.put(Object, Object) + */ + @Test + public void testMapPut() { + resetEmpty(); + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + Object[] newValues = getNewSampleValues(); + + if (isPutAddSupported()) { + for (int i = 0; i < keys.length; i++) { + Object o = map.put(keys[i], values[i]); + confirmed.put(keys[i], values[i]); + verifyAll(); + assertTrue("First map.put should return null", o == null); + assertTrue("Map should contain key after put", + map.containsKey(keys[i])); + assertTrue("Map should contain value after put", + map.containsValue(values[i])); + } + if (isPutChangeSupported()) { + for (int i = 0; i < keys.length; i++) { + Object o = map.put(keys[i], newValues[i]); + confirmed.put(keys[i], newValues[i]); + verifyAll(); + assertEquals("Map.put should return previous value when changed", + values[i], o); + assertTrue("Map should still contain key after put when changed", + map.containsKey(keys[i])); + assertTrue("Map should contain new value after put when changed", + map.containsValue(newValues[i])); + + // if duplicates are allowed, we're not guaranteed that the value + // no longer exists, so don't try checking that. + if (!isAllowDuplicateValues()) { + assertTrue("Map should not contain old value after put when changed", + !map.containsValue(values[i])); + } + } + } + else { + try { + // two possible exception here, either valid + map.put(keys[0], newValues[0]); + fail("Expected IllegalArgumentException or UnsupportedOperationException on put (change)"); + } + catch (IllegalArgumentException ex) { + } + catch (UnsupportedOperationException ex) { + } + } + } + else if (isPutChangeSupported()) { + resetEmpty(); + try { + map.put(keys[0], values[0]); + fail("Expected UnsupportedOperationException or IllegalArgumentException on put (add) when fixed size"); + } + catch (IllegalArgumentException ex) { + } + catch (UnsupportedOperationException ex) { + } + + resetFull(); + int i = 0; + for (Iterator it = map.keySet().iterator(); it.hasNext() && i < newValues.length; i++) { + Object key = it.next(); + Object o = map.put(key, newValues[i]); + Object value = confirmed.put(key, newValues[i]); + verifyAll(); + assertEquals("Map.put should return previous value when changed", + value, o); + assertTrue("Map should still contain key after put when changed", + map.containsKey(key)); + assertTrue("Map should contain new value after put when changed", + map.containsValue(newValues[i])); + + // if duplicates are allowed, we're not guaranteed that the value + // no longer exists, so don't try checking that. + if (!isAllowDuplicateValues()) { + assertTrue("Map should not contain old value after put when changed", + !map.containsValue(values[i])); + } + } + } + else { + try { + map.put(keys[0], values[0]); + fail("Expected UnsupportedOperationException on put (add)"); + } + catch (UnsupportedOperationException ex) { + } + } + } + + /** + * Tests Map.put(null, value) + */ + @Test + public void testMapPutNullKey() { + resetFull(); + Object[] values = getSampleValues(); + + if (isPutAddSupported()) { + if (isAllowNullKey()) { + map.put(null, values[0]); + } + else { + try { + map.put(null, values[0]); + fail("put(null, value) should throw NPE/IAE"); + } + catch (NullPointerException ex) { + } + catch (IllegalArgumentException ex) { + } + } + } + } + + /** + * Tests Map.put(null, value) + */ + @Test + public void testMapPutNullValue() { + resetFull(); + Object[] keys = getSampleKeys(); + + if (isPutAddSupported()) { + if (isAllowNullValue()) { + map.put(keys[0], null); + } + else { + try { + map.put(keys[0], null); + fail("put(key, null) should throw NPE/IAE"); + } + catch (NullPointerException ex) { + } + catch (IllegalArgumentException ex) { + } + } + } + } + + /** + * Tests Map.putAll(map) + */ + @Test + public void testMapPutAll() { + if (!isPutAddSupported()) { + if (!isPutChangeSupported()) { + Map temp = makeFullMap(); + resetEmpty(); + try { + map.putAll(temp); + fail("Expected UnsupportedOperationException on putAll"); + } + catch (UnsupportedOperationException ex) { + } + } + return; + } + + resetEmpty(); + + Map m2 = makeFullMap(); + + map.putAll(m2); + confirmed.putAll(m2); + verifyAll(); + + resetEmpty(); + + m2 = makeConfirmedMap(); + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + for (int i = 0; i < keys.length; i++) { + m2.put(keys[i], values[i]); + } + + map.putAll(m2); + confirmed.putAll(m2); + verifyAll(); + } + + /** + * Tests Map.remove(Object) + */ + @Test + public void testMapRemove() { + if (!isRemoveSupported()) { + try { + resetFull(); + map.remove(map.keySet().iterator().next()); + fail("Expected UnsupportedOperationException on remove"); + } + catch (UnsupportedOperationException ex) { + } + return; + } + + resetEmpty(); + + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + for (int i = 0; i < keys.length; i++) { + Object o = map.remove(keys[i]); + assertTrue("First map.remove should return null", o == null); + } + verifyAll(); + + resetFull(); + + for (int i = 0; i < keys.length; i++) { + Object o = map.remove(keys[i]); + confirmed.remove(keys[i]); + verifyAll(); + + assertEquals("map.remove with valid key should return value", + values[i], o); + } + + Object[] other = getOtherKeys(); + + resetFull(); + int size = map.size(); + for (int i = 0; i < other.length; i++) { + Object o = map.remove(other[i]); + assertEquals("map.remove for nonexistent key should return null", + o, null); + assertEquals("map.remove for nonexistent key should not " + + "shrink map", size, map.size()); + } + verifyAll(); + } + + //----------------------------------------------------------------------- + + /** + * Tests that the {@link Map#values} collection is backed by + * the underlying map for clear(). + */ + @Test + public void testValuesClearChangesMap() { + if (!isRemoveSupported()) { + return; + } + + // clear values, reflected in map + resetFull(); + Collection values = map.values(); + assertTrue(map.size() > 0); + assertTrue(values.size() > 0); + values.clear(); + assertTrue(map.size() == 0); + assertTrue(values.size() == 0); + + // clear map, reflected in values + resetFull(); + values = map.values(); + assertTrue(map.size() > 0); + assertTrue(values.size() > 0); + map.clear(); + assertTrue(map.size() == 0); + assertTrue(values.size() == 0); + } + + /** + * Tests that the {@link Map#keySet} collection is backed by + * the underlying map for clear(). + */ + @Test + public void testKeySetClearChangesMap() { + if (!isRemoveSupported()) { + return; + } + + // clear values, reflected in map + resetFull(); + Set keySet = map.keySet(); + assertTrue(map.size() > 0); + assertTrue(keySet.size() > 0); + keySet.clear(); + assertTrue(map.size() == 0); + assertTrue(keySet.size() == 0); + + // clear map, reflected in values + resetFull(); + keySet = map.keySet(); + assertTrue(map.size() > 0); + assertTrue(keySet.size() > 0); + map.clear(); + assertTrue(map.size() == 0); + assertTrue(keySet.size() == 0); + } + + /** + * Tests that the {@link Map#entrySet()} collection is backed by + * the underlying map for clear(). + */ + @Test + public void testEntrySetClearChangesMap() { + if (!isRemoveSupported()) { + return; + } + + // clear values, reflected in map + resetFull(); + Set entrySet = map.entrySet(); + assertTrue(map.size() > 0); + assertTrue(entrySet.size() > 0); + entrySet.clear(); + assertTrue(map.size() == 0); + assertTrue(entrySet.size() == 0); + + // clear map, reflected in values + resetFull(); + entrySet = map.entrySet(); + assertTrue(map.size() > 0); + assertTrue(entrySet.size() > 0); + map.clear(); + assertTrue(map.size() == 0); + assertTrue(entrySet.size() == 0); + } + + //----------------------------------------------------------------------- + @Test + public void testEntrySetContains1() { + resetFull(); + Set entrySet = map.entrySet(); + Map.Entry entry = (Map.Entry) entrySet.iterator().next(); + assertEquals(true, entrySet.contains(entry)); + } + + @Test + public void testEntrySetContains2() { + resetFull(); + Set entrySet = map.entrySet(); + Map.Entry entry = (Map.Entry) entrySet.iterator().next(); + Map.Entry test = cloneMapEntry(entry); + assertEquals(true, entrySet.contains(test)); + } + + @Test + public void testEntrySetContains3() { + resetFull(); + Set entrySet = map.entrySet(); + Map.Entry entry = (Map.Entry) entrySet.iterator().next(); + HashMap temp = new HashMap(); + temp.put(entry.getKey(), "A VERY DIFFERENT VALUE"); + Map.Entry test = (Map.Entry) temp.entrySet().iterator().next(); + assertEquals(false, entrySet.contains(test)); + } + + @Test + public void testEntrySetRemove1() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + int size = map.size(); + Set entrySet = map.entrySet(); + Map.Entry entry = (Map.Entry) entrySet.iterator().next(); + Object key = entry.getKey(); + + assertEquals(true, entrySet.remove(entry)); + assertEquals(false, map.containsKey(key)); + assertEquals(size - 1, map.size()); + } + + @Test + public void testEntrySetRemove2() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + int size = map.size(); + Set entrySet = map.entrySet(); + Map.Entry entry = (Map.Entry) entrySet.iterator().next(); + Object key = entry.getKey(); + Map.Entry test = cloneMapEntry(entry); + + assertEquals(true, entrySet.remove(test)); + assertEquals(false, map.containsKey(key)); + assertEquals(size - 1, map.size()); + } + + @Test + public void testEntrySetRemove3() { + if (!isRemoveSupported()) { + return; + } + resetFull(); + int size = map.size(); + Set entrySet = map.entrySet(); + Map.Entry entry = (Map.Entry) entrySet.iterator().next(); + Object key = entry.getKey(); + HashMap temp = new HashMap(); + temp.put(entry.getKey(), "A VERY DIFFERENT VALUE"); + Map.Entry test = (Map.Entry) temp.entrySet().iterator().next(); + + assertEquals(false, entrySet.remove(test)); + assertEquals(true, map.containsKey(key)); + assertEquals(size, map.size()); + } + + //----------------------------------------------------------------------- + + /** + * Tests that the {@link Map#values} collection is backed by + * the underlying map by removing from the values collection + * and testing if the value was removed from the map. + *

+ * We should really test the "vice versa" case--that values removed + * from the map are removed from the values collection--also, + * but that's a more difficult test to construct (lacking a + * "removeValue" method.) + *

+ *

+ * See bug + * 9573. + *

+ */ + @Test + public void testValuesRemoveChangesMap() { + resetFull(); + Object[] sampleValues = getSampleValues(); + Collection values = map.values(); + for (int i = 0; i < sampleValues.length; i++) { + if (map.containsValue(sampleValues[i])) { + int j = 0; // loop counter prevents infinite loops when remove is broken + while (values.contains(sampleValues[i]) && j < 10000) { + try { + values.remove(sampleValues[i]); + } + catch (UnsupportedOperationException e) { + // if values.remove is unsupported, just skip this test + return; + } + j++; + } + assertTrue("values().remove(obj) is broken", j < 10000); + assertTrue( + "Value should have been removed from the underlying map.", + !map.containsValue(sampleValues[i])); + } + } + } + + /** + * Tests that the {@link Map#keySet} set is backed by + * the underlying map by removing from the keySet set + * and testing if the key was removed from the map. + */ + @Test + public void testKeySetRemoveChangesMap() { + resetFull(); + Object[] sampleKeys = getSampleKeys(); + Set keys = map.keySet(); + for (int i = 0; i < sampleKeys.length; i++) { + try { + keys.remove(sampleKeys[i]); + } + catch (UnsupportedOperationException e) { + // if key.remove is unsupported, just skip this test + return; + } + assertTrue( + "Key should have been removed from the underlying map.", + !map.containsKey(sampleKeys[i])); + } + } + + // TODO: Need: + // testValuesRemovedFromEntrySetAreRemovedFromMap + // same for EntrySet/KeySet/values's + // Iterator.remove, removeAll, retainAll + + /** + * Utility methods to create an array of Map.Entry objects + * out of the given key and value arrays.

+ * + * @param keys the array of keys + * @param values the array of values + * @return an array of Map.Entry of those keys to those values + */ + private Map.Entry[] makeEntryArray(Object[] keys, Object[] values) { + Map.Entry[] result = new Map.Entry[keys.length]; + for (int i = 0; i < keys.length; i++) { + Map map = makeConfirmedMap(); + map.put(keys[i], values[i]); + result[i] = (Map.Entry) map.entrySet().iterator().next(); + } + return result; + } + + /** + * Bulk test {@link Map#entrySet()}. This method runs through all of + * the tests in {@link SetAbstractTest}. + * After modification operations, {@link #verifyAll()} is invoked to ensure + * that the map and the other collection views are still valid. + * + * @return a {@link SetAbstractTest} instance for testing the map's entry set + */ + /* + public BulkTest bulkTestMapEntrySet() { + return new TestMapEntrySet(); + } + */ + + public class TestMapEntrySet extends SetAbstractTest { + + // Have to implement manually; entrySet doesn't support addAll + public Object[] getFullElements() { + Object[] k = getSampleKeys(); + Object[] v = getSampleValues(); + return makeEntryArray(k, v); + } + + // Have to implement manually; entrySet doesn't support addAll + public Object[] getOtherElements() { + Object[] k = getOtherKeys(); + Object[] v = getOtherValues(); + return makeEntryArray(k, v); + } + + public Set makeEmptySet() { + return makeEmptyMap().entrySet(); + } + + public Set makeFullSet() { + return makeFullMap().entrySet(); + } + + public boolean isAddSupported() { + // Collection views don't support add operations. + return false; + } + + public boolean isRemoveSupported() { + // Entry set should only support remove if map does + return MapAbstractTest.this.isRemoveSupported(); + } + + public boolean isGetStructuralModify() { + return MapAbstractTest.this.isGetStructuralModify(); + } + + public boolean isTestSerialization() { + return false; + } + + public void resetFull() { + MapAbstractTest.this.resetFull(); + collection = map.entrySet(); + TestMapEntrySet.this.confirmed = MapAbstractTest.this.confirmed.entrySet(); + } + + public void resetEmpty() { + MapAbstractTest.this.resetEmpty(); + collection = map.entrySet(); + TestMapEntrySet.this.confirmed = MapAbstractTest.this.confirmed.entrySet(); + } + + @Test + public void testMapEntrySetIteratorEntry() { + resetFull(); + Iterator it = collection.iterator(); + int count = 0; + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + assertEquals(true, MapAbstractTest.this.map.containsKey(entry.getKey())); + assertEquals(true, MapAbstractTest.this.map.containsValue(entry.getValue())); + if (isGetStructuralModify() == false) { + assertEquals(MapAbstractTest.this.map.get(entry.getKey()), entry.getValue()); + } + count++; + } + assertEquals(collection.size(), count); + } + + @Test + public void testMapEntrySetIteratorEntrySetValue() { + Object key1 = getSampleKeys()[0]; + Object key2 = (getSampleKeys().length == 1 ? getSampleKeys()[0] : getSampleKeys()[1]); + Object newValue1 = getNewSampleValues()[0]; + Object newValue2 = (getNewSampleValues().length == 1 ? getNewSampleValues()[0] : getNewSampleValues()[1]); + + resetFull(); + // explicitly get entries as sample values/keys are connected for some maps + // such as BeanMap + Iterator it = TestMapEntrySet.this.collection.iterator(); + Map.Entry entry1 = getEntry(it, key1); + it = TestMapEntrySet.this.collection.iterator(); + Map.Entry entry2 = getEntry(it, key2); + Iterator itConfirmed = TestMapEntrySet.this.confirmed.iterator(); + Map.Entry entryConfirmed1 = getEntry(itConfirmed, key1); + itConfirmed = TestMapEntrySet.this.confirmed.iterator(); + Map.Entry entryConfirmed2 = getEntry(itConfirmed, key2); + verifyAll(); + + if (isSetValueSupported() == false) { + try { + entry1.setValue(newValue1); + } + catch (UnsupportedOperationException ex) { + } + return; + } + + entry1.setValue(newValue1); + entryConfirmed1.setValue(newValue1); + assertEquals(newValue1, entry1.getValue()); + assertEquals(true, MapAbstractTest.this.map.containsKey(entry1.getKey())); + assertEquals(true, MapAbstractTest.this.map.containsValue(newValue1)); + assertEquals(newValue1, MapAbstractTest.this.map.get(entry1.getKey())); + verifyAll(); + + entry1.setValue(newValue1); + entryConfirmed1.setValue(newValue1); + assertEquals(newValue1, entry1.getValue()); + assertEquals(true, MapAbstractTest.this.map.containsKey(entry1.getKey())); + assertEquals(true, MapAbstractTest.this.map.containsValue(newValue1)); + assertEquals(newValue1, MapAbstractTest.this.map.get(entry1.getKey())); + verifyAll(); + + entry2.setValue(newValue2); + entryConfirmed2.setValue(newValue2); + assertEquals(newValue2, entry2.getValue()); + assertEquals(true, MapAbstractTest.this.map.containsKey(entry2.getKey())); + assertEquals(true, MapAbstractTest.this.map.containsValue(newValue2)); + assertEquals(newValue2, MapAbstractTest.this.map.get(entry2.getKey())); + verifyAll(); + } + + public Map.Entry getEntry(Iterator itConfirmed, Object key) { + Map.Entry entry = null; + while (itConfirmed.hasNext()) { + Map.Entry temp = (Map.Entry) itConfirmed.next(); + if (temp.getKey() == null) { + if (key == null) { + entry = temp; + break; + } + } + else if (temp.getKey().equals(key)) { + entry = temp; + break; + } + } + assertNotNull("No matching entry in map for key '" + key + "'", entry); + return entry; + } + + @Test + public void testMapEntrySetRemoveNonMapEntry() { + if (isRemoveSupported() == false) { + return; + } + resetFull(); + assertEquals(false, getSet().remove(null)); + assertEquals(false, getSet().remove(new Object())); + } + + public void verifyAll() { + super.verifyAll(); + MapAbstractTest.this.verifyAll(); + } + } + + /** + * Bulk test {@link Map#keySet()}. This method runs through all of + * the tests in {@link SetAbstractTest}. + * After modification operations, {@link #verifyAll()} is invoked to ensure + * that the map and the other collection views are still valid. + * + * @return a {@link SetAbstractTest} instance for testing the map's key set + */ + /* + public BulkTest bulkTestMapKeySet() { + return new TestMapKeySet(); + } + */ + + public class TestMapKeySet extends SetAbstractTest { + public Object[] getFullElements() { + return getSampleKeys(); + } + + public Object[] getOtherElements() { + return getOtherKeys(); + } + + public Set makeEmptySet() { + return makeEmptyMap().keySet(); + } + + public Set makeFullSet() { + return makeFullMap().keySet(); + } + + public boolean isNullSupported() { + return MapAbstractTest.this.isAllowNullKey(); + } + + public boolean isAddSupported() { + return false; + } + + public boolean isRemoveSupported() { + return MapAbstractTest.this.isRemoveSupported(); + } + + public boolean isTestSerialization() { + return false; + } + + public void resetEmpty() { + MapAbstractTest.this.resetEmpty(); + collection = map.keySet(); + TestMapKeySet.this.confirmed = MapAbstractTest.this.confirmed.keySet(); + } + + public void resetFull() { + MapAbstractTest.this.resetFull(); + collection = map.keySet(); + TestMapKeySet.this.confirmed = MapAbstractTest.this.confirmed.keySet(); + } + + public void verifyAll() { + super.verifyAll(); + MapAbstractTest.this.verifyAll(); + } + } + + /** + * Bulk test {@link Map#values()}. This method runs through all of + * the tests in {@link CollectionAbstractTest}. + * After modification operations, {@link #verifyAll()} is invoked to ensure + * that the map and the other collection views are still valid. + * + * @return a {@link CollectionAbstractTest} instance for testing the map's + * values collection + */ + /* + public BulkTest bulkTestMapValues() { + return new TestMapValues(); + } + */ + + public class TestMapValues extends CollectionAbstractTest { + public Object[] getFullElements() { + return getSampleValues(); + } + + public Object[] getOtherElements() { + return getOtherValues(); + } + + public Collection makeCollection() { + return makeEmptyMap().values(); + } + + public Collection makeFullCollection() { + return makeFullMap().values(); + } + + public boolean isNullSupported() { + return MapAbstractTest.this.isAllowNullKey(); + } + + public boolean isAddSupported() { + return false; + } + + public boolean isRemoveSupported() { + return MapAbstractTest.this.isRemoveSupported(); + } + + public boolean isTestSerialization() { + return false; + } + + public boolean areEqualElementsDistinguishable() { + // equal values are associated with different keys, so they are + // distinguishable. + return true; + } + + public Collection makeConfirmedCollection() { + // never gets called, reset methods are overridden + return null; + } + + public Collection makeConfirmedFullCollection() { + // never gets called, reset methods are overridden + return null; + } + + public void resetFull() { + MapAbstractTest.this.resetFull(); + collection = map.values(); + TestMapValues.this.confirmed = MapAbstractTest.this.confirmed.values(); + } + + public void resetEmpty() { + MapAbstractTest.this.resetEmpty(); + collection = map.values(); + TestMapValues.this.confirmed = MapAbstractTest.this.confirmed.values(); + } + + public void verifyAll() { + super.verifyAll(); + MapAbstractTest.this.verifyAll(); + } + + // TODO: should test that a remove on the values collection view + // removes the proper mapping and not just any mapping that may have + // the value equal to the value returned from the values iterator. + } + + /** + * Resets the {@link #map}, {@link #entrySet}, {@link #keySet}, + * {@link #values} and {@link #confirmed} fields to empty. + */ + public void resetEmpty() { + this.map = makeEmptyMap(); + views(); + this.confirmed = makeConfirmedMap(); + } + + /** + * Resets the {@link #map}, {@link #entrySet}, {@link #keySet}, + * {@link #values} and {@link #confirmed} fields to full. + */ + public void resetFull() { + this.map = makeFullMap(); + views(); + this.confirmed = makeConfirmedMap(); + Object[] k = getSampleKeys(); + Object[] v = getSampleValues(); + for (int i = 0; i < k.length; i++) { + confirmed.put(k[i], v[i]); + } + } + + /** + * Resets the collection view fields. + */ + private void views() { + this.keySet = map.keySet(); + this.values = map.values(); + this.entrySet = map.entrySet(); + } + + /** + * Verifies that {@link #map} is still equal to {@link #confirmed}. + * This method checks that the map is equal to the HashMap, + * and that the map's collection views are still equal to + * the HashMap's collection views. An {@code equals} test + * is done on the maps and their collection views; their size and + * {@code isEmpty} results are compared; their hashCodes are + * compared; and {@code containsAll} tests are run on the + * collection views. + */ + public void verifyAll() { + verifyMap(); + verifyEntrySet(); + verifyKeySet(); + verifyValues(); + } + + public void verifyMap() { + int size = confirmed.size(); + boolean empty = confirmed.isEmpty(); + assertEquals("Map should be same size as HashMap", size, map.size()); + assertEquals("Map should be empty if HashMap is", empty, map.isEmpty()); + assertEquals("hashCodes should be the same", confirmed.hashCode(), map.hashCode()); + // this fails for LRUMap because confirmed.equals() somehow modifies + // map, causing concurrent modification exceptions. + //assertEquals("Map should still equal HashMap", confirmed, map); + // this works though and performs the same verification: + assertTrue("Map should still equal HashMap", map.equals(confirmed)); + // TODO: this should really be reexamined to figure out why LRU map + // behaves like it does (the equals shouldn't modify since all accesses + // by the confirmed collection should be through an iterator, thus not + // causing LRUMap to change). + } + + public void verifyEntrySet() { + int size = confirmed.size(); + boolean empty = confirmed.isEmpty(); + assertEquals("entrySet should be same size as HashMap's\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), + size, entrySet.size()); + assertEquals("entrySet should be empty if HashMap is\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), + empty, entrySet.isEmpty()); + assertTrue("entrySet should contain all HashMap's elements\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), + entrySet.containsAll(confirmed.entrySet())); + assertEquals("entrySet hashCodes should be the same\nTest: " + entrySet + "\nReal: " + confirmed.entrySet(), + confirmed.entrySet().hashCode(), entrySet.hashCode()); + assertEquals("Map's entry set should still equal HashMap's", confirmed.entrySet(), entrySet); + } + + public void verifyKeySet() { + int size = confirmed.size(); + boolean empty = confirmed.isEmpty(); + assertEquals("keySet should be same size as HashMap's\nTest: " + keySet + "\nReal: " + confirmed.keySet(), + size, keySet.size()); + assertEquals("keySet should be empty if HashMap is\nTest: " + keySet + "\nReal: " + confirmed.keySet(), + empty, keySet.isEmpty()); + assertTrue("keySet should contain all HashMap's elements\nTest: " + keySet + "\nReal: " + confirmed.keySet(), + keySet.containsAll(confirmed.keySet())); + assertEquals("keySet hashCodes should be the same\nTest: " + keySet + "\nReal: " + confirmed.keySet(), + confirmed.keySet().hashCode(), keySet.hashCode()); + assertEquals("Map's key set should still equal HashMap's", confirmed.keySet(), keySet); + } + + public void verifyValues() { + List known = new ArrayList(confirmed.values()); + List test = new ArrayList(values); + + int size = confirmed.size(); + boolean empty = confirmed.isEmpty(); + + assertEquals("values should be same size as HashMap's\nTest: " + test + "\nReal: " + known, size, values.size()); + assertEquals("values should be empty if HashMap is\nTest: " + test + "\nReal: " + known, empty, values.isEmpty()); + assertTrue("values should contain all HashMap's elements\nTest: " + test + "\nReal: " + known, test.containsAll(known)); + assertTrue("values should contain all HashMap's elements\nTest: " + test + "\nReal: " + known, known.containsAll(test)); + + for (Object aKnown : known) { + boolean removed = test.remove(aKnown); + assertTrue("Map's values should still equal HashMap's", removed); + } + + assertTrue("Map's values should still equal HashMap's", test.isEmpty()); + } + + /** + * Erases any leftover instance variables by setting them to null. + */ + @After + public void tearDown() throws Exception { + map = null; + keySet = null; + entrySet = null; + values = null; + confirmed = null; + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/NullMapTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/NullMapTest.java similarity index 63% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/NullMapTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/NullMapTest.java index c97dcae9..308ca4c5 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/NullMapTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/NullMapTest.java @@ -1,187 +1,260 @@ -package com.twelvemonkeys.util; - - -import java.util.Map; -import java.util.HashMap; - -/** - * NOTE: This TestCase is written especially for NullMap, and is full of dirty - * tricks. A good indication that NullMap is not a good, general-purpose Map - * implementation... - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/NullMapTestCase.java#2 $ - */ -public class NullMapTestCase extends MapAbstractTestCase { - private boolean empty = true; - - public Map makeEmptyMap() { - return new NullMap(); - } - - public Map makeFullMap() { - return new NullMap(); - } - - public Map makeConfirmedMap() { - // Always empty - return new HashMap(); - } - - public void resetEmpty() { - empty = true; - super.resetEmpty(); - } - - public void resetFull() { - empty = false; - super.resetFull(); - } - - public void verifyAll() { - if (empty) { - super.verifyAll(); - } - } - - // Overriden, as this map is always empty - public void testMapIsEmpty() { - resetEmpty(); - assertEquals("Map.isEmpty() should return true with an empty map", - true, map.isEmpty()); - verifyAll(); - resetFull(); - assertEquals("Map.isEmpty() should return true with a full map", - true, map.isEmpty()); - } - - // Overriden, as this map is always empty - public void testMapSize() { - resetEmpty(); - assertEquals("Map.size() should be 0 with an empty map", - 0, map.size()); - verifyAll(); - - resetFull(); - assertEquals("Map.size() should equal the number of entries " + - "in the map", 0, map.size()); - } - - public void testMapContainsKey() { - Object[] keys = getSampleKeys(); - - resetEmpty(); - for(int i = 0; i < keys.length; i++) { - assertTrue("Map must not contain key when map is empty", - !map.containsKey(keys[i])); - } - verifyAll(); - } - - public void testMapContainsValue() { - Object[] values = getSampleValues(); - - resetEmpty(); - for(int i = 0; i < values.length; i++) { - assertTrue("Empty map must not contain value", - !map.containsValue(values[i])); - } - verifyAll(); - } - - public void testMapEquals() { - resetEmpty(); - assertTrue("Empty maps unequal.", map.equals(confirmed)); - verifyAll(); - } - - public void testMapHashCode() { - resetEmpty(); - assertTrue("Empty maps have different hashCodes.", - map.hashCode() == confirmed.hashCode()); - } - - public void testMapGet() { - resetEmpty(); - - Object[] keys = getSampleKeys(); - - for (int i = 0; i < keys.length; i++) { - assertTrue("Empty map.get() should return null.", - map.get(keys[i]) == null); - } - verifyAll(); - } - - public void testMapPut() { - resetEmpty(); - Object[] keys = getSampleKeys(); - Object[] values = getSampleValues(); - Object[] newValues = getNewSampleValues(); - - for (int i = 0; i < keys.length; i++) { - Object o = map.put(keys[i], values[i]); - //confirmed.put(keys[i], values[i]); - verifyAll(); - assertTrue("First map.put should return null", o == null); - } - for (int i = 0; i < keys.length; i++) { - map.put(keys[i], newValues[i]); - //confirmed.put(keys[i], newValues[i]); - verifyAll(); - } - } - - public void testMapToString() { - resetEmpty(); - assertTrue("Empty map toString() should not return null", - map.toString() != null); - verifyAll(); - } - - public void testMapPutAll() { - // TODO: Find a menaingful way to test this - } - - public void testMapRemove() { - resetEmpty(); - - Object[] keys = getSampleKeys(); - for(int i = 0; i < keys.length; i++) { - Object o = map.remove(keys[i]); - assertTrue("First map.remove should return null", o == null); - } - verifyAll(); - } - - //----------------------------------------------------------------------- - public void testEntrySetClearChangesMap() { - } - - public void testKeySetClearChangesMap() { - } - - public void testKeySetRemoveChangesMap() { - } - - public void testValuesClearChangesMap() { - } - - public void testEntrySetContains1() { - } - - public void testEntrySetContains2() { - } - - public void testEntrySetContains3() { - } - - public void testEntrySetRemove1() { - } - - public void testEntrySetRemove2() { - } - - public void testEntrySetRemove3() { - } -} +/* + * 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.util; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * NOTE: This TestCase is written especially for NullMap, and is full of dirty + * tricks. A good indication that NullMap is not a good, general-purpose Map + * implementation... + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/NullMapTestCase.java#2 $ + */ +public class NullMapTest extends MapAbstractTest { + private boolean empty = true; + + public Map makeEmptyMap() { + return new NullMap(); + } + + public Map makeFullMap() { + return new NullMap(); + } + + public Map makeConfirmedMap() { + // Always empty + return new HashMap(); + } + + public void resetEmpty() { + empty = true; + super.resetEmpty(); + } + + public void resetFull() { + empty = false; + super.resetFull(); + } + + public void verifyAll() { + if (empty) { + super.verifyAll(); + } + } + + // Overriden, as this map is always empty + @Test + @Override + public void testMapIsEmpty() { + resetEmpty(); + assertEquals("Map.isEmpty() should return true with an empty map", + true, map.isEmpty()); + verifyAll(); + resetFull(); + assertEquals("Map.isEmpty() should return true with a full map", + true, map.isEmpty()); + } + + // Overriden, as this map is always empty + @Test + @Override + public void testMapSize() { + resetEmpty(); + assertEquals("Map.size() should be 0 with an empty map", + 0, map.size()); + verifyAll(); + + resetFull(); + assertEquals("Map.size() should equal the number of entries " + + "in the map", 0, map.size()); + } + + @Test + @Override + public void testMapContainsKey() { + Object[] keys = getSampleKeys(); + + resetEmpty(); + for (Object key : keys) { + assertTrue("Map must not contain key when map is empty", !map.containsKey(key)); + } + verifyAll(); + } + + @Test + @Override + public void testMapContainsValue() { + Object[] values = getSampleValues(); + + resetEmpty(); + for (Object value : values) { + assertTrue("Empty map must not contain value", !map.containsValue(value)); + } + verifyAll(); + } + + @Test + @Override + public void testMapEquals() { + resetEmpty(); + assertTrue("Empty maps unequal.", map.equals(confirmed)); + verifyAll(); + } + + @Test + @Override + public void testMapHashCode() { + resetEmpty(); + assertTrue("Empty maps have different hashCodes.", + map.hashCode() == confirmed.hashCode()); + } + + @Test + @Override + public void testMapGet() { + resetEmpty(); + + Object[] keys = getSampleKeys(); + + for (Object key : keys) { + assertTrue("Empty map.get() should return null.", map.get(key) == null); + } + verifyAll(); + } + + @Test + @Override + public void testMapPut() { + resetEmpty(); + Object[] keys = getSampleKeys(); + Object[] values = getSampleValues(); + Object[] newValues = getNewSampleValues(); + + for (int i = 0; i < keys.length; i++) { + Object o = map.put(keys[i], values[i]); + //confirmed.put(keys[i], values[i]); + verifyAll(); + assertTrue("First map.put should return null", o == null); + } + for (int i = 0; i < keys.length; i++) { + map.put(keys[i], newValues[i]); + //confirmed.put(keys[i], newValues[i]); + verifyAll(); + } + } + + @Test + @Override + public void testMapToString() { + resetEmpty(); + assertTrue("Empty map toString() should not return null", + map.toString() != null); + verifyAll(); + } + + @Test + @Override + public void testMapPutAll() { + // TODO: Find a menaingful way to test this + } + + @Test + @Override + public void testMapRemove() { + resetEmpty(); + + Object[] keys = getSampleKeys(); + for(int i = 0; i < keys.length; i++) { + Object o = map.remove(keys[i]); + assertTrue("First map.remove should return null", o == null); + } + verifyAll(); + } + + //----------------------------------------------------------------------- + @Test + @Override + public void testEntrySetClearChangesMap() { + } + + @Test + @Override + public void testKeySetClearChangesMap() { + } + + @Test + @Override + public void testKeySetRemoveChangesMap() { + } + + @Test + @Override + public void testValuesClearChangesMap() { + } + + @Test + @Override + public void testEntrySetContains1() { + } + + @Test + @Override + public void testEntrySetContains2() { + } + + @Test + @Override + public void testEntrySetContains3() { + } + + @Test + @Override + public void testEntrySetRemove1() { + } + + @Test + @Override + public void testEntrySetRemove2() { + } + + @Test + @Override + public void testEntrySetRemove3() { + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/ObjectAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/ObjectAbstractTest.java similarity index 86% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/ObjectAbstractTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/ObjectAbstractTest.java index c781d776..f79cfdb6 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/ObjectAbstractTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/ObjectAbstractTest.java @@ -1,307 +1,347 @@ -/* - * Copyright 2001-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.twelvemonkeys.util; - -import junit.framework.TestCase; - -import java.io.*; - -/** - * Abstract test class for {@link Object} methods and contracts. - *

- * To use, simply extend this class, and implement - * the {@link #makeObject()} method. - *

- * If your {@link Object} fails one of these tests by design, - * you may still use this base set of cases. Simply override the - * test case (method) your {@link Object} fails. - * - * @version $Revision: #2 $ $Date: 2008/07/15 $ - * - * @author Rodney Waldhoff - * @author Stephen Colebourne - * @author Anonymous - */ -public abstract class ObjectAbstractTestCase extends TestCase { - //----------------------------------------------------------------------- - - /** - * Implement this method to return the object to test. - * - * @return the object to test - */ - public abstract Object makeObject(); - - /** - * Override this method if a subclass is testing an object - * that cannot serialize an "empty" Collection. - * (e.g. Comparators have no contents) - * - * @return true - */ - public boolean supportsEmptyCollections() { - return true; - } - - /** - * Override this method if a subclass is testing an object - * that cannot serialize a "full" Collection. - * (e.g. Comparators have no contents) - * - * @return true - */ - public boolean supportsFullCollections() { - return true; - } - - /** - * Is serialization testing supported. - * Default is true. - */ - public boolean isTestSerialization() { - return true; - } - - /** - * Returns true to indicate that the collection supports equals() comparisons. - * This implementation returns true; - */ - public boolean isEqualsCheckable() { - return true; - } - - //----------------------------------------------------------------------- - public void testObjectEqualsSelf() { - Object obj = makeObject(); - assertEquals("A Object should equal itself", obj, obj); - } - - public void testEqualsNull() { - Object obj = makeObject(); - assertEquals(false, obj.equals(null)); // make sure this doesn't throw NPE either - } - - public void testObjectHashCodeEqualsSelfHashCode() { - Object obj = makeObject(); - assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode()); - } - - public void testObjectHashCodeEqualsContract() { - Object obj1 = makeObject(); - if (obj1.equals(obj1)) { - assertEquals( - "[1] When two objects are equal, their hashCodes should be also.", - obj1.hashCode(), obj1.hashCode()); - } - Object obj2 = makeObject(); - if (obj1.equals(obj2)) { - assertEquals( - "[2] When two objects are equal, their hashCodes should be also.", - obj1.hashCode(), obj2.hashCode()); - assertTrue( - "When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true", - obj2.equals(obj1)); - } - } - - public void testSerializeDeserializeThenCompare() throws Exception { - Object obj = makeObject(); - if (obj instanceof Serializable && isTestSerialization()) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(buffer); - out.writeObject(obj); - out.close(); - - ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); - Object dest = in.readObject(); - in.close(); - if (isEqualsCheckable()) { - assertEquals("obj != deserialize(serialize(obj))", obj, dest); - } - } - } - - /** - * Sanity check method, makes sure that any Serializable - * class can be serialized and de-serialized in memory, - * using the handy makeObject() method - * - * @throws java.io.IOException - * @throws ClassNotFoundException - */ - public void testSimpleSerialization() throws Exception { - Object o = makeObject(); - if (o instanceof Serializable && isTestSerialization()) { - byte[] objekt = writeExternalFormToBytes((Serializable) o); - Object p = readExternalFormFromBytes(objekt); - } - } - - /** - * Tests serialization by comparing against a previously stored version in CVS. - * If the test object is serializable, confirm that a canonical form exists. - */ - public void testCanonicalEmptyCollectionExists() { - if (supportsEmptyCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) { - Object object = makeObject(); - if (object instanceof Serializable) { - String name = getCanonicalEmptyCollectionName(object); - assertTrue( - "Canonical empty collection (" + name + ") is not in CVS", - new File(name).exists()); - } - } - } - - /** - * Tests serialization by comparing against a previously stored version in CVS. - * If the test object is serializable, confirm that a canonical form exists. - */ - public void testCanonicalFullCollectionExists() { - if (supportsFullCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) { - Object object = makeObject(); - if (object instanceof Serializable) { - String name = getCanonicalFullCollectionName(object); - assertTrue( - "Canonical full collection (" + name + ") is not in CVS", - new File(name).exists()); - } - } - } - - // protected implementation - //----------------------------------------------------------------------- - /** - * Get the version of Collections that this object tries to - * maintain serialization compatibility with. Defaults to 1, the - * earliest Collections version. (Note: some collections did not - * even exist in this version). - * - * This constant makes it possible for TestMap (and other subclasses, - * if necessary) to automatically check CVS for a versionX copy of a - * Serialized object, so we can make sure that compatibility is maintained. - * See, for example, TestMap.getCanonicalFullMapName(Map map). - * Subclasses can override this variable, indicating compatibility - * with earlier Collections versions. - * - * @return The version, or {@code null} if this object shouldn't be - * tested for compatibility with previous versions. - */ - public String getCompatibilityVersion() { - return "1"; - } - - protected String getCanonicalEmptyCollectionName(Object object) { - StringBuilder retval = new StringBuilder(); - retval.append("data/test/"); - String colName = object.getClass().getName(); - colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length()); - retval.append(colName); - retval.append(".emptyCollection.version"); - retval.append(getCompatibilityVersion()); - retval.append(".obj"); - return retval.toString(); - } - - protected String getCanonicalFullCollectionName(Object object) { - StringBuilder retval = new StringBuilder(); - retval.append("data/test/"); - String colName = object.getClass().getName(); - colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length()); - retval.append(colName); - retval.append(".fullCollection.version"); - retval.append(getCompatibilityVersion()); - retval.append(".obj"); - return retval.toString(); - } - - /** - * Write a Serializable or Externalizable object as - * a file at the given path. NOT USEFUL as part - * of a unit test; this is just a utility method - * for creating disk-based objects in CVS that can become - * the basis for compatibility tests using - * readExternalFormFromDisk(String path) - * - * @param o Object to serialize - * @param path path to write the serialized Object - * @exception java.io.IOException - */ - protected void writeExternalFormToDisk(Serializable o, String path) throws IOException { - FileOutputStream fileStream = new FileOutputStream(path); - writeExternalFormToStream(o, fileStream); - } - - /** - * Converts a Serializable or Externalizable object to - * bytes. Useful for in-memory tests of serialization - * - * @param o Object to convert to bytes - * @return serialized form of the Object - * @exception java.io.IOException - */ - protected byte[] writeExternalFormToBytes(Serializable o) throws IOException { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - writeExternalFormToStream(o, byteStream); - return byteStream.toByteArray(); - } - - /** - * Reads a Serialized or Externalized Object from disk. - * Useful for creating compatibility tests between - * different CVS versions of the same class - * - * @param path path to the serialized Object - * @return the Object at the given path - * @exception java.io.IOException - * @exception ClassNotFoundException - */ - protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException { - FileInputStream stream = new FileInputStream(path); - return readExternalFormFromStream(stream); - } - - /** - * Read a Serialized or Externalized Object from bytes. - * Useful for verifying serialization in memory. - * - * @param b byte array containing a serialized Object - * @return Object contained in the bytes - * @exception java.io.IOException - * @exception ClassNotFoundException - */ - protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException { - ByteArrayInputStream stream = new ByteArrayInputStream(b); - return readExternalFormFromStream(stream); - } - - protected boolean skipSerializedCanonicalTests() { - return Boolean.getBoolean("org.apache.commons.collections:with-clover"); - } - - // private implementation - //----------------------------------------------------------------------- - private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException { - ObjectInputStream oStream = new ObjectInputStream(stream); - return oStream.readObject(); - } - - private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException { - ObjectOutputStream oStream = new ObjectOutputStream(stream); - oStream.writeObject(o); - } - -} +/* + * 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.twelvemonkeys.util; + +import org.junit.Test; + +import java.io.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Abstract test class for {@link Object} methods and contracts. + *

+ * To use, simply extend this class, and implement + * the {@link #makeObject()} method. + *

+ * If your {@link Object} fails one of these tests by design, + * you may still use this base set of cases. Simply override the + * test case (method) your {@link Object} fails. + * + * @version $Revision: #2 $ $Date: 2008/07/15 $ + * + * @author Rodney Waldhoff + * @author Stephen Colebourne + * @author Anonymous + */ +public abstract class ObjectAbstractTest { + //----------------------------------------------------------------------- + + /** + * Implement this method to return the object to test. + * + * @return the object to test + */ + public abstract Object makeObject(); + + /** + * Override this method if a subclass is testing an object + * that cannot serialize an "empty" Collection. + * (e.g. Comparators have no contents) + * + * @return true + */ + public boolean supportsEmptyCollections() { + return true; + } + + /** + * Override this method if a subclass is testing an object + * that cannot serialize a "full" Collection. + * (e.g. Comparators have no contents) + * + * @return true + */ + public boolean supportsFullCollections() { + return true; + } + + /** + * Is serialization testing supported. + * Default is true. + */ + public boolean isTestSerialization() { + return true; + } + + /** + * Returns true to indicate that the collection supports equals() comparisons. + * This implementation returns true; + */ + public boolean isEqualsCheckable() { + return true; + } + + //----------------------------------------------------------------------- + @Test + public void testObjectEqualsSelf() { + Object obj = makeObject(); + assertEquals("A Object should equal itself", obj, obj); + } + + @Test + public void testEqualsNull() { + Object obj = makeObject(); + assertEquals(false, obj.equals(null)); // make sure this doesn't throw NPE either + } + + @Test + public void testObjectHashCodeEqualsSelfHashCode() { + Object obj = makeObject(); + assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode()); + } + + @Test + public void testObjectHashCodeEqualsContract() { + Object obj1 = makeObject(); + if (obj1.equals(obj1)) { + assertEquals( + "[1] When two objects are equal, their hashCodes should be also.", + obj1.hashCode(), obj1.hashCode()); + } + Object obj2 = makeObject(); + if (obj1.equals(obj2)) { + assertEquals( + "[2] When two objects are equal, their hashCodes should be also.", + obj1.hashCode(), obj2.hashCode()); + assertTrue( + "When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true", + obj2.equals(obj1)); + } + } + + @Test + public void testSerializeDeserializeThenCompare() throws Exception { + Object obj = makeObject(); + if (obj instanceof Serializable && isTestSerialization()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(buffer); + out.writeObject(obj); + out.close(); + + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); + Object dest = in.readObject(); + in.close(); + if (isEqualsCheckable()) { + assertEquals("obj != deserialize(serialize(obj))", obj, dest); + } + } + } + + /** + * Sanity check method, makes sure that any Serializable + * class can be serialized and de-serialized in memory, + * using the handy makeObject() method + * + * @throws java.io.IOException + * @throws ClassNotFoundException + */ + @Test + public void testSimpleSerialization() throws Exception { + Object o = makeObject(); + if (o instanceof Serializable && isTestSerialization()) { + byte[] objekt = writeExternalFormToBytes((Serializable) o); + Object p = readExternalFormFromBytes(objekt); + } + } + + /** + * Tests serialization by comparing against a previously stored version in CVS. + * If the test object is serializable, confirm that a canonical form exists. + */ + @Test + public void testCanonicalEmptyCollectionExists() { + if (supportsEmptyCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) { + Object object = makeObject(); + if (object instanceof Serializable) { + String name = getCanonicalEmptyCollectionName(object); + assertTrue( + "Canonical empty collection (" + name + ") is not in CVS", + new File(name).exists()); + } + } + } + + /** + * Tests serialization by comparing against a previously stored version in CVS. + * If the test object is serializable, confirm that a canonical form exists. + */ + @Test + public void testCanonicalFullCollectionExists() { + if (supportsFullCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) { + Object object = makeObject(); + if (object instanceof Serializable) { + String name = getCanonicalFullCollectionName(object); + assertTrue( + "Canonical full collection (" + name + ") is not in CVS", + new File(name).exists()); + } + } + } + + // protected implementation + //----------------------------------------------------------------------- + /** + * Get the version of Collections that this object tries to + * maintain serialization compatibility with. Defaults to 1, the + * earliest Collections version. (Note: some collections did not + * even exist in this version). + * + * This constant makes it possible for TestMap (and other subclasses, + * if necessary) to automatically check CVS for a versionX copy of a + * Serialized object, so we can make sure that compatibility is maintained. + * See, for example, TestMap.getCanonicalFullMapName(Map map). + * Subclasses can override this variable, indicating compatibility + * with earlier Collections versions. + * + * @return The version, or {@code null} if this object shouldn't be + * tested for compatibility with previous versions. + */ + public String getCompatibilityVersion() { + return "1"; + } + + protected String getCanonicalEmptyCollectionName(Object object) { + StringBuilder retval = new StringBuilder(); + retval.append("data/test/"); + String colName = object.getClass().getName(); + colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length()); + retval.append(colName); + retval.append(".emptyCollection.version"); + retval.append(getCompatibilityVersion()); + retval.append(".obj"); + return retval.toString(); + } + + protected String getCanonicalFullCollectionName(Object object) { + StringBuilder retval = new StringBuilder(); + retval.append("data/test/"); + String colName = object.getClass().getName(); + colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length()); + retval.append(colName); + retval.append(".fullCollection.version"); + retval.append(getCompatibilityVersion()); + retval.append(".obj"); + return retval.toString(); + } + + /** + * Write a Serializable or Externalizable object as + * a file at the given path. NOT USEFUL as part + * of a unit test; this is just a utility method + * for creating disk-based objects in CVS that can become + * the basis for compatibility tests using + * readExternalFormFromDisk(String path) + * + * @param o Object to serialize + * @param path path to write the serialized Object + * @exception java.io.IOException + */ + protected void writeExternalFormToDisk(Serializable o, String path) throws IOException { + FileOutputStream fileStream = new FileOutputStream(path); + writeExternalFormToStream(o, fileStream); + } + + /** + * Converts a Serializable or Externalizable object to + * bytes. Useful for in-memory tests of serialization + * + * @param o Object to convert to bytes + * @return serialized form of the Object + * @exception java.io.IOException + */ + protected byte[] writeExternalFormToBytes(Serializable o) throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + writeExternalFormToStream(o, byteStream); + return byteStream.toByteArray(); + } + + /** + * Reads a Serialized or Externalized Object from disk. + * Useful for creating compatibility tests between + * different CVS versions of the same class + * + * @param path path to the serialized Object + * @return the Object at the given path + * @exception java.io.IOException + * @exception ClassNotFoundException + */ + protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException { + FileInputStream stream = new FileInputStream(path); + return readExternalFormFromStream(stream); + } + + /** + * Read a Serialized or Externalized Object from bytes. + * Useful for verifying serialization in memory. + * + * @param b byte array containing a serialized Object + * @return Object contained in the bytes + * @exception java.io.IOException + * @exception ClassNotFoundException + */ + protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException { + ByteArrayInputStream stream = new ByteArrayInputStream(b); + return readExternalFormFromStream(stream); + } + + protected boolean skipSerializedCanonicalTests() { + return Boolean.getBoolean("org.apache.commons.collections:with-clover"); + } + + // private implementation + //----------------------------------------------------------------------- + private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException { + ObjectInputStream oStream = new ObjectInputStream(stream); + return oStream.readObject(); + } + + private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException { + ObjectOutputStream oStream = new ObjectOutputStream(stream); + oStream.writeObject(o); + } + +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/SetAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/SetAbstractTest.java similarity index 71% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/SetAbstractTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/SetAbstractTest.java index 5bdcc797..dc7e32e3 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/SetAbstractTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/SetAbstractTest.java @@ -1,183 +1,215 @@ -/* - * Copyright 2001-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.twelvemonkeys.util; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * Abstract test class for {@link Set} methods and contracts. - *

- * Since {@link Set} doesn't stipulate much new behavior that isn't already - * found in {@link Collection}, this class basically just adds tests for - * {@link Set#equals} and {@link Set#hashCode()} along with an updated - * {@link #verifyAll()} that ensures elements do not appear more than once in the - * set. - *

- * To use, subclass and override the {@link #makeEmptySet()} - * method. You may have to override other protected methods if your - * set is not modifiable, or if your set restricts what kinds of - * elements may be added; see {@link CollectionAbstractTestCase} for more details. - * - * @since Commons Collections 3.0 - * @version $Revision: #2 $ $Date: 2008/07/15 $ - * - * @author Paul Jack - */ -public abstract class SetAbstractTestCase extends CollectionAbstractTestCase { - - //----------------------------------------------------------------------- - /** - * Provides additional verifications for sets. - */ - public void verifyAll() { - super.verifyAll(); - - assertEquals("Sets should be equal", confirmed, collection); - assertEquals("Sets should have equal hashCodes", - confirmed.hashCode(), collection.hashCode()); - Collection set = makeConfirmedCollection(); - Iterator iterator = collection.iterator(); - while (iterator.hasNext()) { - assertTrue("Set.iterator should only return unique elements", - set.add(iterator.next())); - } - } - - //----------------------------------------------------------------------- - /** - * Set equals method is defined. - */ - public boolean isEqualsCheckable() { - return true; - } - - /** - * Returns an empty Set for use in modification testing. - * - * @return a confirmed empty collection - */ - public Collection makeConfirmedCollection() { - return new HashSet(); - } - - /** - * Returns a full Set for use in modification testing. - * - * @return a confirmed full collection - */ - public Collection makeConfirmedFullCollection() { - Collection set = makeConfirmedCollection(); - set.addAll(Arrays.asList(getFullElements())); - return set; - } - - /** - * Makes an empty set. The returned set should have no elements. - * - * @return an empty set - */ - public abstract Set makeEmptySet(); - - /** - * Makes a full set by first creating an empty set and then adding - * all the elements returned by {@link #getFullElements()}. - * - * Override if your set does not support the add operation. - * - * @return a full set - */ - public Set makeFullSet() { - Set set = makeEmptySet(); - set.addAll(Arrays.asList(getFullElements())); - return set; - } - - /** - * Makes an empty collection by invoking {@link #makeEmptySet()}. - * - * @return an empty collection - */ - public final Collection makeCollection() { - return makeEmptySet(); - } - - /** - * Makes a full collection by invoking {@link #makeFullSet()}. - * - * @return a full collection - */ - public final Collection makeFullCollection() { - return makeFullSet(); - } - - //----------------------------------------------------------------------- - /** - * Return the {@link CollectionAbstractTestCase#collection} fixture, but cast as a Set. - */ - public Set getSet() { - return (Set)collection; - } - - /** - * Return the {@link CollectionAbstractTestCase#confirmed} fixture, but cast as a Set. - */ - public Set getConfirmedSet() { - return (Set)confirmed; - } - - //----------------------------------------------------------------------- - /** - * Tests {@link Set#equals(Object)}. - */ - public void testSetEquals() { - resetEmpty(); - assertEquals("Empty sets should be equal", - getSet(), getConfirmedSet()); - verifyAll(); - - Collection set2 = makeConfirmedCollection(); - set2.add("foo"); - assertTrue("Empty set shouldn't equal nonempty set", - !getSet().equals(set2)); - - resetFull(); - assertEquals("Full sets should be equal", getSet(), getConfirmedSet()); - verifyAll(); - - set2.clear(); - set2.addAll(Arrays.asList(getOtherElements())); - assertTrue("Sets with different contents shouldn't be equal", - !getSet().equals(set2)); - } - - /** - * Tests {@link Set#hashCode()}. - */ - public void testSetHashCode() { - resetEmpty(); - assertEquals("Empty sets have equal hashCodes", - getSet().hashCode(), getConfirmedSet().hashCode()); - - resetFull(); - assertEquals("Equal sets have equal hashCodes", - getSet().hashCode(), getConfirmedSet().hashCode()); - } - -} +/* + * 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.twelvemonkeys.util; + +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Abstract test class for {@link Set} methods and contracts. + *

+ * Since {@link Set} doesn't stipulate much new behavior that isn't already + * found in {@link Collection}, this class basically just adds tests for + * {@link Set#equals} and {@link Set#hashCode()} along with an updated + * {@link #verifyAll()} that ensures elements do not appear more than once in the + * set. + *

+ * To use, subclass and override the {@link #makeEmptySet()} + * method. You may have to override other protected methods if your + * set is not modifiable, or if your set restricts what kinds of + * elements may be added; see {@link CollectionAbstractTest} for more details. + * + * @since Commons Collections 3.0 + * @version $Revision: #2 $ $Date: 2008/07/15 $ + * + * @author Paul Jack + */ +public abstract class SetAbstractTest extends CollectionAbstractTest { + + //----------------------------------------------------------------------- + /** + * Provides additional verifications for sets. + */ + public void verifyAll() { + super.verifyAll(); + + assertEquals("Sets should be equal", confirmed, collection); + assertEquals("Sets should have equal hashCodes", + confirmed.hashCode(), collection.hashCode()); + Collection set = makeConfirmedCollection(); + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + assertTrue("Set.iterator should only return unique elements", + set.add(iterator.next())); + } + } + + //----------------------------------------------------------------------- + /** + * Set equals method is defined. + */ + public boolean isEqualsCheckable() { + return true; + } + + /** + * Returns an empty Set for use in modification testing. + * + * @return a confirmed empty collection + */ + public Collection makeConfirmedCollection() { + return new HashSet(); + } + + /** + * Returns a full Set for use in modification testing. + * + * @return a confirmed full collection + */ + public Collection makeConfirmedFullCollection() { + Collection set = makeConfirmedCollection(); + set.addAll(Arrays.asList(getFullElements())); + return set; + } + + /** + * Makes an empty set. The returned set should have no elements. + * + * @return an empty set + */ + public abstract Set makeEmptySet(); + + /** + * Makes a full set by first creating an empty set and then adding + * all the elements returned by {@link #getFullElements()}. + * + * Override if your set does not support the add operation. + * + * @return a full set + */ + public Set makeFullSet() { + Set set = makeEmptySet(); + set.addAll(Arrays.asList(getFullElements())); + return set; + } + + /** + * Makes an empty collection by invoking {@link #makeEmptySet()}. + * + * @return an empty collection + */ + public final Collection makeCollection() { + return makeEmptySet(); + } + + /** + * Makes a full collection by invoking {@link #makeFullSet()}. + * + * @return a full collection + */ + @Override + public final Collection makeFullCollection() { + return makeFullSet(); + } + + //----------------------------------------------------------------------- + /** + * Return the {@link CollectionAbstractTest#collection} fixture, but cast as a Set. + */ + public Set getSet() { + return (Set)collection; + } + + /** + * Return the {@link CollectionAbstractTest#confirmed} fixture, but cast as a Set. + */ + public Set getConfirmedSet() { + return (Set)confirmed; + } + + //----------------------------------------------------------------------- + /** + * Tests {@link Set#equals(Object)}. + */ + @Test + public void testSetEquals() { + resetEmpty(); + assertEquals("Empty sets should be equal", + getSet(), getConfirmedSet()); + verifyAll(); + + Collection set2 = makeConfirmedCollection(); + set2.add("foo"); + assertTrue("Empty set shouldn't equal nonempty set", + !getSet().equals(set2)); + + resetFull(); + assertEquals("Full sets should be equal", getSet(), getConfirmedSet()); + verifyAll(); + + set2.clear(); + set2.addAll(Arrays.asList(getOtherElements())); + assertTrue("Sets with different contents shouldn't be equal", + !getSet().equals(set2)); + } + + /** + * Tests {@link Set#hashCode()}. + */ + @Test + public void testSetHashCode() { + resetEmpty(); + assertEquals("Empty sets have equal hashCodes", + getSet().hashCode(), getConfirmedSet().hashCode()); + + resetFull(); + assertEquals("Equal sets have equal hashCodes", + getSet().hashCode(), getConfirmedSet().hashCode()); + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTest.java similarity index 71% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTest.java index 0895e965..1a591fe1 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTest.java @@ -1,111 +1,143 @@ -package com.twelvemonkeys.util; - - -import java.util.Iterator; - -/** - * StringTokenIteratorTestCase - *

- * - * - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTestCase.java#1 $ - */ -public class StringTokenIteratorTestCase extends TokenIteratorAbstractTestCase { - public void setUp() throws Exception { - super.setUp(); - } - - public void tearDown() throws Exception { - super.tearDown(); - } - - protected TokenIterator createTokenIterator(String pString) { - return new StringTokenIterator(pString); - } - - protected TokenIterator createTokenIterator(String pString, String pDelimiters) { - return new StringTokenIterator(pString, pDelimiters); - } - - public void testEmptyDelimiter() { - Iterator iterator = createTokenIterator("", ""); - assertFalse("Empty string has elements", iterator.hasNext()); - } - - public void testSingleToken() { - Iterator iterator = createTokenIterator("A"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleTokenEmptyDelimiter() { - Iterator iterator = createTokenIterator("A", ""); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleTokenSingleDelimiter() { - Iterator iterator = createTokenIterator("A", ","); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleSeparatorDefaultDelimiter() { - Iterator iterator = createTokenIterator("A B C D"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("D", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleSeparator() { - Iterator iterator = createTokenIterator("A,B,C", ","); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testMultipleSeparatorDefaultDelimiter() { - Iterator iterator = createTokenIterator("A B C\nD\t\t \nE"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("D", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("E", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testMultipleSeparator() { - Iterator iterator = createTokenIterator("A,B,;,C...D, ., ,E", " ,.;:"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("D", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("E", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } -} +/* + * 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.util; + + +import org.junit.Test; + +import java.util.Iterator; + +import static org.junit.Assert.*; + +/** + * StringTokenIteratorTestCase + *

+ * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTestCase.java#1 $ + */ +public class StringTokenIteratorTest extends TokenIteratorAbstractTest { + + protected TokenIterator createTokenIterator(String pString) { + return new StringTokenIterator(pString); + } + + protected TokenIterator createTokenIterator(String pString, String pDelimiters) { + return new StringTokenIterator(pString, pDelimiters); + } + + @Test + public void testEmptyDelimiter() { + Iterator iterator = createTokenIterator("", ""); + assertFalse("Empty string has elements", iterator.hasNext()); + } + + @Test + public void testSingleToken() { + Iterator iterator = createTokenIterator("A"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleTokenEmptyDelimiter() { + Iterator iterator = createTokenIterator("A", ""); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleTokenSingleDelimiter() { + Iterator iterator = createTokenIterator("A", ","); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleSeparatorDefaultDelimiter() { + Iterator iterator = createTokenIterator("A B C D"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("D", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleSeparator() { + Iterator iterator = createTokenIterator("A,B,C", ","); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testMultipleSeparatorDefaultDelimiter() { + Iterator iterator = createTokenIterator("A B C\nD\t\t \nE"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("D", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("E", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testMultipleSeparator() { + Iterator iterator = createTokenIterator("A,B,;,C...D, ., ,E", " ,.;:"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("D", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("E", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java similarity index 87% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java index 8cfb9ab1..dfd297e0 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java @@ -1,632 +1,674 @@ -/**************************************************** - * * - * (c) 2000-2003 WM-data * - * All rights reserved * - * http://www.twelvemonkeys.no * - * * - * $RCSfile: TimeoutMapTestCase.java,v $ - * @version $Revision: #2 $ - * $Date: 2008/07/15 $ - * * - * @author Last modified by: $Author: haku $ - * * - ****************************************************/ - - -package com.twelvemonkeys.util; - - -import junit.framework.Test; -import junit.framework.TestSuite; - -import java.util.*; - - -/** - * TimeoutMapTestCase - *

- * - * - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/TimeoutMapTestCase.java#2 $ - */ -public class TimeoutMapTestCase extends MapAbstractTestCase { - - /** - * Method suite - * - * @return - */ - public static Test suite() { - return new TestSuite(TimeoutMapTestCase.class); - } - - - public Map makeEmptyMap() { - return new TimeoutMap(60 * 60 * 1000); - } - /* - * The basic Map interface lets one associate keys and values: - */ - - /** - * Method testBasicMap - */ - public void testBasicMap() { - - Map map = new TimeoutMap(60000L); - Object key = "key"; - Object value = new Integer(3); - - map.put(key, value); - assertEquals(value, map.get(key)); - } - - /* - * If there is no value associated with a key, - * the basic Map will return null for that key: - */ - - /** - * Method testBasicMapReturnsNullForMissingKey - */ - public void testBasicMapReturnsNullForMissingKey() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.get("key")); - } - - /* - * One can also explicitly store a null value for - * some key: - */ - - /** - * Method testBasicMapAllowsNull - */ - public void testBasicMapAllowsNull() { - - Map map = new TimeoutMap(60000L); - Object key = "key"; - Object value = null; - - map.put(key, value); - assertNull(map.get(key)); - } - - /** - * Method testBasicMapAllowsMultipleTypes - */ - public void testBasicMapAllowsMultipleTypes() { - - Map map = new TimeoutMap(60000L); - - map.put("key-1", "value-1"); - map.put(new Integer(2), "value-2"); - map.put("key-3", new Integer(3)); - map.put(new Integer(4), new Integer(4)); - map.put(Boolean.FALSE, ""); - assertEquals("value-1", map.get("key-1")); - assertEquals("value-2", map.get(new Integer(2))); - assertEquals(new Integer(3), map.get("key-3")); - assertEquals(new Integer(4), map.get(new Integer(4))); - assertEquals("", map.get(Boolean.FALSE)); - } - - /** - * Method testBasicMapStoresOnlyOneValuePerKey - */ - public void testBasicMapStoresOnlyOneValuePerKey() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key", "value-1")); - assertEquals("value-1", map.get("key")); - assertEquals("value-1", map.put("key", "value-2")); - assertEquals("value-2", map.get("key")); - } - - /** - * Method testBasicMapValuesView - */ - public void testBasicMapValuesView() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key-1", new Integer(1))); - assertNull(map.put("key-2", new Integer(2))); - assertNull(map.put("key-3", new Integer(3))); - assertNull(map.put("key-4", new Integer(4))); - assertEquals(4, map.size()); - - Collection values = map.values(); - assertEquals(4, values.size()); - - Iterator it = values.iterator(); - - assertNotNull(it); - int count = 0; - - while (it.hasNext()) { - Object o = it.next(); - - assertNotNull(o); - assertTrue(o instanceof Integer); - count++; - } - assertEquals(4, count); - } - - /** - * Method testBasicMapKeySetView - */ - public void testBasicMapKeySetView() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key-1", "value-1")); - assertNull(map.put("key-2", "value-2")); - assertNull(map.put("key-3", "value-3")); - assertNull(map.put("key-4", "value-4")); - assertEquals(4, map.size()); - Iterator it = map.keySet().iterator(); - - assertNotNull(it); - int count = 0; - - while (it.hasNext()) { - Object o = it.next(); - - assertNotNull(o); - assertTrue(o instanceof String); - count++; - } - assertEquals(4, count); - } - - /** - * Method testBasicMapEntrySetView - */ - public void testBasicMapEntrySetView() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key-1", new Integer(1))); - assertNull(map.put("key-2", "value-2")); - assertNull(map.put("key-3", new Object())); - assertNull(map.put("key-4", Boolean.FALSE)); - assertEquals(4, map.size()); - Iterator it = map.entrySet().iterator(); - - assertNotNull(it); - int count = 0; - - while (it.hasNext()) { - Object o = it.next(); - - assertNotNull(o); - assertTrue(o instanceof Map.Entry); - count++; - } - assertEquals(4, count); - } - - /** - * Method testBasicMapValuesView - */ - public void testBasicMapValuesViewRemoval() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key-1", new Integer(1))); - assertNull(map.put("key-2", new Integer(2))); - assertNull(map.put("key-3", new Integer(3))); - assertNull(map.put("key-4", new Integer(4))); - assertEquals(4, map.size()); - Iterator it = map.values().iterator(); - - assertNotNull(it); - int count = 0; - - while (it.hasNext()) { - Object o = it.next(); - - assertNotNull(o); - assertTrue(o instanceof Integer); - try { - it.remove(); - } - catch (UnsupportedOperationException e) { - fail("Removal failed"); - } - count++; - } - assertEquals(4, count); - } - - /** - * Method testBasicMapKeySetView - */ - public void testBasicMapKeySetViewRemoval() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key-1", "value-1")); - assertNull(map.put("key-2", "value-2")); - assertNull(map.put("key-3", "value-3")); - assertNull(map.put("key-4", "value-4")); - assertEquals(4, map.size()); - Iterator it = map.keySet().iterator(); - - assertNotNull(it); - int count = 0; - - while (it.hasNext()) { - Object o = it.next(); - - assertNotNull(o); - assertTrue(o instanceof String); - try { - it.remove(); - } - catch (UnsupportedOperationException e) { - fail("Removal failed"); - } - count++; - } - assertEquals(4, count); - } - - /** - * Method testBasicMapEntrySetView - */ - public void testBasicMapEntrySetViewRemoval() { - - Map map = new TimeoutMap(60000L); - - assertNull(map.put("key-1", new Integer(1))); - assertNull(map.put("key-2", "value-2")); - assertNull(map.put("key-3", new Object())); - assertNull(map.put("key-4", Boolean.FALSE)); - assertEquals(4, map.size()); - Iterator it = map.entrySet().iterator(); - - assertNotNull(it); - int count = 0; - - while (it.hasNext()) { - Object o = it.next(); - - assertNotNull(o); - assertTrue(o instanceof Map.Entry); - try { - it.remove(); - } - catch (UnsupportedOperationException e) { - fail("Removal failed"); - } - count++; - } - assertEquals(4, count); - } - - /** - * Method testBasicMapStoresOnlyOneValuePerKey - */ - public void testTimeoutReturnNull() { - - Map map = new TimeoutMap(100L); - - assertNull(map.put("key", "value-1")); - assertEquals("value-1", map.get("key")); - assertNull(map.put("another", "value-2")); - assertEquals("value-2", map.get("another")); - synchronized (this) { - try { - Thread.sleep(110L); - } - catch (InterruptedException e) { - // Continue, but might break the timeout thing below... - } - } - - // Values should now time out - assertNull(map.get("key")); - assertNull(map.get("another")); - } - - /** - * Method testTimeoutIsEmpty - */ - public void testTimeoutIsEmpty() { - - TimeoutMap map = new TimeoutMap(50L); - - assertNull(map.put("key", "value-1")); - assertEquals("value-1", map.get("key")); - assertNull(map.put("another", "value-2")); - assertEquals("value-2", map.get("another")); - synchronized (this) { - try { - Thread.sleep(100L); - } - catch (InterruptedException e) { - // Continue, but might break the timeout thing below... - } - } - - // This for loop should not print anything, if the tests succeed. - Set set = map.keySet(); - assertEquals(0, set.size()); - for (Iterator iterator = set.iterator(); iterator.hasNext(); System.out.println(iterator.next())) { - ; - } - assertEquals(0, map.size()); - assertTrue(map.isEmpty()); - } - - /** - * Method testTimeoutWrapIsEmpty - */ - public void testTimeoutWrapIsEmpty() { - - Map map = new TimeoutMap(new LRUMap(2), null, 100L); - - assertNull(map.put("key", "value-1")); - assertEquals("value-1", map.get("key")); - assertNull(map.put("another", "value-2")); - assertEquals("value-2", map.get("another")); - assertNull(map.put("third", "value-3")); - assertEquals("value-3", map.get("third")); - synchronized (this) { - try { - Thread.sleep(110L); - } - catch (InterruptedException e) { - // Continue, but might break the timeout thing below... - } - } - - // This for loop should not print anything, if the tests succeed. - Set set = map.keySet(); - assertEquals(0, set.size()); - for (Iterator iterator = set.iterator(); iterator.hasNext(); System.out.println(iterator.next())) { - ; - } - assertEquals(0, map.size()); - assertTrue(map.isEmpty()); - } - - /** - * Method testTimeoutWrapReturnNull - */ - public void testTimeoutWrapReturnNull() { - - Map map = new TimeoutMap(new LRUMap(), null, 100L); - - assertNull(map.put("key", "value-1")); - assertEquals("value-1", map.get("key")); - assertNull(map.put("another", "value-2")); - assertEquals("value-2", map.get("another")); - synchronized (this) { - try { - Thread.sleep(110L); - } - catch (InterruptedException e) { - // Continue, but might break the timeout thing below... - } - } - - // Values should now time out - assertNull(map.get("key")); - assertNull(map.get("another")); - } - - /** - * Method testWrapMaxSize - */ - public void testWrapMaxSize() { - - LRUMap lru = new LRUMap(); - - lru.setMaxSize(2); - TimeoutMap map = new TimeoutMap(lru, null, 1000L); - - assertNull(map.put("key", "value-1")); - assertEquals("value-1", map.get("key")); - assertNull(map.put("another", "value-2")); - assertEquals("value-2", map.get("another")); - assertNull(map.put("third", "value-3")); - assertEquals("value-3", map.get("third")); - - // This value should have expired - assertNull(map.get("key")); - - // These should be left - assertEquals("value-2", map.get("another")); - assertEquals("value-3", map.get("third")); - } - - /** - * Method testWrapMapContainingValues - */ - public void testWrapMapContainingValues() { - - Map backing = new TreeMap(); - - backing.put("key", "original"); - TimeoutMap map = null; - - try { - map = new TimeoutMap(backing, backing, 1000L); - Object value = map.put("key", "value-1"); - assertNotNull(value); // Should now have value! - assertEquals("original", value); - } - catch (ClassCastException cce) { - cce.printStackTrace(); - fail("Content not converted to TimedEntries properly!"); - } - assertEquals("value-1", map.get("key")); - assertNull(map.put("another", "value-2")); - assertEquals("value-2", map.get("another")); - assertNull(map.put("third", "value-3")); - assertEquals("value-3", map.get("third")); - } - - public void testIteratorRemove() { - Map map = makeFullMap(); - - for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { - iterator.remove(); - } - assertEquals(0, map.size()); - - map = makeFullMap(); - - for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { - iterator.next(); - iterator.remove(); - } - assertEquals(0, map.size()); - } - - public void testIteratorPredictableNext() { - TimeoutMap map = (TimeoutMap) makeFullMap(); - map.setExpiryTime(50l); - assertFalse(map.isEmpty()); - - int count = 0; - for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { - if (count == 0) { - // NOTE: Only wait fist time, to avoid slooow tests - synchronized (this) { - try { - wait(60l); - } - catch (InterruptedException e) { - } - } - } - - try { - Map.Entry entry = (Map.Entry) iterator.next(); - assertNotNull(entry); - count++; - } - catch (NoSuchElementException nse) { - fail("Elements expire between Interator.hasNext() and Iterator.next()"); - } - } - - assertTrue("Elements expired too early, test did not run as expected.", count > 0); - //assertEquals("Elements did not expire as expected.", 1, count); - } - - public void testIteratorPredictableRemove() { - TimeoutMap map = (TimeoutMap) makeFullMap(); - map.setExpiryTime(50l); - assertFalse(map.isEmpty()); - - int count = 0; - for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { - if (count == 0) { - // NOTE: Only wait fist time, to avoid slooow tests - synchronized (this) { - try { - wait(60l); - } - catch (InterruptedException e) { - } - } - } - - try { - iterator.remove(); - count++; - } - catch (NoSuchElementException nse) { - fail("Elements expired between Interator.hasNext() and Iterator.remove()"); - } - } - - assertTrue("Elements expired too early, test did not run as expected.", count > 0); - //assertEquals("Elements did not expire as expected.", 1, count); - } - - public void testIteratorPredictableNextRemove() { - TimeoutMap map = (TimeoutMap) makeFullMap(); - map.setExpiryTime(50l); - assertFalse(map.isEmpty()); - - int count = 0; - for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { - if (count == 0) { - // NOTE: Only wait fist time, to avoid slooow tests - synchronized (this) { - try { - wait(60l); - } - catch (InterruptedException e) { - } - } - } - - try { - Map.Entry entry = (Map.Entry) iterator.next(); - assertNotNull(entry); - } - catch (NoSuchElementException nse) { - fail("Elements expired between Interator.hasNext() and Iterator.next()"); - } - - try { - iterator.remove(); - count++; - } - catch (NoSuchElementException nse) { - fail("Elements expired between Interator.hasNext() and Iterator.remove()"); - } - } - - assertTrue("Elements expired too early, test did not run as expected.", count > 0); - //assertEquals("Elements did not expire as expected.", 1, count); - } - - public void testIteratorPredictableRemovedEntry() { - TimeoutMap map = (TimeoutMap) makeEmptyMap(); - map.setExpiryTime(1000l); // No elements should expire during this test - - map.put("key-1", new Integer(1)); - map.put("key-2", new Integer(2)); - - assertFalse(map.isEmpty()); - - Object removedKey = null; - Object otherKey = null; - Iterator iterator = map.entrySet().iterator(); - assertTrue("Iterator was empty", iterator.hasNext()); - try { - Map.Entry entry = (Map.Entry) iterator.next(); - assertNotNull(entry); - removedKey = entry.getKey(); - otherKey = "key-1".equals(removedKey) ? "key-2" : "key-1"; - } - catch (NoSuchElementException nse) { - fail("Elements expired between Interator.hasNext() and Iterator.next()"); - } - - try { - iterator.remove(); - } - catch (NoSuchElementException nse) { - fail("Elements expired between Interator.hasNext() and Iterator.remove()"); - } - - assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey)); - assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey)); - } -} - +/* + * 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.util; + +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; + +/** + * TimeoutMapTest + *

+ * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/TimeoutMapTestCase.java#2 $ + */ +public class TimeoutMapTest extends MapAbstractTest { + + public Map makeEmptyMap() { + return new TimeoutMap(60 * 60 * 1000); + } + /* + * The basic Map interface lets one associate keys and values: + */ + + /** + * Method testBasicMap + */ + @Test + public void testBasicMap() { + + Map map = new TimeoutMap(60000L); + Object key = "key"; + Object value = new Integer(3); + + map.put(key, value); + assertEquals(value, map.get(key)); + } + + /* + * If there is no value associated with a key, + * the basic Map will return null for that key: + */ + + /** + * Method testBasicMapReturnsNullForMissingKey + */ + @Test + public void testBasicMapReturnsNullForMissingKey() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.get("key")); + } + + /* + * One can also explicitly store a null value for + * some key: + */ + + /** + * Method testBasicMapAllowsNull + */ + @Test + public void testBasicMapAllowsNull() { + + Map map = new TimeoutMap(60000L); + Object key = "key"; + Object value = null; + + map.put(key, value); + assertNull(map.get(key)); + } + + /** + * Method testBasicMapAllowsMultipleTypes + */ + @Test + public void testBasicMapAllowsMultipleTypes() { + + Map map = new TimeoutMap(60000L); + + map.put("key-1", "value-1"); + map.put(new Integer(2), "value-2"); + map.put("key-3", new Integer(3)); + map.put(new Integer(4), new Integer(4)); + map.put(Boolean.FALSE, ""); + assertEquals("value-1", map.get("key-1")); + assertEquals("value-2", map.get(new Integer(2))); + assertEquals(new Integer(3), map.get("key-3")); + assertEquals(new Integer(4), map.get(new Integer(4))); + assertEquals("", map.get(Boolean.FALSE)); + } + + /** + * Method testBasicMapStoresOnlyOneValuePerKey + */ + @Test + public void testBasicMapStoresOnlyOneValuePerKey() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key", "value-1")); + assertEquals("value-1", map.get("key")); + assertEquals("value-1", map.put("key", "value-2")); + assertEquals("value-2", map.get("key")); + } + + /** + * Method testBasicMapValuesView + */ + @Test + public void testBasicMapValuesView() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key-1", new Integer(1))); + assertNull(map.put("key-2", new Integer(2))); + assertNull(map.put("key-3", new Integer(3))); + assertNull(map.put("key-4", new Integer(4))); + assertEquals(4, map.size()); + + Collection values = map.values(); + assertEquals(4, values.size()); + + Iterator it = values.iterator(); + + assertNotNull(it); + int count = 0; + + while (it.hasNext()) { + Object o = it.next(); + + assertNotNull(o); + assertTrue(o instanceof Integer); + count++; + } + assertEquals(4, count); + } + + /** + * Method testBasicMapKeySetView + */ + @Test + public void testBasicMapKeySetView() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key-1", "value-1")); + assertNull(map.put("key-2", "value-2")); + assertNull(map.put("key-3", "value-3")); + assertNull(map.put("key-4", "value-4")); + assertEquals(4, map.size()); + Iterator it = map.keySet().iterator(); + + assertNotNull(it); + int count = 0; + + while (it.hasNext()) { + Object o = it.next(); + + assertNotNull(o); + assertTrue(o instanceof String); + count++; + } + assertEquals(4, count); + } + + /** + * Method testBasicMapEntrySetView + */ + @Test + public void testBasicMapEntrySetView() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key-1", new Integer(1))); + assertNull(map.put("key-2", "value-2")); + assertNull(map.put("key-3", new Object())); + assertNull(map.put("key-4", Boolean.FALSE)); + assertEquals(4, map.size()); + Iterator it = map.entrySet().iterator(); + + assertNotNull(it); + int count = 0; + + while (it.hasNext()) { + Object o = it.next(); + + assertNotNull(o); + assertTrue(o instanceof Map.Entry); + count++; + } + assertEquals(4, count); + } + + /** + * Method testBasicMapValuesView + */ + @Test + public void testBasicMapValuesViewRemoval() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key-1", new Integer(1))); + assertNull(map.put("key-2", new Integer(2))); + assertNull(map.put("key-3", new Integer(3))); + assertNull(map.put("key-4", new Integer(4))); + assertEquals(4, map.size()); + Iterator it = map.values().iterator(); + + assertNotNull(it); + int count = 0; + + while (it.hasNext()) { + Object o = it.next(); + + assertNotNull(o); + assertTrue(o instanceof Integer); + try { + it.remove(); + } + catch (UnsupportedOperationException e) { + fail("Removal failed"); + } + count++; + } + assertEquals(4, count); + } + + /** + * Method testBasicMapKeySetView + */ + @Test + public void testBasicMapKeySetViewRemoval() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key-1", "value-1")); + assertNull(map.put("key-2", "value-2")); + assertNull(map.put("key-3", "value-3")); + assertNull(map.put("key-4", "value-4")); + assertEquals(4, map.size()); + Iterator it = map.keySet().iterator(); + + assertNotNull(it); + int count = 0; + + while (it.hasNext()) { + Object o = it.next(); + + assertNotNull(o); + assertTrue(o instanceof String); + try { + it.remove(); + } + catch (UnsupportedOperationException e) { + fail("Removal failed"); + } + count++; + } + assertEquals(4, count); + } + + /** + * Method testBasicMapEntrySetView + */ + @Test + public void testBasicMapEntrySetViewRemoval() { + + Map map = new TimeoutMap(60000L); + + assertNull(map.put("key-1", new Integer(1))); + assertNull(map.put("key-2", "value-2")); + assertNull(map.put("key-3", new Object())); + assertNull(map.put("key-4", Boolean.FALSE)); + assertEquals(4, map.size()); + Iterator it = map.entrySet().iterator(); + + assertNotNull(it); + int count = 0; + + while (it.hasNext()) { + Object o = it.next(); + + assertNotNull(o); + assertTrue(o instanceof Map.Entry); + try { + it.remove(); + } + catch (UnsupportedOperationException e) { + fail("Removal failed"); + } + count++; + } + assertEquals(4, count); + } + + /** + * Method testBasicMapStoresOnlyOneValuePerKey + */ + @Test + public void testTimeoutReturnNull() { + + Map map = new TimeoutMap(100L); + + assertNull(map.put("key", "value-1")); + assertEquals("value-1", map.get("key")); + assertNull(map.put("another", "value-2")); + assertEquals("value-2", map.get("another")); + synchronized (this) { + try { + Thread.sleep(110L); + } + catch (InterruptedException e) { + // Continue, but might break the timeout thing below... + } + } + + // Values should now time out + assertNull(map.get("key")); + assertNull(map.get("another")); + } + + /** + * Method testTimeoutIsEmpty + */ + @Test + public void testTimeoutIsEmpty() { + + TimeoutMap map = new TimeoutMap(50L); + + assertNull(map.put("key", "value-1")); + assertEquals("value-1", map.get("key")); + assertNull(map.put("another", "value-2")); + assertEquals("value-2", map.get("another")); + synchronized (this) { + try { + Thread.sleep(100L); + } + catch (InterruptedException e) { + // Continue, but might break the timeout thing below... + } + } + + // This for loop should not print anything, if the tests succeed. + Set set = map.keySet(); + assertEquals(0, set.size()); + for (Iterator iterator = set.iterator(); iterator.hasNext(); System.out.println(iterator.next())) { + ; + } + assertEquals(0, map.size()); + assertTrue(map.isEmpty()); + } + + /** + * Method testTimeoutWrapIsEmpty + */ + @Test + public void testTimeoutWrapIsEmpty() { + + Map map = new TimeoutMap(new LRUMap(2), null, 100L); + + assertNull(map.put("key", "value-1")); + assertEquals("value-1", map.get("key")); + assertNull(map.put("another", "value-2")); + assertEquals("value-2", map.get("another")); + assertNull(map.put("third", "value-3")); + assertEquals("value-3", map.get("third")); + synchronized (this) { + try { + Thread.sleep(110L); + } + catch (InterruptedException e) { + // Continue, but might break the timeout thing below... + } + } + + // This for loop should not print anything, if the tests succeed. + Set set = map.keySet(); + assertEquals(0, set.size()); + for (Iterator iterator = set.iterator(); iterator.hasNext(); System.out.println(iterator.next())) { + ; + } + assertEquals(0, map.size()); + assertTrue(map.isEmpty()); + } + + /** + * Method testTimeoutWrapReturnNull + */ + @Test + public void testTimeoutWrapReturnNull() { + + Map map = new TimeoutMap(new LRUMap(), null, 100L); + + assertNull(map.put("key", "value-1")); + assertEquals("value-1", map.get("key")); + assertNull(map.put("another", "value-2")); + assertEquals("value-2", map.get("another")); + synchronized (this) { + try { + Thread.sleep(110L); + } + catch (InterruptedException e) { + // Continue, but might break the timeout thing below... + } + } + + // Values should now time out + assertNull(map.get("key")); + assertNull(map.get("another")); + } + + /** + * Method testWrapMaxSize + */ + @Test + public void testWrapMaxSize() { + + LRUMap lru = new LRUMap(); + + lru.setMaxSize(2); + TimeoutMap map = new TimeoutMap(lru, null, 1000L); + + assertNull(map.put("key", "value-1")); + assertEquals("value-1", map.get("key")); + assertNull(map.put("another", "value-2")); + assertEquals("value-2", map.get("another")); + assertNull(map.put("third", "value-3")); + assertEquals("value-3", map.get("third")); + + // This value should have expired + assertNull(map.get("key")); + + // These should be left + assertEquals("value-2", map.get("another")); + assertEquals("value-3", map.get("third")); + } + + /** + * Method testWrapMapContainingValues + */ + @Test + public void testWrapMapContainingValues() { + + Map backing = new TreeMap(); + + backing.put("key", "original"); + TimeoutMap map = null; + + try { + map = new TimeoutMap(backing, backing, 1000L); + Object value = map.put("key", "value-1"); + assertNotNull(value); // Should now have value! + assertEquals("original", value); + } + catch (ClassCastException cce) { + cce.printStackTrace(); + fail("Content not converted to TimedEntries properly!"); + } + assertEquals("value-1", map.get("key")); + assertNull(map.put("another", "value-2")); + assertEquals("value-2", map.get("another")); + assertNull(map.put("third", "value-3")); + assertEquals("value-3", map.get("third")); + } + + @Test + public void testIteratorRemove() { + Map map = makeFullMap(); + + for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { + iterator.remove(); + } + assertEquals(0, map.size()); + + map = makeFullMap(); + + for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { + iterator.next(); + iterator.remove(); + } + assertEquals(0, map.size()); + } + + @Test + public void testIteratorPredictableNext() { + TimeoutMap map = (TimeoutMap) makeFullMap(); + map.setExpiryTime(50l); + assertFalse(map.isEmpty()); + + int count = 0; + for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { + if (count == 0) { + // NOTE: Only wait fist time, to avoid slooow tests + synchronized (this) { + try { + wait(60l); + } + catch (InterruptedException e) { + } + } + } + + try { + Map.Entry entry = (Map.Entry) iterator.next(); + assertNotNull(entry); + count++; + } + catch (NoSuchElementException nse) { + fail("Elements expire between Interator.hasNext() and Iterator.next()"); + } + } + + assertTrue("Elements expired too early, test did not run as expected.", count > 0); + //assertEquals("Elements did not expire as expected.", 1, count); + } + + @Test + public void testIteratorPredictableRemove() { + TimeoutMap map = (TimeoutMap) makeFullMap(); + map.setExpiryTime(50l); + assertFalse(map.isEmpty()); + + int count = 0; + for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { + if (count == 0) { + // NOTE: Only wait fist time, to avoid slooow tests + synchronized (this) { + try { + wait(60L); + } + catch (InterruptedException e) { + } + } + } + + try { + iterator.remove(); + count++; + } + catch (NoSuchElementException nse) { + fail("Elements expired between Interator.hasNext() and Iterator.remove()"); + } + } + + assertTrue("Elements expired too early, test did not run as expected.", count > 0); + //assertEquals("Elements did not expire as expected.", 1, count); + } + + @Test + public void testIteratorPredictableNextRemove() { + TimeoutMap map = (TimeoutMap) makeFullMap(); + map.setExpiryTime(50l); + assertFalse(map.isEmpty()); + + int count = 0; + for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { + if (count == 0) { + // NOTE: Only wait fist time, to avoid slooow tests + synchronized (this) { + try { + wait(60l); + } + catch (InterruptedException ignore) { + } + } + } + + try { + Map.Entry entry = (Map.Entry) iterator.next(); + assertNotNull(entry); + } + catch (NoSuchElementException nse) { + fail("Elements expired between Interator.hasNext() and Iterator.next()"); + } + + try { + iterator.remove(); + count++; + } + catch (NoSuchElementException nse) { + fail("Elements expired between Interator.hasNext() and Iterator.remove()"); + } + } + + assertTrue("Elements expired too early, test did not run as expected.", count > 0); + //assertEquals("Elements did not expire as expected.", 1, count); + } + + @Test + public void testIteratorPredictableRemovedEntry() { + TimeoutMap map = (TimeoutMap) makeEmptyMap(); + map.setExpiryTime(1000l); // No elements should expire during this test + + map.put("key-1", new Integer(1)); + map.put("key-2", new Integer(2)); + + assertFalse(map.isEmpty()); + + Object removedKey = null; + Object otherKey = null; + Iterator iterator = map.entrySet().iterator(); + assertTrue("Iterator was empty", iterator.hasNext()); + try { + Map.Entry entry = (Map.Entry) iterator.next(); + assertNotNull(entry); + removedKey = entry.getKey(); + otherKey = "key-1".equals(removedKey) ? "key-2" : "key-1"; + } + catch (NoSuchElementException nse) { + fail("Elements expired between Interator.hasNext() and Iterator.next()"); + } + + try { + iterator.remove(); + } + catch (NoSuchElementException nse) { + fail("Elements expired between Interator.hasNext() and Iterator.remove()"); + } + + assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey)); + assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey)); + } + + + @Test + public void testContainsKeyOnEmptyMap() { + // See #600 + Map timeoutMap = new TimeoutMap<>(30); + assertFalse(timeoutMap.containsKey("xyz")); + timeoutMap.put("xyz", "xyz"); + assertTrue(timeoutMap.containsKey("xyz")); + + try { + Thread.sleep(50); // Let the item expire + } + catch (InterruptedException ignore) { + } + + assertFalse(timeoutMap.containsKey("xyz")); + assertNull(timeoutMap.get("xyz")); + } +} + diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTest.java new file mode 100755 index 00000000..8ef7f7aa --- /dev/null +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTest.java @@ -0,0 +1,86 @@ +/* + * 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.util; + +import org.junit.Test; + +import java.util.Iterator; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +/** + * TokenIteratorAbstractTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTestCase.java#1 $ + */ +public abstract class TokenIteratorAbstractTest { + protected abstract TokenIterator createTokenIterator(String pString); + + protected abstract TokenIterator createTokenIterator(String pString, String pDelimiters); + + @Test + public void testNullString() { + try { + createTokenIterator(null); + fail("Null string parameter not allowed"); + } + catch (IllegalArgumentException e) { + // okay! + } + catch (Throwable t) { + fail(t.getMessage()); + } + } + + @Test + public void testNullDelimmiter() { + try { + createTokenIterator("", null); + fail("Null delimiter parameter not allowed"); + } + catch (IllegalArgumentException e) { + // okay! + } + catch (Throwable t) { + fail(t.getMessage()); + } + } + + @Test + public void testEmptyString() { + Iterator iterator = createTokenIterator(""); + assertFalse("Empty string has elements", iterator.hasNext()); + } + +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTestCase.java deleted file mode 100755 index acef4f81..00000000 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTestCase.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.twelvemonkeys.util; - -import junit.framework.TestCase; - -import java.util.Iterator; - -/** - * TokenIteratorAbstractTestCase - *

- * - * - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTestCase.java#1 $ - */ -public abstract class TokenIteratorAbstractTestCase extends TestCase { - protected abstract TokenIterator createTokenIterator(String pString); - - protected abstract TokenIterator createTokenIterator(String pString, String pDelimiters); - - public void testNullString() { - try { - createTokenIterator(null); - fail("Null string parameter not allowed"); - } - catch (IllegalArgumentException e) { - // okay! - } - catch (Throwable t) { - fail(t.getMessage()); - } - } - - public void testNullDelimmiter() { - try { - createTokenIterator("", null); - fail("Null delimiter parameter not allowed"); - } - catch (IllegalArgumentException e) { - // okay! - } - catch (Throwable t) { - fail(t.getMessage()); - } - } - - public void testEmptyString() { - Iterator iterator = createTokenIterator(""); - assertFalse("Empty string has elements", iterator.hasNext()); - } - -} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/ConverterTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/ConverterTest.java new file mode 100755 index 00000000..7ae9ead4 --- /dev/null +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/ConverterTest.java @@ -0,0 +1,51 @@ +/* + * 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.util.convert; + +import org.junit.Ignore; +import org.junit.Test; + +/** + * ConverterTest + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/ConverterTestCase.java#1 $ + */ +public class ConverterTest { + + @Ignore("Not implemented") + @Test + public void testMe() { + // TODO: Implement tests + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/ConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/ConverterTestCase.java deleted file mode 100755 index d974da3f..00000000 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/ConverterTestCase.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.twelvemonkeys.util.convert; - -import junit.framework.TestCase; - -/** - * ConverterTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/ConverterTestCase.java#1 $ - */ -public class ConverterTestCase extends TestCase { - - public void testMe() { - // TODO: Implement tests - } -} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DateConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DateConverterTest.java similarity index 54% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DateConverterTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DateConverterTest.java index ec6aeecc..467502d5 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DateConverterTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DateConverterTest.java @@ -1,61 +1,93 @@ -package com.twelvemonkeys.util.convert; - -import com.twelvemonkeys.lang.DateUtil; -import org.junit.Test; - -import java.text.DateFormat; -import java.util.*; - -/** - * DateConverterTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/DateConverterTestCase.java#2 $ - */ -public class DateConverterTestCase extends PropertyConverterAbstractTestCase { - protected final static String FORMAT_STR_1 = "dd.MM.yyyy HH:mm:ss"; - protected final static String FORMAT_STR_2 = "dd-MM-yyyy hh:mm:ss a"; - - protected PropertyConverter makePropertyConverter() { - return new DateConverter(); - } - - protected Conversion[] getTestConversions() { - // The default format doesn't contain milliseconds, so we have to round - long time = System.currentTimeMillis(); - final Date now = new Date(DateUtil.roundToSecond(time)); - DateFormat df = DateFormat.getDateTimeInstance(); - - return new Conversion[] { - new Conversion("01.11.2006 15:26:23", new GregorianCalendar(2006, 10, 1, 15, 26, 23).getTime(), FORMAT_STR_1), - - // This doesn't really work.. But close enough - new Conversion(df.format(now), now), - - // This format is really stupid - new Conversion("01-11-2006 03:27:44 pm", new GregorianCalendar(2006, 10, 1, 15, 27, 44).getTime(), FORMAT_STR_2, "01-11-2006 03:27:44 PM"), - - // These seems to be an hour off (no timezone?)... - new Conversion("42", new Date(42l), "S"), - new Conversion(String.valueOf(time % 1000l), new Date(time % 1000l), "S"), - }; - } - - @Test - @Override - public void testConvert() { - // Custom setup, to make test cases stable: Always use GMT - TimeZone oldTZ = TimeZone.getDefault(); - - try { - TimeZone.setDefault(TimeZone.getTimeZone("GMT")); - super.testConvert(); - } - finally { - // Restore - TimeZone.setDefault(oldTZ); - } - } +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.DateUtil; +import org.junit.Test; + +import java.text.DateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +/** + * DateConverterTest + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/DateConverterTestCase.java#2 $ + */ +public class DateConverterTest extends PropertyConverterAbstractTest { + protected final static String FORMAT_STR_1 = "dd.MM.yyyy HH:mm:ss"; + protected final static String FORMAT_STR_2 = "dd-MM-yyyy hh:mm:ss a"; + + protected PropertyConverter makePropertyConverter() { + return new DateConverter(); + } + + protected Conversion[] getTestConversions() { + // The default format doesn't contain milliseconds, so we have to round + long time = System.currentTimeMillis(); + final Date now = new Date(DateUtil.roundToSecond(time)); + DateFormat df = DateFormat.getDateTimeInstance(); + + return new Conversion[] { + new Conversion("01.11.2006 15:26:23", new GregorianCalendar(2006, 10, 1, 15, 26, 23).getTime(), FORMAT_STR_1), + + // This doesn't really work.. But close enough + new Conversion(df.format(now), now), + + // This format is really stupid + new Conversion("01-11-2006 03:27:44 pm", new GregorianCalendar(2006, 10, 1, 15, 27, 44).getTime(), FORMAT_STR_2, "01-11-2006 03:27:44 PM"), + + // These seems to be an hour off (no timezone?)... + new Conversion("42", new Date(42l), "S"), + new Conversion(String.valueOf(time % 1000l), new Date(time % 1000l), "S"), + }; + } + + @Test + @Override + public void testConvert() { + // Custom setup, to make test cases stable: Always use GMT + TimeZone oldTZ = TimeZone.getDefault(); + + try { + TimeZone.setDefault(TimeZone.getTimeZone("GMT")); + super.testConvert(); + } + finally { + // Restore + TimeZone.setDefault(oldTZ); + } + } } \ No newline at end of file diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTest.java similarity index 77% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTest.java index 6f1f19e7..394cd795 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTest.java @@ -1,150 +1,180 @@ -package com.twelvemonkeys.util.convert; - -import com.twelvemonkeys.lang.Validate; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; -import java.net.URI; - -import static org.junit.Assert.*; - -/** - * DefaultConverterTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java#1 $ - */ -public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase { - protected PropertyConverter makePropertyConverter() { - return new DefaultConverter(); - } - - protected Conversion[] getTestConversions() { - //noinspection BooleanConstructorCall - return new Conversion[] { - // Booleans - new Conversion("true", Boolean.TRUE), - new Conversion("TRUE", Boolean.TRUE, null, "true"), - new Conversion("false", Boolean.FALSE), - new Conversion("FALSE", false, null, "false"), - - new Conversion("2", 2), - - // Stupid but valid - new Conversion("fooBar", "fooBar"), - //new Conversion("fooBar", new StringBuilder("fooBar")), - StringBuilder does not impl equals()... - - // Stupid test class that reveres chars - new Conversion("fooBar", new FooBar("fooBar")), - - // String array tests - new Conversion("foo, bar, baz", new String[] {"foo", "bar", "baz"}), - new Conversion("foo", new String[] {"foo"}), - new Conversion("foo;bar; baz", new String[] {"foo", "bar", "baz"}, "; ", "foo; bar; baz"), - - // Native array tests - new Conversion("1, 2, 3", new int[] {1, 2, 3}), - new Conversion("-1, 42, 0", new long[] {-1, 42, 0}), - new Conversion("true, true, false", new boolean[] {true, true, false}), - new Conversion(".3, 4E7, .97", new float[] {.3f, 4e7f, .97f}, ", ", "0.3, 4.0E7, 0.97"), - - // Object array test - new Conversion("foo, bar", new FooBar[] {new FooBar("foo"), new FooBar("bar")}), - new Conversion("/temp, /usr/local/bin".replace('/', File.separatorChar), new File[] {new File("/temp"), new File("/usr/local/bin")}), - new Conversion("file:/temp, http://java.net/", new URI[] {URI.create("file:/temp"), URI.create("http://java.net/")}), - - // TODO: More tests - }; - } - - @Test - public void testConvertBooleanPrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertTrue((Boolean) converter.toObject("true", boolean.class, null)); - assertFalse((Boolean) converter.toObject("FalsE", Boolean.TYPE, null)); - } - - @Test - public void testConvertShortPrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals(1, (short) (Short) converter.toObject("1", short.class, null)); - assertEquals(-2, (short) (Short) converter.toObject("-2", Short.TYPE, null)); - } - @Test - public void testConvertIntPrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals(1, (int) (Integer) converter.toObject("1", int.class, null)); - assertEquals(-2, (int) (Integer) converter.toObject("-2", Integer.TYPE, null)); - } - - @Test - public void testConvertLongPrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals(Long.MAX_VALUE, (long) (Long) converter.toObject("9223372036854775807", long.class, null)); - assertEquals(-2, (long) (Long) converter.toObject("-2", Long.TYPE, null)); - } - - @Test - public void testConvertBytePrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals(1, (byte) (Byte) converter.toObject("1", byte.class, null)); - assertEquals(-2, (byte) (Byte) converter.toObject("-2", Byte.TYPE, null)); - } - - @Test - public void testConvertFloatPrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals(1f, (Float) converter.toObject("1.0", float.class, null), 0); - assertEquals(-2.3456f, (Float) converter.toObject("-2.3456", Float.TYPE, null), 0); - } - - @Test - public void testConvertDoublePrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals(1d, (Double) converter.toObject("1.0", double.class, null), 0); - assertEquals(-2.3456, (Double) converter.toObject("-2.3456", Double.TYPE, null), 0); - } - - @Ignore("Known issue. Why would anyone do something like this?") - @Test - public void testConvertCharPrimitive() { - PropertyConverter converter = makePropertyConverter(); - assertEquals('A', (char) (Character) converter.toObject("A", char.class, null)); - assertEquals('Z', (char) (Character) converter.toObject("Z", Character.TYPE, null)); - } - - public static class FooBar { - private final String bar; - - public FooBar(String pFoo) { - Validate.notNull(pFoo, "foo"); - - bar = reverse(pFoo); - } - - private String reverse(String pFoo) { - StringBuilder buffer = new StringBuilder(pFoo.length()); - - for (int i = pFoo.length() - 1; i >= 0; i--) { - buffer.append(pFoo.charAt(i)); - } - - return buffer.toString(); - } - - public String toString() { - return reverse(bar); - } - - public boolean equals(Object obj) { - return obj == this || (obj != null && obj.getClass() == getClass() && ((FooBar) obj).bar.equals(bar)); - } - - public int hashCode() { - return 7 * bar.hashCode(); - } - } -} +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.Validate; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.net.URI; + +import static org.junit.Assert.*; + +/** + * DefaultConverterTest + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java#1 $ + */ +public class DefaultConverterTest extends PropertyConverterAbstractTest { + protected PropertyConverter makePropertyConverter() { + return new DefaultConverter(); + } + + protected Conversion[] getTestConversions() { + //noinspection BooleanConstructorCall + return new Conversion[] { + // Booleans + new Conversion("true", Boolean.TRUE), + new Conversion("TRUE", Boolean.TRUE, null, "true"), + new Conversion("false", Boolean.FALSE), + new Conversion("FALSE", false, null, "false"), + + new Conversion("2", 2), + + // Stupid but valid + new Conversion("fooBar", "fooBar"), + //new Conversion("fooBar", new StringBuilder("fooBar")), - StringBuilder does not impl equals()... + + // Stupid test class that reveres chars + new Conversion("fooBar", new FooBar("fooBar")), + + // String array tests + new Conversion("foo, bar, baz", new String[] {"foo", "bar", "baz"}), + new Conversion("foo", new String[] {"foo"}), + new Conversion("foo;bar; baz", new String[] {"foo", "bar", "baz"}, "; ", "foo; bar; baz"), + + // Native array tests + new Conversion("1, 2, 3", new int[] {1, 2, 3}), + new Conversion("-1, 42, 0", new long[] {-1, 42, 0}), + new Conversion("true, true, false", new boolean[] {true, true, false}), + new Conversion(".3, 4E7, .97", new float[] {.3f, 4e7f, .97f}, ", ", "0.3, 4.0E7, 0.97"), + + // Object array test + new Conversion("foo, bar", new FooBar[] {new FooBar("foo"), new FooBar("bar")}), + new Conversion("/temp, /usr/local/bin".replace('/', File.separatorChar), new File[] {new File("/temp"), new File("/usr/local/bin")}), + new Conversion("file:/temp, http://java.net/", new URI[] {URI.create("file:/temp"), URI.create("http://java.net/")}), + + // TODO: More tests + }; + } + + @Test + public void testConvertBooleanPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertTrue((Boolean) converter.toObject("true", boolean.class, null)); + assertFalse((Boolean) converter.toObject("FalsE", Boolean.TYPE, null)); + } + + @Test + public void testConvertShortPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1, (short) (Short) converter.toObject("1", short.class, null)); + assertEquals(-2, (short) (Short) converter.toObject("-2", Short.TYPE, null)); + } + @Test + public void testConvertIntPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1, (int) (Integer) converter.toObject("1", int.class, null)); + assertEquals(-2, (int) (Integer) converter.toObject("-2", Integer.TYPE, null)); + } + + @Test + public void testConvertLongPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(Long.MAX_VALUE, (long) (Long) converter.toObject("9223372036854775807", long.class, null)); + assertEquals(-2, (long) (Long) converter.toObject("-2", Long.TYPE, null)); + } + + @Test + public void testConvertBytePrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1, (byte) (Byte) converter.toObject("1", byte.class, null)); + assertEquals(-2, (byte) (Byte) converter.toObject("-2", Byte.TYPE, null)); + } + + @Test + public void testConvertFloatPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1f, (Float) converter.toObject("1.0", float.class, null), 0); + assertEquals(-2.3456f, (Float) converter.toObject("-2.3456", Float.TYPE, null), 0); + } + + @Test + public void testConvertDoublePrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1d, (Double) converter.toObject("1.0", double.class, null), 0); + assertEquals(-2.3456, (Double) converter.toObject("-2.3456", Double.TYPE, null), 0); + } + + @Ignore("Known issue. Why would anyone do something like this?") + @Test + public void testConvertCharPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals('A', (char) (Character) converter.toObject("A", char.class, null)); + assertEquals('Z', (char) (Character) converter.toObject("Z", Character.TYPE, null)); + } + + public static class FooBar { + private final String bar; + + public FooBar(String pFoo) { + Validate.notNull(pFoo, "foo"); + + bar = reverse(pFoo); + } + + private String reverse(String pFoo) { + StringBuilder buffer = new StringBuilder(pFoo.length()); + + for (int i = pFoo.length() - 1; i >= 0; i--) { + buffer.append(pFoo.charAt(i)); + } + + return buffer.toString(); + } + + public String toString() { + return reverse(bar); + } + + public boolean equals(Object obj) { + return obj == this || (obj != null && obj.getClass() == getClass() && ((FooBar) obj).bar.equals(bar)); + } + + public int hashCode() { + return 7 * bar.hashCode(); + } + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTest.java similarity index 50% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTest.java index 97ddcb44..ade7c952 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTest.java @@ -1,42 +1,72 @@ -package com.twelvemonkeys.util.convert; - -/** - * NumberConverterTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTestCase.java#2 $ - */ -public class NumberConverterTestCase extends PropertyConverterAbstractTestCase { - protected PropertyConverter makePropertyConverter() { - return new NumberConverter(); - } - - protected Conversion[] getTestConversions() { - return new Conversion[] { - new Conversion("0", 0), - new Conversion("1", 1), - new Conversion("-1001", -1001), - new Conversion("1E3", 1000, null, "1000"), - - new Conversion("-2", -2l), - new Conversion("2000651651854", 2000651651854l), - new Conversion("2E10", 20000000000l, null, "20000000000"), - - new Conversion("3", 3.0f), - new Conversion("3.1", 3.1f), - new Conversion("3.2", 3.2f, "#.#"), - //new Conversion("3,3", new Float(3), "#", "3"), // Seems to need parseIntegerOnly - new Conversion("-3.4", -3.4f), - new Conversion("-3.5E10", -3.5e10f, null, "-35000000512"), - - new Conversion("4", 4.0), - new Conversion("4.1", 4.1), - new Conversion("4.2", 4.2, "#.#"), - //new Conversion("4,3", new Double(4), "#", "4"), // Seems to need parseIntegerOnly - new Conversion("-4.4", -4.4), - new Conversion("-4.5E97", -4.5e97, null, "-45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), - }; - } -} +/* + * 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.util.convert; + +/** + * NumberConverterTest + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTestCase.java#2 $ + */ +public class NumberConverterTest extends PropertyConverterAbstractTest { + protected PropertyConverter makePropertyConverter() { + return new NumberConverter(); + } + + protected Conversion[] getTestConversions() { + return new Conversion[] { + new Conversion("0", 0), + new Conversion("1", 1), + new Conversion("-1001", -1001), + new Conversion("1E3", 1000, null, "1000"), + + new Conversion("-2", -2l), + new Conversion("2000651651854", 2000651651854l), + new Conversion("2E10", 20000000000l, null, "20000000000"), + + new Conversion("3", 3.0f), + new Conversion("3.1", 3.1f), + new Conversion("3.2", 3.2f, "#.#"), + //new Conversion("3,3", new Float(3), "#", "3"), // Seems to need parseIntegerOnly + new Conversion("-3.4", -3.4f), + new Conversion("-3.5E10", -3.5e10f, null, "-35000000512"), + + new Conversion("4", 4.0), + new Conversion("4.1", 4.1), + new Conversion("4.2", 4.2, "#.#"), + //new Conversion("4,3", new Double(4), "#", "4"), // Seems to need parseIntegerOnly + new Conversion("-4.4", -4.4), + new Conversion("-4.5E97", -4.5e97, null, "-45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }; + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTest.java similarity index 75% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTest.java index a848ad75..34a55682 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTest.java @@ -1,154 +1,184 @@ -package com.twelvemonkeys.util.convert; - -import com.twelvemonkeys.lang.ObjectAbstractTestCase; -import com.twelvemonkeys.lang.Validate; -import org.junit.Test; - -import java.util.Arrays; - -import static org.junit.Assert.*; - -/** - * PropertyConverterAbstractTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTestCase.java#2 $ - */ -public abstract class PropertyConverterAbstractTestCase extends ObjectAbstractTestCase { - protected Object makeObject() { - return makePropertyConverter(); - } - - protected abstract PropertyConverter makePropertyConverter(); - - protected abstract Conversion[] getTestConversions(); - - @Test - public void testConvert() { - PropertyConverter converter = makePropertyConverter(); - - Conversion[] tests = getTestConversions(); - - for (Conversion test : tests) { - Object obj; - try { - obj = converter.toObject(test.original(), test.type(), test.format()); - - assertEquals(String.format("'%s' converted to incorrect type", test.original()), test.type(), obj.getClass()); - if (test.type().isArray()) { - assertArrayEquals0(String.format("'%s' not converted", test.original()), test.value(), obj); - } - else { - assertEquals(String.format("'%s' not converted", test.original()), test.value(), obj); - } - - String result = converter.toString(test.value(), test.format()); - - assertEquals(String.format("'%s' does not match", test.converted()), test.converted(), result); - - obj = converter.toObject(result, test.type(), test.format()); - assertEquals(String.format("'%s' converted to incorrect type", test.original()), test.type(), obj.getClass()); - - if (test.type().isArray()) { - assertArrayEquals0(String.format("'%s' did not survive round trip conversion", test.original()), test.value(), obj); - } - else { - assertEquals(String.format("'%s' did not survive round trip conversion", test.original()), test.value(), obj); - } - } - catch (ConversionException e) { - failBecause(String.format("Converting '%s' to %s failed", test.original(), test.type()), e); - } - } - } - - private static void assertArrayEquals0(final String message, final Object left, final Object right) { - Class componentType = left.getClass().getComponentType(); - if (componentType.isPrimitive()) { - if (int.class == componentType) { - assertArrayEquals(message, (int[]) left, (int[]) right); - } - else if (short.class == componentType) { - assertArrayEquals(message, (short[]) left, (short[]) right); - } - else if (long.class == componentType) { - assertArrayEquals(message, (long[]) left, (long[]) right); - } - else if (float.class == componentType) { - assertArrayEquals(message, (float[]) left, (float[]) right, 0f); - } - else if (double.class == componentType) { - assertArrayEquals(message, (double[]) left, (double[]) right, 0d); - } - else if (boolean.class == componentType) { - assertTrue(message, Arrays.equals((boolean[]) left, (boolean[]) right)); - } - else if (byte.class == componentType) { - assertArrayEquals(message, (byte[]) left, (byte[]) right); - } - else if (char.class == componentType) { - assertArrayEquals(message, (char[]) left, (char[]) right); - } - else { - fail(String.format("Unknown primitive type: %s", componentType)); - } - } - else { - assertArrayEquals(message, (Object[]) left, (Object[]) right); - } - } - - private static void failBecause(String message, Throwable exception) { - AssertionError error = new AssertionError(message); - error.initCause(exception); - throw error; - } - - public static final class Conversion { - private final String strVal; - private final Object objVal; - private final String format; - private final String convertedStrVal; - - public Conversion(String pStrVal, Object pObjVal) { - this(pStrVal, pObjVal, null); - } - - public Conversion(String pStrVal, Object pObjVal, String pFormat) { - this(pStrVal, pObjVal, pFormat, pStrVal); - } - - public Conversion(String pStrVal, Object pObjVal, String pFormat, String pConvertedStrVal) { - Validate.notNull(pStrVal, "strVal"); - Validate.notNull(pObjVal, "objVal"); - Validate.notNull(pConvertedStrVal, "convertedStrVal"); - - strVal = pStrVal; - objVal = pObjVal; - format = pFormat; - convertedStrVal = pConvertedStrVal; - } - - public String original() { - return strVal; - } - - public Object value() { - return objVal; - } - - public Class type() { - return objVal.getClass(); - } - - public String format() { - return format; - } - - public String converted() { - return convertedStrVal; - } - } -} +/* + * 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.util.convert; + +import com.twelvemonkeys.lang.ObjectAbstractTest; +import com.twelvemonkeys.lang.Validate; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.*; + +/** + * PropertyConverterAbstractTest + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTestCase.java#2 $ + */ +public abstract class PropertyConverterAbstractTest extends ObjectAbstractTest { + protected Object makeObject() { + return makePropertyConverter(); + } + + protected abstract PropertyConverter makePropertyConverter(); + + protected abstract Conversion[] getTestConversions(); + + @Test + public void testConvert() { + PropertyConverter converter = makePropertyConverter(); + + Conversion[] tests = getTestConversions(); + + for (Conversion test : tests) { + Object obj; + try { + obj = converter.toObject(test.original(), test.type(), test.format()); + + assertEquals(String.format("'%s' converted to incorrect type", test.original()), test.type(), obj.getClass()); + if (test.type().isArray()) { + assertArrayEquals0(String.format("'%s' not converted", test.original()), test.value(), obj); + } + else { + assertEquals(String.format("'%s' not converted", test.original()), test.value(), obj); + } + + String result = converter.toString(test.value(), test.format()); + + assertEquals(String.format("'%s' does not match", test.converted()), test.converted(), result); + + obj = converter.toObject(result, test.type(), test.format()); + assertEquals(String.format("'%s' converted to incorrect type", test.original()), test.type(), obj.getClass()); + + if (test.type().isArray()) { + assertArrayEquals0(String.format("'%s' did not survive round trip conversion", test.original()), test.value(), obj); + } + else { + assertEquals(String.format("'%s' did not survive round trip conversion", test.original()), test.value(), obj); + } + } + catch (ConversionException e) { + failBecause(String.format("Converting '%s' to %s failed", test.original(), test.type()), e); + } + } + } + + private static void assertArrayEquals0(final String message, final Object left, final Object right) { + Class componentType = left.getClass().getComponentType(); + if (componentType.isPrimitive()) { + if (int.class == componentType) { + assertArrayEquals(message, (int[]) left, (int[]) right); + } + else if (short.class == componentType) { + assertArrayEquals(message, (short[]) left, (short[]) right); + } + else if (long.class == componentType) { + assertArrayEquals(message, (long[]) left, (long[]) right); + } + else if (float.class == componentType) { + assertArrayEquals(message, (float[]) left, (float[]) right, 0f); + } + else if (double.class == componentType) { + assertArrayEquals(message, (double[]) left, (double[]) right, 0d); + } + else if (boolean.class == componentType) { + assertTrue(message, Arrays.equals((boolean[]) left, (boolean[]) right)); + } + else if (byte.class == componentType) { + assertArrayEquals(message, (byte[]) left, (byte[]) right); + } + else if (char.class == componentType) { + assertArrayEquals(message, (char[]) left, (char[]) right); + } + else { + fail(String.format("Unknown primitive type: %s", componentType)); + } + } + else { + assertArrayEquals(message, (Object[]) left, (Object[]) right); + } + } + + private static void failBecause(String message, Throwable exception) { + AssertionError error = new AssertionError(message); + error.initCause(exception); + throw error; + } + + public static final class Conversion { + private final String strVal; + private final Object objVal; + private final String format; + private final String convertedStrVal; + + public Conversion(String pStrVal, Object pObjVal) { + this(pStrVal, pObjVal, null); + } + + public Conversion(String pStrVal, Object pObjVal, String pFormat) { + this(pStrVal, pObjVal, pFormat, pStrVal); + } + + public Conversion(String pStrVal, Object pObjVal, String pFormat, String pConvertedStrVal) { + Validate.notNull(pStrVal, "strVal"); + Validate.notNull(pObjVal, "objVal"); + Validate.notNull(pConvertedStrVal, "convertedStrVal"); + + strVal = pStrVal; + objVal = pObjVal; + format = pFormat; + convertedStrVal = pConvertedStrVal; + } + + public String original() { + return strVal; + } + + public Object value() { + return objVal; + } + + public Class type() { + return objVal.getClass(); + } + + public String format() { + return format; + } + + public String converted() { + return convertedStrVal; + } + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTest.java new file mode 100755 index 00000000..6c770cc5 --- /dev/null +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTest.java @@ -0,0 +1,49 @@ +/* + * 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.util.convert; + +/** + * TimeConverterTest + *

+ * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTestCase.java#1 $ + */ +public class TimeConverterTest extends PropertyConverterAbstractTest { + protected PropertyConverter makePropertyConverter() { + return new TimeConverter(); + } + + protected Conversion[] getTestConversions() { + return new Conversion[0];// TODO: Implement + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTestCase.java deleted file mode 100755 index f71898b6..00000000 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTestCase.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.twelvemonkeys.util.convert; - -/** - * TimeConverterTestCase - *

- * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTestCase.java#1 $ - */ -public class TimeConverterTestCase extends PropertyConverterAbstractTestCase { - protected PropertyConverter makePropertyConverter() { - return new TimeConverter(); - } - - protected Conversion[] getTestConversions() { - return new Conversion[0];// TODO: Implement - } -} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTest.java similarity index 71% rename from common/common-lang/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTestCase.java rename to common/common-lang/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTest.java index 69517f60..4a7d2f6b 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTest.java @@ -1,122 +1,153 @@ -package com.twelvemonkeys.util.regex; - -import com.twelvemonkeys.util.TokenIterator; -import com.twelvemonkeys.util.TokenIteratorAbstractTestCase; - -import java.util.Iterator; - -/** - * StringTokenIteratorTestCase - *

- * - * - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTestCase.java#1 $ - */ -public class RegExTokenIteratorTestCase extends TokenIteratorAbstractTestCase { - public void setUp() throws Exception { - super.setUp(); - } - - public void tearDown() throws Exception { - super.tearDown(); - } - - protected TokenIterator createTokenIterator(String pString) { - return new RegExTokenIterator(pString); - } - - protected TokenIterator createTokenIterator(String pString, String pDelimiters) { - return new RegExTokenIterator(pString, pDelimiters); - } - - public void testEmptyDelimiter() { - // TODO: What is it supposed to match? - /* - Iterator iterator = createTokenIterator("", ".*"); - assertTrue("Empty string has no elements", iterator.hasNext()); - iterator.next(); - assertFalse("Empty string has more then one element", iterator.hasNext()); - */ - } - - public void testSingleToken() { - Iterator iterator = createTokenIterator("A"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleTokenEmptyDelimiter() { - // TODO: What is it supposed to match? - /* - Iterator iterator = createTokenIterator("A", ".*"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - */ - } - - public void testSingleTokenSingleDelimiter() { - Iterator iterator = createTokenIterator("A", "[^,]+"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleSeparatorDefaultDelimiter() { - Iterator iterator = createTokenIterator("A B C D"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("D", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testSingleSeparator() { - Iterator iterator = createTokenIterator("A,B,C", "[^,]+"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testMultipleSeparatorDefaultDelimiter() { - Iterator iterator = createTokenIterator("A B C\nD\t\t \nE"); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("A", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("D", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("E", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } - - public void testMultipleSeparator() { - Iterator iterator = createTokenIterator("A,B,;,C...D, ., ,E", "[^ ,.;:]+"); - assertTrue("String has no elements", iterator.hasNext()); - Object o = iterator.next(); - assertEquals("A", o); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("B", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("C", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("D", iterator.next()); - assertTrue("String has no elements", iterator.hasNext()); - assertEquals("E", iterator.next()); - assertFalse("String has more than one element", iterator.hasNext()); - } -} +/* + * 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.util.regex; + +import com.twelvemonkeys.util.TokenIterator; +import com.twelvemonkeys.util.TokenIteratorAbstractTest; +import org.junit.Test; + +import java.util.Iterator; + +import static org.junit.Assert.*; + +/** + * StringTokenIteratorTestCase + *

+ * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTestCase.java#1 $ + */ +public class RegExTokenIteratorTest extends TokenIteratorAbstractTest { + + protected TokenIterator createTokenIterator(String pString) { + return new RegExTokenIterator(pString); + } + + protected TokenIterator createTokenIterator(String pString, String pDelimiters) { + return new RegExTokenIterator(pString, pDelimiters); + } + + @Test + public void testEmptyDelimiter() { + // TODO: What is it supposed to match? + /* + Iterator iterator = createTokenIterator("", ".*"); + assertTrue("Empty string has no elements", iterator.hasNext()); + iterator.next(); + assertFalse("Empty string has more then one element", iterator.hasNext()); + */ + } + + @Test + public void testSingleToken() { + Iterator iterator = createTokenIterator("A"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleTokenEmptyDelimiter() { + // TODO: What is it supposed to match? + /* + Iterator iterator = createTokenIterator("A", ".*"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + */ + } + + @Test + public void testSingleTokenSingleDelimiter() { + Iterator iterator = createTokenIterator("A", "[^,]+"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleSeparatorDefaultDelimiter() { + Iterator iterator = createTokenIterator("A B C D"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("D", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testSingleSeparator() { + Iterator iterator = createTokenIterator("A,B,C", "[^,]+"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testMultipleSeparatorDefaultDelimiter() { + Iterator iterator = createTokenIterator("A B C\nD\t\t \nE"); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("A", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("D", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("E", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } + + @Test + public void testMultipleSeparator() { + Iterator iterator = createTokenIterator("A,B,;,C...D, ., ,E", "[^ ,.;:]+"); + assertTrue("String has no elements", iterator.hasNext()); + Object o = iterator.next(); + assertEquals("A", o); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("B", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("C", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("D", iterator.next()); + assertTrue("String has no elements", iterator.hasNext()); + assertEquals("E", iterator.next()); + assertFalse("String has more than one element", iterator.hasNext()); + } +} diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPI.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPI.java index aa078a3e..9b450d83 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPI.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPI.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.service; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIImpl.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIImpl.java index 8ae8b908..6ce0a476 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIImpl.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIImpl.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.service; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIToo.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIToo.java index 4d2f6c73..6ae0c4eb 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIToo.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/DummySPIToo.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.service; diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/ServiceRegistryTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/ServiceRegistryTest.java index fe7b8f32..8f046cbb 100644 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/service/ServiceRegistryTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/service/ServiceRegistryTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util.service; diff --git a/common/pom.xml b/common/pom.xml index eb530041..58ff27d8 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys twelvemonkeys - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT com.twelvemonkeys.common common @@ -47,7 +47,7 @@ junit junit - 4.7 + 4.13.1 test diff --git a/contrib/pom.xml b/contrib/pom.xml index f50aac23..2f9daefd 100644 --- a/contrib/pom.xml +++ b/contrib/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys twelvemonkeys - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT com.twelvemonkeys.contrib contrib @@ -65,7 +65,7 @@ junit junit - 4.7 + 4.13.1 test diff --git a/contrib/src/main/java/com/twelvemonkeys/contrib/exif/EXIFUtilities.java b/contrib/src/main/java/com/twelvemonkeys/contrib/exif/EXIFUtilities.java new file mode 100644 index 00000000..5a23a43b --- /dev/null +++ b/contrib/src/main/java/com/twelvemonkeys/contrib/exif/EXIFUtilities.java @@ -0,0 +1,158 @@ +package com.twelvemonkeys.contrib.exif; + +import com.twelvemonkeys.image.ImageUtil; +import com.twelvemonkeys.imageio.ImageReaderBase; + +import org.w3c.dom.NodeList; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Iterator; + +import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation; + +/** + * EXIFUtilities. + * + * @author Harald Kuhr + * @version : EXIFUtilities.java,v 1.0 23/06/2020 + */ +public class EXIFUtilities { + /** + * Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}. + * + * @param input a {@code URL} + * @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info. + * @throws IOException if an error occurs during reading. + */ + public static IIOImage readWithOrientation(final URL input) throws IOException { + try (ImageInputStream stream = ImageIO.createImageInputStream(input)) { + return readWithOrientation(stream); + } + } + + /** + * Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}. + * + * @param input an {@code InputStream} + * @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info. + * @throws IOException if an error occurs during reading. + */ + public static IIOImage readWithOrientation(final InputStream input) throws IOException { + try (ImageInputStream stream = ImageIO.createImageInputStream(input)) { + return readWithOrientation(stream); + } + } + + /** + * Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}. + * + * @param input a {@code File} + * @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info. + * @throws IOException if an error occurs during reading. + */ + public static IIOImage readWithOrientation(final File input) throws IOException { + try (ImageInputStream stream = ImageIO.createImageInputStream(input)) { + return readWithOrientation(stream); + } + } + + /** + * Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}. + * + * @param input an {@code ImageInputStream} + * @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info. + * @throws IOException if an error occurs during reading. + */ + public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException { + Iterator readers = ImageIO.getImageReaders(input); + if (!readers.hasNext()) { + return null; + } + + ImageReader reader = readers.next(); + try { + reader.setInput(input, true, false); + + IIOMetadata metadata = reader.getImageMetadata(0); + BufferedImage bufferedImage = applyOrientation(reader.read(0), findImageOrientation(metadata).value()); + + return new IIOImage(bufferedImage, null, metadata); + } + finally { + reader.dispose(); + } + } + + /** + * Finds the {@code ImageOrientation} tag, if any, and returns an {@link Orientation} based on its + * {@code value} attribute. + * If no match is found or the tag is not present, {@code Normal} (the default orientation) is returned. + * + * @param metadata an {@code IIOMetadata} object + * @return the {@code Orientation} matching the {@code value} attribute of the {@code ImageOrientation} tag, + * or {@code Normal}, never {@code null}. + * @see Orientation + * @see Standard (Plug-in Neutral) Metadata Format Specification + */ + public static Orientation findImageOrientation(final IIOMetadata metadata) { + if (metadata != null) { + IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + NodeList imageOrientations = root.getElementsByTagName("ImageOrientation"); + + if (imageOrientations != null && imageOrientations.getLength() > 0) { + IIOMetadataNode imageOrientation = (IIOMetadataNode) imageOrientations.item(0); + return Orientation.fromMetadataOrientation(imageOrientation.getAttribute("value")); + } + } + + return Orientation.Normal; + } + + public static void main(String[] args) throws IOException { + for (String arg : args) { + File input = new File(arg); + + // Read everything but thumbnails (similar to ImageReader.readAll(0, null)), + // and applies the correct image orientation + IIOImage image = readWithOrientation(input); + + if (image == null) { + System.err.printf("No reader for %s%n", input); + continue; + } + + // Finds the orientation as defined by the javax_imageio_1.0 format + Orientation orientation = findImageOrientation(image.getMetadata()); + + // Retrieve the image as a BufferedImage. The image is already rotated by the readWithOrientation method + // In this case it will already be a BufferedImage, so using a cast will also do + // (i.e.: BufferedImage bufferedImage = (BufferedImage) image.getRenderedImage()) + BufferedImage bufferedImage = ImageUtil.toBuffered(image.getRenderedImage()); + + // Demo purpose only, show image with orientation details in title + DisplayHelper.showIt(bufferedImage, input.getName() + ": " + orientation.name() + "/" + orientation.value()); + } + } + + // Don't do this... :-) Provided for convenience/demo only! + static abstract class DisplayHelper extends ImageReaderBase { + private DisplayHelper() { + super(null); + } + + protected static void showIt(BufferedImage image, String title) { + ImageReaderBase.showIt(image, title); + } + } +} diff --git a/contrib/src/main/java/com/twelvemonkeys/contrib/exif/Orientation.java b/contrib/src/main/java/com/twelvemonkeys/contrib/exif/Orientation.java new file mode 100644 index 00000000..3340147f --- /dev/null +++ b/contrib/src/main/java/com/twelvemonkeys/contrib/exif/Orientation.java @@ -0,0 +1,63 @@ +package com.twelvemonkeys.contrib.exif; + +import com.twelvemonkeys.contrib.tiff.TIFFUtilities; + +/** + * Orientation. + * + * @author Harald Kuhr + * @version : Orientation.java,v 1.0 10/07/2020 harald.kuhr + */ +public enum Orientation { + Normal(TIFFUtilities.TIFFBaseline.ORIENTATION_TOPLEFT), + FlipH(TIFFUtilities.TIFFExtension.ORIENTATION_TOPRIGHT), + Rotate180(TIFFUtilities.TIFFExtension.ORIENTATION_BOTRIGHT), + FlipV(TIFFUtilities.TIFFExtension.ORIENTATION_BOTLEFT), + FlipVRotate90(TIFFUtilities.TIFFExtension.ORIENTATION_LEFTTOP), + Rotate270(TIFFUtilities.TIFFExtension.ORIENTATION_RIGHTTOP), + FlipHRotate90(TIFFUtilities.TIFFExtension.ORIENTATION_RIGHTBOT), + Rotate90(TIFFUtilities.TIFFExtension.ORIENTATION_LEFTBOT); + + // name as defined in javax.imageio metadata + private final int value; // value as defined in TIFF spec + + Orientation(int value) { + this.value = value; + } + + public int value() { + return value; + } + + public static Orientation fromMetadataOrientation(final String orientationName) { + if (orientationName != null) { + try { + return valueOf(orientationName); + } + catch (IllegalArgumentException e) { + // Not found, try ignore case match, as some metadata implementations are known to return "normal" etc. + String lowerCaseName = orientationName.toLowerCase(); + + for (Orientation orientation : values()) { + if (orientation.name().toLowerCase().equals(lowerCaseName)) { + return orientation; + } + } + } + } + + // Metadata does not have other orientations, default to Normal + return Normal; + } + + public static Orientation fromTIFFOrientation(final int tiffOrientation) { + for (Orientation orientation : values()) { + if (orientation.value() == tiffOrientation) { + return orientation; + } + } + + // No other TIFF orientations possible, default to Normal + return Normal; + } +} diff --git a/contrib/src/main/java/com/twelvemonkeys/contrib/tiff/TIFFUtilities.java b/contrib/src/main/java/com/twelvemonkeys/contrib/tiff/TIFFUtilities.java index 88b12a58..8dae71af 100644 --- a/contrib/src/main/java/com/twelvemonkeys/contrib/tiff/TIFFUtilities.java +++ b/contrib/src/main/java/com/twelvemonkeys/contrib/tiff/TIFFUtilities.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2013, Harald Kuhr + * Copyright (c) 2013, Oliver Schmidtmer, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.contrib.tiff; @@ -34,11 +36,7 @@ import com.twelvemonkeys.imageio.metadata.CompoundDirectory; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; -import com.twelvemonkeys.imageio.metadata.tiff.IFD; -import com.twelvemonkeys.imageio.metadata.tiff.TIFF; -import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry; -import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; -import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter; +import com.twelvemonkeys.imageio.metadata.tiff.*; import com.twelvemonkeys.lang.Validate; import javax.imageio.IIOException; @@ -47,11 +45,9 @@ import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -204,11 +200,10 @@ public final class TIFFUtilities { } public static List getPages(ImageInputStream imageInput) throws IOException { - ArrayList pages = new ArrayList(); - CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(imageInput); - int pageCount = IFDs.directoryCount(); + final int pageCount = IFDs.directoryCount(); + List pages = new ArrayList<>(pageCount); for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput)); } @@ -360,13 +355,21 @@ public final class TIFFUtilities { } } + int compression = -1; + Entry compressionEntry = IFD.getEntryById(TIFF.TAG_COMPRESSION); + if (compressionEntry != null && compressionEntry.getValue() instanceof Number) { + compression = ((Number) compressionEntry.getValue()).shortValue(); + } + boolean rearrangedByteStrips = false; Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT); Entry oldJpegDataLength = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + long[] jpegByteCounts = null; + long[] jpegOffsets = null; if (oldJpegData != null && oldJpegData.valueCount() > 0) { // convert JPEGInterchangeFormat to new-style-JPEG - long[] jpegByteCounts = new long[0]; - long[] jpegOffsets = getValueAsLongArray(oldJpegData); + jpegByteCounts = new long[0]; + jpegOffsets = getValueAsLongArray(oldJpegData); if (oldJpegDataLength != null && oldJpegDataLength.valueCount() > 0) { jpegByteCounts = getValueAsLongArray(oldJpegDataLength); } @@ -390,6 +393,20 @@ public final class TIFFUtilities { newIFD.remove(oldJpegDataLength); rearrangedByteStrips = true; } + else if (offsets.length == 1 && oldJpegDataLength != null && (jpegOffsets[0] < offsets[0]) && (jpegOffsets[0] + jpegByteCounts[0]) > (offsets[0] + byteCounts[0])) { + + // ByteStrip contains only a part of JPEGInterchangeFormat + newOffsets = writeData(jpegOffsets, jpegByteCounts, outputStream); + + newIFD.remove(stripOffsetsEntry); + newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_OFFSETS : TIFF.TAG_STRIP_OFFSETS, newOffsets)); + newIFD.remove(stripByteCountsEntry); + newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_BYTE_COUNTS : TIFF.TAG_STRIP_BYTE_COUNTS, new int[]{(int) (jpegByteCounts[0])})); + + newIFD.remove(oldJpegData); + newIFD.remove(oldJpegDataLength); + rearrangedByteStrips = true; + } else if (oldJpegDataLength != null) { // multiple bytestrips // search for SOF on first strip and copy to each if needed @@ -425,7 +442,7 @@ public final class TIFFUtilities { byte[] buffer = new byte[(int) byteCounts[i]]; newByteCounts[i] = (int) (jpegInterchangeData.length + byteCounts[i]); stream.readFully(buffer); - if (buffer[0] != 0xff && buffer[1] != 0xda) { + if (buffer[0] != ((byte) 0xff) || buffer[1] != ((byte) 0xda)) { outputStream.write(sosMarker); newByteCounts[i] += sosMarker.length; } @@ -442,7 +459,58 @@ public final class TIFFUtilities { rearrangedByteStrips = true; } } + else if (compression == TIFFExtension.COMPRESSION_OLD_JPEG) { + // old-style but no JPEGInterchangeFormat + long[] yCbCrSubSampling = getValueAsLongArray(IFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING)); + int subsampling = yCbCrSubSampling != null + ? (int) ((yCbCrSubSampling[0] & 0xf) << 4 | yCbCrSubSampling[1] & 0xf) + : 0x22; + int bands = ((Number) IFD.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL).getValue()).intValue(); + + int w = ((Number) IFD.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue()).intValue(); + int h = ((Number) IFD.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue()).intValue(); + + int r = ((Number) (useTiles ? IFD.getEntryById(TIFF.TAG_TILE_HEIGTH) : IFD.getEntryById(TIFF.TAG_ROWS_PER_STRIP)).getValue()).intValue(); + int c = useTiles ? ((Number) IFD.getEntryById(TIFF.TAG_TILE_WIDTH).getValue()).intValue() : w; + + newOffsets = new int[offsets.length]; + int[] newByteCounts = new int[byteCounts.length]; + + // No JPEGInterchangeFormat + for (int i = 0; i < offsets.length; i++) { + byte[] start = new byte[2]; + stream.seek(offsets[i]); + stream.readFully(start); + newOffsets[i] = (int) outputStream.getStreamPosition(); + if (start[0] == ((byte) 0xff) && start[1] == ((byte) 0xd8)) { + // full image stream, nothing to do + writeData(stream, outputStream, offsets[i], byteCounts[i]); + } + else if (start[0] == ((byte) 0xff) && start[1] == ((byte) 0xda)) { + // starts with SOS + outputStream.writeShort(JPEG.SOI); + writeSOF0(outputStream, bands, c, r, subsampling); + writeData(stream, outputStream, offsets[i], byteCounts[i]); + outputStream.writeShort(JPEG.EOI); + } + else { + // raw data + outputStream.writeShort(JPEG.SOI); + writeSOF0(outputStream, bands, c, r, subsampling); + writeSOS(outputStream, bands); + writeData(stream, outputStream, offsets[i], byteCounts[i]); + outputStream.writeShort(JPEG.EOI); + } + newByteCounts[i] = ((int) outputStream.getStreamPosition()) - newOffsets[i]; + } + + newIFD.remove(stripOffsetsEntry); + newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_OFFSETS : TIFF.TAG_STRIP_OFFSETS, newOffsets)); + newIFD.remove(stripByteCountsEntry); + newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_BYTE_COUNTS : TIFF.TAG_STRIP_BYTE_COUNTS, newByteCounts)); + rearrangedByteStrips = true; + } if (!rearrangedByteStrips && stripOffsetsEntry != null && stripByteCountsEntry != null) { newOffsets = writeData(offsets, byteCounts, outputStream); @@ -459,12 +527,21 @@ public final class TIFFUtilities { oldJpegTableQ = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES); oldJpegTableDC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES); oldJpegTableAC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES); - if (oldJpegTableQ != null || oldJpegTableDC != null || oldJpegTableAC != null) { + if ((oldJpegTableQ != null) || (oldJpegTableDC != null) || (oldJpegTableAC != null)) { if (IFD.getEntryById(TIFF.TAG_JPEG_TABLES) != null) { throw new IOException("Found old-style and new-style JPEGTables"); } - newIFD.add(mergeTables(oldJpegTableQ, oldJpegTableDC, oldJpegTableAC)); + boolean tablesInStream = jfifContainsTables(oldJpegTableQ, jpegOffsets, jpegByteCounts); + tablesInStream &= jfifContainsTables(oldJpegTableDC, jpegOffsets, jpegByteCounts); + tablesInStream &= jfifContainsTables(oldJpegTableAC, jpegOffsets, jpegByteCounts); + if (!tablesInStream) { + // merge them only to JPEGTables if they are not already contained within the stream + Entry jpegTables = mergeTables(oldJpegTableQ, oldJpegTableDC, oldJpegTableAC); + if (jpegTables != null) { + newIFD.add(jpegTables); + } + } if (oldJpegTableQ != null) { newIFD.remove(oldJpegTableQ); } @@ -476,16 +553,66 @@ public final class TIFFUtilities { } } - Entry compressionEntry = IFD.getEntryById(TIFF.TAG_COMPRESSION); - Number compression = (Number) compressionEntry.getValue(); - if (compression.shortValue() == TIFFExtension.COMPRESSION_OLD_JPEG) { + if (compressionEntry != null && compression == TIFFExtension.COMPRESSION_OLD_JPEG) { newIFD.remove(compressionEntry); newIFD.add(new TIFFEntry(TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, TIFFExtension.COMPRESSION_JPEG)); } - return newIFD; } + //TODO merge/extract from TIFFReader Jpeg/6 stream reconstruction + private void writeSOF0(ImageOutputStream outputStream, int bands, int width, int height, int subsampling) throws IOException { + outputStream.writeShort(JPEG.SOF0); // TODO: Use correct process for data + outputStream.writeShort(2 + 6 + 3 * bands); // SOF0 len + outputStream.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support + outputStream.writeShort(height); // height + outputStream.writeShort(width); // width + outputStream.writeByte(bands); // Number of components + + for (int comp = 0; comp < bands; comp++) { + outputStream.writeByte(comp); // Component id + outputStream.writeByte(comp == 0 ? subsampling : 0x11); // h/v subsampling + outputStream.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal, correct selection if only 1 or 2 valid tables are contained + } + } + + //TODO merge/extract from TIFFReader Jpeg/6 stream reconstruction + private void writeSOS(ImageOutputStream outputStream, int bands) throws IOException { + outputStream.writeShort(JPEG.SOS); + outputStream.writeShort(6 + 2 * bands); // SOS length + outputStream.writeByte(bands); // Num comp + + for (int component = 0; component < bands; component++) { + outputStream.writeByte(component); // Comp id + outputStream.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector TODO: correct selection if only 1 or 2 valid tables are contained + } + + outputStream.writeByte(0); // Spectral selection start + outputStream.writeByte(0); // Spectral selection end + outputStream.writeByte(0); // Approx high & low + } + + private void writeData(ImageInputStream input, ImageOutputStream output, long offset, long length) throws IOException { + input.seek(offset); + byte[] buffer = new byte[(int) length]; + stream.readFully(buffer); + output.write(buffer); + } + + private boolean jfifContainsTables(Entry tableEntry, long[] jpegOffsets, long[] jpegLengths) throws IOException { + if (jpegLengths == null || jpegOffsets == null || jpegLengths.length == 0) return false; + if (tableEntry != null) { + long[] tableOffsets = getValueAsLongArray(tableEntry); + for (long offset : tableOffsets) { + if (offset < jpegOffsets[0] || offset > (jpegOffsets[0] + jpegLengths[0])) { + return false; + } + } + } + return true; + } + + //TODO merge/extract from TIFFReader Jpeg/6 stream reconstruction private Entry mergeTables(Entry qEntry, Entry dcEntry, Entry acEntry) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); @@ -495,12 +622,16 @@ public final class TIFFUtilities { long[] off = getValueAsLongArray(qEntry); byte[] table = new byte[64]; for (int tableId = 0; tableId < off.length; tableId++) { - stream.seek(off[tableId]); - stream.readFully(table); - dos.writeShort(JPEG.DQT); - dos.writeShort(3 + 64); - dos.writeByte(tableId); - dos.write(table); + try { + stream.seek(off[tableId]); + stream.readFully(table); + dos.writeShort(JPEG.DQT); + dos.writeShort(3 + 64); + dos.writeByte(tableId); + dos.write(table); + } catch (EOFException e) { + // invalid table pointer, ignore + } } } @@ -508,30 +639,50 @@ public final class TIFFUtilities { if (dcEntry != null && dcEntry.valueCount() > 0) { long[] off = getValueAsLongArray(dcEntry); for (int tableId = 0; tableId < off.length; tableId++) { - stream.seek(off[tableId]); - byte[] table = readHUFFTable(); - dos.writeShort(JPEG.DHT); - dos.writeShort(3 + table.length); - dos.writeByte(tableId); - dos.write(table); + try { + stream.seek(off[tableId]); + byte[] table = readHUFFTable(); + if (table.length > (16 + 17)) { + // to long, table is invalid, just ignoe + continue; + } + dos.writeShort(JPEG.DHT); + dos.writeShort(3 + table.length); + dos.writeByte(tableId); + dos.write(table); + } catch (EOFException e) { + // invalid table pointer, ignore + } } } if (acEntry != null && acEntry.valueCount() > 0) { long[] off = getValueAsLongArray(acEntry); for (int tableId = 0; tableId < off.length; tableId++) { - stream.seek(off[tableId]); - byte[] table = readHUFFTable(); - dos.writeShort(JPEG.DHT); - dos.writeShort(3 + table.length); - dos.writeByte(16 | tableId); - dos.write(table); + try { + stream.seek(off[tableId]); + byte[] table = readHUFFTable(); + if (table.length > (16 + 256)) { + // to long, table is invalid, just ignoe + continue; + } + dos.writeShort(JPEG.DHT); + dos.writeShort(3 + table.length); + dos.writeByte(16 | tableId); + dos.write(table); + } catch (EOFException e) { + // invalid table pointer, ignore + } } } dos.writeShort(JPEG.EOI); bos.close(); + if (bos.size() == 4) { + // no valid tables, don't add + return null; + } return new TIFFEntry(TIFF.TAG_JPEG_TABLES, TIFF.TYPE_UNDEFINED, bos.toByteArray()); } @@ -541,15 +692,11 @@ public final class TIFFUtilities { stream.readFully(lengths); int numCodes = 0; for (int i = 0; i < lengths.length; i++) { - numCodes += lengths[i]; + numCodes += ((int) lengths[i]) & 0xff; } byte table[] = new byte[16 + numCodes]; System.arraycopy(lengths, 0, table, 0, 16); - int off = 16; - for (int i = 0; i < lengths.length; i++) { - stream.read(table, off, lengths[i]); - off += lengths[i]; - } + stream.readFully(table, 16, numCodes); return table; } @@ -560,7 +707,11 @@ public final class TIFFUtilities { stream.seek(offsets[i]); byte[] buffer = new byte[(int) byteCounts[i]]; - stream.readFully(buffer); + try { + stream.readFully(buffer); + } catch (EOFException e) { + // invalid strip length + } outputStream.write(buffer); } return newOffsets; @@ -572,7 +723,7 @@ public final class TIFFUtilities { if (entry.valueCount() == 1) { // For single entries, this will be a boxed type - value = new long[] {((Number) entry.getValue()).longValue()}; + value = new long[]{((Number) entry.getValue()).longValue()}; } else if (entry.getValue() instanceof short[]) { short[] shorts = (short[]) entry.getValue(); diff --git a/contrib/src/test/java/com/twelvemonkeys/contrib/exif/OrientationTest.java b/contrib/src/test/java/com/twelvemonkeys/contrib/exif/OrientationTest.java new file mode 100644 index 00000000..956e8cb1 --- /dev/null +++ b/contrib/src/test/java/com/twelvemonkeys/contrib/exif/OrientationTest.java @@ -0,0 +1,75 @@ +package com.twelvemonkeys.contrib.exif; + +import org.junit.Test; + +import static com.twelvemonkeys.contrib.exif.Orientation.*; +import static org.junit.Assert.assertEquals; + +/** + * OrientationTest. + * + * @author Harald Kuhr + * @author last modified by : harald.kuhr$ + * @version : OrientationTest.java,v 1.0 10/07/2020 harald.kuhr Exp$ + */ +public class OrientationTest { + @Test + public void testFromMetadataOrientationNull() { + assertEquals(Normal, Orientation.fromMetadataOrientation(null)); + } + + @Test + public void testFromMetadataOrientation() { + assertEquals(Normal, Orientation.fromMetadataOrientation("Normal")); + assertEquals(Rotate90, Orientation.fromMetadataOrientation("Rotate90")); + assertEquals(Rotate180, Orientation.fromMetadataOrientation("Rotate180")); + assertEquals(Rotate270, Orientation.fromMetadataOrientation("Rotate270")); + assertEquals(FlipH, Orientation.fromMetadataOrientation("FlipH")); + assertEquals(FlipV, Orientation.fromMetadataOrientation("FlipV")); + assertEquals(FlipHRotate90, Orientation.fromMetadataOrientation("FlipHRotate90")); + assertEquals(FlipVRotate90, Orientation.fromMetadataOrientation("FlipVRotate90")); + } + + @Test + public void testFromMetadataOrientationIgnoreCase() { + assertEquals(Normal, Orientation.fromMetadataOrientation("normal")); + assertEquals(Rotate90, Orientation.fromMetadataOrientation("rotate90")); + assertEquals(Rotate180, Orientation.fromMetadataOrientation("ROTATE180")); + assertEquals(Rotate270, Orientation.fromMetadataOrientation("ROTATE270")); + assertEquals(FlipH, Orientation.fromMetadataOrientation("FLIPH")); + assertEquals(FlipV, Orientation.fromMetadataOrientation("flipv")); + assertEquals(FlipHRotate90, Orientation.fromMetadataOrientation("FLIPhrotate90")); + assertEquals(FlipVRotate90, Orientation.fromMetadataOrientation("fLiPVRotAte90")); + } + + @Test + public void testFromMetadataOrientationUnknown() { + assertEquals(Normal, Orientation.fromMetadataOrientation("foo")); + assertEquals(Normal, Orientation.fromMetadataOrientation("90")); + assertEquals(Normal, Orientation.fromMetadataOrientation("randomStringWithNumbers180")); + } + + @Test + public void testFromTIFFOrientation() { + assertEquals(Normal, Orientation.fromTIFFOrientation(1)); + assertEquals(FlipH, Orientation.fromTIFFOrientation(2)); + assertEquals(Rotate180, Orientation.fromTIFFOrientation(3)); + assertEquals(FlipV, Orientation.fromTIFFOrientation(4)); + assertEquals(FlipVRotate90, Orientation.fromTIFFOrientation(5)); + assertEquals(Rotate270, Orientation.fromTIFFOrientation(6)); + assertEquals(FlipHRotate90, Orientation.fromTIFFOrientation(7)); + assertEquals(Rotate90, Orientation.fromTIFFOrientation(8)); + } + + @Test + public void testFromTIFFOrientationUnknown() { + assertEquals(Normal, Orientation.fromTIFFOrientation(-1)); + assertEquals(Normal, Orientation.fromTIFFOrientation(0)); + assertEquals(Normal, Orientation.fromTIFFOrientation(9)); + for (int i = 10; i < 1024; i++) { + assertEquals(Normal, Orientation.fromTIFFOrientation(i)); + } + assertEquals(Normal, Orientation.fromTIFFOrientation(Integer.MAX_VALUE)); + assertEquals(Normal, Orientation.fromTIFFOrientation(Integer.MIN_VALUE)); + } +} \ No newline at end of file diff --git a/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java b/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java index 1b4534e9..c9a81252 100644 --- a/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java +++ b/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2013, Harald Kuhr + * Copyright (c) 2013, Oliver Schmidtmer, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.contrib.tiff; @@ -200,39 +202,70 @@ public class TIFFUtilitiesTest { } @Test - public void testMergeBogusInterchangeFormatLength() throws IOException { - String[] testFiles = new String[] { + public void testOldStyleJPEGTransform() throws IOException { + String[] testFiles = new String[]{ "/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif", // InterchangeFormat before StripOffset, length not including StripOffset "/tiff/old-style-jpeg-no-jpeginterchangeformatlength.tif", // missing JPEGInterChangeFormatLength and JPEGInterchangeFormat == StipOffset - "/tiff/old-style-jpeg-multiple-strips.tif" // InterchangeFormat with multiple strips + "/tiff/old-style-jpeg-multiple-strips.tif", // InterchangeFormat with multiple strips + "/contrib/tiff/old-style-jpeg-invalid-tables.tif", // AC/DC Tables are invalid (to long) and lie within the JPEGInterchangeFormat stream + "/contrib/tiff/smallliz.tif", // InterchangeFormat contains whole JPEG, ByteStrip only raw ImageData after SOS + "/contrib/tiff/WangJPEG.tif", // multiple strips, first strip contains SOS + "/contrib/tiff/zackthecat.tif" // No JPEGInterchangeFormat, ByteStrip contains only raw image data }; for (String testFile : testFiles) { - File output = File.createTempFile("imageiotest", ".tif"); - ImageOutputStream outputStream = ImageIO.createImageOutputStream(output); - InputStream inputStream1 = getClassLoaderResource(testFile).openStream(); - ImageInputStream imageInput1 = ImageIO.createImageInputStream(inputStream1); - InputStream inputStream2 = getClassLoaderResource(testFile).openStream(); - ImageInputStream imageInput2 = ImageIO.createImageInputStream(inputStream2); - ArrayList pages = new ArrayList<>(); - pages.addAll(TIFFUtilities.getPages(imageInput1)); - pages.addAll(TIFFUtilities.getPages(imageInput2)); - TIFFUtilities.writePages(outputStream, pages); + try { + File output = File.createTempFile("imageiotest", ".tif"); + ImageOutputStream outputStream = ImageIO.createImageOutputStream(output); + InputStream inputStream = getClassLoaderResource(testFile).openStream(); + ImageInputStream imageInput = ImageIO.createImageInputStream(inputStream); + List pages = TIFFUtilities.getPages(imageInput); + TIFFUtilities.writePages(outputStream, pages); - ImageInputStream testOutput = ImageIO.createImageInputStream(output); - ImageReader reader = ImageIO.getImageReaders(testOutput).next(); - reader.setInput(testOutput); - int numImages = reader.getNumImages(true); - for (int i = 0; i < numImages; i++) { - reader.read(i); + ImageInputStream testOutput = ImageIO.createImageInputStream(output); + ImageReader reader = ImageIO.getImageReaders(testOutput).next(); + reader.setInput(testOutput); + int numImages = reader.getNumImages(true); + for (int i = 0; i < numImages; i++) { + reader.read(i); + } + + imageInput.close(); + outputStream.close(); + } catch (Exception exc) { + throw new IOException(testFile, exc); } - - imageInput1.close(); - imageInput2.close(); - outputStream.close(); } } + @Test + public void testMergeWithSubIFD() throws IOException { + String testFile = "/tiff/cmyk_jpeg.tif"; + + File output = File.createTempFile("imageiotest", ".tif"); + ImageOutputStream outputStream = ImageIO.createImageOutputStream(output); + InputStream inputStream1 = getClassLoaderResource(testFile).openStream(); + ImageInputStream imageInput1 = ImageIO.createImageInputStream(inputStream1); + InputStream inputStream2 = getClassLoaderResource(testFile).openStream(); + ImageInputStream imageInput2 = ImageIO.createImageInputStream(inputStream2); + ArrayList pages = new ArrayList<>(); + pages.addAll(TIFFUtilities.getPages(imageInput1)); + pages.addAll(TIFFUtilities.getPages(imageInput2)); + TIFFUtilities.writePages(outputStream, pages); + + ImageInputStream testOutput = ImageIO.createImageInputStream(output); + ImageReader reader = ImageIO.getImageReaders(testOutput).next(); + reader.setInput(testOutput); + int numImages = reader.getNumImages(true); + for (int i = 0; i < numImages; i++) { + reader.read(i); + } + + imageInput1.close(); + imageInput2.close(); + outputStream.close(); + } + protected URL getClassLoaderResource(final String pName) { return getClass().getResource(pName); } diff --git a/contrib/src/test/resources/contrib/tiff/WangJPEG.tif b/contrib/src/test/resources/contrib/tiff/WangJPEG.tif new file mode 100644 index 00000000..d1a98ede Binary files /dev/null and b/contrib/src/test/resources/contrib/tiff/WangJPEG.tif differ diff --git a/contrib/src/test/resources/contrib/tiff/old-style-jpeg-invalid-tables.tif b/contrib/src/test/resources/contrib/tiff/old-style-jpeg-invalid-tables.tif new file mode 100644 index 00000000..fdf51209 Binary files /dev/null and b/contrib/src/test/resources/contrib/tiff/old-style-jpeg-invalid-tables.tif differ diff --git a/contrib/src/test/resources/contrib/tiff/smallliz.tif b/contrib/src/test/resources/contrib/tiff/smallliz.tif new file mode 100644 index 00000000..ee9dbb0e Binary files /dev/null and b/contrib/src/test/resources/contrib/tiff/smallliz.tif differ diff --git a/contrib/src/test/resources/contrib/tiff/zackthecat.tif b/contrib/src/test/resources/contrib/tiff/zackthecat.tif new file mode 100644 index 00000000..15185b68 Binary files /dev/null and b/contrib/src/test/resources/contrib/tiff/zackthecat.tif differ diff --git a/imageio/imageio-batik/license.txt b/imageio/imageio-batik/license.txt index 2d8ee79c..5ae89322 100755 --- a/imageio/imageio-batik/license.txt +++ b/imageio/imageio-batik/license.txt @@ -4,22 +4,24 @@ 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 "TwelveMonkeys" nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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 OWNER 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. \ No newline at end of file +* 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. diff --git a/imageio/imageio-batik/pom.xml b/imageio/imageio-batik/pom.xml index 39ca0cdb..46a37678 100644 --- a/imageio/imageio-batik/pom.xml +++ b/imageio/imageio-batik/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-batik TwelveMonkeys :: ImageIO :: Batik Plugin @@ -15,6 +15,27 @@ for more information.]]> + + com.twelvemonkeys.imageio.batik + 1.14 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + true + + + + + + + com.twelvemonkeys.imageio @@ -24,6 +45,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test @@ -49,7 +71,7 @@ org.apache.xmlgraphics xmlgraphics-commons - 2.0.1 + 2.2 provided @@ -86,8 +108,4 @@ - - - 1.8 - diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java index ec7b85aa..adcc98db 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java @@ -1,573 +1,662 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.svg; - -import com.twelvemonkeys.image.ImageUtil; -import com.twelvemonkeys.imageio.ImageReaderBase; -import com.twelvemonkeys.imageio.util.IIOUtil; -import org.apache.batik.anim.dom.SVGDOMImplementation; -import org.apache.batik.anim.dom.SVGOMDocument; -import org.apache.batik.bridge.*; -import org.apache.batik.dom.util.DOMUtilities; -import org.apache.batik.ext.awt.image.GraphicsUtil; -import org.apache.batik.gvt.CanvasGraphicsNode; -import org.apache.batik.gvt.GraphicsNode; -import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory; -import org.apache.batik.gvt.renderer.ImageRenderer; -import org.apache.batik.gvt.renderer.ImageRendererFactory; -import org.apache.batik.transcoder.*; -import org.apache.batik.transcoder.image.ImageTranscoder; -import org.apache.batik.util.ParsedURL; -import org.w3c.dom.DOMImplementation; -import org.w3c.dom.Document; -import org.w3c.dom.svg.SVGSVGElement; - -import javax.imageio.IIOException; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.spi.ImageReaderSpi; -import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.Dimension2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; - -/** - * Image reader for SVG document fragments. - *

- * - * @author Harald Kuhr - * @author Inpspired by code from the Batik Team - * @version $Id: $ - * @see batik-dev - */ -public class SVGImageReader extends ImageReaderBase { - private Rasterizer rasterizer; - - /** - * Creates an {@code SVGImageReader}. - * - * @param pProvider the provider - */ - public SVGImageReader(final ImageReaderSpi pProvider) { - super(pProvider); - } - - protected void resetMembers() { - rasterizer = new Rasterizer(); - } - - @Override - public void dispose() { - super.dispose(); - rasterizer = null; - } - - @Override - public void setInput(Object pInput, boolean seekForwardOnly, boolean ignoreMetadata) { - super.setInput(pInput, seekForwardOnly, ignoreMetadata); - - if (imageInput != null) { - TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput)); - rasterizer.setInput(input); - } - } - - public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException { - checkBounds(pIndex); - - String baseURI = null; - - if (pParam instanceof SVGReadParam) { - SVGReadParam svgParam = (SVGReadParam) pParam; - // Set IIOParams as hints - // Note: The cast to Map invokes a different method that preserves - // unset defaults, DO NOT REMOVE! - rasterizer.setTranscodingHints((Map) paramsToHints(svgParam)); - - // Get the base URI (not a hint) - baseURI = svgParam.getBaseURI(); - } - - Dimension size; - if (pParam != null && (size = pParam.getSourceRenderSize()) != null) { - // Use size... - } - else { - size = new Dimension(getWidth(pIndex), getHeight(pIndex)); - } - - BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height); - - // Read in the image, using the Batik Transcoder - try { - processImageStarted(pIndex); - - rasterizer.transcoderInput.setURI(baseURI); - BufferedImage image = rasterizer.getImage(); - - Graphics2D g = destination.createGraphics(); - try { - g.setComposite(AlphaComposite.Src); - g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); - g.drawImage(image, 0, 0, null); // TODO: Dest offset? - } - finally { - g.dispose(); - } - - processImageComplete(); - - return destination; - } - catch (TranscoderException e) { - throw new IIOException(e.getMessage(), e); - } - } - - private TranscodingHints paramsToHints(SVGReadParam pParam) throws IOException { - TranscodingHints hints = new TranscodingHints(); - // Note: We must allow generic ImageReadParams, so converting to - // TanscodingHints should be done outside the SVGReadParam class. - - // Set dimensions - Dimension size = pParam.getSourceRenderSize(); - Dimension origSize = new Dimension(getWidth(0), getHeight(0)); - if (size == null) { - // SVG is not a pixel based format, but we'll scale it, according to - // the subsampling for compatibility - size = getSourceRenderSizeFromSubsamping(pParam, origSize); - } - - if (size != null) { - hints.put(ImageTranscoder.KEY_WIDTH, new Float(size.getWidth())); - hints.put(ImageTranscoder.KEY_HEIGHT, new Float(size.getHeight())); - } - - // Set area of interest - Rectangle region = pParam.getSourceRegion(); - if (region != null) { - hints.put(ImageTranscoder.KEY_AOI, region); - - // Avoid that the batik transcoder scales the AOI up to original image size - if (size == null) { - hints.put(ImageTranscoder.KEY_WIDTH, new Float(region.getWidth())); - hints.put(ImageTranscoder.KEY_HEIGHT, new Float(region.getHeight())); - } - else { - // Need to resize here... - double xScale = size.getWidth() / origSize.getWidth(); - double yScale = size.getHeight() / origSize.getHeight(); - - hints.put(ImageTranscoder.KEY_WIDTH, new Float(region.getWidth() * xScale)); - hints.put(ImageTranscoder.KEY_HEIGHT, new Float(region.getHeight() * yScale)); - } - } - else if (size != null) { - // Allow non-uniform scaling - hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize)); - } - - // Background color - Paint bg = pParam.getBackgroundColor(); - if (bg != null) { - hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg); - } - - return hints; - } - - private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) { - if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) { - return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()), - (int) (pOrigSize.height / (float) pParam.getSourceYSubsampling())); - } - return null; - } - - public ImageReadParam getDefaultReadParam() { - return new SVGReadParam(); - } - - public int getWidth(int pIndex) throws IOException { - checkBounds(pIndex); - try { - return rasterizer.getDefaultWidth(); - } - catch (TranscoderException e) { - throw new IIOException(e.getMessage(), e); - } - } - - public int getHeight(int pIndex) throws IOException { - checkBounds(pIndex); - try { - return rasterizer.getDefaultHeight(); - } - catch (TranscoderException e) { - throw new IIOException(e.getMessage(), e); - } - } - - public Iterator getImageTypes(int imageIndex) throws IOException { - return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator(); - } - - /** - * An image transcoder that stores the resulting image. - *

- * NOTE: This class includes a lot of copy and paste code from the Batik classes - * and needs major refactoring! - */ - private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ { - - BufferedImage image = null; - private TranscoderInput transcoderInput; - private float defaultWidth; - private float defaultHeight; - private boolean initialized = false; - private SVGOMDocument document; - private String uri; - private GraphicsNode gvtRoot; - private TranscoderException exception; - private BridgeContext context; - - public BufferedImage createImage(final int width, final int height) { - return ImageUtil.createTransparent(width, height);//, BufferedImage.TYPE_INT_ARGB); - } - - // This is cheating... We don't fully transcode after all - protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException { - // Sets up root, curTxf & curAoi - // ---- - if ((document != null) && !(document.getImplementation() instanceof SVGDOMImplementation)) { - DOMImplementation impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); - document = DOMUtilities.deepCloneDocument(document, impl); - - if (uri != null) { - try { - URL url = new URL(uri); - ((SVGOMDocument) document).setURLObject(url); - } - catch (MalformedURLException ignore) { - } - } - } - - ctx = createBridgeContext(); - SVGOMDocument svgDoc = (SVGOMDocument) document; - - // build the GVT tree - builder = new GVTBuilder(); - // flag that indicates if the document is dynamic - boolean isDynamic = - (hints.containsKey(KEY_EXECUTE_ONLOAD) && - (Boolean) hints.get(KEY_EXECUTE_ONLOAD) && - BaseScriptingEnvironment.isDynamicDocument(ctx, svgDoc)); - - if (isDynamic) { - ctx.setDynamicState(BridgeContext.DYNAMIC); - } - - // Modified code below: - GraphicsNode root = null; - try { - root = builder.build(ctx, svgDoc); - } - catch (BridgeException ex) { - // Note: This might fail, but we STILL have the dimensions we need - // However, we need to reparse later... - exception = new TranscoderException(ex); - } - - // ---- - - // get the 'width' and 'height' attributes of the SVG document - Dimension2D docSize = ctx.getDocumentSize(); - if (docSize != null) { - defaultWidth = (float) docSize.getWidth(); - defaultHeight = (float) docSize.getHeight(); - } - else { - defaultWidth = 200; - defaultHeight = 200; - } - - // Hack to work around exception above - if (root != null) { - gvtRoot = root; - } - this.document = svgDoc; - this.uri = uri; - - // Hack to avoid the transcode method wacking my context... - context = ctx; - ctx = null; - } - - private BufferedImage readImage() throws TranscoderException { - init(); - - if (abortRequested()) { - processReadAborted(); - return null; - } - processImageProgress(10f); - - - // Hacky workaround below... - if (gvtRoot == null) { - // Try to reparse, if we had no URI last time... - if (uri != transcoderInput.getURI()) { - try { - context.dispose(); - document.setURLObject(new URL(transcoderInput.getURI())); - transcode(document, transcoderInput.getURI(), null); - } - catch (MalformedURLException ignore) { - // Ignored - } - } - - if (gvtRoot == null) { - throw exception; - } - } - ctx = context; - // /Hacky - if (abortRequested()) { - processReadAborted(); - return null; - } - processImageProgress(20f); - - // ---- - SVGSVGElement root = document.getRootElement(); - // ---- - - - // ---- - setImageSize(defaultWidth, defaultHeight); - - if (abortRequested()) { - processReadAborted(); - return null; - } - processImageProgress(40f); - - // compute the preserveAspectRatio matrix - AffineTransform Px; - String ref = new ParsedURL(uri).getRef(); - - try { - Px = ViewBox.getViewTransform(ref, root, width, height, null); - - } - catch (BridgeException ex) { - throw new TranscoderException(ex); - } - - if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) { - // The document has no viewBox, we need to resize it by hand. - // we want to keep the document size ratio - float xscale, yscale; - xscale = width / defaultWidth; - yscale = height / defaultHeight; - float scale = Math.min(xscale, yscale); - Px = AffineTransform.getScaleInstance(scale, scale); - } - // take the AOI into account if any - if (hints.containsKey(KEY_AOI)) { - Rectangle2D aoi = (Rectangle2D) hints.get(KEY_AOI); - // transform the AOI into the image's coordinate system - aoi = Px.createTransformedShape(aoi).getBounds2D(); - AffineTransform Mx = new AffineTransform(); - double sx = width / aoi.getWidth(); - double sy = height / aoi.getHeight(); - Mx.scale(sx, sy); - double tx = -aoi.getX(); - double ty = -aoi.getY(); - Mx.translate(tx, ty); - // take the AOI transformation matrix into account - // we apply first the preserveAspectRatio matrix - Px.preConcatenate(Mx); - curAOI = aoi; - } - else { - curAOI = new Rectangle2D.Float(0, 0, width, height); - } - - if (abortRequested()) { - processReadAborted(); - return null; - } - processImageProgress(50f); - - CanvasGraphicsNode cgn = getCanvasGraphicsNode(gvtRoot); - if (cgn != null) { - cgn.setViewingTransform(Px); - curTxf = new AffineTransform(); - } - else { - curTxf = Px; - } - - try { - // dispatch an 'onload' event if needed - if (ctx.isDynamic()) { - BaseScriptingEnvironment se; - se = new BaseScriptingEnvironment(ctx); - se.loadScripts(); - se.dispatchSVGLoadEvent(); - } - } - catch (BridgeException ex) { - throw new TranscoderException(ex); - } - - this.root = gvtRoot; - // ---- - - // NOTE: The code below is copied and pasted from the Batik - // ImageTranscoder class' transcode() method: - - // prepare the image to be painted - int w = (int) (width + 0.5); - int h = (int) (height + 0.5); - - // paint the SVG document using the bridge package - // create the appropriate renderer - ImageRendererFactory rendFactory = new ConcreteImageRendererFactory(); - // ImageRenderer renderer = rendFactory.createDynamicImageRenderer(); - ImageRenderer renderer = rendFactory.createStaticImageRenderer(); - renderer.updateOffScreen(w, h); - renderer.setTransform(curTxf); - renderer.setTree(this.root); - this.root = null; // We're done with it... - - if (abortRequested()) { - processReadAborted(); - return null; - } - processImageProgress(75f); - - try { - // now we are sure that the aoi is the image size - Shape raoi = new Rectangle2D.Float(0, 0, width, height); - // Warning: the renderer's AOI must be in user space - renderer.repaint(curTxf.createInverse(). - createTransformedShape(raoi)); - // NOTE: repaint above cause nullpointer exception with fonts..??? - - - BufferedImage rend = renderer.getOffScreen(); - renderer = null; // We're done with it... - - BufferedImage dest = createImage(w, h); - - Graphics2D g2d = GraphicsUtil.createGraphics(dest); - try { - if (hints.containsKey(ImageTranscoder.KEY_BACKGROUND_COLOR)) { - Paint bgcolor = (Paint) hints.get(ImageTranscoder.KEY_BACKGROUND_COLOR); - g2d.setComposite(AlphaComposite.SrcOver); - g2d.setPaint(bgcolor); - g2d.fillRect(0, 0, w, h); - } - - if (rend != null) { // might be null if the svg document is empty - g2d.drawRenderedImage(rend, new AffineTransform()); - } - } - finally { - if (g2d != null) { - g2d.dispose(); - } - } - - if (abortRequested()) { - processReadAborted(); - return null; - } - processImageProgress(99f); - - return dest; - } - catch (Exception ex) { - TranscoderException exception = new TranscoderException(ex.getMessage()); - exception.initCause(ex); - throw exception; - } - finally { - if (context != null) { - context.dispose(); - } - } - } - - private synchronized void init() throws TranscoderException { - if (!initialized) { - if (transcoderInput == null) { - throw new IllegalStateException("input == null"); - } - - initialized = true; - - super.transcode(transcoderInput, null); - } - } - - private BufferedImage getImage() throws TranscoderException { - if (image == null) { - image = readImage(); - } - - return image; - } - - protected int getDefaultWidth() throws TranscoderException { - init(); - return (int) (defaultWidth + 0.5); - } - - protected int getDefaultHeight() throws TranscoderException { - init(); - return (int) (defaultHeight + 0.5); - } - - public void setInput(final TranscoderInput pInput) { - transcoderInput = pInput; - } - } -} +/* + * 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.imageio.plugins.svg; + +import com.twelvemonkeys.image.ImageUtil; +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.lang.StringUtil; +import org.apache.batik.anim.dom.SVGDOMImplementation; +import org.apache.batik.anim.dom.SVGOMDocument; +import org.apache.batik.bridge.*; +import org.apache.batik.css.parser.CSSLexicalUnit; +import org.apache.batik.dom.util.DOMUtilities; +import org.apache.batik.ext.awt.image.GraphicsUtil; +import org.apache.batik.gvt.CanvasGraphicsNode; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory; +import org.apache.batik.gvt.renderer.ImageRenderer; +import org.apache.batik.gvt.renderer.ImageRendererFactory; +import org.apache.batik.transcoder.*; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.util.ParsedURL; +import org.apache.batik.util.SVGConstants; +import org.apache.batik.xml.LexicalUnits; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.svg.SVGSVGElement; + +import javax.imageio.IIOException; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.spi.ImageReaderSpi; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +/** + * Image reader for SVG document fragments. + * + * @author Harald Kuhr + * @author Inpspired by code from the Batik Team + * @version $Id: $ + * @see batik-dev + */ +public class SVGImageReader extends ImageReaderBase { + + final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES = + "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources")); + + private Rasterizer rasterizer; + private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES; + + /** + * Creates an {@code SVGImageReader}. + * + * @param pProvider the provider + */ + public SVGImageReader(final ImageReaderSpi pProvider) { + super(pProvider); + } + + protected void resetMembers() { + rasterizer = new Rasterizer(); + } + + @Override + public void dispose() { + super.dispose(); + rasterizer = null; + } + + @Override + public void setInput(Object pInput, boolean seekForwardOnly, boolean ignoreMetadata) { + super.setInput(pInput, seekForwardOnly, ignoreMetadata); + + if (imageInput != null) { + TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput)); + rasterizer.setInput(input); + } + } + + public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException { + checkBounds(pIndex); + + if (pParam instanceof SVGReadParam) { + SVGReadParam svgParam = (SVGReadParam) pParam; + + // set the external-resource-resolution preference + allowExternalResources = svgParam.isAllowExternalResources(); + + // Get the base URI + // This must be done before converting the params to hints + String baseURI = svgParam.getBaseURI(); + rasterizer.transcoderInput.setURI(baseURI); + + // Set ImageReadParams as hints + // Note: The cast to Map invokes a different method that preserves + // unset defaults, DO NOT REMOVE! + rasterizer.setTranscodingHints((Map) paramsToHints(svgParam)); + } + + Dimension size = null; + if (pParam != null) { + size = pParam.getSourceRenderSize(); + } + if (size == null) { + size = new Dimension(getWidth(pIndex), getHeight(pIndex)); + } + + BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height); + + // Read in the image, using the Batik Transcoder + try { + processImageStarted(pIndex); + + BufferedImage image = rasterizer.getImage(); + + Graphics2D g = destination.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); + g.drawImage(image, 0, 0, null); // TODO: Dest offset? + } + finally { + g.dispose(); + } + + processImageComplete(); + + return destination; + } + catch (TranscoderException e) { + Throwable cause = unwrapException(e); + throw new IIOException(cause.getMessage(), cause); + } + } + + private static Throwable unwrapException(TranscoderException ex) { + // The TranscoderException is generally useless... + return ex.getException() != null ? ex.getException() : ex; + } + + private TranscodingHints paramsToHints(SVGReadParam pParam) throws IOException { + TranscodingHints hints = new TranscodingHints(); + // Note: We must allow generic ImageReadParams, so converting to + // TanscodingHints should be done outside the SVGReadParam class. + + // Set dimensions + Dimension size = pParam.getSourceRenderSize(); + Dimension origSize = new Dimension(getWidth(0), getHeight(0)); + if (size == null) { + // SVG is not a pixel based format, but we'll scale it, according to + // the subsampling for compatibility + size = getSourceRenderSizeFromSubsamping(pParam, origSize); + } + + if (size != null) { + hints.put(ImageTranscoder.KEY_WIDTH, (float) size.getWidth()); + hints.put(ImageTranscoder.KEY_HEIGHT, (float) size.getHeight()); + } + + // Set area of interest + Rectangle region = pParam.getSourceRegion(); + if (region != null) { + hints.put(ImageTranscoder.KEY_AOI, region); + + // Avoid that the batik transcoder scales the AOI up to original image size + if (size == null) { + hints.put(ImageTranscoder.KEY_WIDTH, (float) region.getWidth()); + hints.put(ImageTranscoder.KEY_HEIGHT, (float) region.getHeight()); + } + else { + // Need to resize here... + double xScale = size.getWidth() / origSize.getWidth(); + double yScale = size.getHeight() / origSize.getHeight(); + + hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale)); + hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale)); + } + } + else if (size != null) { + // Allow non-uniform scaling + hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize)); + } + + // Background color + Paint bg = pParam.getBackgroundColor(); + if (bg != null) { + hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg); + } + + return hints; + } + + private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) { + if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) { + return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()), + (int) (pOrigSize.height / (float) pParam.getSourceYSubsampling())); + } + return null; + } + + public SVGReadParam getDefaultReadParam() { + return new SVGReadParam(); + } + + public int getWidth(int pIndex) throws IOException { + checkBounds(pIndex); + try { + return rasterizer.getDefaultWidth(); + } + catch (TranscoderException e) { + throw new IIOException(e.getMessage(), e); + } + } + + public int getHeight(int pIndex) throws IOException { + checkBounds(pIndex); + try { + return rasterizer.getDefaultHeight(); + } + catch (TranscoderException e) { + throw new IIOException(e.getMessage(), e); + } + } + + public Iterator getImageTypes(int imageIndex) throws IOException { + return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator(); + } + + /** + * An image transcoder that stores the resulting image. + *

+ * NOTE: This class includes a lot of copy and paste code from the Batik classes + * and needs major refactoring! + *

+ */ + private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ { + + private BufferedImage image; + private TranscoderInput transcoderInput; + private float defaultWidth; + private float defaultHeight; + private boolean initialized = false; + private SVGOMDocument document; + private String uri; + private GraphicsNode gvtRoot; + private TranscoderException exception; + private BridgeContext context; + + private BufferedImage createImage(final int width, final int height) { + return ImageUtil.createTransparent(width, height); // BufferedImage.TYPE_INT_ARGB + } + + // This is cheating... We don't fully transcode after all + protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException { + // Sets up root, curTxf & curAoi + // ---- + if (document != null) { + if (!(document.getImplementation() instanceof SVGDOMImplementation)) { + DOMImplementation impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION); + document = DOMUtilities.deepCloneDocument(document, impl); + } + + if (uri != null) { + try { + URL url = new URL(uri); + ((SVGOMDocument) document).setURLObject(url); + } + catch (MalformedURLException ignore) { + } + } + } + + ctx = createBridgeContext(); + SVGOMDocument svgDoc = (SVGOMDocument) document; + + // build the GVT tree + builder = new GVTBuilder(); + // flag that indicates if the document is dynamic + boolean isDynamic = + (hints.containsKey(KEY_EXECUTE_ONLOAD) && + (Boolean) hints.get(KEY_EXECUTE_ONLOAD) && + BaseScriptingEnvironment.isDynamicDocument(ctx, svgDoc)); + + if (isDynamic) { + ctx.setDynamicState(BridgeContext.DYNAMIC); + } + + // Modified code below: + GraphicsNode root = null; + try { + root = builder.build(ctx, svgDoc); + } + catch (BridgeException ex) { + // Note: This might fail, but we STILL have the dimensions we need + // However, we need to reparse later... + exception = new TranscoderException(ex); + } + + // ---- + SVGSVGElement rootElement = svgDoc.getRootElement(); + + // get the 'width' and 'height' attributes of the SVG document + UnitProcessor.Context uctx + = UnitProcessor.createContext(ctx, rootElement); + String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE); + String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE); + if (!StringUtil.isEmpty(widthStr)) { + defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx); + } + if(!StringUtil.isEmpty(heightStr)){ + defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx); + } + + boolean hasWidth = defaultWidth > 0.0; + boolean hasHeight = defaultHeight > 0.0; + + if (!hasWidth || !hasHeight) { + String viewBoxStr = rootElement.getAttributeNS + (null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE); + if (viewBoxStr.length() != 0) { + float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null); + // if one dimension is given, calculate other by aspect ratio in viewBox + // or use viewBox if no dimension is given + if (hasWidth) { + defaultHeight = defaultWidth * rect[3] / rect[2]; + } + else if (hasHeight) { + defaultWidth = defaultHeight * rect[2] / rect[3]; + } + else { + defaultWidth = rect[2]; + defaultHeight = rect[3]; + } + } + else { + if (hasHeight) { + defaultWidth = defaultHeight; + } + else if (hasWidth) { + defaultHeight = defaultWidth; + } + else { + // fallback to batik default sizes + defaultWidth = 400; + defaultHeight = 400; + } + } + } + + // Hack to work around exception above + if (root != null) { + gvtRoot = root; + } + this.document = svgDoc; + this.uri = uri; + + // Hack to avoid the transcode method wacking my context... + context = ctx; + ctx = null; + } + + private BufferedImage readImage() throws TranscoderException { + init(); + + if (abortRequested()) { + processReadAborted(); + return null; + } + + processImageProgress(10f); + + // Hacky workaround below... + if (gvtRoot == null) { + // Try to reparse, if we had no URI last time... + if (uri != transcoderInput.getURI()) { + try { + context.dispose(); + document.setURLObject(new URL(transcoderInput.getURI())); + transcode(document, transcoderInput.getURI(), null); + } + catch (MalformedURLException ignore) { + // Ignored + } + } + + if (gvtRoot == null) { + throw exception; + } + } + ctx = context; + // /Hacky + + if (abortRequested()) { + processReadAborted(); + return null; + } + processImageProgress(20f); + + // ---- + SVGSVGElement root = document.getRootElement(); + // ---- + + + // ---- + setImageSize(defaultWidth, defaultHeight); + + if (abortRequested()) { + processReadAborted(); + return null; + } + processImageProgress(40f); + + // compute the preserveAspectRatio matrix + AffineTransform Px; + String ref = new ParsedURL(uri).getRef(); + + try { + Px = ViewBox.getViewTransform(ref, root, width, height, null); + + } + catch (BridgeException ex) { + throw new TranscoderException(ex); + } + + if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) { + // The document has no viewBox, we need to resize it by hand. + // we want to keep the document size ratio + float xscale, yscale; + xscale = width / defaultWidth; + yscale = height / defaultHeight; + float scale = Math.min(xscale, yscale); + Px = AffineTransform.getScaleInstance(scale, scale); + } + // take the AOI into account if any + if (hints.containsKey(KEY_AOI)) { + Rectangle2D aoi = (Rectangle2D) hints.get(KEY_AOI); + // transform the AOI into the image's coordinate system + aoi = Px.createTransformedShape(aoi).getBounds2D(); + AffineTransform Mx = new AffineTransform(); + double sx = width / aoi.getWidth(); + double sy = height / aoi.getHeight(); + Mx.scale(sx, sy); + double tx = -aoi.getX(); + double ty = -aoi.getY(); + Mx.translate(tx, ty); + // take the AOI transformation matrix into account + // we apply first the preserveAspectRatio matrix + Px.preConcatenate(Mx); + curAOI = aoi; + } + else { + curAOI = new Rectangle2D.Float(0, 0, width, height); + } + + if (abortRequested()) { + processReadAborted(); + return null; + } + processImageProgress(50f); + + CanvasGraphicsNode cgn = getCanvasGraphicsNode(gvtRoot); + if (cgn != null) { + cgn.setViewingTransform(Px); + curTxf = new AffineTransform(); + } + else { + curTxf = Px; + } + + try { + // dispatch an 'onload' event if needed + if (ctx.isDynamic()) { + BaseScriptingEnvironment se; + se = new BaseScriptingEnvironment(ctx); + se.loadScripts(); + se.dispatchSVGLoadEvent(); + } + } + catch (BridgeException ex) { + throw new TranscoderException(ex); + } + + this.root = gvtRoot; + // ---- + + // NOTE: The code below is copied and pasted from the Batik + // ImageTranscoder class' transcode() method: + + // prepare the image to be painted + int w = (int) (width + 0.5); + int h = (int) (height + 0.5); + + // paint the SVG document using the bridge package + // create the appropriate renderer + ImageRendererFactory rendFactory = new ConcreteImageRendererFactory(); + // ImageRenderer renderer = rendFactory.createDynamicImageRenderer(); + ImageRenderer renderer = rendFactory.createStaticImageRenderer(); + renderer.updateOffScreen(w, h); + renderer.setTransform(curTxf); + renderer.setTree(this.root); + this.root = null; // We're done with it... + + if (abortRequested()) { + processReadAborted(); + return null; + } + processImageProgress(75f); + + try { + // now we are sure that the aoi is the image size + Shape raoi = new Rectangle2D.Float(0, 0, width, height); + // Warning: the renderer's AOI must be in user space + renderer.repaint(curTxf.createInverse().createTransformedShape(raoi)); + // NOTE: repaint above cause nullpointer exception with fonts..??? + + BufferedImage rend = renderer.getOffScreen(); + renderer = null; // We're done with it... + + BufferedImage dest = createImage(w, h); + + Graphics2D g2d = GraphicsUtil.createGraphics(dest); + try { + if (hints.containsKey(ImageTranscoder.KEY_BACKGROUND_COLOR)) { + Paint bgcolor = (Paint) hints.get(ImageTranscoder.KEY_BACKGROUND_COLOR); + g2d.setComposite(AlphaComposite.SrcOver); + g2d.setPaint(bgcolor); + g2d.fillRect(0, 0, w, h); + } + + if (rend != null) { // might be null if the svg document is empty + g2d.drawRenderedImage(rend, new AffineTransform()); + } + } + finally { + if (g2d != null) { + g2d.dispose(); + } + } + + if (abortRequested()) { + processReadAborted(); + return null; + } + processImageProgress(99f); + + return dest; + } + catch (Exception ex) { + TranscoderException exception = new TranscoderException(ex.getMessage()); + exception.initCause(ex); + throw exception; + } + finally { + if (context != null) { + context.dispose(); + } + } + } + + private synchronized void init() throws TranscoderException { + if (!initialized) { + if (transcoderInput == null) { + throw new IllegalStateException("input == null"); + } + + initialized = true; + + super.transcode(transcoderInput, null); + } + } + + private BufferedImage getImage() throws TranscoderException { + if (image == null) { + image = readImage(); + } + + return image; + } + + int getDefaultWidth() throws TranscoderException { + init(); + return (int) Math.ceil(defaultWidth); + } + + int getDefaultHeight() throws TranscoderException { + init(); + return (int) Math.ceil(defaultHeight); + } + + void setInput(final TranscoderInput pInput) { + transcoderInput = pInput; + } + + @Override + protected UserAgent createUserAgent() { + return new SVGImageReaderUserAgent(); + } + + private class SVGImageReaderUserAgent extends SVGAbstractTranscoderUserAgent { + @Override + public void displayError(Exception e) { + displayError(e.getMessage()); + } + + @Override + public void displayError(String message) { + displayMessage(message); + } + + @Override + public void displayMessage(String message) { + processWarningOccurred(message.replaceAll("[\\r\\n]+", " ")); + } + + @Override + public ExternalResourceSecurity getExternalResourceSecurity(ParsedURL resourceURL, ParsedURL docURL) { + if (allowExternalResources) { + return super.getExternalResourceSecurity(resourceURL, docURL); + } + return new EmbededExternalResourceSecurity(resourceURL); + } + } + } +} diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpi.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpi.java index 4b6cc86a..15141d80 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpi.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpi.java @@ -1,186 +1,187 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.svg; - -import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; -import com.twelvemonkeys.lang.SystemUtil; - -import javax.imageio.ImageReader; -import javax.imageio.spi.ServiceRegistry; -import javax.imageio.stream.ImageInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.util.Locale; - -import static com.twelvemonkeys.imageio.util.IIOUtil.deregisterProvider; - -/** - * SVGImageReaderSpi - *

- * - * @author Harald Kuhr - * @version $Id: SVGImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 haku Exp $ - */ -public final class SVGImageReaderSpi extends ImageReaderSpiBase { - - final static boolean SVG_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader", SVGImageReaderSpi.class); - - /** - * Creates an {@code SVGImageReaderSpi}. - */ - @SuppressWarnings("WeakerAccess") - public SVGImageReaderSpi() { - super(new SVGProviderInfo()); - } - - public boolean canDecodeInput(final Object pSource) throws IOException { - return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource); - } - - @SuppressWarnings("StatementWithEmptyBody") - private static boolean canDecode(final ImageInputStream pInput) throws IOException { - // NOTE: This test is quite quick as it does not involve any parsing, - // however it may not recognize all kinds of SVG documents. - try { - pInput.mark(); - - // TODO: This is not ok for UTF-16 and other wide encodings - // TODO: Use an XML (encoding) aware Reader instance instead - // Need to figure out pretty fast if this is XML or not - int b; - while (Character.isWhitespace((char) (b = pInput.read()))) { - // Skip over leading WS - } - - // If it's not a tag, this can't be valid XML - if (b != '<') { - return false; - } - - // Algorithm for detecting SVG: - // - Skip until begin tag '<' and read 4 bytes - // - if next is "?" skip until "?>" and start over - // - else if next is "!--" skip until "-->" and start over - // - else if next is "!DOCTYPE " skip any whitespace - // - compare next 3 bytes against "svg", return result - // - else - // - compare next 3 bytes against "svg", return result - - byte[] buffer = new byte[4]; - while (true) { - pInput.readFully(buffer); - - if (buffer[0] == '?') { - // This is the XML declaration or a processing instruction - while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) { - // Skip until end of XML declaration or processing instruction or EOF - } - } - else if (buffer[0] == '!') { - if (buffer[1] == '-' && buffer[2] == '-') { - // This is a comment - while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) { - // Skip until end of comment or EOF - } - } - else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C' - && pInput.read() == 'T' && pInput.read() == 'Y' - && pInput.read() == 'P' && pInput.read() == 'E') { - // This is the DOCTYPE declaration - while (Character.isWhitespace((char) (b = pInput.read()))) { - // Skip over WS - } - - if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') { - // It's SVG, identified by DOCTYPE - return true; - } - - // DOCTYPE found, but not SVG - return false; - } - - // Something else, we'll skip - } - else { - // This is a normal tag - if (buffer[0] == 's' && buffer[1] == 'v' && buffer[2] == 'g' - && (Character.isWhitespace((char) buffer[3]) || buffer[3] == ':')) { - // It's SVG, identified by root tag - // TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)! - return true; - } - - // If the tag is not "svg", this isn't SVG - return false; - } - - while ((pInput.readByte() & 0xFF) != '<') { - // Skip over, until next begin tag or EOF - } - } - } - catch (EOFException ignore) { - // Possible for small files... - return false; - } - finally { - //noinspection ThrowFromFinallyBlock - pInput.reset(); - } - } - - public ImageReader createReaderInstance(final Object extension) throws IOException { - return new SVGImageReader(this); - } - - public String getDescription(final Locale locale) { - return "Scalable Vector Graphics (SVG) format image reader"; - } - - @SuppressWarnings({"deprecation"}) - @Override - public void onRegistration(final ServiceRegistry registry, final Class category) { - // TODO: Perhaps just try to create an instance, and de-register if we fail? - if (!SVG_READER_AVAILABLE) { - System.err.println("Could not instantiate SVGImageReader (missing support classes)."); - - try { - // NOTE: This will break, but it gives us some useful debug info - new SVGImageReader(this); - } - catch (Throwable t) { - t.printStackTrace(); - } - - deregisterProvider(registry, this, category); - } - } -} - +/* + * 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.imageio.plugins.svg; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; +import com.twelvemonkeys.lang.SystemUtil; + +import javax.imageio.ImageReader; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.Locale; + +import static com.twelvemonkeys.imageio.util.IIOUtil.deregisterProvider; + +/** + * SVGImageReaderSpi + * + * @author Harald Kuhr + * @version $Id: SVGImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 haku Exp $ + */ +public final class SVGImageReaderSpi extends ImageReaderSpiBase { + + final static boolean SVG_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader", SVGImageReaderSpi.class); + + /** + * Creates an {@code SVGImageReaderSpi}. + */ + @SuppressWarnings("WeakerAccess") + public SVGImageReaderSpi() { + super(new SVGProviderInfo()); + } + + public boolean canDecodeInput(final Object pSource) throws IOException { + return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource); + } + + @SuppressWarnings("StatementWithEmptyBody") + private static boolean canDecode(final ImageInputStream pInput) throws IOException { + // NOTE: This test is quite quick as it does not involve any parsing, + // however it may not recognize all kinds of SVG documents. + try { + pInput.mark(); + + // TODO: This is not ok for UTF-16 and other wide encodings + // TODO: Use an XML (encoding) aware Reader instance instead + // Need to figure out pretty fast if this is XML or not + int b; + while (Character.isWhitespace((char) (b = pInput.read()))) { + // Skip over leading WS + } + + // If it's not a tag, this can't be valid XML + if (b != '<') { + return false; + } + + // Algorithm for detecting SVG: + // - Skip until begin tag '<' and read 4 bytes + // - if next is "?" skip until "?>" and start over + // - else if next is "!--" skip until "-->" and start over + // - else if next is "!DOCTYPE " skip any whitespace + // - compare next 3 bytes against "svg", return result + // - else + // - compare next 3 bytes against "svg", return result + + byte[] buffer = new byte[4]; + while (true) { + pInput.readFully(buffer); + + if (buffer[0] == '?') { + // This is the XML declaration or a processing instruction + while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) { + // Skip until end of XML declaration or processing instruction or EOF + } + } + else if (buffer[0] == '!') { + if (buffer[1] == '-' && buffer[2] == '-') { + // This is a comment + while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) { + // Skip until end of comment or EOF + } + } + else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C' + && pInput.read() == 'T' && pInput.read() == 'Y' + && pInput.read() == 'P' && pInput.read() == 'E') { + // This is the DOCTYPE declaration + while (Character.isWhitespace((char) (b = pInput.read()))) { + // Skip over WS + } + + if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') { + // It's SVG, identified by DOCTYPE + return true; + } + + // DOCTYPE found, but not SVG + return false; + } + + // Something else, we'll skip + } + else { + // This is a normal tag + if (buffer[0] == 's' && buffer[1] == 'v' && buffer[2] == 'g' + && (Character.isWhitespace((char) buffer[3]) || buffer[3] == ':')) { + // It's SVG, identified by root tag + // TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)! + return true; + } + + // If the tag is not "svg", this isn't SVG + return false; + } + + while ((pInput.readByte() & 0xFF) != '<') { + // Skip over, until next begin tag or EOF + } + } + } + catch (EOFException ignore) { + // Possible for small files... + return false; + } + finally { + //noinspection ThrowFromFinallyBlock + pInput.reset(); + } + } + + public ImageReader createReaderInstance(final Object extension) throws IOException { + return new SVGImageReader(this); + } + + public String getDescription(final Locale locale) { + return "Scalable Vector Graphics (SVG) format image reader"; + } + + @SuppressWarnings({"deprecation"}) + @Override + public void onRegistration(final ServiceRegistry registry, final Class category) { + // TODO: Perhaps just try to create an instance, and de-register if we fail? + if (!SVG_READER_AVAILABLE) { + System.err.println("Could not instantiate SVGImageReader (missing support classes)."); + + try { + // NOTE: This will break, but it gives us some useful debug info + new SVGImageReader(this); + } + catch (Throwable t) { + t.printStackTrace(); + } + + deregisterProvider(registry, this, category); + } + } +} + diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java index 5eac8873..7bf4e941 100644 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.svg; diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGReadParam.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGReadParam.java index 9d384807..6fa67199 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGReadParam.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGReadParam.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.svg; @@ -39,6 +41,11 @@ import java.awt.*; public class SVGReadParam extends ImageReadParam { private Paint background; private String baseURI; + private boolean allowExternalResources = SVGImageReader.DEFAULT_ALLOW_EXTERNAL_RESOURCES; + + public SVGReadParam() { + super(); + } public Paint getBackgroundColor() { return background; @@ -56,6 +63,14 @@ public class SVGReadParam extends ImageReadParam { baseURI = pBaseURI; } + public void setAllowExternalResources(boolean allow) { + allowExternalResources = allow; + } + + public boolean isAllowExternalResources() { + return allowExternalResources; + } + @Override public boolean canSetSourceRenderSize() { return true; diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMF.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMF.java index 08159719..c87d1668 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMF.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMF.java @@ -4,28 +4,31 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.wmf; /** @@ -36,7 +39,7 @@ package com.twelvemonkeys.imageio.plugins.wmf; * @version $Id: WMF.java,v 1.0 Feb 17, 2008 5:46:59 PM haraldk Exp$ */ interface WMF { - static byte[] HEADER = new byte[] { + byte[] HEADER = new byte[] { (byte) 0xd7, (byte) 0xcd, (byte) 0xc6, (byte) 0x9a, (byte) 0x00, (byte) 0x00, //(byte) 0x7a, (byte) 0xf3, (byte) 0xa6, (byte) 0xfe, //(byte) 0xf5, (byte) 0x06, (byte) 0x1c, (byte) 0x01, (byte) 0xe8, diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java index 1e78a57e..a53ffedb 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.wmf; diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java index a8106b91..af957044 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java @@ -1,100 +1,101 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.wmf; - -import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; -import com.twelvemonkeys.imageio.util.IIOUtil; - -import javax.imageio.ImageReader; -import javax.imageio.spi.ServiceRegistry; -import javax.imageio.stream.ImageInputStream; -import java.io.IOException; -import java.util.Locale; - -import static com.twelvemonkeys.imageio.plugins.wmf.WMFProviderInfo.WMF_READER_AVAILABLE; - -/** - * WMFImageReaderSpi - *

- * - * @author Harald Kuhr - * @version $Id: WMFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $ - */ -public final class WMFImageReaderSpi extends ImageReaderSpiBase { - - /** - * Creates a {@code WMFImageReaderSpi}. - */ - public WMFImageReaderSpi() { - super(new WMFProviderInfo()); - } - - public boolean canDecodeInput(final Object source) throws IOException { - return source instanceof ImageInputStream && WMF_READER_AVAILABLE && canDecode((ImageInputStream) source); - } - - public static boolean canDecode(final ImageInputStream pInput) throws IOException { - if (pInput == null) { - throw new IllegalArgumentException("input == null"); - } - - try { - pInput.mark(); - - for (byte header : WMF.HEADER) { - int read = (byte) pInput.read(); - if (header != read) { - return false; - } - } - return true; - - } - finally { - pInput.reset(); - } - } - - public ImageReader createReaderInstance(final Object extension) throws IOException { - return new WMFImageReader(this); - } - - public String getDescription(final Locale locale) { - return "Windows Meta File (WMF) image reader"; - } - - @SuppressWarnings({"deprecation"}) - @Override - public void onRegistration(final ServiceRegistry registry, final Class category) { - if (!WMF_READER_AVAILABLE) { - IIOUtil.deregisterProvider(registry, this, category); - } - } -} - +/* + * 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.imageio.plugins.wmf; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; +import com.twelvemonkeys.imageio.util.IIOUtil; + +import javax.imageio.ImageReader; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.util.Locale; + +import static com.twelvemonkeys.imageio.plugins.wmf.WMFProviderInfo.WMF_READER_AVAILABLE; + +/** + * WMFImageReaderSpi + * + * @author Harald Kuhr + * @version $Id: WMFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $ + */ +public final class WMFImageReaderSpi extends ImageReaderSpiBase { + + /** + * Creates a {@code WMFImageReaderSpi}. + */ + public WMFImageReaderSpi() { + super(new WMFProviderInfo()); + } + + public boolean canDecodeInput(final Object source) throws IOException { + return source instanceof ImageInputStream && WMF_READER_AVAILABLE && canDecode((ImageInputStream) source); + } + + public static boolean canDecode(final ImageInputStream pInput) throws IOException { + if (pInput == null) { + throw new IllegalArgumentException("input == null"); + } + + try { + pInput.mark(); + + for (byte header : WMF.HEADER) { + int read = (byte) pInput.read(); + if (header != read) { + return false; + } + } + return true; + + } + finally { + pInput.reset(); + } + } + + public ImageReader createReaderInstance(final Object extension) throws IOException { + return new WMFImageReader(this); + } + + public String getDescription(final Locale locale) { + return "Windows Meta File (WMF) image reader"; + } + + @SuppressWarnings({"deprecation"}) + @Override + public void onRegistration(final ServiceRegistry registry, final Class category) { + if (!WMF_READER_AVAILABLE) { + IIOUtil.deregisterProvider(registry, this, category); + } + } +} + diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java index 6db9a085..9d5e671f 100644 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java @@ -4,28 +4,31 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.wmf; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpiTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpiTest.java index cbe2c457..03752acb 100644 --- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpiTest.java +++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderSpiTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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.svg; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java index 59a06967..602acc33 100755 --- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java +++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java @@ -4,46 +4,59 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.svg; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Ignore; import org.junit.Test; +import javax.imageio.IIOException; +import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; +import javax.imageio.event.IIOReadWarningListener; import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ImagingOpException; import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; /** * SVGImageReaderTest @@ -53,30 +66,25 @@ import static org.junit.Assert.assertEquals; * @version $Id: SVGImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ */ public class SVGImageReaderTest extends ImageReaderAbstractTest { - private SVGImageReaderSpi provider = new SVGImageReaderSpi(); + + @Override + protected ImageReaderSpi createProvider() { + return new SVGImageReaderSpi(); + } protected List getTestData() { return Arrays.asList( new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500)), new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)), new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100)), - new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(400, 400)) + new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(294, 345)), + new TestData(getClassLoaderResource("/svg/sizes/w50h50.svg"), new Dimension(50, 50)), + new TestData(getClassLoaderResource("/svg/sizes/w50_1to2.svg"), new Dimension(25, 50)), + new TestData(getClassLoaderResource("/svg/sizes/h50_1to2.svg"), new Dimension(50, 100)), + new TestData(getClassLoaderResource("/svg/sizes/w50noview.svg"), new Dimension(50, 50)) ); } - protected ImageReaderSpi createProvider() { - return provider; - } - - @Override - protected SVGImageReader createReader() { - return new SVGImageReader(createProvider()); - } - - protected Class getReaderClass() { - return SVGImageReader.class; - } - protected List getFormatNames() { return Collections.singletonList("svg"); } @@ -89,13 +97,62 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest return Collections.singletonList("image/svg+xml"); } + @Test + public void testScaleViewBox() throws IOException { + URL svgUrl = getClassLoaderResource("/svg/quadrants.svg"); + + SVGImageReader reader = createReader(); + SVGReadParam param = new SVGReadParam(); + + int[] sizes = new int[]{16, 32, 64, 128}; + for (int size : sizes) { + try (InputStream svgStream = svgUrl.openStream(); ImageInputStream iis = ImageIO.createImageInputStream(svgStream)) { + reader.reset(); + reader.setInput(iis); + + + param.setSourceRenderSize(new Dimension(size, size)); + BufferedImage image = reader.read(0, param); + checkQuadrantColors(image); + } + finally { + reader.dispose(); + } + } + } + + private void checkQuadrantColors(BufferedImage image) { + int quadPoint = image.getWidth() / 2; + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + int current = image.getRGB(x, y); + if (x < quadPoint) { + if (y < quadPoint) { + assertEquals("x=" + x + " y=" + y + " q=" + quadPoint, 0xFF0000FF, current); + } + else { + assertEquals("x=" + x + " y=" + y + " q=" + quadPoint, 0xFFFF0000, current); + } + } + else { + if (y < quadPoint) { + assertEquals("x=" + x + " y=" + y + " q=" + quadPoint, 0xFF00FF00, current); + } + else { + assertEquals("x=" + x + " y=" + y + " q=" + quadPoint, 0xFF000000, current); + } + } + } + } + } + @Test @Override - public void testReadWithSizeParam() { + public void testReadWithSizeParam() throws IOException { try { super.testReadWithSizeParam(); } - catch (AssertionError failure) { + catch (AssertionError | IOException failure) { Throwable cause = failure; while (cause.getCause() != null) { @@ -144,4 +201,142 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest BufferedImage imageBlue = reader.read(0, param); assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF); } -} \ No newline at end of file + + @Test + public void testEmbeddedBaseURI() throws URISyntaxException, IOException { + URL resource = getClassLoaderResource("/svg/barChart.svg"); + + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + IIOReadWarningListener listener = mock(IIOReadWarningListener.class); + reader.addIIOReadWarningListener(listener); + + SVGReadParam param = reader.getDefaultReadParam(); + param.setAllowExternalResources(true); + param.setBaseURI(resource.toURI().toASCIIString()); + BufferedImage image = reader.read(0, param); + + assertNotNull(image); + assertEquals(450, image.getWidth()); + assertEquals(500, image.getHeight()); + + // CSS and embedded resources all go! + verifyZeroInteractions(listener); + } + finally { + reader.dispose(); + } + } + + @Test + public void testEmbeddedBeforeBaseURI() throws URISyntaxException, IOException { + // Asking for metadata, width, height etc, before attempting to read using a param, + // will cause the document to be parsed without a base URI. + // This will work, but may not use the CSS... + // since the param is not available before the read operation is invoked, + // this test-case MUST use the system-property for backwards compatibility + URL resource = getClassLoaderResource("/svg/barChart.svg"); + + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + IIOReadWarningListener listener = mock(IIOReadWarningListener.class); + reader.addIIOReadWarningListener(listener); + + assertEquals(450, reader.getWidth(0)); + assertEquals(500, reader.getHeight(0)); + + // Expect the warning about the missing CSS + verify(listener, atMost(1)).warningOccurred(any(ImageReader.class), anyString()); + reset(listener); + + SVGReadParam param = reader.getDefaultReadParam(); + param.setBaseURI(resource.toURI().toASCIIString()); + BufferedImage image = reader.read(0, param); + + assertNotNull(image); + assertEquals(450, image.getWidth()); + assertEquals(500, image.getHeight()); + + // No more warnings now that the base URI is set + verifyZeroInteractions(listener); + } + finally { + reader.dispose(); + } + } + + @Test + public void testEmbeddedNoBaseURI() throws IOException { + // With no base URI, we will throw an exception, about the missing embedded resource + URL resource = getClassLoaderResource("/svg/barChart.svg"); + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + SVGReadParam params = reader.getDefaultReadParam(); + params.setAllowExternalResources(true); + reader.read(0, params); + + fail("reader.read should've thrown an exception, but didn't"); + } + catch (IIOException allowed) { + assertTrue(allowed.getMessage().contains("batikLogo.svg")); // The embedded resource we don't find + } + finally { + reader.dispose(); + } + } + + @Test + public void testReadEmbeddedWithDisallowExternalResources() throws IOException{ + // File using "data:" URLs for embedded resources + URL resource = getClassLoaderResource("/svg/embedded-data-resource.svg"); + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + SVGReadParam param = reader.getDefaultReadParam(); + param.setAllowExternalResources(false); + reader.read(0, param); + } + finally { + reader.dispose(); + } + } + + @Test(expected = SecurityException.class) + public void testDisallowedExternalResources() throws URISyntaxException, IOException { + // system-property set to true in surefire-plugin-settings in the pom + URL resource = getClassLoaderResource("/svg/barChart.svg"); + SVGImageReader reader = createReader(); + + TestData data = new TestData(resource, (Dimension) null); + try (ImageInputStream stream = data.getInputStream()) { + reader.setInput(stream); + + SVGReadParam param = reader.getDefaultReadParam(); + param.setBaseURI(resource.toURI().toASCIIString()); + param.setAllowExternalResources(false); + // even when the system-property is set to true, + // `reader.read` for `/svg/barChart.svg` should raise + // a SecurityException when External Resources are blocked + // because the API invocation gets preference + reader.read(0, param); + } + finally { + reader.dispose(); + } + } +} diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfoTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfoTest.java index 2537db4f..a51c167d 100644 --- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfoTest.java +++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.svg; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java index b634b138..0fdb4c50 100755 --- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java +++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java @@ -4,31 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.wmf; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Ignore; import org.junit.Test; @@ -47,7 +50,10 @@ import java.util.List; * @version $Id: WMFImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ */ public class WMFImageReaderTest extends ImageReaderAbstractTest { - private WMFImageReaderSpi provider = new WMFImageReaderSpi(); + @Override + protected ImageReaderSpi createProvider() { + return new WMFImageReaderSpi(); + } protected List getTestData() { return Collections.singletonList( @@ -55,27 +61,17 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest ); } - protected ImageReaderSpi createProvider() { - return provider; - } - @Override - protected WMFImageReader createReader() { - return new WMFImageReader(createProvider()); - } - - protected Class getReaderClass() { - return WMFImageReader.class; - } - protected List getFormatNames() { return Collections.singletonList("wmf"); } + @Override protected List getSuffixes() { return Arrays.asList("wmf", "emf"); } + @Override protected List getMIMETypes() { return Arrays.asList("image/x-wmf", "application/x-msmetafile"); } diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java index 6b96d2cf..f6fc2801 100644 --- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java +++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.wmf; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-batik/src/test/resources/svg/barChart.svg b/imageio/imageio-batik/src/test/resources/svg/barChart.svg new file mode 100644 index 00000000..5afd1750 --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/barChart.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + Bar Chart + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Shoe + Car + Travel + Computer + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imageio/imageio-batik/src/test/resources/svg/embedded-data-resource.svg b/imageio/imageio-batik/src/test/resources/svg/embedded-data-resource.svg new file mode 100644 index 00000000..d6dce10f --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/embedded-data-resource.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/imageio/imageio-batik/src/test/resources/svg/quadrants.svg b/imageio/imageio-batik/src/test/resources/svg/quadrants.svg new file mode 100644 index 00000000..b0527044 --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/quadrants.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/imageio/imageio-batik/src/test/resources/svg/sizes/h50_1to2.svg b/imageio/imageio-batik/src/test/resources/svg/sizes/h50_1to2.svg new file mode 100644 index 00000000..2f29bfb1 --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/sizes/h50_1to2.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/imageio/imageio-batik/src/test/resources/svg/sizes/w50_1to2.svg b/imageio/imageio-batik/src/test/resources/svg/sizes/w50_1to2.svg new file mode 100644 index 00000000..0b99c157 --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/sizes/w50_1to2.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/imageio/imageio-batik/src/test/resources/svg/sizes/w50h50.svg b/imageio/imageio-batik/src/test/resources/svg/sizes/w50h50.svg new file mode 100644 index 00000000..bb34679d --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/sizes/w50h50.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/imageio/imageio-batik/src/test/resources/svg/sizes/w50noview.svg b/imageio/imageio-batik/src/test/resources/svg/sizes/w50noview.svg new file mode 100644 index 00000000..888927a4 --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/sizes/w50noview.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/imageio/imageio-batik/src/test/resources/svg/style/test.css b/imageio/imageio-batik/src/test/resources/svg/style/test.css new file mode 100644 index 00000000..bf25007b --- /dev/null +++ b/imageio/imageio-batik/src/test/resources/svg/style/test.css @@ -0,0 +1,29 @@ +/* + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ + +.title { + font-family: Arial, Helvetica; + font-size: 16pt; + text-anchor: middle; +} +.legend { + font-family: Arial, Helvetica; + font-size: 10pt; + text-anchor: middle; +} diff --git a/imageio/imageio-bmp/license.txt b/imageio/imageio-bmp/license.txt index fe399516..e5f570e1 100755 --- a/imageio/imageio-bmp/license.txt +++ b/imageio/imageio-bmp/license.txt @@ -4,22 +4,24 @@ 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 "TwelveMonkeys" nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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 OWNER 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. \ No newline at end of file +* 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. diff --git a/imageio/imageio-bmp/pom.xml b/imageio/imageio-bmp/pom.xml index e4a24e22..aaa52103 100644 --- a/imageio/imageio-bmp/pom.xml +++ b/imageio/imageio-bmp/pom.xml @@ -4,12 +4,16 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-bmp TwelveMonkeys :: ImageIO :: BMP plugin ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format. + + com.twelvemonkeys.imageio.bmp + + com.twelvemonkeys.imageio @@ -19,6 +23,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/AbstractRLEDecoder.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/AbstractRLEDecoder.java index 1404df31..69676e68 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/AbstractRLEDecoder.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/AbstractRLEDecoder.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -38,7 +40,6 @@ import java.util.Arrays; /** * Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format. - *

* * @author Harald Kuhr * @version $Id: AbstractRLEDecoder.java#1 $ diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java index 9151dce6..290a07be 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java @@ -4,30 +4,54 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.io.DataInput; +import java.io.File; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Collections; +import java.util.Iterator; + +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.event.IIOReadUpdateListener; +import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; + import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.util.IIOUtil; @@ -37,30 +61,13 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.xml.XMLSerializer; -import javax.imageio.*; -import javax.imageio.event.IIOReadUpdateListener; -import javax.imageio.event.IIOReadWarningListener; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; -import javax.imageio.spi.ImageReaderSpi; -import javax.imageio.stream.ImageInputStream; -import java.awt.*; -import java.awt.color.ColorSpace; -import java.awt.image.*; -import java.io.DataInput; -import java.io.File; -import java.io.IOException; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Iterator; - /** * ImageReader for Microsoft Windows Bitmap (BMP) format. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: CURImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$ - * @see com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader + * @version $Id: BMPImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$ + * @see ICOImageReader */ public final class BMPImageReader extends ImageReaderBase { private long pixelOffset; @@ -123,6 +130,10 @@ public final class BMPImageReader extends ImageReaderBase { // Read DIB header header = DIBHeader.read(imageInput); + + if (pixelOffset < header.size + DIB.BMP_FILE_HEADER_SIZE) { + throw new IIOException("Invalid pixel offset: " + pixelOffset); + } } } @@ -194,7 +205,7 @@ public final class BMPImageReader extends ImageReaderBase { checkBounds(pImageIndex); // TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB - return Arrays.asList(getRawImageType(pImageIndex)).iterator(); + return Collections.singletonList(getRawImageType(pImageIndex)).iterator(); } @Override @@ -205,87 +216,56 @@ public final class BMPImageReader extends ImageReaderBase { throw new IIOException("Multiple planes not supported"); } - switch (header.getBitCount()) { - case 1: - case 2: - case 4: - case 8: - return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap()); + try { + switch (header.getBitCount()) { + case 1: + case 2: + case 4: + case 8: + return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap()); - case 16: - if (header.hasMasks()) { - int[] masks = getMasks(); + case 16: + if (header.hasMasks()) { + return ImageTypeSpecifiers.createPacked( + ColorSpace.getInstance(ColorSpace.CS_sRGB), + header.masks[0], header.masks[1], header.masks[2], header.masks[3], + DataBuffer.TYPE_USHORT, false + ); + } - return ImageTypeSpecifiers.createPacked( - ColorSpace.getInstance(ColorSpace.CS_sRGB), - masks[0], masks[1], masks[2], masks[3], - DataBuffer.TYPE_USHORT, false - ); - } + // Default if no mask is 555 + return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB); - // Default if no mask is 555 - return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB); + case 24: + if (header.getCompression() != DIB.COMPRESSION_RGB) { + throw new IIOException("Unsupported compression for RGB: " + header.getCompression()); + } - case 24: - if (header.getCompression() != DIB.COMPRESSION_RGB) { - throw new IIOException("Unsupported compression for RGB: " + header.getCompression()); - } + return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); - return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); + case 32: + if (header.hasMasks()) { + return ImageTypeSpecifiers.createPacked( + ColorSpace.getInstance(ColorSpace.CS_sRGB), + header.masks[0], header.masks[1], header.masks[2], header.masks[3], + DataBuffer.TYPE_INT, false + ); + } - case 32: - if (header.hasMasks()) { - int[] masks = getMasks(); + // Default if no mask + return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); - return ImageTypeSpecifiers.createPacked( - ColorSpace.getInstance(ColorSpace.CS_sRGB), - masks[0], masks[1], masks[2], masks[3], - DataBuffer.TYPE_INT, false - ); - } - - // Default if no mask - return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); - - case 0: - if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) { - return initReaderDelegate(header.getCompression()).getRawImageType(0); - } - default: - throw new IIOException("Unsupported bit count: " + header.getBitCount()); + case 0: + if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) { + return initReaderDelegate(header.getCompression()).getRawImageType(0); + } + default: + throw new IIOException("Unsupported bit count: " + header.getBitCount()); + } } - } - - private int[] getMasks() throws IOException { - if (header.masks != null) { - // Get mask and create either 555, 565 or 444/4444 etc - return header.masks; + catch (IllegalArgumentException e) { + throw new IIOException(e.getMessage(), e); } - - switch (header.getCompression()) { - case DIB.COMPRESSION_BITFIELDS: - case DIB.COMPRESSION_ALPHA_BITFIELDS: - // Consult BITFIELDS/ALPHA_BITFIELDS - return readBitFieldsMasks(); - default: - return null; - } - } - - private int[] readBitFieldsMasks() throws IOException { - long offset = DIB.BMP_FILE_HEADER_SIZE + header.getSize(); - - if (offset != imageInput.getStreamPosition()) { - imageInput.seek(offset); - } - - int[] masks = DIBHeader.readMasks(imageInput); - - if (header.getCompression() != DIB.COMPRESSION_ALPHA_BITFIELDS) { - masks[3] = 0; - } - - return masks; } @Override @@ -430,6 +410,13 @@ public final class BMPImageReader extends ImageReaderBase { private ImageReader initReaderDelegate(int compression) throws IOException { ImageReader reader = getImageReaderDelegate(compression); + reader.reset(); + + // Install listener + ListenerDelegator listenerDelegator = new ListenerDelegator(); + reader.addIIOReadWarningListener(listenerDelegator); + reader.addIIOReadProgressListener(listenerDelegator); + reader.addIIOReadUpdateListener(listenerDelegator); imageInput.seek(pixelOffset); reader.setInput(new SubImageInputStream(imageInput, header.getImageSize())); @@ -470,12 +457,6 @@ public final class BMPImageReader extends ImageReaderBase { ImageReader reader = readers.next(); - // Install listener - ListenerDelegator listenerDelegator = new ListenerDelegator(); - reader.addIIOReadWarningListener(listenerDelegator); - reader.addIIOReadProgressListener(listenerDelegator); - reader.addIIOReadUpdateListener(listenerDelegator); - // Cache for later use switch (compression) { case DIB.COMPRESSION_JPEG: @@ -648,12 +629,13 @@ public final class BMPImageReader extends ImageReaderBase { } // Why, oh why..? Instead of accepting it's own native format as it should, - // The BMPImageWriter only accepts instances of com.sun.imageio.plugins.bmp.BMPMetadata... + // The DIBImageWriter only accepts instances of com.sun.imageio.plugins.bmp.BMPMetadata... // TODO: Consider reflectively construct a BMPMetadata and inject fields return new BMPMetadata(header, colors); } - public static void main(String[] args) throws IOException { + @SuppressWarnings("ConstantConditions") + public static void main(String[] args) { BMPImageReaderSpi provider = new BMPImageReaderSpi(); BMPImageReader reader = new BMPImageReader(provider); @@ -706,7 +688,7 @@ public final class BMPImageReader extends ImageReaderBase { } } - @SuppressWarnings({"unchecked", "UnusedDeclaration"}) + @SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" }) static void throwAs(final Class pType, final Throwable pThrowable) throws T { throw (T) pThrowable; } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderSpi.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderSpi.java index 45a86b82..62e7f767 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderSpi.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderSpi.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriter.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriter.java new file mode 100644 index 00000000..3a2b9961 --- /dev/null +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriter.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2017, 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.bmp; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.plugins.bmp.BMPImageWriteParam; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteOrder; + +/** + * BMPImageWriter + */ +public final class BMPImageWriter extends DIBImageWriter { + protected BMPImageWriter(ImageWriterSpi provider) { + super(provider); + } + + @Override + public ImageWriteParam getDefaultWriteParam() { + // We can use the existing BMPImageWriteParam, as it's part of the javax.imageio API. + return new BMPImageWriteParam(getLocale()); + } + + @Override + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) { + // TODO + return null; + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) { + // TODO: Support both our own and the com.sun.. metadata + standard metadata + return null; + } + + @Override + public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException { + assertOutput(); + + if (image == null) { + throw new IllegalArgumentException("image may not be null"); + } + + if (image.hasRaster()) { + // TODO: The default BMPWriter seems to support this, consider doing so as well + throw new UnsupportedOperationException("image has a Raster!"); + } + + imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + + clearAbortRequest(); + processImageStarted(0); + + if (param == null) { + param = getDefaultWriteParam(); + } + + // Default to bottom-up + // TODO: top-down only allowed for RGB and BITFIELDS compressions (not RLE or PNG)! + // Though Windows seems to support top-down for RLE too... + final boolean isTopDown = param instanceof BMPImageWriteParam && ((BMPImageWriteParam) param).isTopDown(); +// int compression = DIB.COMPRESSION_ALPHA_BITFIELDS; // BMP can use BIFTFIELDS or ALPHA_BITFIELDS + int compression = DIB.COMPRESSION_RGB; + + // TODO: Fix + BufferedImage img = (BufferedImage) image.getRenderedImage(); + + int height = img.getHeight(); + int width = img.getWidth(); + + // Write File header + // TODO: Always use V4/V5 header, when writing with alpha, to avoid ambiguity + // TODO: Allow writing normal BITMAP_INFO_HEADER_SIZE with "fake" alpha as well? + int infoHeaderSize = DIB.BITMAP_INFO_HEADER_SIZE; + boolean hasExtraMasks = infoHeaderSize == DIB.BITMAP_INFO_HEADER_SIZE && (compression == DIB.COMPRESSION_BITFIELDS || compression == DIB.COMPRESSION_ALPHA_BITFIELDS); + // TODO: Allow writing without file header for ICO/CUR support + writeFileHeader(infoHeaderSize, DIB.BMP_FILE_HEADER_SIZE + infoHeaderSize + width * height * 4, hasExtraMasks); + writeDIBHeader(infoHeaderSize, img.getWidth(), img.getHeight(), isTopDown, img.getColorModel().getPixelSize(), compression); +// writeDIBHeader(infoHeaderSize, img, isTopDown, DIB.COMPRESSION_RGB); + + if (hasExtraMasks) { + imageOutput.writeInt(0x000000FF); // B + imageOutput.writeInt(0x0000FF00); // G + imageOutput.writeInt(0x00FF0000); // R + imageOutput.writeInt(0xFF000000); // A + } + + writeUncompressed(isTopDown, img, height, width); + + processImageComplete(); + } + + private void writeFileHeader(int infoHeaderSize, int fileSize, boolean hasMasks) throws IOException { + // 14 bytes + imageOutput.writeShort('M' << 8 | 'B'); + imageOutput.writeInt(fileSize + (hasMasks ? 16 : 0)); // File size (only known at this time if uncompressed!) + imageOutput.writeShort(0); // Reserved + imageOutput.writeShort(0); // Reserved + + imageOutput.writeInt(DIB.BMP_FILE_HEADER_SIZE + infoHeaderSize + (hasMasks ? 16 : 0)); // Offset to image data + } + + public static void main(String[] args) throws IOException { + File input = new File(args[0]); + File output = new File(args[0].replace('.', '_') + "_copy.bmp"); + + try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) { + DIBImageWriter writer = new BMPImageWriter(null); + writer.setOutput(stream); + writer.write(ImageIO.read(input)); + } + } + +} diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriterSpi.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriterSpi.java new file mode 100644 index 00000000..070c75d9 --- /dev/null +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriterSpi.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, 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.bmp; + +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; +import java.awt.image.BufferedImage; +import java.util.Locale; + +import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName; + +/** + * BMPImageWriterSpi + */ +public final class BMPImageWriterSpi extends ImageWriterSpiBase { + public BMPImageWriterSpi() { + super(new BMPProviderInfo()); + } + + @Override + public void onRegistration(ServiceRegistry registry, Class category) { + // Make sure we register BEHIND the built-in BMP writer + ImageWriterSpi sunSpi = lookupProviderByName(registry, "com.sun.imageio.plugins.bmp.BMPImageWriterSpi", ImageWriterSpi.class); + + if (sunSpi != null && sunSpi.getVendorName() != null) { + registry.setOrdering((Class) category, sunSpi, this); + } + } + + @Override + public boolean canEncodeImage(final ImageTypeSpecifier type) { + // TODO: Support more types, as time permits. + return type.getBufferedImageType() == BufferedImage.TYPE_4BYTE_ABGR; + } + + @Override + public BMPImageWriter createWriterInstance(final Object extension) { + return new BMPImageWriter(this); + } + + @Override + public String getDescription(final Locale locale) { + return "Windows Device Independent Bitmap Format (BMP) Writer"; + } +} diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java index 1988d376..8f7bb80a 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -168,87 +170,90 @@ final class BMPMetadata extends AbstractMetadata { protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression = new IIOMetadataNode("Compression"); IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null); - compressionTypeName.setAttribute("value", "NONE"); + + // TODO: Should the compression names always match the compression names used in the ImageWriteParam? + // OR should they be as standard as possible..? + // The built-in plugin uses "BI_RGB", "BI_RLE8", "BI_RLE4", "BI_BITFIELDS", "BI_JPEG and "BI_PNG" + switch (header.compression) { + case DIB.COMPRESSION_RLE4: + case DIB.COMPRESSION_RLE8: + compressionTypeName.setAttribute("value", "RLE"); + break; + case DIB.COMPRESSION_JPEG: + compressionTypeName.setAttribute("value", "JPEG"); + break; + case DIB.COMPRESSION_PNG: + compressionTypeName.setAttribute("value", "PNG"); + break; + case DIB.COMPRESSION_RGB: + case DIB.COMPRESSION_BITFIELDS: + case DIB.COMPRESSION_ALPHA_BITFIELDS: + default: + compressionTypeName.setAttribute("value", "NONE"); + break; + } return compression; -// switch (header.getImageType()) { -// case TGA.IMAGETYPE_COLORMAPPED_RLE: -// case TGA.IMAGETYPE_TRUECOLOR_RLE: -// case TGA.IMAGETYPE_MONOCHROME_RLE: -// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN: -// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE: -// IIOMetadataNode node = new IIOMetadataNode("Compression"); -// IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); -// -// // Compression can be RLE4, RLE8, PNG, JPEG or NONE -// String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE -// ? "Uknown" : "RLE"; -// compressionTypeName.setAttribute("value", value); -// node.appendChild(compressionTypeName); -// -// IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); -// lossless.setAttribute("value", "TRUE"); // TODO: Unless JPEG! -// node.appendChild(lossless); -// -// return node; -// default: -// // No compression -// return null; -// } } @Override protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); -// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); -// planarConfiguration.setAttribute("value", "PixelInterleaved"); -// node.appendChild(planarConfiguration); - -// IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); -// switch (header.getImageType()) { -// case TGA.IMAGETYPE_COLORMAPPED: -// case TGA.IMAGETYPE_COLORMAPPED_RLE: -// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN: -// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE: -// sampleFormat.setAttribute("value", "Index"); -// break; -// default: -// sampleFormat.setAttribute("value", "UnsignedIntegral"); -// break; -// } - -// node.appendChild(sampleFormat); - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); switch (header.getBitCount()) { + // TODO: case 0: determined by embedded format (PNG/JPEG) case 1: case 2: case 4: case 8: bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getBitCount()))); break; + case 16: - // TODO: Consult masks here! - bitsPerSample.setAttribute("value", createListValue(4, Integer.toString(4))); + // Default is 555 + bitsPerSample.setAttribute("value", header.hasMasks() + ? createBitsPerSampleForBitMasks() + : createListValue(3, Integer.toString(5))); break; + case 24: bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8))); break; + case 32: - bitsPerSample.setAttribute("value", createListValue(4, Integer.toString(8))); + // Default is 888 + bitsPerSample.setAttribute("value", header.hasMasks() + ? createBitsPerSampleForBitMasks() + : createListValue(3, Integer.toString(8))); + break; } node.appendChild(bitsPerSample); - // TODO: Do we need MSB? -// IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); -// sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0")); - return node; } + private String createBitsPerSampleForBitMasks() { + boolean hasAlpha = header.masks[3] != 0; + + return createListValue(hasAlpha ? 4 : 3, + Integer.toString(countMaskBits(header.masks[0])), Integer.toString(countMaskBits(header.masks[1])), + Integer.toString(countMaskBits(header.masks[2])), Integer.toString(countMaskBits(header.masks[3]))); + } + + private int countMaskBits(int mask) { + // See https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan + int count; + + for (count = 0; mask != 0; count++) { + mask &= mask - 1; // clear the least significant bit set + } + + return count; + } + private String createListValue(final int itemCount, final String... values) { StringBuilder buffer = new StringBuilder(); @@ -265,28 +270,31 @@ final class BMPMetadata extends AbstractMetadata { @Override protected IIOMetadataNode getStandardDimensionNode() { - if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) { - IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); + IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); - addChildNode(dimension, "PixelAspectRatio", null); - addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null); - addChildNode(dimension, "VerticalPhysicalPixelSpacing", null); + if (header.xPixelsPerMeter > 0 && header.yPixelsPerMeter > 0) { + float ratio = header.xPixelsPerMeter / (float) header.yPixelsPerMeter; + addChildNode(dimension, "PixelAspectRatio", null) + .setAttribute("value", String.valueOf(ratio)); -// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); -// -// if (header.topDown) { -// imageOrientation.setAttribute("value", "FlipH"); -// } -// else { -// imageOrientation.setAttribute("value", "Normal"); -// } -// -// dimension.appendChild(imageOrientation); + addChildNode(dimension, "HorizontalPixelSize", null) + .setAttribute("value", String.valueOf(1f / header.xPixelsPerMeter * 1000)); + addChildNode(dimension, "VerticalPixelSize", null) + .setAttribute("value", String.valueOf(1f / header.yPixelsPerMeter * 1000)); - return dimension; + // Hmmm.. The JRE version includes these for some reason, even if values seem to be same as default... + addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null) + .setAttribute("value", String.valueOf(0)); + addChildNode(dimension, "VerticalPhysicalPixelSpacing", null) + .setAttribute("value", String.valueOf(0)); } - return null; + if (header.topDown) { + addChildNode(dimension, "ImageOrientation", null) + .setAttribute("value", "FlipH"); // For BMP, bottom-up is "normal"... + } + + return dimension; } // No document node @@ -297,16 +305,15 @@ final class BMPMetadata extends AbstractMetadata { @Override protected IIOMetadataNode getStandardTransparencyNode() { - return null; + if (header.hasMasks() && header.masks[3] != 0) { + IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); + IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); + alpha.setAttribute("value", "nonpremultiplied"); + transparency.appendChild(alpha); -// IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); -// -// IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); -// -// // TODO: Consult masks -// alpha.setAttribute("value", header.getBitCount() == 32 ? "nonpremultiplied" : "none"); -// transparency.appendChild(alpha); -// -// return transparency; + return transparency; + } + + return null; } } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfo.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfo.java index 9afcf685..40d875ad 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfo.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfo.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.bmp; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; @@ -10,22 +40,24 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; * @version $Id: BMPProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ */ final class BMPProviderInfo extends ReaderWriterProviderInfo { - protected BMPProviderInfo() { + BMPProviderInfo() { super( BMPProviderInfo.class, new String[] {"bmp", "BMP"}, new String[] {"bmp", "rle"}, new String[] { "image/bmp", - "image/x-bmp" -// "image/vnd.microsoft.bitmap", // TODO: Official IANA MIME + "image/x-bmp", + "image/vnd.microsoft.bitmap" }, "com.twelvemonkeys.imageio.plugins.bmp.BMPImageReader", new String[] {"com.twelvemonkeys.imageio.plugins.bmp.BMPImageReaderSpi"}, - "com.sun.imageio.plugins.bmp.BMPImageWriter", - new String[]{"com.sun.imageio.plugins.bmp.BMPImageWriterSpi"}, // We support the same native metadata format - false, null, null, null, null, - true, BMPMetadata.nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat", null, null + "com.twelvemonkeys.imageio.plugins.bmp.BMPImageWriter", + new String[] {"com.twelvemonkeys.imageio.plugins.bmp.BMPImageWriterSpi"}, + false, null, null, + null, null, + true, BMPMetadata.nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat", + null, null ); } } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java index a5a00f6a..ff8e80b7 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java @@ -4,33 +4,35 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; -import com.twelvemonkeys.lang.Validate; +import static com.twelvemonkeys.lang.Validate.notNull; import java.awt.image.BufferedImage; +import java.io.IOException; /** * Describes a bitmap structure. @@ -46,14 +48,11 @@ abstract class BitmapDescriptor { protected BitmapMask mask; public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) { - Validate.notNull(pEntry, "entry"); - Validate.notNull(pHeader, "header"); - - entry = pEntry; - header = pHeader; + entry = notNull(pEntry, "entry");; + header = notNull(pHeader, "header"); } - abstract public BufferedImage getImage(); + abstract public BufferedImage getImage() throws IOException; public final int getWidth() { return entry.getWidth(); diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java index cb2c413a..0d6417af 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java @@ -4,28 +4,29 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; import java.awt.image.BufferedImage; @@ -162,6 +163,7 @@ class BitmapIndexed extends BitmapDescriptor { return transparent; } + @Override public BufferedImage getImage() { if (image == null) { image = createImageIndexed(); diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java index 5d619cf6..526c3dab 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java index 05f43600..d3f74d7c 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -44,6 +46,7 @@ class BitmapRGB extends BitmapDescriptor { super(pEntry, pHeader); } + @Override public BufferedImage getImage() { // Test is mask != null rather than hasMask(), as 32 bit (w/alpha) // might still have bitmask, but we don't read or use it. diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapUnsupported.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapUnsupported.java index 7498976e..2c070378 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapUnsupported.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapUnsupported.java @@ -4,31 +4,36 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.imageio.IIOException; /** * Represents bitmap structures we can't read. @@ -40,13 +45,14 @@ import java.awt.image.BufferedImage; class BitmapUnsupported extends BitmapDescriptor { private String message; - public BitmapUnsupported(final DirectoryEntry pEntry, final String pMessage) { - super(pEntry, null); + public BitmapUnsupported(final DirectoryEntry pEntry, DIBHeader header, final String pMessage) { + super(pEntry, header); message = pMessage; } - public BufferedImage getImage() { - throw new IllegalStateException(message); + @Override + public BufferedImage getImage() throws IOException { + throw new IIOException(message); } } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReader.java index 14eafe78..1cb5dd65 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReader.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderSpi.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderSpi.java index 10e9fc81..b3693a4a 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderSpi.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderSpi.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java index 6444d5a6..9421ceea 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -38,7 +40,7 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; * @version $Id: CURProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ */ final class CURProviderInfo extends ReaderWriterProviderInfo { - protected CURProviderInfo() { + CURProviderInfo() { super( CURProviderInfo.class, new String[]{"cur", "CUR"}, diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIB.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIB.java index 1384ab92..1cfe02a3 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIB.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIB.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBHeader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBHeader.java index cf696f51..04c3e4cd 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBHeader.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBHeader.java @@ -4,34 +4,38 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; -import javax.imageio.IIOException; import java.io.DataInput; +import java.io.DataOutput; import java.io.IOException; +import javax.imageio.IIOException; + /** * Represents the DIB (Device Independent Bitmap) Information header structure. * @@ -104,17 +108,15 @@ abstract class DIBHeader { case DIB.OS2_V2_HEADER_SIZE: return new BitmapCoreHeaderV2(); case DIB.BITMAP_INFO_HEADER_SIZE: + case DIB.BITMAP_V2_INFO_HEADER_SIZE: + case DIB.BITMAP_V3_INFO_HEADER_SIZE: // ICO and CUR always uses the Microsoft Windows 3.0 DIB header, which is 40 bytes. // This is also the most common format for persistent BMPs. return new BitmapInfoHeader(); - case DIB.BITMAP_V3_INFO_HEADER_SIZE: - return new BitmapV3InfoHeader(); case DIB.BITMAP_V4_INFO_HEADER_SIZE: return new BitmapV4InfoHeader(); case DIB.BITMAP_V5_INFO_HEADER_SIZE: return new BitmapV5InfoHeader(); - case DIB.BITMAP_V2_INFO_HEADER_SIZE: - throw new IIOException(String.format("Windows Bitmap Information Header (size: %s) not supported", pSize)); default: throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", pSize)); } @@ -159,7 +161,7 @@ abstract class DIBHeader { } public int getColorsUsed() { - return colorsUsed != 0 ? colorsUsed : 1 << bitCount; + return colorsUsed != 0 ? colorsUsed : 1 << Math.min(24, bitCount); } public int getColorsImportant() { @@ -187,9 +189,11 @@ abstract class DIBHeader { ); } - static int[] readMasks(final DataInput pStream) throws IOException { + private static int[] readMasks(final DataInput pStream, final boolean hasAlphaMask) throws IOException { + int maskCount = hasAlphaMask ? 4 : 3; int[] masks = new int[4]; - for (int i = 0; i < masks.length; i++) { + + for (int i = 0; i < maskCount; i++) { masks[i] = pStream.readInt(); } @@ -210,7 +214,7 @@ abstract class DIBHeader { // NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)! width = pStream.readUnsignedShort(); - height = pStream.readUnsignedShort(); + height = pStream.readShort(); if (height < 0) { height = -height; @@ -228,14 +232,16 @@ abstract class DIBHeader { /** * OS/2 BitmapCoreHeader Version 2. - *

+ *

* NOTE: According to the docs this header is variable size. * However, it seems that the size is either 16, 40 or 64, which is covered * (40 is the size of the normal {@link BitmapInfoHeader}, and has the same layout). + *

* * @see OS/2 Bitmap File Format Summary */ static final class BitmapCoreHeaderV2 extends DIBHeader { + @SuppressWarnings("unused") protected void read(final int pSize, final DataInput pStream) throws IOException { if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) { throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE)); @@ -287,15 +293,19 @@ abstract class DIBHeader { * Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure. * This is the common format for persistent DIB structures, even if Windows * may use the later versions at run-time. - *

+ *

+ * Note: Some variations that includes the mask fields into the header size exists, + * but is no longer part of the documented spec. + *

* * @author Harald Kuhr * @version $Id: DIBHeader.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ * @see BMP file format (Wikipedia) + * @see BITMAPV3INFOHEADER. */ static final class BitmapInfoHeader extends DIBHeader { protected void read(final int pSize, final DataInput pStream) throws IOException { - if (pSize != DIB.BITMAP_INFO_HEADER_SIZE) { + if (!(pSize == DIB.BITMAP_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V2_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V3_INFO_HEADER_SIZE)) { throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_INFO_HEADER_SIZE)); } @@ -320,51 +330,45 @@ abstract class DIBHeader { colorsUsed = pStream.readInt(); colorsImportant = pStream.readInt(); + + // Read masks if we have V2 or V3 + // or if we have compression BITFIELDS or ALPHA_BITFIELDS + if (size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) { + masks = readMasks(pStream, false); + } + else if (size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) { + masks = readMasks(pStream, true); + } + } + + void write(final DataOutput stream) throws IOException { + stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE); + + stream.writeInt(width); + stream.writeInt(topDown ? -height : height); + + stream.writeShort(planes); + stream.writeShort(bitCount); + stream.writeInt(compression); + + stream.writeInt(imageSize); + + stream.writeInt(xPixelsPerMeter); + stream.writeInt(yPixelsPerMeter); + + stream.writeInt(colorsUsed); + stream.writeInt(colorsImportant); + + // TODO: Write masks, if bitfields } public String getBMPVersion() { // This is to be compatible with the native metadata of the original com.sun....BMPMetadata - return compression == DIB.COMPRESSION_BITFIELDS ? "BMP v. 3.x NT" : "BMP v. 3.x"; - } - } - - /** - * Represents the semi-undocumented structure BITMAPV3INFOHEADER. - * @see BITMAPV3INFOHEADER - */ - static final class BitmapV3InfoHeader extends DIBHeader { - protected void read(final int pSize, final DataInput pStream) throws IOException { - if (pSize != DIB.BITMAP_V3_INFO_HEADER_SIZE) { - throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V3_INFO_HEADER_SIZE)); - } - - size = pSize; - - width = pStream.readInt(); - height = pStream.readInt(); - - if (height < 0) { - height = -height; - topDown = true; - } - - planes = pStream.readUnsignedShort(); - bitCount = pStream.readUnsignedShort(); - compression = pStream.readInt(); - - imageSize = pStream.readInt(); - - xPixelsPerMeter = pStream.readInt(); - yPixelsPerMeter = pStream.readInt(); - - colorsUsed = pStream.readInt(); - colorsImportant = pStream.readInt(); - - masks = readMasks(pStream); - } - - public String getBMPVersion() { - return "BMP v. 3.x Photoshop"; + return size > DIB.BITMAP_INFO_HEADER_SIZE + ? "BMP V2/V3 INFO" + : compression == DIB.COMPRESSION_BITFIELDS || compression == DIB.COMPRESSION_ALPHA_BITFIELDS + ? "BMP v. 3.x NT" + : "BMP v. 3.x"; } } @@ -399,7 +403,7 @@ abstract class DIBHeader { colorsUsed = pStream.readInt(); colorsImportant = pStream.readInt(); - masks = readMasks(pStream); + masks = readMasks(pStream, true); colorSpaceType = pStream.readInt(); // Should be 0 for V4 cieXYZEndpoints = new double[9]; @@ -451,7 +455,7 @@ abstract class DIBHeader { colorsUsed = pStream.readInt(); colorsImportant = pStream.readInt(); - masks = readMasks(pStream); + masks = readMasks(pStream, true); colorSpaceType = pStream.readInt(); diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java index 12d4e5fe..6591b35d 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java @@ -4,40 +4,32 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; -import com.twelvemonkeys.image.ImageUtil; -import com.twelvemonkeys.imageio.ImageReaderBase; -import com.twelvemonkeys.imageio.util.IIOUtil; -import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; -import com.twelvemonkeys.util.WeakWeakMap; - -import javax.imageio.*; -import javax.imageio.spi.ImageReaderSpi; -import javax.imageio.stream.ImageInputStream; -import javax.swing.*; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.event.WindowAdapter; @@ -45,16 +37,32 @@ import java.awt.event.WindowEvent; import java.awt.image.*; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteOrder; -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.swing.*; + +import com.twelvemonkeys.image.ImageUtil; +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; +import com.twelvemonkeys.util.WeakWeakMap; /** * ImageReader for Microsoft Windows ICO (icon) format. * 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit * true color support with alpha. Also supports Windows Vista PNG encoded icons. - *

* * @author Harald Kuhr * @version $Id: ICOImageReader.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ @@ -84,7 +92,7 @@ abstract class DIBImageReader extends ImageReaderBase { protected void resetMembers() { directory = null; - + headers.clear(); descriptors.clear(); @@ -234,8 +242,9 @@ abstract class DIBImageReader extends ImageReaderBase { ImageReader pngReader = getPNGReader(); imageInput.seek(pEntry.getOffset()); - InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize()); - ImageInputStream stream = ImageIO.createImageInputStream(inputStream); +// InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize()); +// ImageInputStream stream = ImageIO.createImageInputStream(inputStream); + ImageInputStream stream = new SubImageInputStream(imageInput, pEntry.getSize()); // NOTE: Will throw IOException on later reads if input is not PNG pngReader.setInput(stream); @@ -285,8 +294,8 @@ abstract class DIBImageReader extends ImageReaderBase { } // TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8 - if (header.getCompression() != 0) { - descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression())); + if (header.getCompression() != DIB.COMPRESSION_RGB) { + descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported compression: %d", header.getCompression())); } else { int bitCount = header.getBitCount(); @@ -314,7 +323,7 @@ abstract class DIBImageReader extends ImageReaderBase { break; default: - descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount)); + descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported bit count %d", bitCount)); } } @@ -354,7 +363,7 @@ abstract class DIBImageReader extends ImageReaderBase { } private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException { - int width = adjustToPadding(pBitmap.getWidth() >> 3); + int width = adjustToPadding((pBitmap.getWidth() + 7) >> 3); byte[] row = new byte[width]; for (int y = 0; y < pBitmap.getHeight(); y++) { @@ -388,7 +397,7 @@ abstract class DIBImageReader extends ImageReaderBase { } private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException { - int width = adjustToPadding(pBitmap.getWidth() >> 1); + int width = adjustToPadding((pBitmap.getWidth() + 1) >> 1); byte[] row = new byte[width]; for (int y = 0; y < pBitmap.getHeight(); y++) { @@ -456,13 +465,12 @@ abstract class DIBImageReader extends ImageReaderBase { } private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException { - // TODO: No idea if this actually works.. short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()]; // TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts // Will create TYPE_USHORT_555 DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F); - DataBuffer buffer = new DataBufferShort(pixels, pixels.length); + DataBuffer buffer = new DataBufferUShort(pixels, pixels.length); WritableRaster raster = Raster.createPackedRaster( buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null ); @@ -550,7 +558,7 @@ abstract class DIBImageReader extends ImageReaderBase { if (abortRequested()) { processReadAborted(); break; - } + } processImageProgress(100 * y / (float) pBitmap.getHeight()); } @@ -590,7 +598,7 @@ abstract class DIBImageReader extends ImageReaderBase { return directory.getEntry(pImageIndex); } - + /// Test code below, ignore.. :-) public static void main(final String[] pArgs) throws IOException { if (pArgs.length == 0) { @@ -700,10 +708,10 @@ abstract class DIBImageReader extends ImageReaderBase { } }); - button.setText("" + image.getWidth() + "x" + + button.setText(image.getWidth() + "x" + image.getHeight() + ": " + ((image.getColorModel() instanceof IndexColorModel) ? - "" + ((IndexColorModel) image.getColorModel()).getMapSize() : + String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) : "TrueColor")); pParent.add(button); diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageWriter.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageWriter.java new file mode 100644 index 00000000..f9a507cb --- /dev/null +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageWriter.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, 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.bmp; + +import com.twelvemonkeys.imageio.ImageWriterBase; +import com.twelvemonkeys.imageio.plugins.bmp.DIBHeader.BitmapInfoHeader; + +import javax.imageio.IIOException; +import javax.imageio.spi.ImageWriterSpi; +import java.awt.image.*; +import java.io.IOException; +import java.nio.ByteOrder; + +/** + * DIBImageWriter + */ +abstract class DIBImageWriter extends ImageWriterBase { + + DIBImageWriter(ImageWriterSpi provider) { + super(provider); + } + + @Override + public void setOutput(Object output) { + super.setOutput(output); + if (imageOutput != null) { + imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } + } + + void writeDIBHeader(int infoHeaderSize, int width, int height, boolean isTopDown, int pixelSize, int compression) throws IOException { + switch (infoHeaderSize) { + case DIB.BITMAP_INFO_HEADER_SIZE: + BitmapInfoHeader header = new BitmapInfoHeader(); + // TODO: Consider a constructor/factory for this + header.width = width; + header.height = height; + header.topDown = isTopDown; + + header.planes = 1; // Always 1 plane + header.bitCount = pixelSize; + header.compression = compression; + + header.colorsUsed = 0; // Means 2 ^ bitCount + header.colorsImportant = 0; // Means all colors important + + header.imageSize = header.height * ((header.width * header.bitCount + 31) / 32) * 4; // Rows padded to 32 bit + + header.xPixelsPerMeter = 2835; // 72 DPI + header.yPixelsPerMeter = 2835; + + header.write(imageOutput); + break; + default: + throw new IIOException("Unsupported header size: " + infoHeaderSize); + } + } + + void writeUncompressed(boolean isTopDown, BufferedImage img, int height, int width) throws IOException { + if (img.getType() != BufferedImage.TYPE_4BYTE_ABGR) { + throw new IIOException("Only TYPE_4BYTE_ABGR supported"); + } + + // Support + // - TODO: IndexColorModel (ucompressed, RLE4, RLE8 or BI_PNG) + // - TODO: ComponentColorModel (1 channel gray, 3 channel BGR and 4 channel BGRA, uncompressed and RLE8? BI_BITFIELDS? BI_PNG? BI_JPEG?) + // - TODO: Packed/DirectColorModel (16 and 32 bit, BI_BITFIELDS, BI_PNG? BI_JPEG?) + + Raster raster = img.getRaster(); + WritableRaster rowRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, 1, width * 4, 4, new int[] {2, 1, 0, 3}, null); + byte[] row = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); + final int[] bandList = {2, 1, 0, 3}; + + for (int i = 0; i < height; i++) { + int line = isTopDown ? i : height - 1 - i; + rowRaster.setDataElements(0, 0, raster.createChild(0, line, width, 1, 0, 0, bandList)); + + imageOutput.write(row); + + if (abortRequested()) { + processWriteAborted(); + break; + } + + processImageProgress(100f * i / (float) height); + } + } +} diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/Directory.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/Directory.java index 7865dad6..988b65f7 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/Directory.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/Directory.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -35,7 +37,6 @@ import java.util.List; /** * Directory - *

* * @author Harald Kuhr * @version $Id: Directory.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DirectoryEntry.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DirectoryEntry.java index 5675d94f..47210114 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DirectoryEntry.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DirectoryEntry.java @@ -4,33 +4,38 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; import javax.imageio.IIOException; import java.awt.*; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; import java.io.DataInput; +import java.io.DataOutput; import java.io.IOException; /** @@ -42,13 +47,13 @@ import java.io.IOException; * @see Wikipedia */ abstract class DirectoryEntry { - private int width; - private int height; - private int colorCount; + int width; + int height; + int colorCount; int planes; int bitCount; - private int size; - private int offset; + int size; + int offset; DirectoryEntry() { } @@ -96,6 +101,18 @@ abstract class DirectoryEntry { offset = pStream.readInt(); } + void write(final DataOutput output) throws IOException { + output.writeByte(width % 256); + output.writeByte(height % 256); + output.writeByte(colorCount); + output.writeByte(0); // Reserved + output.writeShort(1); // Color planes 0 or 1 + output.writeShort(bitCount); + output.writeInt(size); // Size, depends on compression... + output.writeInt(offset); + + } + public String toString() { return String.format( "%s: width: %d, height: %d, colors: %d, planes: %d, bit count: %d, size: %d, offset: %d", @@ -160,5 +177,16 @@ abstract class DirectoryEntry { * Icon directory entry. */ static final class ICOEntry extends DirectoryEntry { + private ICOEntry() {} + + ICOEntry(final int width, final int height, final ColorModel colorModel, int size, final int offset) { + this.width = width; + this.height = height; + this.colorCount = colorModel instanceof IndexColorModel ? ((IndexColorModel) colorModel).getMapSize() : 0; + this.planes = 1; + this.bitCount = colorModel.getPixelSize(); + this.size = size; + this.offset = offset; + } } } \ No newline at end of file diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReader.java index 042ada42..88657bf1 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReader.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -47,5 +49,4 @@ public final class ICOImageReader extends DIBImageReader { protected ICOImageReader(final ImageReaderSpi pProvider) { super(pProvider); } - } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderSpi.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderSpi.java index 3ef75af0..e21f1937 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderSpi.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderSpi.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriteParam.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriteParam.java new file mode 100644 index 00000000..7d360506 --- /dev/null +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriteParam.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017, 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.bmp; + +import javax.imageio.ImageWriteParam; +import java.util.Locale; + +/** + * ICOImageWriteParam + */ +public final class ICOImageWriteParam extends ImageWriteParam { + public ICOImageWriteParam(final Locale locale) { + super(locale); + + // The names are consistent with those from the BMPImageWriteParam + compressionTypes = new String[] {"BI_RGB", "BI_RLE8", "BI_RLE4", "BI_PNG"}; + compressionType = compressionTypes[DIB.COMPRESSION_RGB]; + + canWriteCompressed = true; + } +} diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriter.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriter.java new file mode 100644 index 00000000..1f8709c3 --- /dev/null +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriter.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2017, 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.bmp; + +import com.twelvemonkeys.imageio.stream.SubImageOutputStream; +import com.twelvemonkeys.imageio.util.ProgressListenerBase; + +import javax.imageio.*; +import javax.imageio.event.IIOWriteWarningListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.RenderedImage; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +import static com.twelvemonkeys.imageio.plugins.bmp.DirectoryEntry.ICOEntry; + +/** + * ImageWriter implementation for Windows Icon (ICO) format. + */ +public final class ICOImageWriter extends DIBImageWriter { + + // TODO: Support appending/updating an existing ICO file? + // - canInsertImage/canRemoveImage + + private static final int ENTRY_SIZE = 16; + private static final int ICO_MAX_DIMENSION = 256; + private static final int INITIAL_ENTRY_COUNT = 8; + + private int sequenceIndex = -1; + + private ImageWriter pngDelegate; + + protected ICOImageWriter(final ImageWriterSpi provider) { + super(provider); + } + + @Override + protected void resetMembers() { + sequenceIndex = -1; + + if (pngDelegate != null) { + pngDelegate.dispose(); + pngDelegate = null; + } + } + + @Override + public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) { + return null; + } + + @Override + public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException { + prepareWriteSequence(streamMetadata); + writeToSequence(image, param); + endWriteSequence(); + } + + @Override + public boolean canWriteSequence() { + return true; + } + + @Override + public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException { + assertOutput(); + + if (sequenceIndex >= 0) { + throw new IllegalStateException("writeSequence already started"); + } + + writeICOHeader(); + + // Count: Needs to be updated for each new image + imageOutput.writeShort(0); + sequenceIndex = 0; + + // TODO: Allow passing the initial size of the directory in the stream metadata? + // - as this is much more efficient than growing... + // How do we update the "image directory" containing "image entries", + // and which must be written *before* the image data? + // - Allocate a block of N * 16 bytes + // - If image count % N > N, we need to move the first image backwards in the file and allocate another N items... + imageOutput.write(new byte[INITIAL_ENTRY_COUNT * ENTRY_SIZE]); // Allocate room for 8 entries for now + } + + @Override + public void endWriteSequence() throws IOException { + assertOutput(); + + if (sequenceIndex < 0) { + throw new IllegalStateException("prepareWriteSequence not called"); + } + + sequenceIndex = -1; + } + + @Override + public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException { + assertOutput(); + + if (sequenceIndex < 0) { + throw new IllegalStateException("prepareWriteSequence not called"); + } + + if (image.hasRaster()) { + throw new UnsupportedOperationException("Raster not supported"); + } + + if (sequenceIndex >= INITIAL_ENTRY_COUNT) { + growIfNecessary(); + } + + int width = image.getRenderedImage().getWidth(); + int height = image.getRenderedImage().getHeight(); + ColorModel colorModel = image.getRenderedImage().getColorModel(); + + // TODO: The output size may depend on the param (subsampling, source region, etc) + if (width > ICO_MAX_DIMENSION || height > ICO_MAX_DIMENSION) { + throw new IIOException(String.format("ICO maximum width or height (%d) exceeded", ICO_MAX_DIMENSION)); + } + + long imageOffset = imageOutput.getStreamPosition(); + + if (imageOffset > Integer.MAX_VALUE) { + throw new IIOException("ICO file too large"); + } + + // Uncompressed, RLE4/RLE8 or PNG compressed + boolean pngCompression = param != null && "BI_PNG".equals(param.getCompressionType()); + + processImageStarted(sequenceIndex); + + if (pngCompression) { + // NOTE: Embedding a PNG in a ICO is slightly different than a BMP with BI_PNG compression, + // so we'll just handle it directly + ImageWriter writer = getPNGDelegate(); + writer.setOutput(new SubImageOutputStream(imageOutput)); + writer.write(null, image, copyParam(param, writer)); + } + else { + RenderedImage img = image.getRenderedImage(); + // ICO needs height to include height of mask, even if mask isn't written + writeDIBHeader(DIB.BITMAP_INFO_HEADER_SIZE, img.getWidth(), img.getHeight() * 2, + false, img.getColorModel().getPixelSize(), DIB.COMPRESSION_RGB); + writeUncompressed(false, (BufferedImage) img, img.getWidth(), img.getHeight()); + // TODO: Write mask + imageOutput.write(new byte[((width * height + 31) / 32) * 4]); +// writeUncompressed(false, new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY), img.getWidth(), img.getHeight()); + } + + processImageComplete(); + + long nextPosition = imageOutput.getStreamPosition(); + + // Update count + imageOutput.seek(4); + imageOutput.writeShort(sequenceIndex + 1); + + // Write entry + int entryPosition = 6 + sequenceIndex * ENTRY_SIZE; + imageOutput.seek(entryPosition); + + long size = nextPosition - imageOffset; + writeEntry(width, height, colorModel, (int) size, (int) imageOffset); + + sequenceIndex++; + + imageOutput.seek(nextPosition); + } + + private void writeICOHeader() throws IOException { + if (imageOutput.getStreamPosition() != 0) { + throw new IllegalStateException("Stream already written to"); + } + + imageOutput.writeShort(0); + imageOutput.writeShort(DIB.TYPE_ICO); + imageOutput.flushBefore(imageOutput.getStreamPosition()); + } + + private void growIfNecessary() { + // TODO: Allow growing the directory index... + // Move the first icon to the back, update offset + throw new IllegalStateException(String.format("Maximum number of icons supported (%d) exceeded", INITIAL_ENTRY_COUNT)); + } + + @Override + public ImageWriteParam getDefaultWriteParam() { + return new ICOImageWriteParam(getLocale()); + } + + private ImageWriteParam copyParam(final ImageWriteParam param, ImageWriter writer) { + if (param == null) { + return null; + } + + ImageWriteParam writeParam = writer.getDefaultWriteParam(); + writeParam.setSourceSubsampling(param.getSourceXSubsampling(), param.getSourceYSubsampling(), param.getSubsamplingXOffset(), param.getSubsamplingYOffset()); + writeParam.setSourceRegion(param.getSourceRegion()); + writeParam.setSourceBands(param.getSourceBands()); + + return writeParam; + } + + private ImageWriter getPNGDelegate() { + if (pngDelegate == null) { + // There's always a PNG writer... + pngDelegate = ImageIO.getImageWritersByFormatName("PNG").next(); + pngDelegate.setLocale(getLocale()); + pngDelegate.addIIOWriteProgressListener(new ProgressListenerBase() { + @Override + public void imageProgress(ImageWriter source, float percentageDone) { + processImageProgress(percentageDone); + } + + @Override + public void writeAborted(ImageWriter source) { + processWriteAborted(); + } + }); + pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() { + @Override + public void warningOccurred(ImageWriter source, int imageIndex, String warning) { + processWarningOccurred(sequenceIndex, warning); + } + }); + } + + return pngDelegate; + } + + private void writeEntry(final int width, final int height, final ColorModel colorModel, int size, final int offset) throws IOException { + new ICOEntry(width, height, colorModel, size, offset) + .write(imageOutput); + } + + public static void main(String[] args) throws IOException { + boolean pngCompression = false; + int firstArg = 0; + + while (args.length > firstArg && args[firstArg].charAt(0) == '-') { + if (args[firstArg].equals("-p") || args[firstArg].equals("--png")) { + pngCompression = true; + } + + firstArg++; + } + + if (args.length - firstArg < 2) { + System.err.println("Usage: command [-p|--png] [...]"); + System.exit(1); + } + + try (ImageOutputStream out = ImageIO.createImageOutputStream(new File(args[firstArg++]))) { + ImageWriter writer = new ICOImageWriter(null); + writer.setOutput(out); + + ImageWriteParam param = writer.getDefaultWriteParam(); + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionType(pngCompression ? "BI_PNG" : "BI_RGB"); + + writer.prepareWriteSequence(null); + + for (int i = firstArg; i < args.length; i++) { + File inFile = new File(args[i]); + try (ImageInputStream input = ImageIO.createImageInputStream(inFile)) { + Iterator readers = ImageIO.getImageReaders(input); + + if (!readers.hasNext()) { + System.err.printf("Can't read %s\n", inFile.getAbsolutePath()); + } + else { + ImageReader reader = readers.next(); + reader.setInput(input); + for (int j = 0; j < reader.getNumImages(true); j++) { + IIOImage image = reader.readAll(j, null); + writer.writeToSequence(image, param); + } + } + } + } + + writer.endWriteSequence(); + writer.dispose(); + } + } +} diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriterSpi.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriterSpi.java new file mode 100644 index 00000000..bb216808 --- /dev/null +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriterSpi.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 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.bmp; + +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; + +import javax.imageio.ImageTypeSpecifier; +import java.awt.image.BufferedImage; +import java.util.Locale; + +/** + * ICOImageWriterSpi + */ +public final class ICOImageWriterSpi extends ImageWriterSpiBase { + public ICOImageWriterSpi() { + super(new ICOProviderInfo()); + } + + @Override + public boolean canEncodeImage(final ImageTypeSpecifier type) { + // TODO: Support more types, as time permits. + // NOTE: We do support more types, if writing using PNG compression + return type.getBufferedImageType() == BufferedImage.TYPE_4BYTE_ABGR; + } + + @Override + public ICOImageWriter createWriterInstance(final Object extension) { + return new ICOImageWriter(this); + } + + @Override + public String getDescription(final Locale locale) { + return "Windows Icon Format (ICO) Writer"; + } +} diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java index 9b29e1fc..e8c6e9d2 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2015, Harald Kuhr + * Copyright (c) 2017, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; @@ -31,14 +33,14 @@ package com.twelvemonkeys.imageio.plugins.bmp; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; /** - * CURProviderInfo. + * ICOProviderInfo. * * @author Harald Kuhr * @author last modified by $Author: harald.kuhr$ - * @version $Id: CURProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ + * @version $Id: ICOProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ */ final class ICOProviderInfo extends ReaderWriterProviderInfo { - protected ICOProviderInfo() { + ICOProviderInfo() { super( ICOProviderInfo.class, new String[]{"ico", "ICO"}, @@ -50,9 +52,12 @@ final class ICOProviderInfo extends ReaderWriterProviderInfo { }, "com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader", new String[] {"com.twelvemonkeys.imageio.plugins.bmp.ICOImageReaderSpi"}, + "com.twelvemonkeys.imageio.plugins.bmp.ICOImageWriter", + new String[] {"com.twelvemonkeys.imageio.plugins.bmp.ICOImageWriterSpi"}, + false, null, null, null, null, - false, null, null, null, null, - true, null, null, null, null + true, null, null, + null, null ); } } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4Decoder.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4Decoder.java index b3cef980..999b449d 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4Decoder.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4Decoder.java @@ -4,45 +4,45 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; -import java.io.InputStream; import java.io.IOException; -import java.nio.ByteBuffer; +import java.io.InputStream; import java.util.Arrays; /** * Implements 4 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format. - *

* * @author Harald Kuhr * @version $Id: RLE4Decoder.java#1 $ */ final class RLE4Decoder extends AbstractRLEDecoder { - final static int BIT_MASKS[] = {0xf0, 0x0f}; - final static int BIT_SHIFTS[] = {4, 0}; + final static int[] BIT_MASKS = {0xf0, 0x0f}; + final static int[] BIT_SHIFTS = {4, 0}; public RLE4Decoder(final int width) { super(width, 4); @@ -94,7 +94,7 @@ final class RLE4Decoder extends AbstractRLEDecoder { boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0; int packed = 0; - for (int i = 0; i < byte2; i++) { + for (int i = 0; i < byte2 && srcX / 2 < row.length; i++) { if (i % 2 == 0) { packed = checkEOF(stream.read()); } @@ -111,7 +111,7 @@ final class RLE4Decoder extends AbstractRLEDecoder { else { // Encoded mode // Replicate the two samples in byte2 as many times as byte1 says - for (int i = 0; i < byte1; i++) { + for (int i = 0; i < byte1 && srcX / 2 < row.length; i++) { row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]); srcX++; } diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8Decoder.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8Decoder.java index c4d38eab..bbe0a728 100644 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8Decoder.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8Decoder.java @@ -4,37 +4,38 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.bmp; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; /** * Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format. - *

* * @author Harald Kuhr * @version $Id: RLE8Decoder.java#1 $ @@ -93,7 +94,7 @@ final class RLE8Decoder extends AbstractRLEDecoder { // an additional padding byte is in the stream and must be skipped boolean paddingByte = (byte2 % 2) != 0; - while (byte2-- > 0) { + while (byte2-- > 0 && srcX < row.length) { row[srcX++] = (byte) checkEOF(stream.read()); } @@ -106,7 +107,7 @@ final class RLE8Decoder extends AbstractRLEDecoder { // Encoded mode // Replicate byte2 as many times as byte1 says byte value = (byte) byte2; - while (byte1-- > 0) { + while (byte1-- > 0 && srcX < row.length) { row[srcX++] = value; } } diff --git a/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi index f2a7b76e..b9e5486e 100755 --- a/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi +++ b/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -1,3 +1,32 @@ +# +# Copyright (c) 2017, 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. + com.twelvemonkeys.imageio.plugins.bmp.BMPImageReaderSpi com.twelvemonkeys.imageio.plugins.bmp.CURImageReaderSpi com.twelvemonkeys.imageio.plugins.bmp.ICOImageReaderSpi diff --git a/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 00000000..f41a5c4d --- /dev/null +++ b/imageio/imageio-bmp/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1,31 @@ +# +# Copyright (c) 2017, 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. + +com.twelvemonkeys.imageio.plugins.bmp.BMPImageWriterSpi +com.twelvemonkeys.imageio.plugins.bmp.ICOImageWriterSpi \ No newline at end of file diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java index 12646e9a..6c81bdd6 100755 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java @@ -1,32 +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.imageio.plugins.bmp; -import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; +import static org.junit.Assert.*; +import static org.junit.Assume.assumeNoException; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.spi.ImageReaderSpi; + import org.junit.Ignore; import org.junit.Test; import org.mockito.InOrder; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import javax.imageio.*; -import javax.imageio.event.IIOReadProgressListener; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataNode; -import javax.imageio.spi.ImageReaderSpi; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import static org.junit.Assert.*; -import static org.junit.Assume.assumeNoException; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.*; +import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; +import com.twelvemonkeys.xml.XMLSerializer; /** * BMPImageReaderTest @@ -36,6 +76,12 @@ import static org.mockito.Mockito.*; * @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ */ public class BMPImageReaderTest extends ImageReaderAbstractTest { + @Override + protected ImageReaderSpi createProvider() { + return new BMPImageReaderSpi(); + } + + @Override protected List getTestData() { return Arrays.asList( // BMP Suite "Good" @@ -112,27 +158,17 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest ); } - protected ImageReaderSpi createProvider() { - return new BMPImageReaderSpi(); - } - @Override - protected BMPImageReader createReader() { - return new BMPImageReader(createProvider()); - } - - protected Class getReaderClass() { - return BMPImageReader.class; - } - protected List getFormatNames() { return Collections.singletonList("bmp"); } + @Override protected List getSuffixes() { return Arrays.asList("bmp", "rle"); } + @Override protected List getMIMETypes() { return Collections.singletonList("image/bmp"); } @@ -146,7 +182,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest ImageTypeSpecifier rawType = reader.getRawImageType(0); - // As the JPEGImageReader we delegate to returns null for YCbCr, we'll have to do the same + // As the JPEGImageReader we delegate to may return null for YCbCr, we'll have to do the same if (rawType == null && data.getInput().toString().contains("jpeg")) { continue; } @@ -241,7 +277,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest } @Test - public void testAddIIOReadProgressListenerCallbacksJPEG() { + public void testAddIIOReadProgressListenerCallbacksJPEG() throws IOException { ImageReader reader = createReader(); TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24jpeg.bmp"), new Dimension(127, 64)); reader.setInput(data.getInputStream()); @@ -264,7 +300,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest } @Test - public void testAddIIOReadProgressListenerCallbacksPNG() { + public void testAddIIOReadProgressListenerCallbacksPNG() throws IOException { ImageReader reader = createReader(); TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24png.bmp"), new Dimension(127, 64)); reader.setInput(data.getInputStream()); @@ -287,7 +323,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest } @Test - public void testMetadataEqualsJRE() throws IOException, URISyntaxException { + public void testMetadataEqualsJRE() throws IOException { ImageReader jreReader; try { @SuppressWarnings("unchecked") @@ -306,6 +342,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest for (TestData data : getTestData()) { if (data.getInput().toString().contains("pal8offs")) { + // Skip: Contains extra bogus PaletteEntry nodes continue; } @@ -322,9 +359,10 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest System.err.println("WARNING: Reading " + data + " caused exception: " + e.getMessage()); continue; } + IIOMetadata jreMetadata = jreReader.getImageMetadata(0); - assertEquals(true, metadata.isStandardMetadataFormatSupported()); + assertTrue(metadata.isStandardMetadataFormatSupported()); assertEquals(jreMetadata.getNativeMetadataFormatName(), metadata.getNativeMetadataFormatName()); assertArrayEquals(jreMetadata.getExtraMetadataFormatNames(), metadata.getExtraMetadataFormatNames()); @@ -334,23 +372,24 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest String absolutePath = data.toString(); String localPath = absolutePath.substring(absolutePath.lastIndexOf("test-classes") + 12); + // TODO: blauesglas_16_bitmask444 fails BMP Version for 11+ Node expectedTree = jreMetadata.getAsTree(format); Node actualTree = metadata.getAsTree(format); -// try { + try { assertNodeEquals(localPath + " - " + format, expectedTree, actualTree); -// } -// catch (AssertionError e) { -// ByteArrayOutputStream expected = new ByteArrayOutputStream(); -// ByteArrayOutputStream actual = new ByteArrayOutputStream(); -// -// new XMLSerializer(expected, "UTF-8").serialize(expectedTree, false); -// new XMLSerializer(actual, "UTF-8").serialize(actualTree, false); -// -// assertEquals(e.getMessage(), new String(expected.toByteArray(), "UTF-8"), new String(actual.toByteArray(), "UTF-8")); -// -// throw e; -// } + } + catch (AssertionError e) { + ByteArrayOutputStream expected = new ByteArrayOutputStream(); + ByteArrayOutputStream actual = new ByteArrayOutputStream(); + + new XMLSerializer(expected, "UTF-8").serialize(expectedTree, false); + new XMLSerializer(actual, "UTF-8").serialize(actualTree, false); + + assertEquals(e.getMessage(), new String(expected.toByteArray(), StandardCharsets.UTF_8), new String(actual.toByteArray(), StandardCharsets.UTF_8)); + + throw e; + } } } } @@ -372,18 +411,27 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest NodeList expectedChildNodes = expected.getChildNodes(); NodeList actualChildNodes = actual.getChildNodes(); - assertEquals(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes), - expectedChildNodes.getLength(), actualChildNodes.getLength()); + assertTrue(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes), + expectedChildNodes.getLength() <= actualChildNodes.getLength()); for (int i = 0; i < expectedChildNodes.getLength(); i++) { Node expectedChild = expectedChildNodes.item(i); + Node actualChild = actualChildNodes.item(i); + for (int j = 0; j < actualChildNodes.getLength(); j++) { + if (actualChildNodes.item(j).getLocalName().equals(expectedChild.getLocalName())) { + actualChild = actualChildNodes.item(j); + break; + } + } + assertEquals(message + " node name differs", expectedChild.getLocalName(), actualChild.getLocalName()); assertNodeEquals(message + "/" + expectedChild.getLocalName(), expectedChild, actualChild); } } + @SuppressWarnings("RedundantIfStatement") private boolean excludeEqualValueTest(final Node expected) { if (expected.getLocalName().equals("ImageSize")) { // JRE metadata returns 0, even if known in reader... diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriterTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriterTest.java new file mode 100644 index 00000000..f437f95c --- /dev/null +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageWriterTest.java @@ -0,0 +1,30 @@ +package com.twelvemonkeys.imageio.plugins.bmp; + +import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest; + +import javax.imageio.spi.ImageWriterSpi; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.util.Collections; +import java.util.List; + +/** + * BMPImageWriterTest. + * + * @author Harald Kuhr + * @author last modified by : harald.kuhr$ + * @version : BMPImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$ + */ +public class BMPImageWriterTest extends ImageWriterAbstractTest { + @Override + protected ImageWriterSpi createProvider() { + return new BMPImageWriterSpi(); + } + + @Override + protected List getTestData() { + return Collections.singletonList( + new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR) + ); + } +} \ No newline at end of file diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfoTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfoTest.java index 97773065..d3dba961 100644 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfoTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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.bmp; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java index a40ba06d..6523c31b 100755 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java @@ -1,6 +1,37 @@ +/* + * 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.imageio.plugins.bmp; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Ignore; import org.junit.Test; @@ -23,6 +54,12 @@ import static org.junit.Assert.*; * @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ */ public class CURImageReaderTest extends ImageReaderAbstractTest { + @Override + protected ImageReaderSpi createProvider() { + return new CURImageReaderSpi(); + } + + @Override protected List getTestData() { return Arrays.asList( new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)), @@ -30,27 +67,17 @@ public class CURImageReaderTest extends ImageReaderAbstractTest ); } - protected ImageReaderSpi createProvider() { - return new CURImageReaderSpi(); - } - @Override - protected CURImageReader createReader() { - return new CURImageReader(); - } - - protected Class getReaderClass() { - return CURImageReader.class; - } - protected List getFormatNames() { return Collections.singletonList("cur"); } + @Override protected List getSuffixes() { return Collections.singletonList("cur"); } + @Override protected List getMIMETypes() { return Arrays.asList("image/vnd.microsoft.cursor", "image/cursor", "image/x-cursor"); } @@ -69,7 +96,7 @@ public class CURImageReaderTest extends ImageReaderAbstractTest assertNotNull("Hotspot for cursor not present", hotspot); // Image weirdness - assertTrue("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty != hotspot); + assertNotSame("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty, hotspot); assertTrue(String.format("Hotspot not a java.awt.Point: %s", hotspot.getClass()), hotspot instanceof Point); assertEquals(pExpected, hotspot); diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java index 61a814b2..3128da26 100644 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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.bmp; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java index 9c121287..a915625f 100755 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java @@ -1,6 +1,37 @@ +/* + * 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.imageio.plugins.bmp; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Ignore; import org.junit.Test; @@ -19,6 +50,12 @@ import java.util.List; * @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ */ public class ICOImageReaderTest extends ImageReaderAbstractTest { + @Override + protected ImageReaderSpi createProvider() { + return new ICOImageReaderSpi(); + } + + @Override protected List getTestData() { return Arrays.asList( new TestData( @@ -45,27 +82,17 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest ); } - protected ImageReaderSpi createProvider() { - return new ICOImageReaderSpi(); - } - @Override - protected ICOImageReader createReader() { - return new ICOImageReader(); - } - - protected Class getReaderClass() { - return ICOImageReader.class; - } - protected List getFormatNames() { return Collections.singletonList("ico"); } + @Override protected List getSuffixes() { return Collections.singletonList("ico"); } + @Override protected List getMIMETypes() { return Arrays.asList("image/vnd.microsoft.icon", "image/ico", "image/x-icon"); } diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriterTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriterTest.java new file mode 100644 index 00000000..fed5b45d --- /dev/null +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageWriterTest.java @@ -0,0 +1,34 @@ +package com.twelvemonkeys.imageio.plugins.bmp; + +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; + +/** + * ICOImageWriterTest. + * + * @author Harald Kuhr + * @author last modified by : harald.kuhr$ + * @version : ICOImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$ + */ +public class ICOImageWriterTest extends ImageWriterAbstractTest { + @Override + protected ImageWriterSpi createProvider() { + return new ICOImageWriterSpi(); + } + + @Override + protected List getTestData() { + return Arrays.asList( + new BufferedImage(8, 8, BufferedImage.TYPE_4BYTE_ABGR), + new BufferedImage(16, 16, BufferedImage.TYPE_4BYTE_ABGR), + new BufferedImage(32, 32, BufferedImage.TYPE_4BYTE_ABGR), + new BufferedImage(64, 64, BufferedImage.TYPE_4BYTE_ABGR), + new BufferedImage(128, 128, BufferedImage.TYPE_4BYTE_ABGR) + ); + } +} \ No newline at end of file diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java index 832d0ec9..caeba58c 100644 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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.bmp; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4DecoderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4DecoderTest.java index b3348a3c..44e739bd 100644 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4DecoderTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE4DecoderTest.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.plugins.bmp; import com.twelvemonkeys.io.enc.Decoder; diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8DecoderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8DecoderTest.java index e353d33d..c7597a69 100644 --- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8DecoderTest.java +++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/RLE8DecoderTest.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.plugins.bmp; import com.twelvemonkeys.io.enc.Decoder; diff --git a/imageio/imageio-clippath/license.txt b/imageio/imageio-clippath/license.txt index fe399516..e5f570e1 100755 --- a/imageio/imageio-clippath/license.txt +++ b/imageio/imageio-clippath/license.txt @@ -4,22 +4,24 @@ 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 "TwelveMonkeys" nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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 OWNER 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. \ No newline at end of file +* 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. diff --git a/imageio/imageio-clippath/pom.xml b/imageio/imageio-clippath/pom.xml index 45ba89f4..feb0c0be 100755 --- a/imageio/imageio-clippath/pom.xml +++ b/imageio/imageio-clippath/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-clippath TwelveMonkeys :: ImageIO :: Photoshop Path Support @@ -12,6 +12,10 @@ Photoshop Clipping Path Support. + + com.twelvemonkeys.imageio.clippath + + com.twelvemonkeys.imageio @@ -21,6 +25,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test com.twelvemonkeys.imageio diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java old mode 100644 new mode 100755 index fa9faeea..065fd864 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java @@ -1,242 +1,66 @@ /* - * Copyright (c) 2014, Harald Kuhr + * Copyright (c) 2020, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.path; -import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; - -import javax.imageio.IIOException; -import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.io.DataInput; -import java.io.EOFException; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static com.twelvemonkeys.lang.Validate.isTrue; -import static com.twelvemonkeys.lang.Validate.notNull; /** - * Creates a {@code Shape} object from an Adobe Photoshop Path resource. + * AdobePathBuilder. * - * @see Adobe Photoshop Path resource format - * @author Jason Palmer, itemMaster LLC - * @author Harald Kuhr + * @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release. */ public final class AdobePathBuilder { - final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.path.debug")); - private final DataInput data; + private final AdobePathReader delegate; /** - * Creates a path builder that will read its data from a {@code DataInput}, such as an - * {@code ImageInputStream}. - * The data length is assumed to be a multiple of 26. - * - * @param data the input to read data from. - * @throws java.lang.IllegalArgumentException if {@code data} is {@code null} + * @see AdobePathReader#AdobePathReader(DataInput) */ public AdobePathBuilder(final DataInput data) { - notNull(data, "data"); - this.data = data; + this.delegate = new AdobePathReader(data); } /** - * Creates a path builder that will read its data from a {@code byte} array. - * The array length must be a multiple of 26, and greater than 0. - * - * @param data the array to read data from. - * @throws java.lang.IllegalArgumentException if {@code data} is {@code null}, or not a multiple of 26. + * @see AdobePathReader#AdobePathReader(byte[]) */ public AdobePathBuilder(final byte[] data) { - this(new ByteArrayImageInputStream( - notNull(data, "data"), 0, - isTrue(data.length > 0 && data.length % 26 == 0, data.length, "data.length must be a multiple of 26: %d") - )); + this.delegate = new AdobePathReader(data); } /** - * Builds the path. - * - * @return the path - * @throws javax.imageio.IIOException if the input contains a bad path data. - * @throws IOException if a general I/O exception occurs during reading. + * @see AdobePathReader#readPath() */ public Path2D path() throws IOException { - List> subPaths = new ArrayList>(); - List currentPath = null; - int currentPathLength = 0; - - AdobePathSegment segment; - while ((segment = nextSegment()) != null) { - - if (DEBUG) { - System.out.println(segment); - } - - if (segment.selector == AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD || segment.selector == AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD) { - if (currentPath != null) { - if (currentPathLength != currentPath.size()) { - throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size())); - } - subPaths.add(currentPath); - } - - currentPath = new ArrayList(segment.length); - currentPathLength = segment.length; - } - else if (segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED - || segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED - || segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED - || segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) { - if (currentPath == null) { - throw new IIOException("Bad path, missing subpath length record"); - } - if (currentPath.size() >= currentPathLength) { - throw new IIOException(String.format("Bad path, expected %d segments, found%d", currentPathLength, currentPath.size())); - } - - currentPath.add(segment); - } - } - - // now add the last one - if (currentPath != null) { - if (currentPathLength != currentPath.size()) { - throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size())); - } - - subPaths.add(currentPath); - } - - // now we have collected the PathPoints now create a Shape. - return pathToShape(subPaths); - } - - /** - * The Correct Order... P1, P2, P3, P4, P5, P6 (Closed) moveTo(P1) - * curveTo(P1.cpl, P2.cpp, P2.ap); curveTo(P2.cpl, P3.cppy, P3.ap); - * curveTo(P3.cpl, P4.cpp, P4.ap); curveTo(P4.cpl, P5.cpp, P5.ap); - * curveTo(P5.cply, P6.cpp, P6.ap); curveTo(P6.cpl, P1.cpp, P1.ap); - * closePath() - */ - private Path2D pathToShape(final List> paths) { - GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD, paths.size()); - GeneralPath subpath = null; - - for (List points : paths) { - int length = points.size(); - - for (int i = 0; i < points.size(); i++) { - AdobePathSegment current = points.get(i); - - int step = i == 0 ? 0 : i == length - 1 ? 2 : 1; - - switch (step) { - // begin - case 0: { - subpath = new GeneralPath(Path2D.WIND_EVEN_ODD, length); - subpath.moveTo(current.apx, current.apy); - - if (length > 1) { - AdobePathSegment next = points.get((i + 1)); - subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy); - } - else { - subpath.lineTo(current.apx, current.apy); - } - - break; - } - // middle - case 1: { - AdobePathSegment next = points.get((i + 1)); // we are always guaranteed one more. - subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy); - - break; - } - // end - case 2: { - AdobePathSegment first = points.get(0); - - if (first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED || first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) { - subpath.curveTo(current.cplx, current.cply, first.cppx, first.cppy, first.apx, first.apy); - subpath.closePath(); - path.append(subpath, false); - } - else { - subpath.lineTo(current.apx, current.apy); - path.append(subpath, true); - } - - break; - } - } - } - } - - return path; - } - - private AdobePathSegment nextSegment() throws IOException { - // Each segment is 26 bytes - int selector; - try { - selector = data.readUnsignedShort(); - } - catch (EOFException eof) { - // No more data, we're done - return null; - } - - // Spec says Fill rule is ignored by Photoshop... Probably not.. ;-) - // TODO: Replace with switch + handle all types! - // TODO: ...or Move logic to AdobePathSegment? - if (selector == AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD || selector == AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD) { - int size = data.readUnsignedShort(); - // data.position(data.position() + 22); // Skip remaining - data.skipBytes(22); - return new AdobePathSegment(selector, size); - } - - return new AdobePathSegment( - selector, - readFixedPoint(data.readInt()), - readFixedPoint(data.readInt()), - readFixedPoint(data.readInt()), - readFixedPoint(data.readInt()), - readFixedPoint(data.readInt()), - readFixedPoint(data.readInt()) - ); - } - - private static double readFixedPoint(final int fixed) { - return ((double) fixed / 0x1000000); + return delegate.readPath(); } } diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java new file mode 100755 index 00000000..88e24d6e --- /dev/null +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2014-2020, 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.path; + +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; + +import javax.imageio.IIOException; +import java.awt.geom.Path2D; +import java.io.DataInput; +import java.io.EOFException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * Reads a {@code Shape} object from an Adobe Photoshop Path resource. + * + * @see Adobe Photoshop Path resource format + * @author Jason Palmer, itemMaster LLC + * @author Harald Kuhr + */ +public final class AdobePathReader { + static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.path.debug")); + + private final DataInput data; + + /** + * Creates a path reader that will read its data from a {@code DataInput}, + * such as an {@code ImageInputStream}. + * The data length is assumed to be a multiple of 26. + * + * @param data the input to read data from. + * @throws java.lang.IllegalArgumentException if {@code data} is {@code null} + */ + public AdobePathReader(final DataInput data) { + notNull(data, "data"); + this.data = data; + } + + /** + * Creates a path reader that will read its data from a {@code byte} array. + * The array length must be a multiple of 26, and greater than 0. + * + * @param data the array to read data from. + * @throws java.lang.IllegalArgumentException if {@code data} is {@code null}, or not a multiple of 26. + */ + public AdobePathReader(final byte[] data) { + this(new ByteArrayImageInputStream( + notNull(data, "data"), 0, + isTrue(data.length > 0 && data.length % 26 == 0, data.length, "data.length must be a multiple of 26: %d") + )); + } + + /** + * Builds the path by reading from the supplied input. + * + * @return the path + * @throws javax.imageio.IIOException if the input contains a bad path data. + * @throws IOException if a general I/O exception occurs during reading. + */ + public Path2D readPath() throws IOException { + List> subPaths = new ArrayList<>(); + List currentPath = null; + int currentPathLength = 0; + + AdobePathSegment segment; + while ((segment = nextSegment()) != null) { + + if (DEBUG) { + System.out.println(segment); + } + + if (segment.selector == AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD || segment.selector == AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD) { + if (currentPath != null) { + if (currentPathLength != currentPath.size()) { + throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size())); + } + subPaths.add(currentPath); + } + + currentPath = new ArrayList<>(segment.lengthOrRule); + currentPathLength = segment.lengthOrRule; + } + else if (segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED + || segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED + || segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED + || segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) { + if (currentPath == null) { + throw new IIOException("Bad path, missing subpath length record"); + } + if (currentPath.size() >= currentPathLength) { + throw new IIOException(String.format("Bad path, expected %d segments, found%d", currentPathLength, currentPath.size())); + } + + currentPath.add(segment); + } + } + + // now add the last one + if (currentPath != null) { + if (currentPathLength != currentPath.size()) { + throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size())); + } + + subPaths.add(currentPath); + } + + // We have collected the Path points, now create a Shape + return pathToShape(subPaths); + } + + /** + * The Correct Order... P1, P2, P3, P4, P5, P6 (Closed) moveTo(P1) + * curveTo(P1.cpl, P2.cpp, P2.ap); curveTo(P2.cpl, P3.cpp, P3.ap); + * curveTo(P3.cpl, P4.cpp, P4.ap); curveTo(P4.cpl, P5.cpp, P5.ap); + * curveTo(P5.cpl, P6.cpp, P6.ap); curveTo(P6.cpl, P1.cpp, P1.ap); + * closePath() + */ + private Path2D pathToShape(final List> paths) { + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD, paths.size()); + Path2D subpath = null; + + for (List points : paths) { + int length = points.size(); + + for (int i = 0; i < points.size(); i++) { + AdobePathSegment current = points.get(i); + + int step = i == 0 ? 0 : i == length - 1 ? 2 : 1; + + switch (step) { + // Begin + case 0: { + subpath = new Path2D.Float(Path2D.WIND_EVEN_ODD, length); + subpath.moveTo(current.apx, current.apy); + + if (length > 1) { + AdobePathSegment next = points.get((i + 1)); + subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy); + } + else { + subpath.lineTo(current.apx, current.apy); + } + + break; + } + // Middle + case 1: { + AdobePathSegment next = points.get((i + 1)); // We are always guaranteed one more. + subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy); + + break; + } + // End + case 2: { + AdobePathSegment first = points.get(0); + + if (first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED || first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) { + subpath.curveTo(current.cplx, current.cply, first.cppx, first.cppy, first.apx, first.apy); + subpath.closePath(); + path.append(subpath, false); + } + else { + subpath.lineTo(current.apx, current.apy); + path.append(subpath, true); + } + + break; + } + default: + throw new AssertionError(); + } + } + } + + return path; + } + + private AdobePathSegment nextSegment() throws IOException { + // Each segment is 26 bytes + int selector; + try { + selector = data.readUnsignedShort(); + } + catch (EOFException eof) { + // No more data, we're done + return null; + } + + switch (selector) { + case AdobePathSegment.INITIAL_FILL_RULE_RECORD: + case AdobePathSegment.PATH_FILL_RULE_RECORD: + // Spec says Fill rule is ignored by Photoshop, we'll read it anyway + case AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD: + case AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD: + int lengthOrRule = data.readUnsignedShort(); + data.skipBytes(22); + return new AdobePathSegment(selector, lengthOrRule); + default: + return new AdobePathSegment( + selector, + AdobePathSegment.fromFixedPoint(data.readInt()), + AdobePathSegment.fromFixedPoint(data.readInt()), + AdobePathSegment.fromFixedPoint(data.readInt()), + AdobePathSegment.fromFixedPoint(data.readInt()), + AdobePathSegment.fromFixedPoint(data.readInt()), + AdobePathSegment.fromFixedPoint(data.readInt()) + ); + } + } + +} diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java old mode 100644 new mode 100755 index c8d91449..bad9c479 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java @@ -1,34 +1,36 @@ /* - * Copyright (c) 2014, Harald Kuhr + * Copyright (c) 2014-2020, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.path; -import com.twelvemonkeys.lang.Validate; +import static com.twelvemonkeys.lang.Validate.isTrue; /** * Adobe path segment. @@ -38,17 +40,17 @@ import com.twelvemonkeys.lang.Validate; * @author Harald Kuhr */ final class AdobePathSegment { - public final static int CLOSED_SUBPATH_LENGTH_RECORD = 0; - public final static int CLOSED_SUBPATH_BEZIER_LINKED = 1; - public final static int CLOSED_SUBPATH_BEZIER_UNLINKED = 2; - public final static int OPEN_SUBPATH_LENGTH_RECORD = 3; - public final static int OPEN_SUBPATH_BEZIER_LINKED = 4; - public final static int OPEN_SUBPATH_BEZIER_UNLINKED = 5; - public final static int PATH_FILL_RULE_RECORD = 6; - public final static int CLIPBOARD_RECORD = 7; - public final static int INITIAL_FILL_RULE_RECORD = 8; + static final int CLOSED_SUBPATH_LENGTH_RECORD = 0; + static final int CLOSED_SUBPATH_BEZIER_LINKED = 1; + static final int CLOSED_SUBPATH_BEZIER_UNLINKED = 2; + static final int OPEN_SUBPATH_LENGTH_RECORD = 3; + static final int OPEN_SUBPATH_BEZIER_LINKED = 4; + static final int OPEN_SUBPATH_BEZIER_UNLINKED = 5; + static final int PATH_FILL_RULE_RECORD = 6; + static final int CLIPBOARD_RECORD = 7; + static final int INITIAL_FILL_RULE_RECORD = 8; - public final static String[] SELECTOR_NAMES = { + static final String[] SELECTOR_NAMES = { "Closed subpath length record", "Closed subpath Bezier knot, linked", "Closed subpath Bezier knot, unlinked", @@ -61,12 +63,18 @@ final class AdobePathSegment { }; final int selector; - final int length; + final int lengthOrRule; + // TODO: Consider keeping these in 8.24FP format + // Control point preceding knot final double cppy; final double cppx; + + // Anchor point final double apy; final double apx; + + // Control point leaving knot final double cply; final double cplx; @@ -77,11 +85,14 @@ final class AdobePathSegment { this(selector, -1, cppy, cppx, apy, apx, cply, cplx); } - AdobePathSegment(final int selector, final int length) { - this(selector, length, -1, -1, -1, -1, -1, -1); + AdobePathSegment(final int selector, final int lengthOrRule) { + this(isTrue(selector == CLOSED_SUBPATH_LENGTH_RECORD || selector == OPEN_SUBPATH_LENGTH_RECORD + || selector == PATH_FILL_RULE_RECORD || selector == INITIAL_FILL_RULE_RECORD, selector, "Expected path length or fill rule record (0/3 or 6/8): %s"), + lengthOrRule, + -1, -1, -1, -1, -1, -1); } - private AdobePathSegment(final int selector, final int length, + private AdobePathSegment(final int selector, final int lengthOrRule, final double cppy, final double cppx, final double apy, final double apx, final double cply, final double cplx) { @@ -89,27 +100,29 @@ final class AdobePathSegment { switch (selector) { case CLOSED_SUBPATH_LENGTH_RECORD: case OPEN_SUBPATH_LENGTH_RECORD: - Validate.isTrue(length >= 0, length, "Bad size: %d"); + isTrue(lengthOrRule >= 0, lengthOrRule, "Expected positive length: %d"); break; case CLOSED_SUBPATH_BEZIER_LINKED: case CLOSED_SUBPATH_BEZIER_UNLINKED: case OPEN_SUBPATH_BEZIER_LINKED: case OPEN_SUBPATH_BEZIER_UNLINKED: - Validate.isTrue( - cppx >= 0 && cppx <= 1 && cppy >= 0 && cppy <= 1, - String.format("Unexpected point: [%f, %f]", cppx ,cppy) + isTrue( + cppx >= -16 && cppx <= 16 && cppy >= -16 && cppy <= 16, + String.format("Expected point in range [-16...16]: (%f, %f)", cppx ,cppy) ); break; case PATH_FILL_RULE_RECORD: - case CLIPBOARD_RECORD: case INITIAL_FILL_RULE_RECORD: + isTrue(lengthOrRule == 0 || lengthOrRule == 1, lengthOrRule, "Expected rule (1 or 0): %d"); + break; + case CLIPBOARD_RECORD: break; default: - throw new IllegalArgumentException("Bad selector: " + selector); + throw new IllegalArgumentException("Unknown selector: " + selector); } this.selector = selector; - this.length = length; + this.lengthOrRule = lengthOrRule; this.cppy = cppy; this.cppx = cppx; this.apy = apy; @@ -118,6 +131,14 @@ final class AdobePathSegment { this.cplx = cplx; } + static int toFixedPoint(final double value) { + return (int) Math.round(value * 0x1000000); + } + + static double fromFixedPoint(final int fixed) { + return ((double) fixed / 0x1000000); + } + @Override public boolean equals(final Object other) { if (this == other) { @@ -137,7 +158,7 @@ final class AdobePathSegment { && Double.compare(that.cppx, cppx) == 0 && Double.compare(that.cppy, cppy) == 0 && selector == that.selector - && length == that.length; + && lengthOrRule == that.lengthOrRule; } @@ -146,7 +167,7 @@ final class AdobePathSegment { long tempBits; int result = selector; - result = 31 * result + length; + result = 31 * result + lengthOrRule; tempBits = Double.doubleToLongBits(cppy); result = 31 * result + (int) (tempBits ^ (tempBits >>> 32)); tempBits = Double.doubleToLongBits(cppx); @@ -168,13 +189,13 @@ final class AdobePathSegment { switch (selector) { case INITIAL_FILL_RULE_RECORD: case PATH_FILL_RULE_RECORD: - return String.format("Rule(selector=%s, rule=%d)", SELECTOR_NAMES[selector], length); + return String.format("Rule(selector=%s, rule=%d)", SELECTOR_NAMES[selector], lengthOrRule); case CLOSED_SUBPATH_LENGTH_RECORD: case OPEN_SUBPATH_LENGTH_RECORD: - return String.format("Len(selector=%s, totalPoints=%d)", SELECTOR_NAMES[selector], length); + return String.format("Len(selector=%s, length=%d)", SELECTOR_NAMES[selector], lengthOrRule); default: // fall-through } - return String.format("Pt(preX=%.3f, preY=%.3f, knotX=%.3f, knotY=%.3f, postX=%.3f, postY=%.3f, selector=%s)", cppx, cppy, apx, apy, cplx, cply, SELECTOR_NAMES[selector]); + return String.format("Pt(pre=(%.3f, %.3f), knot=(%.3f, %.3f), post=(%.3f, %.3f), selector=%s)", cppx, cppy, apx, apy, cplx, cply, SELECTOR_NAMES[selector]); } } diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java new file mode 100755 index 00000000..40abc4b2 --- /dev/null +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2020 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.path; + +import com.twelvemonkeys.imageio.metadata.psd.PSD; + +import java.awt.*; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.twelvemonkeys.imageio.path.AdobePathReader.DEBUG; +import static com.twelvemonkeys.imageio.path.AdobePathSegment.*; +import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * Writes a {@code Shape} object to an Adobe Photoshop Path or Path resource. + * + * @see Adobe Photoshop Path resource format + * @author Harald Kuhr + */ +public final class AdobePathWriter { + + // TODO: Might need to get hold of more real Photoshop samples to tune this threshold... + private static final double COLLINEARITY_THRESHOLD = 0.00000001; + + private final List segments; + + /** + * Creates an AdobePathWriter for the given path. + *

+ * NOTE: Photoshop paths are stored with the coordinates + * (0,0) representing the top left corner of the image, + * and (1,1) representing the bottom right corner, + * regardless of image dimensions. + *

+ * + * @param path A {@code Shape} instance that has {@link Path2D#WIND_EVEN_ODD WIND_EVEN_ODD} rule, + * is contained within the rectangle [x=0.0,y=0.0,w=1.0,h=1.0], and is closed. + * @throws IllegalArgumentException if {@code path} is {@code null}, + * the paths winding rule is not @link Path2D#WIND_EVEN_ODD} or + * the paths bounding box is outside [x=0.0,y=0.0,w=1.0,h=1.0] or + * the path is not closed. + */ + public AdobePathWriter(final Shape path) { + notNull(path, "path"); + isTrue(new Rectangle(0, 0, 1, 1).contains(path.getBounds2D()), path.getBounds2D(), "Path bounds must be within [x=0,y=0,w=1,h=1]: %s"); + + segments = pathToSegments(path.getPathIterator(null)); + } + + // TODO: Look at the API so that conversion both ways are aligned. The read part builds a path from List... + private static List pathToSegments(final PathIterator pathIterator) { + // TODO: Test if PS really ignores winding rule as documented... Otherwise we could support writing non-zero too. + isTrue(pathIterator.getWindingRule() == Path2D.WIND_EVEN_ODD, pathIterator.getWindingRule(), "Only even/odd winding rule supported: %d"); + + double[] coords = new double[6]; + AdobePathSegment prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, 0, 0, 0, 0); + + List subpath = new ArrayList<>(); + List segments = new ArrayList<>(); + segments.add(new AdobePathSegment(PATH_FILL_RULE_RECORD, 0)); + segments.add(new AdobePathSegment(INITIAL_FILL_RULE_RECORD, 0)); + + while (!pathIterator.isDone()) { + int segmentType = pathIterator.currentSegment(coords); + + if (DEBUG) { + System.out.println("segmentType: " + segmentType); + System.out.println("coords: " + Arrays.toString(coords)); + } + + // We write collinear points as linked segments + boolean collinear = isCollinear(prev.cppx, prev.cppy, prev.apx, prev.apy, coords[0], coords[1]); + + switch (segmentType) { + case PathIterator.SEG_MOVETO: + // TODO: What if we didn't close before the moveto? Start new segment here? + + // Dummy starting point, will be updated later + prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, coords[1], coords[0], 0, 0); + break; + + case PathIterator.SEG_LINETO: + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); + prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[1], coords[0], coords[1], coords[0], 0, 0); + break; + + case PathIterator.SEG_QUADTO: + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); + prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[3], coords[2], coords[3], coords[2], 0, 0); + break; + + case PathIterator.SEG_CUBICTO: + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); + prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[3], coords[2], coords[5], coords[4], 0, 0); + break; + + case PathIterator.SEG_CLOSE: + AdobePathSegment initial = subpath.get(0); + + if (initial.apx != prev.apx || initial.apy != prev.apy) { + // Line back to initial if last anchor point does not equal initial anchor + collinear = isCollinear(prev.cppx, prev.cppy, initial.apx, initial.apy, initial.apx, initial.apy); + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, initial.apy, initial.apx)); + prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, initial.apy, initial.apx, initial.apy, initial.apx, 0, 0); + } + + close(initial, prev, subpath, segments); + subpath.clear(); + + break; + } + + pathIterator.next(); + } + + // If subpath is not empty at this point, there was no close segment... + // Wrap up if coordinates match, otherwise throw exception + if (!subpath.isEmpty()) { + AdobePathSegment initial = subpath.get(0); + + if (initial.apx != prev.apx || initial.apy != prev.apy) { + throw new IllegalArgumentException("Path must be closed"); + } + + close(initial, prev, subpath, segments); + } + + return segments; + } + + private static void close(AdobePathSegment initial, AdobePathSegment prev, List subpath, List segments) { + // Replace initial point. + boolean collinear = isCollinear(prev.cppx, prev.cppy, initial.apx, initial.apy, initial.cplx, initial.cply); + subpath.set(0, new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, initial.apy, initial.apx, initial.cply, initial.cplx)); + + // Add to full path + segments.add(new AdobePathSegment(CLOSED_SUBPATH_LENGTH_RECORD, subpath.size())); + segments.addAll(subpath); + } + + private static boolean isCollinear(double x1, double y1, double x2, double y2, double x3, double y3) { + // Photoshop seems to write as linked if all points are the same.... + return (x1 == x2 && x2 == x3 && y1 == y2 && y2 == y3) || + (x1 != x2 || y1 != y2) && (x2 != x3 || y2 != y3) && + Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) <= COLLINEARITY_THRESHOLD; // With some slack... + + } + + /** + * Writes the path as a complete Adobe Photoshop clipping path resource to the given stream. + * + * @param resourceId the resource id, typically {@link PSD#RES_CLIPPING_PATH} (0x07D0). + * @param output the stream to write to. + * @throws IOException if an I/O exception happens during writing. + */ + public void writePathResource(int resourceId, final DataOutput output) throws IOException { + output.writeInt(PSD.RESOURCE_TYPE); + output.writeShort(resourceId); + output.writeShort(0); // Path name (Pascal string) empty + pad + output.writeInt(segments.size() * 26); // Resource size + + writePath(output); + } + + /** + * Writes the path as a set of Adobe Photoshop path segments to the given stream. + * + * @param output the stream to write to. + * @throws IOException if an I/O exception happens during writing. + */ + public void writePath(final DataOutput output) throws IOException { + if (DEBUG) { + System.out.println("segments: " + segments.size()); + System.out.println(segments); + } + + for (AdobePathSegment segment : segments) { + switch (segment.selector) { + case PATH_FILL_RULE_RECORD: + case INITIAL_FILL_RULE_RECORD: + // The first 26-byte path record contains a selector value of 6, path fill rule record. + // The remaining 24 bytes of the first record are zeroes. Paths use even/odd ruling. + output.writeShort(segment.selector); + output.write(new byte[24]); + break; + case OPEN_SUBPATH_LENGTH_RECORD: + case CLOSED_SUBPATH_LENGTH_RECORD: + output.writeShort(segment.selector); + output.writeShort(segment.lengthOrRule); // Subpath length + output.write(new byte[22]); + break; + default: + output.writeShort(segment.selector); + output.writeInt(toFixedPoint(segment.cppy)); + output.writeInt(toFixedPoint(segment.cppx)); + output.writeInt(toFixedPoint(segment.apy)); + output.writeInt(toFixedPoint(segment.apx)); + output.writeInt(toFixedPoint(segment.cply)); + output.writeInt(toFixedPoint(segment.cplx)); + break; + } + } + } + + /** + * Transforms the path to a byte array, containing a complete Adobe Photoshop path resource. + * + * @param resourceId the resource id, typically {@link PSD#RES_CLIPPING_PATH} (0x07D0). + * @return a new byte array, containing the clipping path resource. + */ + public byte[] writePathResource(int resourceId) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + try (DataOutputStream stream = new DataOutputStream(bytes)) { + writePathResource(resourceId, stream); + } + catch (IOException e) { + throw new AssertionError("ByteArrayOutputStream threw IOException", e); + } + + return bytes.toByteArray(); + } + + /** + * Transforms the path to a byte array, containing a set of Adobe Photoshop path segments. + * + * @return a new byte array, containing the path segments. + */ + public byte[] writePath() { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + try (DataOutputStream stream = new DataOutputStream(bytes)) { + writePath(stream); + } + catch (IOException e) { + throw new AssertionError("ByteArrayOutputStream threw IOException", e); + } + + return bytes.toByteArray(); + } +} diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java old mode 100644 new mode 100755 index 7ab5f441..ba4a3adf --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2014, Harald Kuhr + * Copyright (c) 2014-2020, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.path; @@ -41,22 +43,31 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream; -import javax.imageio.ImageIO; +import javax.imageio.*; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageInputStream; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; -import java.util.LinkedHashMap; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; import java.util.List; import java.util.Map; import static com.twelvemonkeys.lang.Validate.isTrue; import static com.twelvemonkeys.lang.Validate.notNull; +import static java.util.Arrays.asList; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; /** * Support for various Adobe Photoshop Path related operations: @@ -64,10 +75,11 @@ import static java.util.Collections.singletonList; *
  • Extract a path from an image input stream, {@link #readPath}
  • *
  • Apply a given path to a given {@code BufferedImage} {@link #applyClippingPath}
  • *
  • Read an image with path applied {@link #readClipped}
  • + *
  • Write an image with embedded path {@link #writeClipped}
  • * * * @see Adobe Photoshop Path resource format - * @see com.twelvemonkeys.imageio.path.AdobePathBuilder + * @see AdobePathReader * @author Jason Palmer, itemMaster LLC * @author Harald Kuhr * @author last modified by $Author: harald.kuhr$ @@ -88,7 +100,7 @@ public final class Paths { * @throws javax.imageio.IIOException if the input contains a bad path data. * @throws java.lang.IllegalArgumentException is {@code stream} is {@code null}. * - * @see com.twelvemonkeys.imageio.path.AdobePathBuilder + * @see AdobePathReader */ public static Path2D readPath(final ImageInputStream stream) throws IOException { notNull(stream, "stream"); @@ -97,7 +109,7 @@ public final class Paths { if (magic == PSD.RESOURCE_TYPE) { // This is a PSD Image Resource Block, we can parse directly - return buildPathFromPhotoshopResources(stream); + return readPathFromPhotoshopResources(stream); } else if (magic == PSD.SIGNATURE_8BPS) { // PSD version @@ -113,17 +125,21 @@ public final class Paths { long imageResourcesLen = stream.readUnsignedInt(); // Image resources - return buildPathFromPhotoshopResources(new SubImageInputStream(stream, imageResourcesLen)); + return readPathFromPhotoshopResources(new SubImageInputStream(stream, imageResourcesLen)); } else if (magic >>> 16 == JPEG.SOI && (magic & 0xff00) == 0xff00) { // JPEG version - Map> segmentIdentifiers = new LinkedHashMap<>(); - segmentIdentifiers.put(JPEG.APP13, singletonList("Photoshop 3.0")); - + Map> segmentIdentifiers = singletonMap(JPEG.APP13, singletonList("Photoshop 3.0")); List photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers); if (!photoshop.isEmpty()) { - return buildPathFromPhotoshopResources(new MemoryCacheImageInputStream(photoshop.get(0).data())); + InputStream data = null; + + for (JPEGSegment ps : photoshop) { + data = data == null ? ps.data() : new SequenceInputStream(data, ps.data()); + } + + return readPathFromPhotoshopResources(new MemoryCacheImageInputStream(data)); } } else if (magic >>> 16 == TIFF.BYTE_ORDER_MARK_BIG_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC @@ -135,7 +151,7 @@ public final class Paths { Entry photoshop = directory.getEntryById(TIFF.TAG_PHOTOSHOP); if (photoshop != null) { - return buildPathFromPhotoshopResources(new ByteArrayImageInputStream((byte[]) photoshop.getValue())); + return readPathFromPhotoshopResources(new ByteArrayImageInputStream((byte[]) photoshop.getValue())); } } @@ -154,17 +170,17 @@ public final class Paths { } } - private static Path2D buildPathFromPhotoshopResources(final ImageInputStream stream) throws IOException { + private static Path2D readPathFromPhotoshopResources(final ImageInputStream stream) throws IOException { Directory resourceBlocks = new PSDReader().read(stream); - if (AdobePathBuilder.DEBUG) { + if (AdobePathReader.DEBUG) { System.out.println("resourceBlocks: " + resourceBlocks); } - Entry resourceBlock = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH); + Entry pathResource = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH); - if (resourceBlock != null) { - return new AdobePathBuilder((byte[]) resourceBlock.getValue()).path(); + if (pathResource != null) { + return new AdobePathReader((byte[]) pathResource.getValue()).readPath(); } return null; @@ -252,9 +268,149 @@ public final class Paths { return applyClippingPath(clip, image); } + /** + * Writes the image along with a clipping path resource, in the given format, to the supplied output. + * The image is written to the + * {@code ImageOutputStream} starting at the current stream + * pointer, overwriting existing stream data from that point + * forward, if present. + *

    + * Note: As {@link ImageIO#write(RenderedImage, String, ImageOutputStream)}, this method does + * not close the output stream. + * It is the responsibility of the caller to close the stream, if desired. + *

    + *

    + * Implementation note: Only JPEG (using the "javax_imageio_jpeg_image_1.0" metadata format) and + * TIFF (using the "javax_imageio_tiff_image_1.0" or "com_sun_media_imageio_plugins_tiff_image_1.0" metadata formats) + * formats are currently supported. + *

    + * + * @param image the image to be written, may not be {@code null}. + * @param clipPath the clip path, may not be {@code null}. + * @param formatName the informal format name, may not be {@code null}. + * @param output the stream to write to, may not be {@code null}. + * + * @return {@code true} if the image was written, + * otherwise {@code false} (ie. no writer was found for the specified format). + * + * @exception IllegalArgumentException if any parameter is {@code null}. + * @exception IOException if an error occurs during writing. + */ + public static boolean writeClipped(final RenderedImage image, Shape clipPath, final String formatName, final ImageOutputStream output) throws IOException { + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + if (output == null) { + throw new IllegalArgumentException("output == null!"); + } + + ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image); + Iterator writers = ImageIO.getImageWriters(type, formatName); + + if (writers.hasNext()) { + ImageWriter writer = writers.next(); + + ImageWriteParam param = writer.getDefaultWriteParam(); + IIOMetadata metadata = writer.getDefaultImageMetadata(type, param); + List metadataFormats = asList(metadata.getMetadataFormatNames()); + + byte[] pathResource = new AdobePathWriter(clipPath).writePathResource(PSD.RES_CLIPPING_PATH); + + if (metadataFormats.contains("javax_imageio_tiff_image_1.0") || metadataFormats.contains("com_sun_media_imageio_plugins_tiff_image_1.0")) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionType("Deflate"); + + // Check if the format is that of the bundled TIFF writer, otherwise use JAI format + String metadataFormat = metadataFormats.contains("javax_imageio_tiff_image_1.0") + ? "javax_imageio_tiff_image_1.0" + : "com_sun_media_imageio_plugins_tiff_image_1.0"; // Fails in mergeTree, if not supported + IIOMetadataNode root = new IIOMetadataNode(metadataFormat); + IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD"); + + IIOMetadataNode pathField = new IIOMetadataNode("TIFFField"); + pathField.setAttribute("number", String.valueOf(TIFF.TAG_PHOTOSHOP)); + IIOMetadataNode pathValue = new IIOMetadataNode("TIFFUndefined"); // Use undefined for simplicity, could also use bytes + pathValue.setAttribute("value", arrayAsString(pathResource)); + + pathField.appendChild(pathValue); + ifd.appendChild(pathField); + root.appendChild(ifd); + + metadata.mergeTree(metadataFormat, root); + + writer.setOutput(output); + writer.write(null, new IIOImage(image, null, metadata), param); + + return true; + } + else if (metadataFormats.contains("javax_imageio_jpeg_image_1.0")) { + String metadataFormat = "javax_imageio_jpeg_image_1.0"; + IIOMetadataNode root = new IIOMetadataNode(metadataFormat); + + root.appendChild(new IIOMetadataNode("JPEGvariety")); + + IIOMetadataNode sequence = new IIOMetadataNode("markerSequence"); + + // App13/Photshop 3.0 + IIOMetadataNode unknown = new IIOMetadataNode("unknown"); + unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF)); + + byte[] identifier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII); + byte[] data = new byte[identifier.length + 1 + pathResource.length]; + System.arraycopy(identifier, 0, data, 0, identifier.length); + System.arraycopy(pathResource, 0, data, identifier.length + 1, pathResource.length); + + unknown.setUserObject(data); + + sequence.appendChild(unknown); + root.appendChild(sequence); + + metadata.mergeTree(metadataFormat, root); + + writer.setOutput(output); + writer.write(null, new IIOImage(image, null, metadata), param); + + return true; + } + // TODO: Else if PSD... Requires PSD write + new metadata format... + } + + return false; + } + + private static String arrayAsString(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + for (int i = 0; ; i++) { + builder.append(bytes[i]); + + if (i == bytes.length - 1) { + return builder.toString(); + } + + builder.append(","); // NOTE: The javax_imageio_tiff_image_1.0 format does not allow whitespace here... + } + } + // Test code public static void main(final String[] args) throws IOException, InterruptedException { - BufferedImage destination = readClipped(ImageIO.createImageInputStream(new File(args[0]))); + BufferedImage destination; + if (args.length == 1) { + // Embedded path + destination = readClipped(ImageIO.createImageInputStream(new File(args[0]))); + } + else { + // Separate path and image + try (ImageInputStream input = ImageIO.createImageInputStream(new File(args[1]))) { + destination = applyClippingPath(readPath(input), ImageIO.read(new File(args[0]))); + } + } File tempFile = File.createTempFile("clipped-", ".png"); tempFile.deleteOnExit(); @@ -268,5 +424,4 @@ public final class Paths { System.err.printf("%s not deleted\n", tempFile); } } - } diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java index 5ae16d25..ddd05dc5 100644 --- a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2014, 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.path; import org.junit.Test; @@ -13,6 +43,7 @@ import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; import static com.twelvemonkeys.imageio.path.PathsTest.readExpectedPath; import static org.junit.Assert.assertNotNull; +@SuppressWarnings("deprecation") public class AdobePathBuilderTest { @Test(expected = IllegalArgumentException.class) diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathReaderTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathReaderTest.java new file mode 100644 index 00000000..2837bbe9 --- /dev/null +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathReaderTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2020 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.path; + +import org.junit.Test; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.awt.geom.Path2D; +import java.io.DataInput; +import java.io.IOException; +import java.nio.ByteBuffer; + +import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; +import static com.twelvemonkeys.imageio.path.PathsTest.readExpectedPath; +import static org.junit.Assert.assertNotNull; + +public class AdobePathReaderTest { + + @Test(expected = IllegalArgumentException.class) + public void testCreateNullBytes() { + new AdobePathReader((byte[]) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateNull() { + new AdobePathReader((DataInput) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateEmpty() { + new AdobePathReader(new byte[0]); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateShortPath() { + new AdobePathReader(new byte[3]); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateImpossiblePath() { + new AdobePathReader(new byte[7]); + } + + @Test + public void testCreate() { + new AdobePathReader(new byte[52]); + } + + @Test + public void testNoPath() throws IOException { + Path2D path = new AdobePathReader(new byte[26]).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testShortPath() throws IOException { + byte[] data = new byte[26]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD); + buffer.putShort((short) 1); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testShortPathToo() throws IOException { + byte[] data = new byte[52]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD); + buffer.putShort((short) 2); + buffer.position(buffer.position() + 22); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testLongPath() throws IOException { + byte[] data = new byte[78]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD); + buffer.putShort((short) 1); + buffer.position(buffer.position() + 22); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + buffer.position(buffer.position() + 24); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testPathMissingLength() throws IOException { + byte[] data = new byte[26]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test + public void testSimplePath() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 34, length: 32598 + // Clipping path: offset: 31146, length: 1248 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/psd/grape_with_path.psd"); + stream.seek(34 + 31146); + byte[] data = new byte[1248]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + + assertNotNull(path); + assertPathEquals(path, readExpectedPath("/ser/grape-path.ser")); + } + + @Test + public void testComplexPath() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 16970, length: 11152 + // Clipping path: offset: 9250, length: 1534 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/tiff/big-endian-multiple-clips.tif"); + stream.seek(16970 + 9250); + byte[] data = new byte[1534]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + + assertNotNull(path); + assertPathEquals(path, readExpectedPath("/ser/multiple-clips.ser")); + } +} \ No newline at end of file diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java index fbdda65b..27988541 100644 --- a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2014, 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.path; import org.junit.Test; @@ -33,7 +63,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD, 42); assertEquals(AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD, segment.selector); - assertEquals(42, segment.length); + assertEquals(42, segment.lengthOrRule); assertEquals(-1, segment.cppx, 0); assertEquals(-1, segment.cppy, 0); assertEquals(-1, segment.apx, 0); @@ -52,7 +82,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD, 27); assertEquals(AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD, segment.selector); - assertEquals(27, segment.length); + assertEquals(27, segment.lengthOrRule); assertEquals(-1, segment.cppx, 0); assertEquals(-1, segment.cppy, 0); assertEquals(-1, segment.apx, 0); @@ -68,7 +98,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -83,8 +113,13 @@ public class AdobePathSegmentTest { } @Test(expected = IllegalArgumentException.class) - public void testCreateOpenLinkedRecordNegative() { - new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, -.5, -.5, 0, 0, 1, 1); + public void testCreateOpenLinkedRecordOutOfRangeNegative() { + new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, -16.1, -16.1, 0, 0, 1, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateOpenLinkedRecordOutOfRangePositive() { + new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, 16.1, 16.1, 0, 0, 1, 1); } @Test @@ -92,7 +127,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -108,8 +143,13 @@ public class AdobePathSegmentTest { @Test(expected = IllegalArgumentException.class) - public void testCreateOpenUnlinkedRecordNegative() { - new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, -.5, -.5, 0, 0, 1, 1); + public void testCreateOpenUnlinkedRecordOutOfRangeNegative() { + new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, -16.5, 0, 0, 0, 1, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateOpenUnlinkedRecorOutOfRangePositive() { + new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, 0, -17, 0, 0, 16.5, 1); } /// Closed subpath @@ -119,7 +159,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -134,8 +174,13 @@ public class AdobePathSegmentTest { } @Test(expected = IllegalArgumentException.class) - public void testCreateClosedLinkedRecordNegative() { - new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, -.5, -.5, 0, 0, 1, 1); + public void testCreateClosedLinkedRecordOutOfRangeNegative() { + new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, -16.5, -.5, 0, 0, 1, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateClosedLinkedRecordOutOfRangePositive() { + new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, .5, 16.5, 0, 0, 1, 1); } @Test @@ -143,7 +188,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -159,17 +204,22 @@ public class AdobePathSegmentTest { @Test(expected = IllegalArgumentException.class) - public void testCreateClosedUnlinkedRecordNegative() { - new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, -.5, -.5, 0, 0, 1, 1); + public void testCreateClosedUnlinkedRecordOutOfRangeNegative() { + new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, -.5, -16.5, 0, 0, 1, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateClosedUnlinkedRecordOutOfRangePositive() { + new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, 16.5, .5, 0, 0, 1, 1); } @Test public void testToStringRule() { - String string = new AdobePathSegment(AdobePathSegment.INITIAL_FILL_RULE_RECORD, 2).toString(); + String string = new AdobePathSegment(AdobePathSegment.INITIAL_FILL_RULE_RECORD, 0).toString(); assertTrue(string, string.startsWith("Rule")); assertTrue(string, string.contains("Initial")); assertTrue(string, string.contains("fill")); - assertTrue(string, string.contains("rule=2")); + assertTrue(string, string.contains("rule=0")); } @Test @@ -178,13 +228,13 @@ public class AdobePathSegmentTest { assertTrue(string, string.startsWith("Len")); assertTrue(string, string.contains("Closed")); assertTrue(string, string.contains("subpath")); - assertTrue(string, string.contains("totalPoints=2")); + assertTrue(string, string.contains("length=2")); string = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD, 42).toString(); assertTrue(string, string.startsWith("Len")); assertTrue(string, string.contains("Open")); assertTrue(string, string.contains("subpath")); - assertTrue(string, string.contains("totalPoints=42")); + assertTrue(string, string.contains("length=42")); } @Test diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathWriterTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathWriterTest.java new file mode 100644 index 00000000..5179ed1e --- /dev/null +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathWriterTest.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2020 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.path; + +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import java.awt.*; +import java.awt.geom.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import static com.twelvemonkeys.imageio.path.AdobePathSegment.*; +import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +/** + * AdobePathWriterTest. + * + * @author Harald Kuhr + * @author last modified by haraldk: harald.kuhr$ + * @version : AdobePathWriterTest.java,v 1.0 2020-01-02 harald.kuhr Exp$ + */ +public class AdobePathWriterTest { + + @Test(expected = IllegalArgumentException.class) + public void testCreateWriterNull() { + new AdobePathWriter(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWriterInvalid() { + new AdobePathWriter(new Path2D.Double(Path2D.WIND_NON_ZERO)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWriterOutOfBounds() { + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Double(.5, 0.5, 2, 2), false); + + new AdobePathWriter(path); + } + + @Test + public void testCreateWriterValid() { + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Double(.25, .25, .5, .5), false); + + new AdobePathWriter(path); + } + + @Test + public void testCreateWriterMulti() { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Float(.25f, .25f, .5f, .5f), false); + path.append(new Rectangle2D.Double(0, 0, 1, .5), false); + path.append(new Polygon(new int[] {1, 2, 0, 1}, new int[] {0, 2, 2, 0}, 4) + .getPathIterator(AffineTransform.getScaleInstance(1 / 2.0, 1 / 2.0)), false); + + new AdobePathWriter(path); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateNotClosed() { + GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.moveTo(.5, .5); + path.lineTo(1, .5); + path.curveTo(1, 1, 1, 1, .5, 1); + + new AdobePathWriter(path).writePath(); + } + + @Test + public void testCreateClosed() { + GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.moveTo(.5, .5); + path.lineTo(1, .5); + path.curveTo(1, 1, 1, 1, .5, 1); + path.closePath(); + + byte[] bytes = new AdobePathWriter(path).writePath(); + + assertEquals(6 * 26, bytes.length); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 1: 0, 0, 1, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + } + + @Test + public void testCreateImplicitClosed() { + GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.moveTo(.5, .5); + path.lineTo(1, .5); + path.curveTo(1, 1, 1, 1, .5, 1); + path.lineTo(.5, .5); + + byte[] bytes = new AdobePathWriter(path).writePath(); + + assertEquals(6 * 26, bytes.length); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 1: 0, 0, 1, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + + } + + @Test + public void testCreateDoubleClosed() { + GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.moveTo(.5, .5); + path.lineTo(1, .5); + path.curveTo(1, 1, 1, 1, .5, 1); + path.lineTo(.5, .5); + path.closePath(); + + byte[] bytes = new AdobePathWriter(path).writePath(); + + assertEquals(6 * 26, bytes.length); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 1: 0, 0, 1, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + } + + @Test + public void testWriteToStream() throws IOException { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Double(0, 0, 1, 1), false); + path.append(new Ellipse2D.Double(.5, .5, .5, .5), false); + path.append(new Ellipse2D.Double(.25, .25, .5, .5), false); + + AdobePathWriter pathCreator = new AdobePathWriter(path); + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (ImageOutputStream output = ImageIO.createImageOutputStream(byteStream)) { + pathCreator.writePath(output); + } + + assertEquals(17 * 26, byteStream.size()); + + byte[] bytes = byteStream.toByteArray(); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Elipse 1: 0, 0, 1, 1 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 57, 78, -68, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 0, -58, -79, 68, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 1, 0, 0, 0, 0, -58, -79, 68, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 0, 57, 78, -68}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -58, -79, 68, 0, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0, 0, 57, 78, -68, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, 0, 0, 0, 57, 78, -68, 0, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0, 0, -58, -79, 68}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Elipse 2: .5, .5, .5, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -100, -89, 94, 1, 0, 0, 0, 0, -64, 0, 0, 1, 0, 0, 0, 0, -29, 88, -94, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 1, 0, 0, 0, 0, -29, 88, -94, 1, 0, 0, 0, 0, -64, 0, 0, 1, 0, 0, 0, 0, -100, -89, 94}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -29, 88, -94, 0, -128, 0, 0, 0, -64, 0, 0, 0, -128, 0, 0, 0, -100, -89, 94, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -128, 0, 0, 0, -100, -89, 94, 0, -128, 0, 0, 0, -64, 0, 0, 0, -128, 0, 0, 0, -29, 88, -94}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Elipse32: .25, .25, .5, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 92, -89, 94, 0, -64, 0, 0, 0, -128, 0, 0, 0, -64, 0, 0, 0, -93, 88, -94, 0, -64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -64, 0, 0, 0, -93, 88, -94, 0, -64, 0, 0, 0, -128, 0, 0, 0, -64, 0, 0, 0, 92, -89, 94}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -93, 88, -94, 0, 64, 0, 0, 0, -128, 0, 0, 0, 64, 0, 0, 0, 92, -89, 94, 0, 64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 64, 0, 0, 0, 92, -89, 94, 0, 64, 0, 0, 0, -128, 0, 0, 0, 64, 0, 0, 0, -93, 88, -94}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + } + + @Test + public void testCreateArray() { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Rectangle2D.Double(0, 0, 1, .5), false); + path.append(new Rectangle2D.Double(.25, .25, .5, .5), false); + + AdobePathWriter pathCreator = new AdobePathWriter(path); + + byte[] bytes = pathCreator.writePath(); + + assertEquals(12 * 26, bytes.length); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 1: 0, 0, 1, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 0, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 2: .25, .25, .5, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, -64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 64, 0, 0, 0, -64, 0, 0, 0, 64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, 64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -64, 0, 0, 0, 64, 0, 0, 0, -64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + } + + @Test + public void testRoundtrip0() throws IOException { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Rectangle2D.Double(0, 0, 1, .5), false); + path.append(new Rectangle2D.Double(.25, .25, .5, .5), false); + + byte[] bytes = new AdobePathWriter(path).writePath(); + Path2D readPath = new AdobePathReader(new ByteArrayImageInputStream(bytes)).readPath(); + + assertEquals(path.getWindingRule(), readPath.getWindingRule()); + assertEquals(path.getBounds2D(), readPath.getBounds2D()); + + // TODO: Would be nice, but hard to do, as we convert all points to cubic... +// assertPathEquals(path, readPath); + } + + @Test + public void testRoundtrip1() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 34, length: 32598 + // Clipping path: offset: 31146, length: 1248 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/psd/grape_with_path.psd"); + stream.seek(34 + 31146); + byte[] data = new byte[1248]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + byte[] bytes = new AdobePathWriter(path).writePath(); + + Path2D readPath = new AdobePathReader(new ByteArrayImageInputStream(bytes)).readPath(); + assertEquals(path.getWindingRule(), readPath.getWindingRule()); + assertEquals(path.getBounds2D(), readPath.getBounds2D()); + + assertPathEquals(path, readPath); + + assertEquals(data.length, bytes.length); + + // Path segment 3 contains some unknown bits in the filler bytes, we'll ignore those... + cleanLengthRecords(data); + + assertEquals(formatSegments(data), formatSegments(bytes)); + assertArrayEquals(data, bytes); + } + + private static void cleanLengthRecords(byte[] data) { + for (int i = 0; i < data.length; i += 26) { + if (data[i + 1] == CLOSED_SUBPATH_LENGTH_RECORD) { + // Clean everything after record type and length field + for (int j = 4; j < 26; j++) { + data[i + j] = 0; + } + } + } + } + + private static String formatSegments(byte[] data) { + StringBuilder builder = new StringBuilder(data.length * 5); + + for (int i = 0; i < data.length; i += 26) { + builder.append(Arrays.toString(Arrays.copyOfRange(data, i, i + 26))).append('\n'); + } + + return builder.toString(); + } + + @Test + public void testRoundtrip2() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 16970, length: 11152 + // Clipping path: offset: 9250, length: 1534 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/tiff/big-endian-multiple-clips.tif"); + stream.seek(16970 + 9250); + byte[] data = new byte[1534]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + byte[] bytes = new AdobePathWriter(path).writePath(); + + Path2D readPath = new AdobePathReader(new ByteArrayImageInputStream(bytes)).readPath(); + assertEquals(path.getWindingRule(), readPath.getWindingRule()); + assertEquals(path.getBounds2D(), readPath.getBounds2D()); + + assertPathEquals(path, readPath); + + assertEquals(data.length, bytes.length); + + // Path segment 3 and 48 contains some unknown bits in the filler bytes, we'll ignore that: + cleanLengthRecords(data); + + assertEquals(formatSegments(data), formatSegments(bytes)); + assertArrayEquals(data, bytes); + } +} diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java index c7115f67..9f5b373b 100644 --- a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2014, 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.path; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; @@ -8,15 +38,18 @@ import org.junit.Test; import javax.imageio.ImageIO; import javax.imageio.spi.IIORegistry; import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; /** * PathsTest. @@ -95,12 +128,12 @@ public class PathsTest { } @Test(expected = IllegalArgumentException.class) - public void testApplyClippingPathNullPath() throws IOException { + public void testApplyClippingPathNullPath() { Paths.applyClippingPath(null, new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY)); } @Test(expected = IllegalArgumentException.class) - public void testApplyClippingPathNullSource() throws IOException { + public void testApplyClippingPathNullSource() { Paths.applyClippingPath(new GeneralPath(), null); } @@ -117,7 +150,7 @@ public class PathsTest { assertEquals(source.getWidth(), image.getWidth()); assertEquals(source.getHeight(), image.getHeight()); // Transparent - assertTrue(image.getColorModel().getTransparency() == Transparency.TRANSLUCENT); + assertEquals(Transparency.TRANSLUCENT, image.getColorModel().getTransparency()); // Corners (at least) should be transparent assertEquals(0, image.getRGB(0, 0)); @@ -131,8 +164,9 @@ public class PathsTest { // TODO: Mor sophisticated test that tests all pixels outside path... } + @SuppressWarnings("ConstantConditions") @Test(expected = IllegalArgumentException.class) - public void testApplyClippingPathNullDestination() throws IOException { + public void testApplyClippingPathNullDestination() { Paths.applyClippingPath(new GeneralPath(), new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), null); } @@ -179,7 +213,7 @@ public class PathsTest { assertEquals(857, image.getWidth()); assertEquals(1800, image.getHeight()); // Transparent - assertTrue(image.getColorModel().getTransparency() == Transparency.TRANSLUCENT); + assertEquals(Transparency.TRANSLUCENT, image.getColorModel().getTransparency()); // Corners (at least) should be transparent assertEquals(0, image.getRGB(0, 0)); @@ -200,34 +234,70 @@ public class PathsTest { } static Path2D readExpectedPath(final String resource) throws IOException { - ObjectInputStream ois = new ObjectInputStream(PathsTest.class.getResourceAsStream(resource)); - - try { + try (ObjectInputStream ois = new ObjectInputStream(PathsTest.class.getResourceAsStream(resource))) { return (Path2D) ois.readObject(); } catch (ClassNotFoundException e) { throw new IOException(e); } - finally { - ois.close(); - } } static void assertPathEquals(final Path2D expectedPath, final Path2D actualPath) { + assertNotNull("Expected path is null, check your tests...", expectedPath); + assertNotNull(actualPath); + PathIterator expectedIterator = expectedPath.getPathIterator(null); PathIterator actualIterator = actualPath.getPathIterator(null); + float[] expectedCoords = new float[6]; float[] actualCoords = new float[6]; - while(!actualIterator.isDone()) { + while(!expectedIterator.isDone()) { + assertFalse("Less points than expected", actualIterator.isDone()); + int expectedType = expectedIterator.currentSegment(expectedCoords); int actualType = actualIterator.currentSegment(actualCoords); - assertEquals(expectedType, actualType); - assertArrayEquals(expectedCoords, actualCoords, 0); + assertEquals("Unexpected segment type", expectedType, actualType); + assertArrayEquals("Unexpected coordinates", expectedCoords, actualCoords, 0); actualIterator.next(); expectedIterator.next(); } + + assertTrue("More points than expected", actualIterator.isDone()); + } + + @Test + public void testWriteJPEG() throws IOException { + Path2D originalPath = readExpectedPath("/ser/multiple-clips.ser"); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_3BYTE_BGR); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(bytes)) { + boolean written = Paths.writeClipped(image, originalPath, "JPEG", stream); + assertTrue(written); + } + assertTrue(bytes.size() > 1024); // Actual size may be plugin specific... + + Path2D actualPath = Paths.readPath(new ByteArrayImageInputStream(bytes.toByteArray())); + assertPathEquals(originalPath, actualPath); + } + + @Test + public void testWriteTIFF() throws IOException { + Path2D originalPath = readExpectedPath("/ser/grape-path.ser"); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(bytes)) { + boolean written = Paths.writeClipped(image, originalPath, "TIFF", stream); + assumeTrue(written); // TIFF support is optional + } + + assertTrue(bytes.size() > 1024); // Actual size may be plugin specific... + + Path2D actualPath = Paths.readPath(new ByteArrayImageInputStream(bytes.toByteArray())); + assertPathEquals(originalPath, actualPath); } } diff --git a/imageio/imageio-core/license.txt b/imageio/imageio-core/license.txt index 3d296265..22f6384e 100755 --- a/imageio/imageio-core/license.txt +++ b/imageio/imageio-core/license.txt @@ -4,25 +4,28 @@ 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 "TwelveMonkeys" nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +* 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. -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 OWNER 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. --- diff --git a/imageio/imageio-core/pom.xml b/imageio/imageio-core/pom.xml index 34e815db..9698b451 100644 --- a/imageio/imageio-core/pom.xml +++ b/imageio/imageio-core/pom.xml @@ -4,11 +4,15 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-core TwelveMonkeys :: ImageIO :: Core + + com.twelvemonkeys.imageio.core + + com.twelvemonkeys.common diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java index d8ff9607..1e8ba89c 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; @@ -128,7 +130,7 @@ public abstract class AbstractMetadata extends IIOMetadata implements Cloneable } throw new IllegalArgumentException( - String.format("Bad format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames)) + String.format("Unsupported format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames)) ); } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java index 90e16063..9ae42c28 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; @@ -70,12 +72,13 @@ public abstract class ImageReaderBase extends ImageReader { /** * Constructs an {@code ImageReader} and sets its * {@code originatingProvider} field to the supplied value. - *

    - *

    Subclasses that make use of extensions should provide a + *

    + * Subclasses that make use of extensions should provide a * constructor with signature {@code (ImageReaderSpi, * Object)} in order to retrieve the extension object. If * the extension object is unsuitable, an * {@code IllegalArgumentException} should be thrown. + *

    * * @param provider the {@code ImageReaderSpi} that is invoking this constructor, or {@code null}. */ @@ -203,9 +206,10 @@ public abstract class ImageReaderBase extends ImageReader { /** * Returns the {@code BufferedImage} to which decoded pixel data should be written. - *

    + *

    * As {@link javax.imageio.ImageReader#getDestination} but tests if the explicit destination * image (if set) is valid according to the {@code ImageTypeSpecifier}s given in {@code types}. + *

    * * @param param an {@code ImageReadParam} to be used to get * the destination image or image type, or {@code null}. @@ -261,8 +265,9 @@ public abstract class ImageReaderBase extends ImageReader { // - transferType is ok // - bands are ok // TODO: Test if color model is ok? - if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() && - specifier.getNumBands() <= dest.getSampleModel().getNumBands()) { + if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() + && Arrays.equals(specifier.getSampleModel().getSampleSize(), dest.getSampleModel().getSampleSize()) + && specifier.getNumBands() <= dest.getSampleModel().getNumBands()) { found = true; break; } @@ -308,8 +313,14 @@ public abstract class ImageReaderBase extends ImageReader { int destWidth = destRegion.x + destRegion.width; int destHeight = destRegion.y + destRegion.height; - if ((long) destWidth * destHeight > Integer.MAX_VALUE) { - throw new IllegalArgumentException(String.format("destination width * height > Integer.MAX_VALUE: %d", (long) destWidth * destHeight)); + long dimension = (long) destWidth * destHeight; + if (dimension > Integer.MAX_VALUE) { + throw new IIOException(String.format("destination width * height > Integer.MAX_VALUE: %d", dimension)); + } + + long size = dimension * imageType.getSampleModel().getNumDataElements(); + if (size > Integer.MAX_VALUE) { + throw new IIOException(String.format("destination width * height * samplesPerPixel > Integer.MAX_VALUE: %d", size)); } // Create a new image based on the type specifier @@ -320,9 +331,10 @@ public abstract class ImageReaderBase extends ImageReader { * Utility method for getting the area of interest (AOI) of an image. * The AOI is defined by the {@link javax.imageio.IIOParam#setSourceRegion(java.awt.Rectangle)} * method. - *

    + *

    * Note: If it is possible for the reader to read the AOI directly, such a * method should be used instead, for efficiency. + *

    * * @param pImage the image to get AOI from * @param pParam the param optionally specifying the AOI @@ -340,12 +352,14 @@ public abstract class ImageReaderBase extends ImageReader { * The subsampling is defined by the * {@link javax.imageio.IIOParam#setSourceSubsampling(int, int, int, int)} * method. - *

    + *

    * NOTE: This method does not take the subsampling offsets into * consideration. - *

    + *

    + *

    * Note: If it is possible for the reader to subsample directly, such a * method should be used instead, for efficiency. + *

    * * @param pImage the image to subsample * @param pParam the param optionally specifying subsampling @@ -427,6 +441,7 @@ public abstract class ImageReaderBase extends ImageReader { static final String ZOOM_IN = "zoom-in"; static final String ZOOM_OUT = "zoom-out"; static final String ZOOM_ACTUAL = "zoom-actual"; + static final String ZOOM_FIT = "zoom-fit"; private BufferedImage image; @@ -502,9 +517,20 @@ public abstract class ImageReaderBase extends ImageReader { private void setupActions() { // Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always... - bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0)); - bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0)); - bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0)); + bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN, + KeyStroke.getKeyStroke('+'), + KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + KeyStroke.getKeyStroke(KeyEvent.VK_ADD, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT, + KeyStroke.getKeyStroke('-'), + KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, + KeyStroke.getKeyStroke('0'), + KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + bindAction(new ZoomToFitAction("Zoom fit"), ZOOM_FIT, + KeyStroke.getKeyStroke('9'), + KeyStroke.getKeyStroke(KeyEvent.VK_9, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); bindAction(TransferHandler.getCopyAction(), (String) TransferHandler.getCopyAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); bindAction(TransferHandler.getPasteAction(), (String) TransferHandler.getPasteAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); @@ -521,6 +547,7 @@ public abstract class ImageReaderBase extends ImageReader { private JPopupMenu createPopupMenu() { JPopupMenu popup = new JPopupMenu(); + popup.add(getActionMap().get(ZOOM_FIT)); popup.add(getActionMap().get(ZOOM_ACTUAL)); popup.add(getActionMap().get(ZOOM_IN)); popup.add(getActionMap().get(ZOOM_OUT)); @@ -541,7 +568,7 @@ public abstract class ImageReaderBase extends ImageReader { addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group); addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group); background.addSeparator(); - ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE); + ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : new Color(0xFF6600)); chooseBackgroundAction.putValue(Action.SELECTED_KEY, backgroundPaint == defaultBG); addCheckBoxItem(chooseBackgroundAction, background, group); @@ -655,14 +682,41 @@ public abstract class ImageReaderBase extends ImageReader { } else { Icon current = getIcon(); - int w = (int) Math.max(Math.min(current.getIconWidth() * zoomFactor, image.getWidth() * 16), image.getWidth() / 16); - int h = (int) Math.max(Math.min(current.getIconHeight() * zoomFactor, image.getHeight() * 16), image.getHeight() / 16); + int w = Math.max(Math.min((int) (current.getIconWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 16); + int h = Math.max(Math.min((int) (current.getIconHeight() * zoomFactor), image.getHeight() * 16), image.getHeight() / 16); setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), w > image.getWidth() || h > image.getHeight())); } } } + private class ZoomToFitAction extends ZoomAction { + public ZoomToFitAction(final String name) { + super(name, -1); + } + + public void actionPerformed(final ActionEvent e) { + JComponent source = (JComponent) e.getSource(); + + if (source instanceof JMenuItem) { + JPopupMenu menu = (JPopupMenu) SwingUtilities.getAncestorOfClass(JPopupMenu.class, source); + source = (JComponent) menu.getInvoker(); + } + + Container container = SwingUtilities.getAncestorOfClass(JViewport.class, source); + + double ratioX = container.getWidth() / (double) image.getWidth(); + double ratioY = container.getHeight() / (double) image.getHeight(); + + double zoomFactor = Math.min(ratioX, ratioY); + + int w = Math.max(Math.min((int) (image.getWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 16); + int h = Math.max(Math.min((int) (image.getHeight() * zoomFactor), image.getHeight() * 16), image.getHeight() / 16); + + setIcon(new BufferedImageIcon(image, w, h, zoomFactor > 1)); + } + } + private static class ImageTransferable implements Transferable { private final BufferedImage image; @@ -681,7 +735,7 @@ public abstract class ImageReaderBase extends ImageReader { } @Override - public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException { + public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException { if (isDataFlavorSupported(flavor)) { return image; } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java index f6239068..f9e29d1b 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; @@ -58,12 +60,13 @@ public abstract class ImageWriterBase extends ImageWriter { * Constructs an {@code ImageWriter} and sets its * {@code originatingProvider} instance variable to the * supplied value. - *

    - *

    Subclasses that make use of extensions should provide a + *

    + * Subclasses that make use of extensions should provide a * constructor with signature {@code (ImageWriterSpi, * Object)} in order to retrieve the extension object. If * the extension object is unsuitable, an * {@code IllegalArgumentException} should be thrown. + *

    * * @param provider the {@code ImageWriterSpi} that is constructing this object, or {@code null}. */ @@ -100,9 +103,15 @@ public abstract class ImageWriterBase extends ImageWriter { } @Override - public void reset() { - super.reset(); + public void dispose() { resetMembers(); + super.dispose(); + } + + @Override + public void reset() { + resetMembers(); + super.reset(); } protected void resetMembers() { @@ -137,9 +146,10 @@ public abstract class ImageWriterBase extends ImageWriter { * Utility method for getting the area of interest (AOI) of an image. * The AOI is defined by the {@link javax.imageio.IIOParam#setSourceRegion(java.awt.Rectangle)} * method. - *

    - * Note: If it is possible for the reader to read the AOI directly, such a + *

    + * Note: If it is possible for the writer to write the AOI directly, such a * method should be used instead, for efficiency. + *

    * * @param pImage the image to get AOI from * @param pParam the param optionally specifying the AOI @@ -157,12 +167,14 @@ public abstract class ImageWriterBase extends ImageWriter { * The subsampling is defined by the * {@link javax.imageio.IIOParam#setSourceSubsampling(int, int, int, int)} * method. - *

    + *

    * NOTE: This method does not take the subsampling offsets into * consideration. - *

    - * Note: If it is possible for the reader to subsample directly, such a + *

    + *

    + * Note: If it is possible for the writer to subsample directly, such a * method should be used instead, for efficiency. + *

    * * @param pImage the image to subsample * @param pParam the param optionally specifying subsampling diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java index 3abe5f47..56648938 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.lang.Validate; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java index 2223ec77..7599d9d0 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CMYKColorSpace.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java index 2350fb4a..49665ba0 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; @@ -48,10 +50,11 @@ import java.util.Properties; /** * A helper class for working with ICC color profiles and color spaces. - *

    + *

    * Standard ICC color profiles are read from system-specific locations * for known operating systems. - *

    + *

    + *

    * 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. @@ -59,8 +62,10 @@ import java.util.Properties; * can be downloaded from * ICC, * Adobe or other places. - *

    + * *

    + *

    * Example property file: + *

    *
      * # icc_profiles.properties
      * ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
    @@ -98,13 +103,27 @@ public final class ColorSpaces {
         // Cache for the latest used color spaces
         private static final Map cache = new LRUHashMap<>(10);
     
    +    static {
    +        try {
    +            // Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
    +            ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
    +        }
    +        catch (Throwable disasters) {
    +            System.err.println("ICC Color Profile not properly activated due to the exception below.");
    +            System.err.println("Expect to see JDK-6986863 in action, and consider filing a bug report to your JRE provider.");
    +
    +            disasters.printStackTrace();
    +        }
    +    }
    +
         private ColorSpaces() {}
     
         /**
          * Creates an ICC color space from the given ICC color profile.
    -     * 

    + *

    * For standard Java color spaces, the built-in instance is returned. * Otherwise, color spaces are looked up from cache and created on demand. + *

    * * @param profile the ICC color profile. May not be {@code null}. * @return an ICC color space @@ -203,10 +222,14 @@ public final class ColorSpaces { } } - private static void validateColorSpace(ICC_ColorSpace cs) { - // Validate the color space, to avoid caching bad color spaces + 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[] {1f, 0f, 0f}); + cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f}); + + // This breaks *some times* after validation of bad profiles, + // we'll let it blow up early in this case + cs.getProfile().getData(); } /** @@ -226,11 +249,12 @@ public final class ColorSpaces { /** * Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}. - *

    + *

    * * 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}. * + *

    * * @param profile the ICC color profile. May not be {@code null}. * @return {@code true} if known to be offending, {@code false} otherwise @@ -258,11 +282,12 @@ public final class ColorSpaces { /** * Tests whether an ICC color profile is valid. * Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}. - *

    + *

    * * 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}. * + *

    * * @param profile the ICC color profile. May not be {@code null}. * @return {@code profile} if valid. @@ -279,9 +304,10 @@ public final class ColorSpaces { /** * Returns the color space specified by the given color space constant. - *

    + *

    * For standard Java color spaces, the built-in instance is returned. * Otherwise, color spaces are looked up from cache and created on demand. + *

    * * @param colorSpace the color space constant. * @return the {@link ColorSpace} specified by the color space constant. diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java index db3a1bd7..0f5eb4e4 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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 java.awt.*; @@ -7,20 +37,26 @@ import static com.twelvemonkeys.lang.Validate.notNull; /** * This class represents a hybrid between an {@link IndexColorModel} and a {@link ComponentColorModel}, - * having both a color map and a full, discrete alpha channel. + * having both a color map and a full, discrete alpha channel and/or one or more "extra" channels. * The color map entries are assumed to be fully opaque and should have no transparent index. *

    * ColorSpace will always be the default sRGB color space (as with {@code IndexColorModel}). *

    - * Component order is always P, A, where P is a palette index, and A is the alpha value. + * Component order is always I, A, X1, X2... Xn, + * where I is a palette index, A is the alpha value and Xn are extra samples (ignored for display). * * @see IndexColorModel * @see ComponentColorModel */ +// TODO: ExtraSamplesIndexColorModel might be a better name? +// TODO: Allow specifying which channel is the transparency mask? public final class DiscreteAlphaIndexColorModel extends ColorModel { // Our IndexColorModel delegate private final IndexColorModel icm; + private final int samples; + private final boolean hasAlpha; + /** * Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups * to the given {@code IndexColorModel}. @@ -29,13 +65,34 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel { * fully opaque, any transparency or transparent index will be ignored. */ public DiscreteAlphaIndexColorModel(final IndexColorModel icm) { + this(icm, 1, true); + } + + /** + * Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups + * to the given {@code IndexColorModel}. + * + * @param icm The {@code IndexColorModel} delegate. Color map entries are assumed to be + * fully opaque, any transparency or transparent index will be ignored. + * @param extraSamples the number of extra samples in the color model. + * @param hasAlpha {@code true} if the extra samples contains alpha, otherwise {@code false}. + */ + public DiscreteAlphaIndexColorModel(final IndexColorModel icm, int extraSamples, boolean hasAlpha) { super( - notNull(icm, "IndexColorModel").getPixelSize() * 2, + notNull(icm, "IndexColorModel").getPixelSize() * (1 + extraSamples), new int[] {icm.getPixelSize(), icm.getPixelSize(), icm.getPixelSize(), icm.getPixelSize()}, - icm.getColorSpace(), true, false, Transparency.TRANSLUCENT, icm.getTransferType() + icm.getColorSpace(), hasAlpha, false, hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE, + icm.getTransferType() ); this.icm = icm; + this.samples = 1 + extraSamples; + this.hasAlpha = hasAlpha; + } + + @Override + public int getNumComponents() { + return samples; } @Override @@ -55,7 +112,7 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel { @Override public final int getAlpha(final int pixel) { - return (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f); + return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff; } private int getSample(final Object inData, final int index) { @@ -98,17 +155,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel { @Override public final int getAlpha(final Object inData) { - return getAlpha(getSample(inData, 1)); + return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff; } @Override public final SampleModel createCompatibleSampleModel(final int w, final int h) { - return new PixelInterleavedSampleModel(transferType, w, h, 2, w * 2, new int[] {0, 1}); + return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples)); + } + + private int[] createOffsets(int samples) { + int[] offsets = new int[samples]; + + for (int i = 0; i < samples; i++) { + offsets[i] = i; + } + + return offsets; } @Override public final boolean isCompatibleSampleModel(final SampleModel sm) { - return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == 2; + return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples; } @Override @@ -120,7 +187,13 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel { public final boolean isCompatibleRaster(final Raster raster) { int size = raster.getSampleModel().getSampleSize(0); return ((raster.getTransferType() == transferType) && - (raster.getNumBands() == 2) && ((1 << size) >= icm.getMapSize())); + (raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize())); + } + + @Override + public boolean equals(Object obj) { + return this == obj + || obj != null && getClass() == obj.getClass() && icm.equals(((DiscreteAlphaIndexColorModel) obj).icm); } public String toString() { diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java index 0ff6738c..7c2fdbcd 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.lang.SystemUtil; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java index 3a38023f..64d55fe9 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategy.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategy.java index 557d22c1..ed4ecf8d 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategy.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategy.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.lang.Validate; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategy.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategy.java index 2bd0d88e..88c42a25 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategy.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategy.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.lang.Validate; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/UInt32ColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/UInt32ColorModel.java index 25318f60..1c9d0959 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/UInt32ColorModel.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/UInt32ColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; @@ -59,7 +61,7 @@ public final class UInt32ColorModel extends ComponentColorModel { // This class only supports DataBuffer.TYPE_INT, cast is safe int[] ipixel = (int[]) pixel; for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) { - normComponents[nc] = ((float) (ipixel[c] & 0xffffffffl)) / ((float) ((1l << getComponentSize(c)) - 1)); + normComponents[nc] = ((float) (ipixel[c] & 0xFFFFFFFFL)) / ((float) ((1L << getComponentSize(c)) - 1)); } int numColorComponents = getNumColorComponents(); diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java index 8b1f8eef..aedb1231 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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; /** diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageReaderSpiBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageReaderSpiBase.java index 1c521644..cf9f09a9 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageReaderSpiBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageReaderSpiBase.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.spi; import javax.imageio.spi.ImageReaderSpi; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageWriterSpiBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageWriterSpiBase.java index 3989f5e8..b6d1efb7 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageWriterSpiBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ImageWriterSpiBase.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.spi; import javax.imageio.spi.ImageWriterSpi; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java index 61048791..d0a88da6 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java @@ -4,27 +4,30 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.spi; import com.twelvemonkeys.lang.Validate; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java index a6138816..c53f659c 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.spi; @@ -47,10 +49,10 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo { private final String[] mimeTypes; private final String readerClassName; private final String[] readerSpiClassNames; - private final Class[] inputTypes = new Class[] {ImageInputStream.class}; + private final Class[] inputTypes = new Class[] {ImageInputStream.class}; private final String writerClassName; private final String[] writerSpiClassNames; - private final Class[] outputTypes = new Class[] {ImageOutputStream.class}; + private final Class[] outputTypes = new Class[] {ImageOutputStream.class}; private final boolean supportsStandardStreamMetadata; private final String nativeStreamMetadataFormatName; private final String nativeStreamMetadataFormatClassName; @@ -78,7 +80,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo { final String writerClassName, final String[] writerSpiClassNames, final boolean supportsStandardStreamMetadata, - final String nativeStreameMetadataFormatName, + final String nativeStreamMetadataFormatName, final String nativeStreamMetadataFormatClassName, final String[] extraStreamMetadataFormatNames, final String[] extraStreamMetadataFormatClassNames, @@ -97,7 +99,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo { this.writerClassName = writerClassName; this.writerSpiClassNames = writerSpiClassNames; this.supportsStandardStreamMetadata = supportsStandardStreamMetadata; - this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName; + this.nativeStreamMetadataFormatName = nativeStreamMetadataFormatName; this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName; this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames; this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java index cc3d2c1b..7dc46817 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides helper classes for service provider implementations. */ diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStream.java new file mode 100644 index 00000000..e8c98f7e --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStream.java @@ -0,0 +1,259 @@ +/* + * 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.stream; + +import javax.imageio.stream.ImageInputStreamImpl; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static com.twelvemonkeys.lang.Validate.notNull; +import static java.lang.Math.max; + +/** + * A buffered replacement for {@link javax.imageio.stream.FileImageInputStream} + * that provides greatly improved performance for shorter reads, like single + * byte or bit reads. + * As with {@code javax.imageio.stream.FileImageInputStream}, either + * {@link File} or {@link RandomAccessFile} can be used as input. + * + * @see javax.imageio.stream.FileImageInputStream + */ +// TODO: Create a memory-mapped version? +// Or not... From java.nio.channels.FileChannel.map: +// For most operating systems, mapping a file into memory is more +// expensive than reading or writing a few tens of kilobytes of data via +// the usual {@link #read read} and {@link #write write} methods. From the +// standpoint of performance it is generally only worth mapping relatively +// large files into memory. +public final class BufferedFileImageInputStream extends ImageInputStreamImpl { + static final int DEFAULT_BUFFER_SIZE = 8192; + + private byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + private int bufferPos; + private int bufferLimit; + + private final ByteBuffer integralCache = ByteBuffer.allocate(8); + private final byte[] integralCacheArray = integralCache.array(); + + private RandomAccessFile raf; + + /** + * Constructs a BufferedFileImageInputStream that will read from a given File. + * + * @param file a File to read from. + * @throws IllegalArgumentException if file is null. + * @throws FileNotFoundException if file is a directory or cannot be opened for reading + * for any reason. + */ + public BufferedFileImageInputStream(final File file) throws FileNotFoundException { + this(new RandomAccessFile(notNull(file, "file"), "r")); + } + + /** + * Constructs a BufferedFileImageInputStream that will read from a given RandomAccessFile. + * + * @param raf a RandomAccessFile to read from. + * @throws IllegalArgumentException if raf is null. + */ + public BufferedFileImageInputStream(final RandomAccessFile raf) { + this.raf = notNull(raf, "raf"); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean fillBuffer() throws IOException { + int length = raf.read(buffer, 0, buffer.length); + bufferPos = 0; + bufferLimit = max(length, 0); + + return bufferLimit > 0; + } + + private boolean bufferEmpty() { + return bufferPos >= bufferLimit; + } + + @Override + public void setByteOrder(ByteOrder byteOrder) { + super.setByteOrder(byteOrder); + integralCache.order(byteOrder); + } + + @Override + public int read() throws IOException { + checkClosed(); + + if (bufferEmpty() && !fillBuffer()) { + return -1; + } + + bitOffset = 0; + streamPos++; + + return buffer[bufferPos++] & 0xff; + } + + @Override + public int read(final byte[] bytes, final int offset, final int length) throws IOException { + checkClosed(); + bitOffset = 0; + + if (bufferEmpty()) { + // Bypass buffer if buffer is empty for reads longer than buffer + if (length >= buffer.length) { + return readDirect(bytes, offset, length); + } + else if (!fillBuffer()) { + return -1; + } + } + + int fromBuffer = readBuffered(bytes, offset, length); + + if (length > fromBuffer) { + // Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully, + // we'll read as much as possible from the buffer, and the rest directly after + return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer)); + } + + return fromBuffer; + } + + private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException { + // Invalidate the buffer, as its contents is no longer in sync with the stream's position. + bufferLimit = 0; + int read = raf.read(bytes, offset, length); + + if (read > 0) { + streamPos += read; + } + + return read; + } + + private int readBuffered(final byte[] bytes, final int offset, final int length) { + // Read as much as possible from buffer + int available = Math.min(bufferLimit - bufferPos, length); + + if (available > 0) { + System.arraycopy(buffer, bufferPos, bytes, offset, available); + bufferPos += available; + streamPos += available; + } + + return available; + } + + public long length() { + // WTF?! This method is allowed to throw IOException in the interface... + try { + checkClosed(); + return raf.length(); + } + catch (IOException ignore) { + } + + return -1; + } + + public void close() throws IOException { + super.close(); + + raf.close(); + + raf = null; + buffer = null; + } + + // Need to override the readShort(), readInt() and readLong() methods, + // because the implementations in ImageInputStreamImpl expects the + // read(byte[], int, int) to always read the expected number of bytes, + // causing uninitialized values, alignment issues and EOFExceptions at + // random places... + // Notes: + // * readUnsignedXx() is covered by their signed counterparts + // * readChar() is covered by readShort() + // * readFloat() and readDouble() is covered by readInt() and readLong() + // respectively. + // * readLong() may be covered by two readInt()s, we'll override to be safe + + @Override + public short readShort() throws IOException { + readFully(integralCacheArray, 0, 2); + + return integralCache.getShort(0); + } + + @Override + public int readInt() throws IOException { + readFully(integralCacheArray, 0, 4); + + return integralCache.getInt(0); + } + + @Override + public long readLong() throws IOException { + readFully(integralCacheArray, 0, 8); + + return integralCache.getLong(0); + } + + @Override + public void seek(long position) throws IOException { + checkClosed(); + + if (position < flushedPos) { + throw new IndexOutOfBoundsException("position < flushedPos!"); + } + + bitOffset = 0; + + if (streamPos == position) { + return; + } + + // Optimized to not invalidate buffer if new position is within current buffer + long newBufferPos = bufferPos + position - streamPos; + if (newBufferPos >= 0 && newBufferPos < bufferLimit) { + bufferPos = (int) newBufferPos; + } + else { + // Will invalidate buffer + bufferLimit = 0; + raf.seek(position); + } + + streamPos = position; + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpi.java new file mode 100644 index 00000000..edc3b169 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpi.java @@ -0,0 +1,104 @@ +/* + * 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.stream; + +import com.twelvemonkeys.imageio.spi.ProviderInfo; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Iterator; +import java.util.Locale; + +/** + * BufferedFileImageInputStreamSpi + * Experimental + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ + */ +public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi { + public BufferedFileImageInputStreamSpi() { + this(new StreamProviderInfo()); + } + + private BufferedFileImageInputStreamSpi(ProviderInfo providerInfo) { + super(providerInfo.getVendorName(), providerInfo.getVersion(), File.class); + } + + @Override + public void onRegistration(final ServiceRegistry registry, final Class category) { + Iterator providers = registry.getServiceProviders(ImageInputStreamSpi.class, new FileInputFilter(), true); + + while (providers.hasNext()) { + ImageInputStreamSpi provider = providers.next(); + if (provider != this) { + registry.setOrdering(ImageInputStreamSpi.class, this, provider); + } + } + } + + public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) { + if (input instanceof File) { + try { + return new BufferedFileImageInputStream((File) input); + } + catch (FileNotFoundException e) { + // For consistency with the JRE bundled SPIs, we'll return null here, + // even though the spec does not say that's allowed. + // The problem is that the SPIs can only declare that they support an input type like a File, + // instead they should be allowed to inspect the instance, to see that the file does exist... + return null; + } + } + + throw new IllegalArgumentException("Expected input of type File: " + input); + } + + @Override + public boolean canUseCacheFile() { + return false; + } + + public String getDescription(final Locale pLocale) { + return "Service provider that instantiates an ImageInputStream from a File"; + } + + private static class FileInputFilter implements ServiceRegistry.Filter { + @Override + public boolean filter(final Object provider) { + return ((ImageInputStreamSpi) provider).getInputClass() == File.class; + } + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java index 7e9a8678..da5dc1ed 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.stream; import javax.imageio.stream.ImageInputStream; @@ -13,22 +43,28 @@ import static com.twelvemonkeys.lang.Validate.notNull; * A buffered {@code ImageInputStream}. * Experimental - seems to be effective for {@link javax.imageio.stream.FileImageInputStream} * and {@link javax.imageio.stream.FileCacheImageInputStream} when doing a lot of single-byte reads - * (or short byte-array reads) on OS X at least. + * (or short byte-array reads). * Code that uses the {@code readFully} methods are not affected by the issue. + *

    + * NOTE: Invoking {@code close()} will NOT close the wrapped stream. + *

    * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: BufferedFileImageInputStream.java,v 1.0 May 15, 2008 4:36:49 PM haraldk Exp$ + * + * @deprecated Use {@link BufferedFileImageInputStream} instead. */ -// TODO: Create a provider for this (wrapping the FileIIS and FileCacheIIS classes), and disable the Sun built-in spis? -// TODO: Test on other platforms, might be just an OS X issue +@Deprecated public final class BufferedImageInputStream extends ImageInputStreamImpl implements ImageInputStream { static final int DEFAULT_BUFFER_SIZE = 8192; private ImageInputStream stream; - private ByteBuffer buffer; - private ByteBuffer integralCache = ByteBuffer.allocate(8); + private ByteBuffer buffer; + + private final ByteBuffer integralCache = ByteBuffer.allocate(8); + private final byte[] integralCacheArray = integralCache.array(); public BufferedImageInputStream(final ImageInputStream pStream) throws IOException { this(pStream, DEFAULT_BUFFER_SIZE); @@ -67,10 +103,10 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme if (!buffer.hasRemaining()) { fillBuffer(); - } - if (!buffer.hasRemaining()) { - return -1; + if (!buffer.hasRemaining()) { + return -1; + } } bitOffset = 0; @@ -142,21 +178,21 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme @Override public short readShort() throws IOException { - readFully(integralCache.array(), 0, 2); + readFully(integralCacheArray, 0, 2); return integralCache.getShort(0); } @Override public int readInt() throws IOException { - readFully(integralCache.array(), 0, 4); + readFully(integralCacheArray, 0, 4); return integralCache.getInt(0); } @Override public long readLong() throws IOException { - readFully(integralCache.array(), 0, 8); + readFully(integralCacheArray, 0, 8); return integralCache.getLong(0); } @@ -223,6 +259,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme } int val = buffer.get() & 0xff; + streamPos++; accum <<= 8; accum |= val; @@ -232,9 +269,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme // Move byte position back if in the middle of a byte if (newBitOffset != 0) { buffer.position(buffer.position() - 1); - } - else { - streamPos++; + streamPos--; } this.bitOffset = newBitOffset; @@ -249,26 +284,26 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme } @Override - public void seek(long pPosition) throws IOException { + public void seek(long position) throws IOException { checkClosed(); bitOffset = 0; - if (streamPos == pPosition) { + if (streamPos == position) { return; } // Optimized to not invalidate buffer if new position is within current buffer - long newBufferPos = buffer.position() + pPosition - streamPos; + long newBufferPos = buffer.position() + position - streamPos; if (newBufferPos >= 0 && newBufferPos <= buffer.limit()) { buffer.position((int) newBufferPos); } else { // Will invalidate buffer buffer.limit(0); - stream.seek(pPosition); + stream.seek(position); } - streamPos = pPosition; + streamPos = position; } @Override @@ -300,7 +335,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme @Override public void close() throws IOException { if (stream != null) { - //stream.close(); + // TODO: FixMe: Need to close underlying stream here! + // For call sites that relies on not closing, we should instead not close the buffered stream. +// stream.close(); stream = null; buffer = null; } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpi.java new file mode 100644 index 00000000..69bac835 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpi.java @@ -0,0 +1,95 @@ +/* + * 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.stream; + +import com.twelvemonkeys.imageio.spi.ProviderInfo; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Iterator; +import java.util.Locale; + +/** + * BufferedRAFImageInputStreamSpi + * Experimental + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ + */ +public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi { + public BufferedRAFImageInputStreamSpi() { + this(new StreamProviderInfo()); + } + + private BufferedRAFImageInputStreamSpi(ProviderInfo providerInfo) { + super(providerInfo.getVendorName(), providerInfo.getVersion(), RandomAccessFile.class); + } + + @Override + public void onRegistration(final ServiceRegistry registry, final Class category) { + Iterator providers = registry.getServiceProviders(ImageInputStreamSpi.class, new RAFInputFilter(), true); + + while (providers.hasNext()) { + ImageInputStreamSpi provider = providers.next(); + if (provider != this) { + registry.setOrdering(ImageInputStreamSpi.class, this, provider); + } + } + } + + public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) { + if (input instanceof RandomAccessFile) { + return new BufferedFileImageInputStream((RandomAccessFile) input); + } + + throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input); + } + + @Override + public boolean canUseCacheFile() { + return false; + } + + public String getDescription(final Locale pLocale) { + return "Service provider that instantiates an ImageInputStream from a RandomAccessFile"; + } + + private static class RAFInputFilter implements ServiceRegistry.Filter { + @Override + public boolean filter(final Object provider) { + return ((ImageInputStreamSpi) provider).getInputClass() == RandomAccessFile.class; + } + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java index 92aee63d..679013ea 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.stream; import javax.imageio.stream.ImageInputStreamImpl; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpi.java index ae148269..29655d1c 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpi.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpi.java @@ -1,9 +1,40 @@ +/* + * 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.imageio.stream; +import com.twelvemonkeys.imageio.spi.ProviderInfo; + import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.stream.ImageInputStream; import java.io.File; -import java.io.IOException; import java.util.Locale; /** @@ -17,10 +48,14 @@ import java.util.Locale; public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi { public ByteArrayImageInputStreamSpi() { - super("TwelveMonkeys", "1.0 BETA", byte[].class); + this(new StreamProviderInfo()); } - public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) throws IOException { + private ByteArrayImageInputStreamSpi(ProviderInfo providerInfo) { + super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class); + } + + public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) { if (pInput instanceof byte[]) { return new ByteArrayImageInputStream((byte[]) pInput); } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/StreamProviderInfo.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/StreamProviderInfo.java new file mode 100644 index 00000000..42a48cac --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/StreamProviderInfo.java @@ -0,0 +1,9 @@ +package com.twelvemonkeys.imageio.stream; + +import com.twelvemonkeys.imageio.spi.ProviderInfo; + +final class StreamProviderInfo extends ProviderInfo { + StreamProviderInfo() { + super(StreamProviderInfo.class.getPackage()); + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java index 46600aa3..ce61c856 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.stream; import com.twelvemonkeys.lang.Validate; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java index e72cde21..e798a636 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.stream; import javax.imageio.stream.ImageOutputStream; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java index 64c1ae6e..b56204c7 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java @@ -1,8 +1,39 @@ +/* + * 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.stream; +import com.twelvemonkeys.imageio.spi.ProviderInfo; + import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.stream.FileCacheImageInputStream; -import javax.imageio.stream.FileImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream; import java.io.File; @@ -23,7 +54,11 @@ import java.util.Locale; // TODO: URI instead of URL? public class URLImageInputStreamSpi extends ImageInputStreamSpi { public URLImageInputStreamSpi() { - super("TwelveMonkeys", "1.0 BETA", URL.class); + this(new StreamProviderInfo()); + } + + private URLImageInputStreamSpi(ProviderInfo providerInfo) { + super(providerInfo.getVendorName(), providerInfo.getVersion(), URL.class); } // TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats @@ -36,7 +71,7 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi { // Special case for file protocol, a lot faster than FileCacheImageInputStream if ("file".equals(url.getProtocol())) { try { - return new BufferedImageInputStream(new FileImageInputStream(new File(url.toURI()))); + return new BufferedFileImageInputStream(new File(url.toURI())); } catch (URISyntaxException ignore) { // This should never happen, but if it does, we'll fall back to using the stream @@ -45,29 +80,29 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi { } // Otherwise revert to cached - final InputStream stream = url.openStream(); + final InputStream urlStream = url.openStream(); if (pUseCache) { - return new BufferedImageInputStream(new FileCacheImageInputStream(stream, pCacheDir) { + return new FileCacheImageInputStream(urlStream, pCacheDir) { @Override public void close() throws IOException { try { super.close(); } finally { - stream.close(); // NOTE: If this line throws IOE, it will shadow the original.. + urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original.. } } - }); + }; } else { - return new MemoryCacheImageInputStream(stream) { + return new MemoryCacheImageInputStream(urlStream) { @Override public void close() throws IOException { try { super.close(); } finally { - stream.close(); // NOTE: If this line throws IOE, it will shadow the original.. + urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original.. } } }; @@ -78,6 +113,11 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi { } } + @Override + public boolean canUseCacheFile() { + return true; + } + public String getDescription(final Locale pLocale) { return "Service provider that instantiates an ImageInputStream from a URL"; } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java index 71618df3..b3039049 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides various additional stream implementations. */ diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java index 9e5b1d52..f9dfea2b 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; @@ -36,9 +38,10 @@ import java.io.InputStream; /** * IIOInputStreamAdapter - *

    + *

    * Note: You should always wrap this stream in a {@code BufferedInputStream}. * If not, performance may degrade significantly. + *

    * * @author Harald Kuhr * @author last modified by $Author: haraldk$ diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java index c83fd3d4..1922fd40 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java old mode 100755 new mode 100644 index 7e667b2f..4084a442 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java @@ -4,30 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; import com.twelvemonkeys.image.ImageUtil; +import com.twelvemonkeys.lang.Validate; import javax.imageio.IIOParam; import javax.imageio.ImageIO; @@ -83,9 +87,10 @@ public final class IIOUtil { /** * Creates an {@code OutputStream} adapter that writes to an underlying {@code ImageOutputStream}. - *

    + *

    * Note: The adapter is buffered, and MUST be properly flushed/closed after use, * otherwise data may be lost. + *

    * * @param pStream the stream to write to. * @return an {@code OutputSteam} writing to {@code pStream}. @@ -144,10 +149,9 @@ public final class IIOUtil { return null; } - if (pSourceRegion != null) { - if (pSourceRegion.x != 0 || pSourceRegion.y != 0 || pSourceRegion.width != pImage.getWidth() || pSourceRegion.height != pImage.getHeight()) { - return pImage.getSubimage(pSourceRegion.x, pSourceRegion.y, pSourceRegion.width, pSourceRegion.height); - } + if (pSourceRegion != null + && (pSourceRegion.x != 0 || pSourceRegion.y != 0 || pSourceRegion.width != pImage.getWidth() || pSourceRegion.height != pImage.getHeight())) { + return pImage.getSubimage(pSourceRegion.x, pSourceRegion.y, pSourceRegion.width, pSourceRegion.height); } return pImage; @@ -188,7 +192,7 @@ public final class IIOUtil { * The names are all upper-case, and contains no duplicates. * * @return a normalized array of {@code String}s. - * @see javax.imageio.ImageIO#getReaderFormatNames() + * @see ImageIO#getReaderFormatNames() */ public static String[] getNormalizedReaderFormatNames() { return normalizeNames(ImageIO.getReaderFormatNames()); @@ -199,7 +203,7 @@ public final class IIOUtil { * The names are all upper-case, and contains no duplicates. * * @return a normalized array of {@code String}s. - * @see javax.imageio.ImageIO#getWriterFormatNames() + * @see ImageIO#getWriterFormatNames() */ public static String[] getNormalizedWriterFormatNames() { return normalizeNames(ImageIO.getWriterFormatNames()); @@ -212,6 +216,111 @@ public final class IIOUtil { normalizedNames.add(name.toUpperCase()); } - return normalizedNames.toArray(new String[normalizedNames.size()]); + return normalizedNames.toArray(new String[0]); + } + + // TODO: RasterUtils? Subsampler? + public static void subsampleRow(byte[] srcRow, int srcPos, int srcWidth, + byte[] destRow, int destPos, + int samplesPerPixel, int bitsPerSample, int samplePeriod) { + // Period == 1 is a no-op... + if (samplePeriod == 1) { + return; + } + + Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); + Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 8 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), + "bitsPerSample must be > 0 and <= 8 and a power of 2"); + Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); + Validate.isTrue(samplesPerPixel * bitsPerSample <= 8 || samplesPerPixel * bitsPerSample % 8 == 0, + "samplesPerPixel * bitsPerSample must be < 8 or a multiple of 8 "); + + if (bitsPerSample * samplesPerPixel % 8 == 0) { + int pixelStride = bitsPerSample * samplesPerPixel / 8; + for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { + // System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1 + System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); + } + } + else { + // Start bit fiddling... + int pixelStride = bitsPerSample * samplesPerPixel; + int mask = (1 << pixelStride) - 1; + + for (int x = 0; x < srcWidth; x += samplePeriod) { + int dstOff = (destPos + x / samplePeriod) * pixelStride / 8; + int srcOff = (srcPos + x) * pixelStride / 8; + + int srcBitPos = 8 - pixelStride - (x * pixelStride) % 8; + int srcMask = mask << srcBitPos; + + int dstBitPos = 8 - pixelStride - (x * pixelStride / samplePeriod) % 8; + int dstMask = ~(mask << dstBitPos); + + int val = ((srcRow[srcOff] & srcMask) >> srcBitPos); + destRow[dstOff] = (byte) ((destRow[dstOff] & dstMask) | val << dstBitPos); + } + } + } + + public static void subsampleRow(short[] srcRow, int srcPos, int srcWidth, + short[] destRow, int destPos, + int samplesPerPixel, int bitsPerSample, int samplePeriod) { + // Period == 1 is a no-op... + if (samplePeriod == 1) { + return; + } + + Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); + Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 16 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), + "bitsPerSample must be > 0 and <= 16 and a power of 2"); + Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); + Validate.isTrue(samplesPerPixel * bitsPerSample <= 16 || samplesPerPixel * bitsPerSample % 16 == 0, + "samplesPerPixel * bitsPerSample must be < 16 or a multiple of 16 "); + + int pixelStride = bitsPerSample * samplesPerPixel / 16; + for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { + // System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1 + System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); + } + } + + public static void subsampleRow(int[] srcRow, int srcPos, int srcWidth, + int[] destRow, int destPos, + int samplesPerPixel, int bitsPerSample, int samplePeriod) { + // Period == 1 is a no-op... + if (samplePeriod == 1) { + return; + } + + Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); + Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), + "bitsPerSample must be > 0 and <= 32 and a power of 2"); + Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); + Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, + "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 "); + + int pixelStride = bitsPerSample * samplesPerPixel / 32; + for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { + // System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1 + System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); + } + } + + public static void subsampleRow(float[] srcRow, int srcPos, int srcWidth, + float[] destRow, int destPos, + int samplesPerPixel, int bitsPerSample, int samplePeriod) { + Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op... + Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), + "bitsPerSample must be > 0 and <= 32 and a power of 2"); + Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); + Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, + "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 "); + + int pixelStride = bitsPerSample * samplesPerPixel / 32; + for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { + // System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1 + System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); + } } } \ No newline at end of file diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java index c572010c..7a59d3de 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java @@ -4,45 +4,54 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; -import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel; - -import javax.imageio.ImageTypeSpecifier; -import java.awt.color.ColorSpace; -import java.awt.image.*; - import static com.twelvemonkeys.lang.Validate.isTrue; import static com.twelvemonkeys.lang.Validate.notNull; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.SampleModel; + +import javax.imageio.ImageTypeSpecifier; + +import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel; + /** * Factory class for creating {@code ImageTypeSpecifier}s. * Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but * in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}. * - * @see javax.imageio.ImageTypeSpecifier + * @see ImageTypeSpecifier * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: ImageTypeSpecifiers.java,v 1.0 24.01.11 17.51 haraldk Exp$ @@ -52,6 +61,28 @@ public final class ImageTypeSpecifiers { private ImageTypeSpecifiers() {} public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) { + switch (bufferedImageType) { + // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types + case BufferedImage.TYPE_USHORT_565_RGB: + return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), + 0xF800, + 0x07E0, + 0x001F, + 0x0, + DataBuffer.TYPE_USHORT, + false); + + case BufferedImage.TYPE_USHORT_555_RGB: + return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), + 0x7C00, + 0x03E0, + 0x001F, + 0x0, + DataBuffer.TYPE_USHORT, + false); + default: + } + return ImageTypeSpecifier.createFromBufferedImageType(bufferedImageType); } @@ -145,21 +176,36 @@ public final class ImageTypeSpecifiers { int numEntries = 1 << bits; - byte[] arr = new byte[numEntries]; - byte[] arg = new byte[numEntries]; - byte[] arb = new byte[numEntries]; + ColorModel colorModel; - // Scale array values according to color profile.. - for (int i = 0; i < numEntries; i++) { - float[] gray = new float[]{i / (float) (numEntries - 1)}; - float[] rgb = colorSpace.toRGB(gray); + if (ColorSpace.getInstance(ColorSpace.CS_GRAY).equals(colorSpace)) { + // For default gray, use linear response + byte[] gray = new byte[numEntries]; - arr[i] = (byte) (rgb[0] * 255); - arg[i] = (byte) (rgb[1] * 255); - arb[i] = (byte) (rgb[2]* 255); + for (int i = 0; i < numEntries; i++) { + gray[i] = (byte) ((i * 255) / (numEntries - 1)); + } + + colorModel = new IndexColorModel(bits, numEntries, gray, gray, gray); + } + else { + byte[] r = new byte[numEntries]; + byte[] g = new byte[numEntries]; + byte[] b = new byte[numEntries]; + + // Scale array values according to color profile.. + for (int i = 0; i < numEntries; i++) { + float[] gray = new float[] { i / (float) (numEntries - 1) }; + float[] rgb = colorSpace.toRGB(gray); + + r[i] = (byte) Math.round(rgb[0] * 255); + g[i] = (byte) Math.round(rgb[1] * 255); + b[i] = (byte) Math.round(rgb[2] * 255); + } + + colorModel = new IndexColorModel(bits, numEntries, r, g, b); } - ColorModel colorModel = new IndexColorModel(bits, numEntries, arr, arg, arb); SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits); return new ImageTypeSpecifier(colorModel, sampleModel); @@ -177,11 +223,16 @@ public final class ImageTypeSpecifiers { } public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) { - return IndexedImageTypeSpecifier.createFromIndexColorModel(pColorModel); + return new IndexedImageTypeSpecifier(pColorModel); } public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) { ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); } + + public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel pColorModel, int extraSamples, boolean hasAlpha) { + ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha); + return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); + } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifier.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifier.java index 65dee377..07dfb018 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifier.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifier.java @@ -1,13 +1,43 @@ +/* + * 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.imageio.util; -import javax.imageio.ImageTypeSpecifier; -import java.awt.image.BufferedImage; -import java.awt.image.IndexColorModel; -import java.awt.image.WritableRaster; -import java.util.Hashtable; - import static com.twelvemonkeys.lang.Validate.notNull; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; + +import javax.imageio.ImageTypeSpecifier; + /** * IndexedImageTypeSpecifier * @@ -15,27 +45,24 @@ import static com.twelvemonkeys.lang.Validate.notNull; * @author last modified by $Author: haraldk$ * @version $Id: IndexedImageTypeSpecifier.java,v 1.0 May 19, 2008 11:04:28 AM haraldk Exp$ */ -final class IndexedImageTypeSpecifier { - private IndexedImageTypeSpecifier() {} +final class IndexedImageTypeSpecifier extends ImageTypeSpecifier { + IndexedImageTypeSpecifier(final ColorModel colorModel) { + // For some reason, we need a sample model, even though we won't use it + super(notNull(colorModel, "colorModel"), colorModel.createCompatibleSampleModel(1, 1)); + } - static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) { - // For some reason, we need a sample model - return new ImageTypeSpecifier(notNull(pColorModel, "colorModel"), pColorModel.createCompatibleSampleModel(1, 1)) { - - @Override - public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) { - try { - // This is a fix for the super-method, that first creates a sample model, and then - // creates a raster from it, using Raster.createWritableRaster. The problem with - // that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images. - WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight); - return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable()); - } - catch (NegativeArraySizeException e) { - // Exception most likely thrown from a DataBuffer constructor - throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!"); - } - } - }; + @Override + public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) { + try { + // This is a fix for the super-method, that first creates a sample model, and then + // creates a raster from it, using Raster.createWritableRaster. The problem with + // that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images. + WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight); + return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null); + } + catch (NegativeArraySizeException e) { + // Exception most likely thrown from a DataBuffer constructor + throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!"); + } } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java index e79ed3d3..50ff738d 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; @@ -38,7 +40,7 @@ import java.awt.image.PixelInterleavedSampleModel; /** * ImageTypeSpecifier for interleaved 16 bit signed integral samples. * - * @see com.twelvemonkeys.imageio.color.Int16ColorModel + * @see com.twelvemonkeys.imageio.color.Int16ComponentColorModel * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: Int16ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$ diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ProgressListenerBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ProgressListenerBase.java index 816dc142..2115fd99 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ProgressListenerBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ProgressListenerBase.java @@ -1,94 +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 "TwelveMonkeys" 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 OWNER 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.util; - -import javax.imageio.ImageReader; -import javax.imageio.ImageWriter; -import javax.imageio.event.IIOReadProgressListener; -import javax.imageio.event.IIOWriteProgressListener; - -/** - * ProgressListenerBase - *

    - * - * @author Harald Kuhr - * @version $Id: ProgressListenerBase.java,v 1.0 26.aug.2005 14:29:42 haku Exp$ - */ -public abstract class ProgressListenerBase implements IIOReadProgressListener, IIOWriteProgressListener { - protected ProgressListenerBase() { - } - - public void imageComplete(ImageReader source) { - } - - public void imageProgress(ImageReader source, float percentageDone) { - } - - public void imageStarted(ImageReader source, int imageIndex) { - } - - public void readAborted(ImageReader source) { - } - - public void sequenceComplete(ImageReader source) { - } - - public void sequenceStarted(ImageReader source, int minIndex) { - } - - public void thumbnailComplete(ImageReader source) { - } - - public void thumbnailProgress(ImageReader source, float percentageDone) { - } - - public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) { - } - - public void imageComplete(ImageWriter source) { - } - - public void imageProgress(ImageWriter source, float percentageDone) { - } - - public void imageStarted(ImageWriter source, int imageIndex) { - } - - public void thumbnailComplete(ImageWriter source) { - } - - public void thumbnailProgress(ImageWriter source, float percentageDone) { - } - - public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) { - } - - public void writeAborted(ImageWriter source) { - } -} +/* + * 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.imageio.util; + +import javax.imageio.ImageReader; +import javax.imageio.ImageWriter; +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.event.IIOWriteProgressListener; + +/** + * ProgressListenerBase + * + * @author Harald Kuhr + * @version $Id: ProgressListenerBase.java,v 1.0 26.aug.2005 14:29:42 haku Exp$ + */ +public abstract class ProgressListenerBase implements IIOReadProgressListener, IIOWriteProgressListener { + protected ProgressListenerBase() { + } + + public void imageComplete(ImageReader source) { + } + + public void imageProgress(ImageReader source, float percentageDone) { + } + + public void imageStarted(ImageReader source, int imageIndex) { + } + + public void readAborted(ImageReader source) { + } + + public void sequenceComplete(ImageReader source) { + } + + public void sequenceStarted(ImageReader source, int minIndex) { + } + + public void thumbnailComplete(ImageReader source) { + } + + public void thumbnailProgress(ImageReader source, float percentageDone) { + } + + public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) { + } + + public void imageComplete(ImageWriter source) { + } + + public void imageProgress(ImageWriter source, float percentageDone) { + } + + public void imageStarted(ImageWriter source, int imageIndex) { + } + + public void thumbnailComplete(ImageWriter source) { + } + + public void thumbnailProgress(ImageWriter source, float percentageDone) { + } + + public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) { + } + + public void writeAborted(ImageWriter source) { + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/RasterUtils.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/RasterUtils.java new file mode 100644 index 00000000..97259ef0 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/RasterUtils.java @@ -0,0 +1,152 @@ +/* + * 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.util; + +import java.awt.*; +import java.awt.image.*; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * A class containing various raster utility methods. + */ +public final class RasterUtils { + + private RasterUtils() { + } + + /** + * Returns a raster with {@code DataBuffer.TYPE_BYTE} transfer type. + * Works for any raster from a {@code BufferedImage.TYPE_INT_*} image + * + * @param raster a {@code Raster} with either transfer type {@code DataBuffer.TYPE_BYTE} + * or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`, not {@code null}. + * @return a raster with {@code DataBuffer.TYPE_BYTE} transfer type. + * @throws IllegalArgumentException if {@code raster} does not have transfer type {@code DataBuffer.TYPE_BYTE} + * or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel` + * @throws NullPointerException if {@code raster} is {@code null}. + */ + public static Raster asByteRaster(final Raster raster) { + return asByteRaster0(raster); + } + + /** + * Returns a writable raster with {@code DataBuffer.TYPE_BYTE} transfer type. + * Works for any raster from a {@code BufferedImage.TYPE_INT_*} image. + * + * @param raster a {@code WritableRaster} with either transfer type {@code DataBuffer.TYPE_BYTE} + * or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`, not {@code null}. + * @return a writable raster with {@code DataBuffer.TYPE_BYTE} transfer type. + * @throws IllegalArgumentException if {@code raster} does not have transfer type {@code DataBuffer.TYPE_BYTE} + * or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel` + * @throws NullPointerException if {@code raster} is {@code null}. + */ + public static WritableRaster asByteRaster(final WritableRaster raster) { + return (WritableRaster) asByteRaster0(raster); + } + + private static Raster asByteRaster0(final Raster raster) { + switch (raster.getTransferType()) { + case DataBuffer.TYPE_BYTE: + return raster; + case DataBuffer.TYPE_INT: + SampleModel sampleModel = raster.getSampleModel(); + + if (!(sampleModel instanceof SinglePixelPackedSampleModel)) { + throw new IllegalArgumentException(String.format("Requires SinglePixelPackedSampleModel, %s not supported", sampleModel.getClass().getSimpleName())); + } + + final int bands = 4; + final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); + + int w = raster.getWidth(); + int h = raster.getHeight(); + int size = buffer.getSize(); + + return new WritableRaster( + new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, bands, w * bands, createBandOffsets((SinglePixelPackedSampleModel) sampleModel)), + new DataBuffer(DataBuffer.TYPE_BYTE, size * bands) { + final int[] MASKS = { + 0xffffff00, + 0xffff00ff, + 0xff00ffff, + 0x00ffffff, + }; + + @Override + public int getElem(int bank, int i) { + int index = i / bands; + int shift = (i % bands) * 8; + + return (buffer.getElem(index) >>> shift) & 0xff; + } + + @Override + public void setElem(int bank, int i, int val) { + int index = i / bands; + int element = i % bands; + int shift = element * 8; + + int value = (buffer.getElem(index) & MASKS[element]) | ((val & 0xff) << shift); + buffer.setElem(index, value); + } + }, new Point()) { + }; + + default: + throw new IllegalArgumentException(String.format("Raster type %d not supported", raster.getTransferType())); + } + } + + private static int[] createBandOffsets(final SinglePixelPackedSampleModel sampleModel) { + notNull(sampleModel, "sampleModel"); + + int[] masks = sampleModel.getBitMasks(); + int[] offs = new int[masks.length]; + + for (int i = 0; i < masks.length; i++) { + int mask = masks[i]; + int off = 0; + + // TODO: FixMe! This only works for standard 8 bit masks (0xFF) + if (mask != 0) { + while ((mask & 0xFF) == 0) { + mask >>>= 8; + off++; + } + } + + offs[i] = off; + } + + return offs; + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java index e94b61b0..56f968f7 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java @@ -1,99 +1,100 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import com.twelvemonkeys.io.FileUtil; -import com.twelvemonkeys.lang.StringUtil; - -import javax.imageio.ImageIO; -import javax.swing.filechooser.FileFilter; -import java.io.File; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * ReaderFileSuffixFilter - *

    - * - * @author Harald Kuhr - * @author last modified by $Author: haku$ - * @version $Id: ReaderFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$ - */ -public final class ReaderFileSuffixFilter extends FileFilter implements java.io.FileFilter { - private final String description; - private final Map knownSuffixes = new HashMap(32); - - public ReaderFileSuffixFilter() { - this("Images (all supported input formats)"); - } - - public ReaderFileSuffixFilter(String pDescription) { - description = pDescription; - } - - public boolean accept(File pFile) { - // Directories are always supported - if (pFile.isDirectory()) { - return true; - } - - // See if we have an ImageReader for this suffix - String suffix = FileUtil.getExtension(pFile); - - return !StringUtil.isEmpty(suffix) && hasReaderForSuffix(suffix); - } - - private boolean hasReaderForSuffix(String pSuffix) { - if (knownSuffixes.get(pSuffix) == Boolean.TRUE) { - return true; - } - - try { - // Cahce lookup - Iterator iterator = ImageIO.getImageReadersBySuffix(pSuffix); - - if (iterator.hasNext()) { - knownSuffixes.put(pSuffix, Boolean.TRUE); - return true; - } - else { - knownSuffixes.put(pSuffix, Boolean.FALSE); - return false; - } - } - catch (IllegalArgumentException iae) { - return false; - } - } - - public String getDescription() { - return description; - } -} +/* + * 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.imageio.util; + +import com.twelvemonkeys.io.FileUtil; +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.ImageIO; +import javax.swing.filechooser.FileFilter; +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * ReaderFileSuffixFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haku$ + * @version $Id: ReaderFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$ + */ +public final class ReaderFileSuffixFilter extends FileFilter implements java.io.FileFilter { + private final String description; + private final Map knownSuffixes = new HashMap(32); + + public ReaderFileSuffixFilter() { + this("Images (all supported input formats)"); + } + + public ReaderFileSuffixFilter(String pDescription) { + description = pDescription; + } + + public boolean accept(File pFile) { + // Directories are always supported + if (pFile.isDirectory()) { + return true; + } + + // See if we have an ImageReader for this suffix + String suffix = FileUtil.getExtension(pFile); + + return !StringUtil.isEmpty(suffix) && hasReaderForSuffix(suffix); + } + + private boolean hasReaderForSuffix(String pSuffix) { + if (knownSuffixes.get(pSuffix) == Boolean.TRUE) { + return true; + } + + try { + // Cahce lookup + Iterator iterator = ImageIO.getImageReadersBySuffix(pSuffix); + + if (iterator.hasNext()) { + knownSuffixes.put(pSuffix, Boolean.TRUE); + return true; + } + else { + knownSuffixes.put(pSuffix, Boolean.FALSE); + return false; + } + } + catch (IllegalArgumentException iae) { + return false; + } + } + + public String getDescription() { + return description; + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifier.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifier.java index 94060844..2601a7a7 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifier.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifier.java @@ -4,39 +4,42 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; -import com.twelvemonkeys.imageio.color.UInt32ColorModel; - -import javax.imageio.ImageTypeSpecifier; import java.awt.color.ColorSpace; import java.awt.image.BandedSampleModel; import java.awt.image.DataBuffer; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.SampleModel; +import javax.imageio.ImageTypeSpecifier; + +import com.twelvemonkeys.imageio.color.UInt32ColorModel; + /** * ImageTypeSpecifier for interleaved 32 bit unsigned integral samples. * @@ -45,11 +48,13 @@ import java.awt.image.SampleModel; * @author last modified by $Author: haraldk$ * @version $Id: UInt32ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$ */ -final class UInt32ImageTypeSpecifier { - private UInt32ImageTypeSpecifier() {} +final class UInt32ImageTypeSpecifier extends ImageTypeSpecifier { + private UInt32ImageTypeSpecifier(final ColorSpace cs, final boolean hasAlpha, final boolean isAlphaPremultiplied, final SampleModel sampleModel) { + super(new UInt32ColorModel(cs, hasAlpha, isAlphaPremultiplied), sampleModel); + } static ImageTypeSpecifier createInterleaved(final ColorSpace cs, final int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) { - return create( + return new UInt32ImageTypeSpecifier( cs, hasAlpha, isAlphaPremultiplied, new PixelInterleavedSampleModel( DataBuffer.TYPE_INT, 1, 1, @@ -61,7 +66,7 @@ final class UInt32ImageTypeSpecifier { } static ImageTypeSpecifier createBanded(final ColorSpace cs, final int[] bandIndices, final int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) { - return create( + return new UInt32ImageTypeSpecifier( cs, hasAlpha, isAlphaPremultiplied, new BandedSampleModel( DataBuffer.TYPE_INT, 1, 1, 1, @@ -70,7 +75,13 @@ final class UInt32ImageTypeSpecifier { ); } - private static ImageTypeSpecifier create(final ColorSpace cs, final boolean hasAlpha, final boolean isAlphaPremultiplied, final SampleModel sampleModel) { - return new ImageTypeSpecifier(new UInt32ColorModel(cs, hasAlpha, isAlphaPremultiplied), sampleModel); + @Override + public boolean equals(final Object other) { + if (!(other instanceof UInt32ImageTypeSpecifier)) { + return false; + } + + UInt32ImageTypeSpecifier that = (UInt32ImageTypeSpecifier) other; + return colorModel.equals(that.colorModel) && sampleModel.equals(that.sampleModel); } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java index 20a43ae4..54cb73b9 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java @@ -1,99 +1,100 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import com.twelvemonkeys.io.FileUtil; -import com.twelvemonkeys.lang.StringUtil; - -import javax.imageio.ImageIO; -import javax.swing.filechooser.FileFilter; -import java.io.File; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * WriterFileSuffixFilter - *

    - * - * @author Harald Kuhr - * @author last modified by $Author: haku$ - * @version $Id: WriterFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$ - */ -public final class WriterFileSuffixFilter extends FileFilter implements java.io.FileFilter { - private final String description; - private Map knownSuffixes = new HashMap(32); - - public WriterFileSuffixFilter() { - this("Images (all supported output formats)"); - } - - public WriterFileSuffixFilter(String pDescription) { - description = pDescription; - } - - public boolean accept(File pFile) { - // Directories are always supported - if (pFile.isDirectory()) { - return true; - } - - // Test if we have an ImageWriter for this suffix - String suffix = FileUtil.getExtension(pFile); - - return !StringUtil.isEmpty(suffix) && hasWriterForSuffix(suffix); - } - - private boolean hasWriterForSuffix(String pSuffix) { - if (knownSuffixes.get(pSuffix) == Boolean.TRUE) { - return true; - } - - try { - // Cahce lookup - Iterator iterator = ImageIO.getImageWritersBySuffix(pSuffix); - - if (iterator.hasNext()) { - knownSuffixes.put(pSuffix, Boolean.TRUE); - return true; - } - else { - knownSuffixes.put(pSuffix, Boolean.FALSE); - return false; - } - } - catch (IllegalArgumentException iae) { - return false; - } - } - - public String getDescription() { - return description; - } -} +/* + * 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.imageio.util; + +import com.twelvemonkeys.io.FileUtil; +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.ImageIO; +import javax.swing.filechooser.FileFilter; +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * WriterFileSuffixFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haku$ + * @version $Id: WriterFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$ + */ +public final class WriterFileSuffixFilter extends FileFilter implements java.io.FileFilter { + private final String description; + private Map knownSuffixes = new HashMap(32); + + public WriterFileSuffixFilter() { + this("Images (all supported output formats)"); + } + + public WriterFileSuffixFilter(String pDescription) { + description = pDescription; + } + + public boolean accept(File pFile) { + // Directories are always supported + if (pFile.isDirectory()) { + return true; + } + + // Test if we have an ImageWriter for this suffix + String suffix = FileUtil.getExtension(pFile); + + return !StringUtil.isEmpty(suffix) && hasWriterForSuffix(suffix); + } + + private boolean hasWriterForSuffix(String pSuffix) { + if (knownSuffixes.get(pSuffix) == Boolean.TRUE) { + return true; + } + + try { + // Cahce lookup + Iterator iterator = ImageIO.getImageWritersBySuffix(pSuffix); + + if (iterator.hasNext()) { + knownSuffixes.put(pSuffix, Boolean.TRUE); + return true; + } + else { + knownSuffixes.put(pSuffix, Boolean.FALSE); + return false; + } + } + catch (IllegalArgumentException iae) { + return false; + } + } + + public String getDescription() { + return description; + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java index 87091685..1c0da421 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java @@ -1,3 +1,33 @@ +/* + * 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. + */ + /** * Provides various common utilities for reading and writing images. */ diff --git a/imageio/imageio-core/src/main/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi b/imageio/imageio-core/src/main/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi new file mode 100644 index 00000000..c31ffbaf --- /dev/null +++ b/imageio/imageio-core/src/main/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi @@ -0,0 +1,2 @@ +com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi +com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi diff --git a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties index 706fe6d6..b82f9513 100644 --- a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties +++ b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties @@ -4,26 +4,28 @@ # # 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 "TwelveMonkeys" 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 OWNER 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. +# * 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. # #GENERIC_CMYK=unknown, use built in for now #ADOBE_RGB_1998=unknown, use built in for now \ No newline at end of file diff --git a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_osx.properties b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_osx.properties index a516ec1c..55791c41 100644 --- a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_osx.properties +++ b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_osx.properties @@ -4,26 +4,28 @@ # # 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 "TwelveMonkeys" 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 OWNER 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. +# * 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. # GENERIC_CMYK=/System/Library/ColorSync/Profiles/Generic CMYK Profile.icc ADOBE_RGB_1998=/System/Library/ColorSync/Profiles/AdobeRGB1998.icc diff --git a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties index 21fef9f9..7f8eb489 100644 --- a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties +++ b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties @@ -4,26 +4,28 @@ # # 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 "TwelveMonkeys" 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 OWNER 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. +# * 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. # GENERIC_CMYK=/C:/Windows/System32/spool/drivers/color/RSWOP.icm #ADOBE_RGB_1998=unknown, use built in for now \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/ImageReaderBaseTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/ImageReaderBaseTest.java index d0fb66df..8a7b1871 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/ImageReaderBaseTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/ImageReaderBaseTest.java @@ -4,35 +4,37 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; -import org.junit.Test; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import javax.imageio.IIOException; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageTypeSpecifier; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; @@ -40,8 +42,13 @@ import java.awt.image.DataBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Set; -import static org.junit.Assert.*; +import javax.imageio.IIOException; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; + +import org.junit.Test; /** * ImageReaderBaseTest @@ -208,18 +215,25 @@ public class ImageReaderBaseTest { assertEquals(TYPES.get(0).getBufferedImageType(), destination.getType()); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IIOException.class) public void testGetDestinationParamDestinationExceedsIntegerMax() throws IIOException { ImageReadParam param = new ImageReadParam(); param.setSourceRegion(new Rectangle(3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE)); // 6 442 057 734 pixels ImageReaderBase.getDestination(param, TYPES.iterator(), 6 * Short.MAX_VALUE, 4 * Short.MAX_VALUE); // 25 768 230 936 pixels } - @Test(expected = IllegalArgumentException.class) - public void testGetDestinationExceedsIntegerMax() throws IIOException { + @Test(expected = IIOException.class) + public void testGetDestinationDimensionExceedsIntegerMax() throws IIOException { ImageReaderBase.getDestination(null, TYPES.iterator(), 3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE); // 6 442 057 734 pixels } + @Test(expected = IIOException.class) + public void testGetDestinationStorageExceedsIntegerMax() throws IIOException { + Set byteTypes = singleton(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); + ImageReaderBase.getDestination(null, byteTypes.iterator(), Short.MAX_VALUE, Short.MAX_VALUE); // 1 073 676 289 pixels + // => 3 221 028 867 bytes needed in continuous array, not possible + } + @Test public void testHasExplicitDestinationNull() { assertFalse(ImageReaderBase.hasExplicitDestination(null)); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java index ff3ddbeb..6c49b4a9 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.imageio.color.CIELabColorConverter.Illuminant; diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java index 87d18f8f..412bdb6f 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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; diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java index 72d42186..325e917e 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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 org.hamcrest.CoreMatchers; @@ -5,6 +35,8 @@ import org.junit.Test; import java.awt.image.*; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; public class DiscreteAlphaIndexColorModelTest { @@ -132,7 +164,7 @@ public class DiscreteAlphaIndexColorModelTest { assertEquals(2, sampleModel.getHeight()); assertTrue(colorModel.isCompatibleSampleModel(sampleModel)); - assertThat(sampleModel, CoreMatchers.is(PixelInterleavedSampleModel.class)); + assertThat(sampleModel, instanceOf(PixelInterleavedSampleModel.class)); assertThat(sampleModel.getDataType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE)); } @@ -150,7 +182,7 @@ public class DiscreteAlphaIndexColorModelTest { assertEquals(2, sampleModel.getHeight()); assertTrue(colorModel.isCompatibleSampleModel(sampleModel)); - assertThat(sampleModel, CoreMatchers.is(PixelInterleavedSampleModel.class)); + assertThat(sampleModel, instanceOf(PixelInterleavedSampleModel.class)); assertThat(sampleModel.getDataType(), CoreMatchers.equalTo(DataBuffer.TYPE_USHORT)); } diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategyTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategyTest.java index cfcd8932..e5012a34 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategyTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/KCMSSanitizerStrategyTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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 org.junit.Test; diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategyTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategyTest.java index ed7e9e5a..d7b63ea2 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategyTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/LCMSSanitizerStrategyTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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 org.junit.Test; diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/UInt32ColorModelTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/UInt32ColorModelTest.java index 81ea76d6..96bbba41 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/UInt32ColorModelTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/UInt32ColorModelTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2014, 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 org.junit.Test; diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ProviderInfoTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ProviderInfoTest.java similarity index 61% rename from imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ProviderInfoTestCase.java rename to imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ProviderInfoTest.java index 35fb902a..4b4b4ca3 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ProviderInfoTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ProviderInfoTest.java @@ -1,21 +1,55 @@ +/* + * 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.imageio.spi; -import junit.framework.TestCase; +import org.junit.Test; import java.net.URL; +import static org.junit.Assert.*; + /** - * ProviderInfoTestCase + * ProviderInfoTest * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: ProviderInfoTestCase.java,v 1.0 Oct 31, 2009 3:51:22 PM haraldk Exp$ + * @version $Id: ProviderInfoTest.java,v 1.0 Oct 31, 2009 3:51:22 PM haraldk Exp$ */ -public class ProviderInfoTestCase extends TestCase { - public void testCreateNorma() { +public class ProviderInfoTest { + @Test + public void testCreateNormal() { new ProviderInfo(Package.getPackage("java.util")); } + @Test public void testCreateNullPackage() { try { new ProviderInfo(null); @@ -26,6 +60,7 @@ public class ProviderInfoTestCase extends TestCase { } } + @Test public void testGetVendorUnknownNonJARPackage() { ProviderInfo info = new ProviderInfo(mockNonJARPackage("org.foo")); @@ -38,6 +73,7 @@ public class ProviderInfoTestCase extends TestCase { assertEquals("Unspecified", version); } + @Test public void testGetVendorNonJARTMPackage() { ProviderInfo info = new ProviderInfo(mockNonJARPackage("com.twelvemonkeys")); @@ -50,6 +86,7 @@ public class ProviderInfoTestCase extends TestCase { assertEquals("DEV", version); } + @Test public void testGetVendorKnownJARPackage() { ProviderInfo info = new ProviderInfo(mockJARPackage("com.acme", "1.7u4-BETA-b39", "Acme")); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java index af0df7ed..ffd1baea 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java @@ -1,8 +1,38 @@ +/* + * Copyright (c) 2016, 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.spi; import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; import org.junit.Test; -import org.junit.internal.matchers.TypeSafeMatcher; import javax.imageio.ImageReader; import javax.imageio.ImageWriter; @@ -12,6 +42,7 @@ import javax.imageio.spi.ImageWriterSpi; import java.util.List; import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; /** @@ -32,52 +63,52 @@ public abstract class ReaderWriterProviderInfoTest { } @Test - public void readerClassName() throws Exception { + public void readerClassName() { assertClassExists(providerInfo.readerClassName(), ImageReader.class); } @Test - public void readerSpiClassNames() throws Exception { + public void readerSpiClassNames() { assertClassesExist(providerInfo.readerSpiClassNames(), ImageReaderSpi.class); } @Test - public void inputTypes() throws Exception { + public void inputTypes() { assertNotNull(providerInfo.inputTypes()); } @Test - public void writerClassName() throws Exception { + public void writerClassName() { assertClassExists(providerInfo.writerClassName(), ImageWriter.class); } @Test - public void writerSpiClassNames() throws Exception { + public void writerSpiClassNames() { assertClassesExist(providerInfo.writerSpiClassNames(), ImageWriterSpi.class); } @Test - public void outputTypes() throws Exception { + public void outputTypes() { assertNotNull(providerInfo.outputTypes()); } @Test - public void nativeStreamMetadataFormatClassName() throws Exception { + public void nativeStreamMetadataFormatClassName() { assertClassExists(providerInfo.nativeStreamMetadataFormatClassName(), IIOMetadataFormat.class); } @Test - public void extraStreamMetadataFormatClassNames() throws Exception { + public void extraStreamMetadataFormatClassNames() { assertClassesExist(providerInfo.extraStreamMetadataFormatClassNames(), IIOMetadataFormat.class); } @Test - public void nativeImageMetadataFormatClassName() throws Exception { + public void nativeImageMetadataFormatClassName() { assertClassExists(providerInfo.nativeImageMetadataFormatClassName(), IIOMetadataFormat.class); } @Test - public void extraImageMetadataFormatClassNames() throws Exception { + public void extraImageMetadataFormatClassNames() { assertClassesExist(providerInfo.extraImageMetadataFormatClassNames(), IIOMetadataFormat.class); } @@ -85,7 +116,7 @@ public abstract class ReaderWriterProviderInfoTest { public void formatNames() { String[] names = providerInfo.formatNames(); assertNotNull(names); - assertFalse(names.length == 0); + assertNotEquals(0, names.length); List list = asList(names); @@ -102,7 +133,7 @@ public abstract class ReaderWriterProviderInfoTest { public void suffixes() { String[] suffixes = providerInfo.suffixes(); assertNotNull(suffixes); - assertFalse(suffixes.length == 0); + assertNotEquals(0, suffixes.length); for (String suffix : suffixes) { assertNotNull(suffix); @@ -114,7 +145,7 @@ public abstract class ReaderWriterProviderInfoTest { public void mimeTypes() { String[] mimeTypes = providerInfo.mimeTypes(); assertNotNull(mimeTypes); - assertFalse(mimeTypes.length == 0); + assertNotEquals(0, mimeTypes.length); for (String mimeType : mimeTypes) { assertNotNull(mimeType); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpiTest.java new file mode 100644 index 00000000..9c2a2b18 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpiTest.java @@ -0,0 +1,30 @@ +package com.twelvemonkeys.imageio.stream; + +import org.junit.Test; + +import javax.imageio.spi.ImageInputStreamSpi; +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertNull; +import static org.junit.Assume.assumeFalse; + +public class BufferedFileImageInputStreamSpiTest extends ImageInputStreamSpiTest { + @Override + protected ImageInputStreamSpi createProvider() { + return new BufferedFileImageInputStreamSpi(); + } + + @Override + protected File createInput() throws IOException { + return File.createTempFile("test-", ".tst"); + } + + @Test + public void testReturnNullWhenFileDoesNotExist() throws IOException { + // This is really stupid behavior, but it is consistent with the JRE bundled SPIs. + File input = new File("a-file-that-should-not-exist-ever.fnf"); + assumeFalse("File should not exist: " + input.getPath(), input.exists()); + assertNull(provider.createInputStreamInstance(input)); + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamTest.java new file mode 100755 index 00000000..0d4b2d12 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamTest.java @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2020, 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.stream; + +import org.junit.Test; +import org.junit.function.ThrowingRunnable; + +import javax.imageio.stream.ImageInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.util.Random; + +import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * BufferedFileImageInputStreamTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ + */ +public class BufferedFileImageInputStreamTest { + private final Random random = new Random(170984354357234566L); + + private File randomDataToFile(byte[] data) throws IOException { + random.nextBytes(data); + + File file = File.createTempFile("read", ".tmp"); + Files.write(file.toPath(), data); + return file; + } + + @Test + public void testCreate() throws IOException { + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(File.createTempFile("empty", ".tmp"))) { + assertEquals("Data length should be same as stream length", 0, stream.length()); + } + } + + @Test + public void testCreateNullFile() throws IOException { + try { + new BufferedFileImageInputStream((File) null); + fail("Expected IllegalArgumentException"); + } + catch (IllegalArgumentException expected) { + assertNotNull("Null exception message", expected.getMessage()); + String message = expected.getMessage().toLowerCase(); + assertTrue("Exception message does not contain parameter name", message.contains("file")); + assertTrue("Exception message does not contain null", message.contains("null")); + } + } + + @Test + public void testCreateNullRAF() { + try { + new BufferedFileImageInputStream((RandomAccessFile) null); + fail("Expected IllegalArgumentException"); + } + catch (IllegalArgumentException expected) { + assertNotNull("Null exception message", expected.getMessage()); + String message = expected.getMessage().toLowerCase(); + assertTrue("Exception message does not contain parameter name", message.contains("raf")); + assertTrue("Exception message does not contain null", message.contains("null")); + } + } + + @Test + public void testRead() throws IOException { + byte[] data = new byte[1024 * 1024]; + File file = randomDataToFile(data); + + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file)) { + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + for (byte value : data) { + assertEquals("Wrong data read", value & 0xff, stream.read()); + } + } + } + + @Test + public void testReadArray() throws IOException { + byte[] data = new byte[1024 * 1024]; + File file = randomDataToFile(data); + + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file)) { + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] result = new byte[1024]; + + for (int i = 0; i < data.length / result.length; i++) { + stream.readFully(result); + assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length)); + } + } + } + + @Test + public void testReadSkip() throws IOException { + byte[] data = new byte[1024 * 14]; + File file = randomDataToFile(data); + + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file)) { + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] result = new byte[7]; + + for (int i = 0; i < data.length / result.length; i += 2) { + stream.readFully(result); + stream.skipBytes(result.length); + assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length)); + } + } + } + + @Test + public void testReadSeek() throws IOException { + byte[] data = new byte[1024 * 18]; + File file = randomDataToFile(data); + + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file)) { + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] result = new byte[9]; + + for (int i = 0; i < data.length / result.length; i++) { + // Read backwards + long newPos = stream.length() - result.length - i * result.length; + stream.seek(newPos); + assertEquals("Wrong stream position", newPos, stream.getStreamPosition()); + stream.readFully(result); + assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length)); + } + } + } + + @Test + public void testReadOutsideDataSeek0Read() throws IOException { + byte[] data = new byte[256]; + File file = randomDataToFile(data); + + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file)) { + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] buffer = new byte[data.length * 2]; + stream.read(buffer); + stream.seek(0); + assertNotEquals(-1, stream.read()); + assertNotEquals(-1, stream.read(buffer)); + } + } + + @Test + public void testReadBitRandom() throws IOException { + byte[] bytes = new byte[8]; + File file = randomDataToFile(bytes); + long value = ByteBuffer.wrap(bytes).getLong(); + + // Create stream + try (ImageInputStream stream = new BufferedFileImageInputStream(file)) { + for (int i = 1; i <= 64; i++) { + assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit()); + } + } + } + + @Test + public void testReadBitsRandom() throws IOException { + byte[] bytes = new byte[8]; + File file = randomDataToFile(bytes); + long value = ByteBuffer.wrap(bytes).getLong(); + + // Create stream + try (ImageInputStream stream = new BufferedFileImageInputStream(file)) { + for (int i = 1; i <= 64; i++) { + stream.seek(0); + assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i)); + assertEquals(i % 8, stream.getBitOffset()); + } + } + } + + @Test + public void testReadBitsRandomOffset() throws IOException { + byte[] bytes = new byte[8]; + File file = randomDataToFile(bytes); + long value = ByteBuffer.wrap(bytes).getLong(); + + // Create stream + try (ImageInputStream stream = new BufferedFileImageInputStream(file)) { + for (int i = 1; i <= 60; i++) { + stream.seek(0); + stream.setBitOffset(i % 8); + assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i)); + assertEquals(i * 2 % 8, stream.getBitOffset()); + } + } + } + + @Test + public void testReadShort() throws IOException { + byte[] bytes = new byte[8743]; // Slightly more than one buffer size + File file = randomDataToFile(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + + try (final ImageInputStream stream = new BufferedFileImageInputStream(file)) { + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + for (int i = 0; i < bytes.length / 2; i++) { + assertEquals(buffer.getShort(), stream.readShort()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readShort(); + } + }); + + stream.seek(0); + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < bytes.length / 2; i++) { + assertEquals(buffer.getShort(), stream.readShort()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readShort(); + } + }); + } + } + + @Test + public void testReadInt() throws IOException { + byte[] bytes = new byte[8743]; // Slightly more than one buffer size + File file = randomDataToFile(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + + try (final ImageInputStream stream = new BufferedFileImageInputStream(file)) { + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + for (int i = 0; i < bytes.length / 4; i++) { + assertEquals(buffer.getInt(), stream.readInt()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readInt(); + } + }); + + stream.seek(0); + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < bytes.length / 4; i++) { + assertEquals(buffer.getInt(), stream.readInt()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readInt(); + } + }); + } + } + + @Test + public void testReadLong() throws IOException { + byte[] bytes = new byte[8743]; // Slightly more than one buffer size + File file = randomDataToFile(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + + try (final ImageInputStream stream = new BufferedFileImageInputStream(file)) { + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + for (int i = 0; i < bytes.length / 8; i++) { + assertEquals(buffer.getLong(), stream.readLong()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readLong(); + } + }); + + stream.seek(0); + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < bytes.length / 8; i++) { + assertEquals(buffer.getLong(), stream.readLong()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readLong(); + } + }); + } + } + + @Test + public void testSeekPastEOF() throws IOException { + byte[] bytes = new byte[9]; + File file = randomDataToFile(bytes); + + try (final ImageInputStream stream = new BufferedFileImageInputStream(file)) { + stream.seek(1000); + + assertEquals(-1, stream.read()); + assertEquals(-1, stream.read(new byte[1], 0, 1)); + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readFully(new byte[1]); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readByte(); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readShort(); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readInt(); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readLong(); + } + }); + + stream.seek(0); + for (byte value : bytes) { + assertEquals(value, stream.readByte()); + } + + assertEquals(-1, stream.read()); + } + } + + @Test + public void testClose() throws IOException { + // Create wrapper stream + RandomAccessFile mock = mock(RandomAccessFile.class); + ImageInputStream stream = new BufferedFileImageInputStream(mock); + + stream.close(); + verify(mock, only()).close(); + } + + @Test + public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException { + // See #606 for details. + // Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int). + // Ie: Relies on read to return all bytes at once, without blocking + int size = BufferedFileImageInputStream.DEFAULT_BUFFER_SIZE * 7; + byte[] bytes = new byte[size]; + File file = randomDataToFile(bytes); + + try (BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file)) { + byte[] result = new byte[size]; + int head = stream.read(result, 0, 12); // Provoke a buffered read + int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read + + assertEquals(size, len + head); + assertArrayEquals(bytes, result); + } + } +} diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTest.java index 8bbf9b36..17dab84c 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTest.java @@ -1,17 +1,50 @@ +/* + * 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.imageio.stream; import com.twelvemonkeys.io.ole2.CompoundDocument; import com.twelvemonkeys.io.ole2.Entry; + import org.junit.Test; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Random; import static java.util.Arrays.fill; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * BufferedImageInputStreamTest @@ -42,6 +75,257 @@ public class BufferedImageInputStreamTest { } } + @Test + public void testReadBit() throws IOException { + byte[] bytes = new byte[] {(byte) 0xF0, (byte) 0x0F}; + + // Create wrapper stream + BufferedImageInputStream stream = new BufferedImageInputStream(new ByteArrayImageInputStream(bytes)); + + // Read all bits + assertEquals(1, stream.readBit()); + assertEquals(1, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(2, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(3, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(4, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); + assertEquals(5, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); + assertEquals(6, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); + assertEquals(7, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); // last bit + assertEquals(0, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + // Full reset, read same sequence again + stream.seek(0); + assertEquals(0, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + assertEquals(0, stream.readBit()); + assertEquals(0, stream.readBit()); + assertEquals(0, stream.readBit()); + assertEquals(0, stream.readBit()); + + assertEquals(0, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + // Full reset, read partial + stream.seek(0); + + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + + // Byte reset, read same sequence again + stream.setBitOffset(0); + + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + assertEquals(1, stream.readBit()); + assertEquals(0, stream.readBit()); + + // Byte reset, read partial sequence again + stream.setBitOffset(3); + + assertEquals(1, stream.readBit()); + assertEquals(0, stream.readBit()); + assertEquals(0, stream.getStreamPosition()); + + // Byte reset, read partial sequence again + stream.setBitOffset(6); + + assertEquals(0, stream.readBit()); + assertEquals(0, stream.readBit()); + assertEquals(1, stream.getStreamPosition()); + + // Read all bits, second byte + assertEquals(0, stream.readBit()); + assertEquals(1, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); + assertEquals(2, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); + assertEquals(3, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(0, stream.readBit()); + assertEquals(4, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(5, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(6, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); + assertEquals(7, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(1, stream.readBit()); // last bit + assertEquals(0, stream.getBitOffset()); + assertEquals(2, stream.getStreamPosition()); + } + + @Test + public void testReadBits() throws IOException { + byte[] bytes = new byte[] {(byte) 0xF0, (byte) 0xCC, (byte) 0xAA}; + + // Create wrapper stream + BufferedImageInputStream stream = new BufferedImageInputStream(new ByteArrayImageInputStream(bytes)); + + // Read all bits, first byte + assertEquals(3, stream.readBits(2)); + assertEquals(2, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(3, stream.readBits(2)); + assertEquals(4, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0, stream.readBits(2)); + assertEquals(6, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0, stream.readBits(2)); + assertEquals(0, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + // Read all bits, second byte + assertEquals(3, stream.readBits(2)); + assertEquals(2, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(0, stream.readBits(2)); + assertEquals(4, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(3, stream.readBits(2)); + assertEquals(6, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(0, stream.readBits(2)); + assertEquals(0, stream.getBitOffset()); + assertEquals(2, stream.getStreamPosition()); + + // Read all bits, third byte + assertEquals(2, stream.readBits(2)); + assertEquals(2, stream.getBitOffset()); + assertEquals(2, stream.getStreamPosition()); + + assertEquals(2, stream.readBits(2)); + assertEquals(4, stream.getBitOffset()); + assertEquals(2, stream.getStreamPosition()); + + assertEquals(2, stream.readBits(2)); + assertEquals(6, stream.getBitOffset()); + assertEquals(2, stream.getStreamPosition()); + + assertEquals(2, stream.readBits(2)); + assertEquals(0, stream.getBitOffset()); + assertEquals(3, stream.getStreamPosition()); + + // Full reset, read same sequence again + stream.seek(0); + assertEquals(0, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + // Read all bits, increasing size + assertEquals(7, stream.readBits(3)); // 111 + assertEquals(3, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(8, stream.readBits(4)); // 1000 + assertEquals(7, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(12, stream.readBits(5)); // 01100 + assertEquals(4, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(50, stream.readBits(6)); // 110010 + assertEquals(2, stream.getBitOffset()); + assertEquals(2, stream.getStreamPosition()); + + assertEquals(42, stream.readBits(6)); // 101010 + assertEquals(0, stream.getBitOffset()); + assertEquals(3, stream.getStreamPosition()); + + // Full reset, read same sequence again + stream.seek(0); + assertEquals(0, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + // Read all bits multi-byte + assertEquals(0xF0C, stream.readBits(12)); // 111100001100 + assertEquals(4, stream.getBitOffset()); + assertEquals(1, stream.getStreamPosition()); + + assertEquals(0xCAA, stream.readBits(12)); // 110010101010 + assertEquals(0, stream.getBitOffset()); + assertEquals(3, stream.getStreamPosition()); + + // Full reset, read same sequence again, all bits in one go + stream.seek(0); + assertEquals(0, stream.getBitOffset()); + assertEquals(0, stream.getStreamPosition()); + + assertEquals(0xF0CCAA, stream.readBits(24)); + } + + @Test + public void testReadBitsRandom() throws IOException { + long value = random.nextLong(); + byte[] bytes = new byte[8]; + ByteBuffer.wrap(bytes).putLong(value); + + // Create wrapper stream + BufferedImageInputStream stream = new BufferedImageInputStream(new ByteArrayImageInputStream(bytes)); + + for (int i = 1; i < 64; i++) { + stream.seek(0); + assertEquals(i + " bits differ", value >>> (64L - i), stream.readBits(i)); + } + } + + @Test + public void testClose() throws IOException { + // Create wrapper stream + ImageInputStream mock = mock(ImageInputStream.class); + BufferedImageInputStream stream = new BufferedImageInputStream(mock); + + stream.close(); + verify(mock, never()).close(); + } + // TODO: Write other tests // TODO: Create test that exposes read += -1 (eof) bug diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpiTest.java new file mode 100644 index 00000000..8441bcb8 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpiTest.java @@ -0,0 +1,18 @@ +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.spi.ImageInputStreamSpi; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class BufferedRAFImageInputStreamSpiTest extends ImageInputStreamSpiTest { + @Override + protected ImageInputStreamSpi createProvider() { + return new BufferedRAFImageInputStreamSpi(); + } + + @Override + protected RandomAccessFile createInput() throws IOException { + return new RandomAccessFile(File.createTempFile("test-", ".tst"), "r"); + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpiTest.java new file mode 100644 index 00000000..3b5242a4 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamSpiTest.java @@ -0,0 +1,16 @@ +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.spi.ImageInputStreamSpi; + +public class ByteArrayImageInputStreamSpiTest extends ImageInputStreamSpiTest { + + @Override + protected ImageInputStreamSpi createProvider() { + return new ByteArrayImageInputStreamSpi(); + } + + @Override + protected byte[] createInput() { + return new byte[0]; + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java index ff4e2f03..5d770994 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.stream; import org.junit.Test; @@ -9,11 +39,11 @@ import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rang import static org.junit.Assert.*; /** - * ByteArrayImageInputStreamTestCase + * ByteArrayImageInputStreamTest * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: ByteArrayImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ + * @version $Id: ByteArrayImageInputStreamTest.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ */ public class ByteArrayImageInputStreamTest { private final Random random = new Random(1709843507234566L); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java new file mode 100644 index 00000000..00e9841c --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java @@ -0,0 +1,84 @@ +package com.twelvemonkeys.imageio.stream; + +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.spi.ImageInputStreamSpi; +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.util.Locale; + +import static org.junit.Assert.*; + +abstract class ImageInputStreamSpiTest { + protected final ImageInputStreamSpi provider = createProvider(); + + @SuppressWarnings("unchecked") + protected final Class inputClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + + protected abstract ImageInputStreamSpi createProvider(); + + protected abstract T createInput() throws IOException; + + @Test + public void testInputClass() { + assertEquals(inputClass, provider.getInputClass()); + } + + @Test + public void testVendorName() { + assertNotNull(provider.getVendorName()); + assertEquals("TwelveMonkeys", provider.getVendorName()); + } + + @Test + public void testVersion() { + assertNotNull(provider.getVersion()); + } + + @Test + public void testDescription() { + assertNotNull(provider.getDescription(null)); + assertNotNull(provider.getDescription(Locale.ENGLISH)); + } + + @Test(expected = IllegalArgumentException.class) + public void createNull() throws IOException { + provider.createInputStreamInstance(null); + } + + @Test(expected = IllegalArgumentException.class) + public void createNullCached() throws IOException { + provider.createInputStreamInstance(null, true, ImageIO.getCacheDirectory()); + } + + @Test + public void createCachedNullCache() throws IOException { + try { + provider.createInputStreamInstance(createInput(), true, null); + } + catch (IllegalArgumentException expected) { + // All good + assertFalse(provider.needsCacheFile()); + } + } + + @Test + public void create() throws IOException { + assertNotNull(provider.createInputStreamInstance(createInput())); + } + + @Test + public void createCached() throws IOException { + if (provider.canUseCacheFile()) { + assertNotNull(provider.createInputStreamInstance(createInput(), true, ImageIO.getCacheDirectory())); + } + } + + @Test + public void createNonCached() throws IOException { + if (!provider.needsCacheFile()) { + assertNotNull(provider.createInputStreamInstance(createInput(), false, ImageIO.getCacheDirectory())); + } + } +} diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/StreamProviderInfoTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/StreamProviderInfoTest.java new file mode 100644 index 00000000..ffa29e5a --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/StreamProviderInfoTest.java @@ -0,0 +1,23 @@ +package com.twelvemonkeys.imageio.stream; + +import com.twelvemonkeys.imageio.spi.ProviderInfo; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class StreamProviderInfoTest { + private final ProviderInfo providerInfo = new StreamProviderInfo(); + + @Test + public void testVendorName() { + assertNotNull(providerInfo.getVendorName()); + assertEquals("TwelveMonkeys", providerInfo.getVendorName()); + } + + @Test + public void testVersion() { + assertNotNull(providerInfo.getVersion()); + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTest.java index fc7ab1ec..6d7537e7 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTest.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.stream; import org.junit.Test; diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpiTest.java new file mode 100644 index 00000000..51882f86 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpiTest.java @@ -0,0 +1,16 @@ +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.spi.ImageInputStreamSpi; +import java.net.URL; + +public class URLImageInputStreamSpiTest extends ImageInputStreamSpiTest { + @Override + protected ImageInputStreamSpi createProvider() { + return new URLImageInputStreamSpi(); + } + + @Override + protected URL createInput() { + return getClass().getResource("/empty-stream.txt"); + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapterTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapterTest.java similarity index 74% rename from imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapterTestCase.java rename to imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapterTest.java index bb725a5f..ccb4a040 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapterTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapterTest.java @@ -4,31 +4,33 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; -import com.twelvemonkeys.io.InputStreamAbstractTestCase; +import com.twelvemonkeys.io.InputStreamAbstractTest; import org.junit.Test; import javax.imageio.stream.MemoryCacheImageInputStream; @@ -36,7 +38,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * IIOInputStreamAdapter @@ -45,7 +48,7 @@ import static org.junit.Assert.*; * @author last modified by $Author: haraldk$ * @version $Id: IIOInputStreamAdapter.java,v 1.0 Apr 11, 2008 1:04:42 PM haraldk Exp$ */ -public class IIOInputStreamAdapterTestCase extends InputStreamAbstractTestCase { +public class IIOInputStreamAdapterTest extends InputStreamAbstractTest { protected InputStream makeInputStream(byte[] pBytes) { return new IIOInputStreamAdapter(new MemoryCacheImageInputStream(new ByteArrayInputStream(pBytes)), pBytes.length); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapterTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapterTest.java similarity index 59% rename from imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapterTestCase.java rename to imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapterTest.java index 0fdcf9a4..dc59bb5b 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapterTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapterTest.java @@ -4,31 +4,33 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; -import com.twelvemonkeys.io.OutputStreamAbstractTestCase; +import com.twelvemonkeys.io.OutputStreamAbstractTest; import org.junit.Test; import javax.imageio.stream.MemoryCacheImageOutputStream; @@ -36,7 +38,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * IIOOutputStreamAdapterTestCase @@ -45,7 +47,7 @@ import static org.junit.Assert.*; * @author last modified by $Author: haraldk$ * @version $Id: IIOOutputStreamAdapterTestCase.java,v 1.0 30.11.11 12:21 haraldk Exp$ */ -public class IIOOutputStreamAdapterTestCase extends OutputStreamAbstractTestCase { +public class IIOOutputStreamAdapterTest extends OutputStreamAbstractTest { @Override protected OutputStream makeObject() { return new IIOOutputStreamAdapter(new MemoryCacheImageOutputStream(new ByteArrayOutputStream())); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java new file mode 100644 index 00000000..05b9fa64 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java @@ -0,0 +1,145 @@ +package com.twelvemonkeys.imageio.util; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; + +/** + * IIOUtilTest + */ +public class IIOUtilTest { + + @Test + public void subsampleRowPeriod2Byte() { + int period = 2; + + byte[] input = {-1, 0, (byte) 0xAA, 0, -1}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {-1, (byte) 0xAA, -1}; + + IIOUtil.subsampleRow(input, 0, input.length, output, 0, 1, 8, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2ByteStride3() { + int period = 2; + + byte[] input = {-1, -1, -1, 0, 0, 0, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, 0, 0, 0, -1, -1, -1}; + byte[] output = new byte[9]; + byte[] expected = {-1, -1, -1, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, -1, -1, -1}; + + IIOUtil.subsampleRow(input, 0, input.length / 3, output, 0, 3, 8, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2Byte1() { + int period = 2; + + byte[] input = {(byte) 0xaa, (byte) 0xaa, (byte) 0xaa}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff, (byte) 0xf0}; + + IIOUtil.subsampleRow(input, 0, input.length * 8, output, 0, 1, 1, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod3_1Bit() { + int period = 3; + + byte[] input = {(byte) 0x92, (byte) 0x49, (byte) 0x24}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff}; + + IIOUtil.subsampleRow(input, 0, input.length * 8, output, 0, 1, 1, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2_2Bit() { + int period = 2; + + byte[] input = {(byte) 0xcc, (byte) 0xcc, (byte) 0xcc}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff, (byte) 0xf0}; + + IIOUtil.subsampleRow(input, 0, input.length * 4, output, 0, 1, 2, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2_4Bit() { + int period = 2; + + byte[] input = {(byte) 0xf0, (byte) 0xf0, (byte) 0xf0}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff, (byte) 0xf0}; + + IIOUtil.subsampleRow(input, 0, input.length * 2, output, 0, 1, 4, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2_1Bit2Samples() { + int period = 2; + + byte[] input = {(byte) 0xcc, (byte) 0xcc, (byte) 0xcc}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff, (byte) 0xf0}; + + IIOUtil.subsampleRow(input, 0, input.length * 4, output, 0, 2, 1, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2_2Bit2Samples() { + int period = 2; + + byte[] input = {(byte) 0xf0, (byte) 0xf0, (byte) 0xf0}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff, (byte) 0xf0}; + + IIOUtil.subsampleRow(input, 0, input.length * 2, output, 0, 2, 2, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2_4Bit2Samples() { + int period = 2; + + byte[] input = {-1, 0, (byte) 0xAA, 0, -1}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {-1, (byte) 0xAA, -1}; + + IIOUtil.subsampleRow(input, 0, input.length, output, 0, 2, 4, period); + + assertArrayEquals(expected, output); + } + + @Test + public void subsampleRowPeriod2_1BitOffset1() { + int period = 2; + + byte[] input = {(byte) 0xaa, (byte) 0xaa, (byte) 0xaa}; + byte[] output = new byte[divCeil(input.length, period)]; + byte[] expected = {(byte) 0xff, (byte) 0xf0}; + + IIOUtil.subsampleRow(input, 1, input.length * 8, output, 0, 1, 1, period); + + assertArrayEquals(expected, output); + } + + private int divCeil(int numerator, int denominator) { + return (numerator + denominator - 1) / denominator; + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java index d83c21d6..78de748e 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java @@ -4,31 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.util; import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; + import org.junit.Ignore; import org.junit.Test; import org.mockito.InOrder; @@ -47,6 +50,7 @@ import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.io.File; import java.io.IOException; +import java.lang.reflect.ParameterizedType; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -67,41 +71,34 @@ public abstract class ImageReaderAbstractTest { // TODO: Should we really test if the provider is installed? // - Pro: Tests the META-INF/services config // - Con: Not all providers should be installed at runtime... + // TODO: Create own subclass for testing the Spis? static { IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi()); ImageIO.setUseCache(false); } - protected abstract List getTestData(); + @SuppressWarnings("unchecked") + private final Class readerClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + + protected final ImageReaderSpi provider = createProvider(); protected abstract ImageReaderSpi createProvider(); - protected abstract Class getReaderClass(); - - protected T createReader() { - try { - return getReaderClass().newInstance(); - } - catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } + protected final T createReader() throws IOException { + return readerClass.cast(provider.createReaderInstance(null)); } + protected abstract List getTestData(); + protected abstract List getFormatNames(); protected abstract List getSuffixes(); protected abstract List getMIMETypes(); - protected boolean allowsNullRawImageType() { - return false; - } - protected static void failBecause(String message, Throwable exception) { - AssertionError error = new AssertionError(message); - error.initCause(exception); - throw error; + throw new AssertionError(message, exception); } protected void assertProviderInstalledForName(final String pFormat, final Class pReaderClass) { @@ -121,17 +118,20 @@ public abstract class ImageReaderAbstractTest { boolean found = false; while (pReaders.hasNext()) { ImageReader reader = pReaders.next(); - if (reader.getClass() == pReaderClass) { + if (reader.getClass() == pReaderClass && isOurProvider(reader.getOriginatingProvider())) { found = true; } } - assertTrue(String.format("%s not installed for %s", pReaderClass.getSimpleName(), pFormat), found); + assertTrue(String.format("%s not provided by %s for '%s'", pReaderClass.getSimpleName(), provider.getClass().getSimpleName(), pFormat), found); + } + + private boolean isOurProvider(final ImageReaderSpi spi) { + return provider.getClass().isInstance(spi); } @Test public void testProviderInstalledForNames() { - Class readerClass = getReaderClass(); for (String name : getFormatNames()) { assertProviderInstalledForName(name, readerClass); } @@ -139,7 +139,6 @@ public abstract class ImageReaderAbstractTest { @Test public void testProviderInstalledForSuffixes() { - Class readerClass = getReaderClass(); for (String suffix : getSuffixes()) { assertProviderInstalledForSuffix(suffix, readerClass); } @@ -147,7 +146,6 @@ public abstract class ImageReaderAbstractTest { @Test public void testProviderInstalledForMIMETypes() { - Class readerClass = getReaderClass(); for (String type : getMIMETypes()) { assertProviderInstalledForMIMEType(type, readerClass); } @@ -157,7 +155,6 @@ public abstract class ImageReaderAbstractTest { public void testProviderCanRead() throws IOException { List testData = getTestData(); - ImageReaderSpi provider = createProvider(); for (TestData data : testData) { ImageInputStream stream = data.getInputStream(); assertNotNull(stream); @@ -170,7 +167,7 @@ public abstract class ImageReaderAbstractTest { boolean canRead = false; try { - canRead = createProvider().canDecodeInput(null); + canRead = provider.canDecodeInput(null); } catch (IllegalArgumentException ignore) { } @@ -185,7 +182,7 @@ public abstract class ImageReaderAbstractTest { } @Test - public void testSetInput() { + public void testSetInput() throws IOException { // Should just pass with no exceptions ImageReader reader = createReader(); assertNotNull(reader); @@ -193,18 +190,21 @@ public abstract class ImageReaderAbstractTest { for (TestData data : getTestData()) { reader.setInput(data.getInputStream()); } + + reader.dispose(); } @Test - public void testSetInputNull() { + public void testSetInputNull() throws IOException { // Should just pass with no exceptions ImageReader reader = createReader(); assertNotNull(reader); reader.setInput(null); + reader.dispose(); } @Test - public void testRead() { + public void testRead() throws IOException { ImageReader reader = createReader(); for (TestData data : getTestData()) { @@ -217,6 +217,7 @@ public abstract class ImageReaderAbstractTest { image = reader.read(i); } catch (Exception e) { + e.printStackTrace(); failBecause(String.format("Image %s index %s could not be read: %s", data.getInput(), i, e), e); } @@ -233,10 +234,12 @@ public abstract class ImageReaderAbstractTest { ); } } + + reader.dispose(); } @Test - public void testReadIndexNegative() { + public void testReadIndexNegative() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -252,10 +255,12 @@ public abstract class ImageReaderAbstractTest { failBecause("Image could not be read", e); } assertNull(image); + + reader.dispose(); } @Test - public void testReadIndexOutOfBounds() { + public void testReadIndexOutOfBounds() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -271,24 +276,8 @@ public abstract class ImageReaderAbstractTest { failBecause("Image could not be read", e); } assertNull(image); - } - @Test - public void testReadNoInput() { - ImageReader reader = createReader(); - // Do not set input - - BufferedImage image = null; - try { - image = reader.read(0); - fail("Read image with no input"); - } - catch (IllegalStateException ignore) { - } - catch (IOException e) { - failBecause("Image could not be read", e); - } - assertNull(image); + reader.dispose(); } @Test @@ -307,69 +296,79 @@ public abstract class ImageReaderAbstractTest { assertEquals(first.getType(), second.getType()); assertEquals(first.getWidth(), second.getWidth()); assertEquals(first.getHeight(), second.getHeight()); + + reader.dispose(); } - @Test - public void testReadIndexNegativeWithParam() { - ImageReader reader = createReader(); - TestData data = getTestData().get(0); - reader.setInput(data.getInputStream()); - - BufferedImage image = null; - try { - image = reader.read(-1, reader.getDefaultReadParam()); - fail("Read image with illegal index"); - } - catch (IndexOutOfBoundsException ignore) { - } - catch (IOException e) { - failBecause("Image could not be read", e); - } - - assertNull(image); - } - - @Test - public void testReadIndexOutOfBoundsWithParam() { - ImageReader reader = createReader(); - TestData data = getTestData().get(0); - reader.setInput(data.getInputStream()); - - BufferedImage image = null; - try { - image = reader.read(99, reader.getDefaultReadParam()); - fail("Read image with index out of bounds"); - } - catch (IndexOutOfBoundsException ignore) { - } - catch (IOException e) { - failBecause("Image could not be read", e); - } - - assertNull(image); - } - - @Test - public void testReadNoInputWithParam() { + @Test(expected = IllegalStateException.class) + public void testReadNoInput() throws IOException { ImageReader reader = createReader(); // Do not set input - BufferedImage image = null; try { - image = reader.read(0, reader.getDefaultReadParam()); + reader.read(0); fail("Read image with no input"); } - catch (IllegalStateException ignore) { - } catch (IOException e) { failBecause("Image could not be read", e); } + } - assertNull(image); + @Test(expected = IndexOutOfBoundsException.class) + public void testReadIndexNegativeWithParam() throws IOException { + ImageReader reader = createReader(); + TestData data = getTestData().get(0); + reader.setInput(data.getInputStream()); + + try { + reader.read(-1, reader.getDefaultReadParam()); + fail("Read image with illegal index"); + } + catch (IOException e) { + failBecause("Image could not be read", e); + } + finally { + reader.dispose(); + } + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testReadIndexOutOfBoundsWithParam() throws IOException { + ImageReader reader = createReader(); + TestData data = getTestData().get(0); + reader.setInput(data.getInputStream()); + + try { + reader.read(Short.MAX_VALUE, reader.getDefaultReadParam()); + fail("Read image with index out of bounds"); + } + catch (IOException e) { + failBecause("Image could not be read", e); + } + finally { + reader.dispose(); + } + } + + @Test(expected = IllegalStateException.class) + public void testReadNoInputWithParam() throws IOException { + ImageReader reader = createReader(); + // Do not set input + + try { + reader.read(0, reader.getDefaultReadParam()); + fail("Read image with no input"); + } + catch (IOException e) { + failBecause("Image could not be read", e); + } + finally { + reader.dispose(); + } } @Test - public void testReadWithNewParam() { + public void testReadWithNewParam() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -385,10 +384,12 @@ public abstract class ImageReaderAbstractTest { assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: " + image.getWidth(), data.getDimension(0).width, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), data.getDimension(0).height, image.getHeight()); + + reader.dispose(); } @Test - public void testReadWithDefaultParam() { + public void testReadWithDefaultParam() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -404,10 +405,12 @@ public abstract class ImageReaderAbstractTest { assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: " + image.getWidth(), data.getDimension(0).width, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), data.getDimension(0).height, image.getHeight()); + + reader.dispose(); } @Test - public void testReadWithNullParam() { + public void testReadWithNullParam() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -423,10 +426,12 @@ public abstract class ImageReaderAbstractTest { assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: " + image.getWidth(), data.getDimension(0).width, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), data.getDimension(0).height, image.getHeight()); + + reader.dispose(); } @Test - public void testReadWithSizeParam() { + public void testReadWithSizeParam() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -447,10 +452,12 @@ public abstract class ImageReaderAbstractTest { assertEquals("Read image has wrong width: " + image.getWidth(), 10, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), 10, image.getHeight()); } + + reader.dispose(); } @Test - public void testReadWithSubsampleParamDimensions() { + public void testReadWithSubsampleParamDimensions() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -469,6 +476,8 @@ public abstract class ImageReaderAbstractTest { assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: ", (data.getDimension(0).width + 4) / 5, image.getWidth()); assertEquals("Read image has wrong height: ", (data.getDimension(0).height + 4) / 5, image.getHeight()); + + reader.dispose(); } @Test @@ -492,11 +501,14 @@ public abstract class ImageReaderAbstractTest { } assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param); + + reader.dispose(); } // TODO: Subsample all test data // TODO: Subsample with varying ratios and offsets + @SuppressWarnings("SameParameterValue") protected final void assertSubsampledImageDataEquals(String message, BufferedImage expected, BufferedImage actual, ImageReadParam param) throws IOException { assertNotNull("Expected image was null", expected); assertNotNull("Actual image was null!", actual); @@ -520,10 +532,10 @@ public abstract class ImageReaderAbstractTest { int actualRGB = actual.getRGB(x, y); try { - assertEquals(String.format("%s alpha at (%d, %d)", message, x, y), (expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 5); - assertEquals(String.format("%s red at (%d, %d)", message, x, y), (expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, 5); - assertEquals(String.format("%s green at (%d, %d)", message, x, y), (expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, 5); - assertEquals(String.format("%s blue at (%d, %d)", message, x, y), expectedRGB & 0xff, actualRGB & 0xff, 5); + assertEquals((expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 5); + assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, 5); + assertEquals((expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, 5); + assertEquals(expectedRGB & 0xff, actualRGB & 0xff, 5); } catch (AssertionError e) { File tempExpected = File.createTempFile("junit-expected-", ".png"); @@ -533,7 +545,6 @@ public abstract class ImageReaderAbstractTest { System.err.println("tempActual.getAbsolutePath(): " + tempActual.getAbsolutePath()); ImageIO.write(actual, "PNG", tempActual); - assertEquals(String.format("%s ARGB at (%d, %d)", message, x, y), String.format("#%08x", expectedRGB), String.format("#%08x", actualRGB)); } } @@ -562,7 +573,7 @@ public abstract class ImageReaderAbstractTest { } @Test - public void testReadWithSourceRegionParam() { + public void testReadWithSourceRegionParam() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -581,6 +592,8 @@ public abstract class ImageReaderAbstractTest { assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: " + image.getWidth(), 10, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), 10, image.getHeight()); + + reader.dispose(); } @Test @@ -591,38 +604,53 @@ public abstract class ImageReaderAbstractTest { protected void assertReadWithSourceRegionParamEqualImage(final Rectangle r, final TestData data, final int imageIndex) throws IOException { ImageReader reader = createReader(); - reader.setInput(data.getInputStream()); - ImageReadParam param = reader.getDefaultReadParam(); + try (ImageInputStream inputStream = data.getInputStream()) { + reader.setInput(inputStream); + ImageReadParam param = reader.getDefaultReadParam(); - // Read full image and get sub image for comparison - final BufferedImage roi = reader.read(imageIndex, param).getSubimage(r.x, r.y, r.width, r.height); + // Read full image and get sub image for comparison + BufferedImage original = reader.read(imageIndex, param); + final BufferedImage roi = original.getSubimage(r.x, r.y, r.width, r.height); - param.setSourceRegion(r); + param.setSourceRegion(r); - final BufferedImage image = reader.read(imageIndex, param); + final BufferedImage image = reader.read(imageIndex, param); -// try { -// SwingUtilities.invokeAndWait(new Runnable() { -// public void run() { -// JPanel panel = new JPanel(new FlowLayout()); -// panel.add(new JLabel(new BufferedImageIcon(roi, r.width * 10, r.height * 10, true))); -// panel.add(new JLabel(new BufferedImageIcon(image, r.width * 10, r.height * 10, true))); -// JOptionPane.showConfirmDialog(null, panel); -// } -// }); -// } -// catch (Exception e) { -// throw new RuntimeException(e); -// } + assertNotNull("Image was null!", image); + assertEquals("Read image has wrong width: " + image.getWidth(), r.width, image.getWidth()); + assertEquals("Read image has wrong height: " + image.getHeight(), r.height, image.getHeight()); - assertNotNull("Image was null!", image); - assertEquals("Read image has wrong width: " + image.getWidth(), r.width, image.getWidth()); - assertEquals("Read image has wrong height: " + image.getHeight(), r.height, image.getHeight()); - assertImageDataEquals("Images differ", roi, image); + try { + assertImageDataEquals("Images differ", roi, image); + } + catch (AssertionError e) { + File tempExpected = File.createTempFile("junit-expected-", ".png"); + System.err.println("tempExpected.getAbsolutePath(): " + tempExpected.getAbsolutePath()); + + Graphics2D graphics = original.createGraphics(); + try { + graphics.setColor(Color.RED); + graphics.draw(r); + } + finally { + graphics.dispose(); + } + + ImageIO.write(original, "PNG", tempExpected); + File tempActual = File.createTempFile("junit-actual-", ".png"); + System.err.println("tempActual.getAbsolutePath(): " + tempActual.getAbsolutePath()); + ImageIO.write(image, "PNG", tempActual); + + throw e; + } + } + finally { + reader.dispose(); + } } @Test - public void testReadWithSizeAndSourceRegionParam() { + public void testReadWithSizeAndSourceRegionParam() throws IOException { // TODO: Is this test correct??? ImageReader reader = createReader(); TestData data = getTestData().get(0); @@ -648,10 +676,12 @@ public abstract class ImageReaderAbstractTest { assertEquals("Read image has wrong width: " + image.getWidth(), 10, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), 10, image.getHeight()); } + + reader.dispose(); } @Test - public void testReadWithSubsampleAndSourceRegionParam() { + public void testReadWithSubsampleAndSourceRegionParam() throws IOException { // NOTE: The "standard" (com.sun.imageio.plugin.*) ImageReaders pass // this test, so the test should be correct... ImageReader reader = createReader(); @@ -673,10 +703,11 @@ public abstract class ImageReaderAbstractTest { assertEquals("Read image has wrong width: " + image.getWidth(), 5, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), 5, image.getHeight()); + reader.dispose(); } @Test - public void testReadAsRenderedImageIndexNegative() { + public void testReadAsRenderedImageIndexNegative() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -692,11 +723,14 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { failBecause("Image could not be read", e); } + assertNull(image); + + reader.dispose(); } @Test - public void testReadAsRenderedImageIndexOutOfBounds() throws IIOException { + public void testReadAsRenderedImageIndexOutOfBounds() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -716,17 +750,20 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { failBecause("Image could not be read", e); } + assertNull(image); + + reader.dispose(); } @Test - public void testReadAsRenderedImageNoInput() { + public void testReadAsRenderedImageNoInput() throws IOException { ImageReader reader = createReader(); // Do not set input - BufferedImage image = null; + RenderedImage image = null; try { - image = reader.read(0, reader.getDefaultReadParam()); + image = reader.readAsRenderedImage(0, reader.getDefaultReadParam()); fail("Read image with no input"); } catch (IllegalStateException expected) { @@ -735,11 +772,14 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { failBecause("Image could not be read", e); } + assertNull(image); + + reader.dispose(); } @Test - public void testReadAsRenderedImage() { + public void testReadAsRenderedImage() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -751,15 +791,18 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { failBecause("Image could not be read", e); } + assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: " + image.getWidth(), data.getDimension(0).width, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), data.getDimension(0).height, image.getHeight()); + + reader.dispose(); } @Test - public void testReadAsRenderedImageWithDefaultParam() { + public void testReadAsRenderedImageWithDefaultParam() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -771,22 +814,26 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { failBecause("Image could not be read", e); } + assertNotNull("Image was null!", image); assertEquals("Read image has wrong width: " + image.getWidth(), data.getDimension(0).width, image.getWidth()); assertEquals("Read image has wrong height: " + image.getHeight(), data.getDimension(0).height, image.getHeight()); + + reader.dispose(); } @Test - public void testGetDefaultReadParam() { + public void testGetDefaultReadParam() throws IOException { ImageReader reader = createReader(); ImageReadParam param = reader.getDefaultReadParam(); assertNotNull(param); + reader.dispose(); } @Test - public void testGetFormatName() { + public void testGetFormatName() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -798,10 +845,11 @@ public abstract class ImageReaderAbstractTest { fail(e.getMessage()); } assertNotNull(name); + reader.dispose(); } @Test - public void testGetMinIndex() { + public void testGetMinIndex() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -813,10 +861,11 @@ public abstract class ImageReaderAbstractTest { catch (IllegalStateException ignore) { } assertEquals(0, num); + reader.dispose(); } @Test - public void testGetMinIndexNoInput() { + public void testGetMinIndexNoInput() throws IOException { ImageReader reader = createReader(); int num = 0; @@ -826,10 +875,11 @@ public abstract class ImageReaderAbstractTest { catch (IllegalStateException ignore) { } assertEquals(0, num); + reader.dispose(); } @Test - public void testGetNumImages() { + public void testGetNumImages() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -854,10 +904,11 @@ public abstract class ImageReaderAbstractTest { if (data.getImageCount() != num) { System.err.println("WARNING: Image count not equal to test data count"); } + reader.dispose(); } @Test - public void testGetNumImagesNoInput() { + public void testGetNumImagesNoInput() throws IOException { ImageReader reader = createReader(); int num = -1; @@ -881,10 +932,11 @@ public abstract class ImageReaderAbstractTest { fail(e.getMessage()); } assertEquals(-1, num); + reader.dispose(); } @Test - public void testGetWidth() { + public void testGetWidth() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -897,10 +949,11 @@ public abstract class ImageReaderAbstractTest { fail("Could not read image width: " + e); } assertEquals("Wrong width reported", data.getDimension(0).width, width); + reader.dispose(); } @Test - public void testGetWidthIndexOutOfBounds() { + public void testGetWidthIndexOutOfBounds() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -917,16 +970,17 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { fail("Could not read image aspect ratio: " + e); } + reader.dispose(); } @Test - public void testGetWidthNoInput() { + public void testGetWidthNoInput() throws IOException { ImageReader reader = createReader(); int width = 0; try { width = reader.getWidth(0); - fail("Width read without imput"); + fail("Width read without input"); } catch (IllegalStateException ignore) { } @@ -934,10 +988,11 @@ public abstract class ImageReaderAbstractTest { fail("Could not read image width: " + e); } assertEquals("Wrong width reported", 0, width); + reader.dispose(); } @Test - public void testGetHeight() { + public void testGetHeight() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -950,16 +1005,17 @@ public abstract class ImageReaderAbstractTest { fail("Could not read image height: " + e); } assertEquals("Wrong height reported", data.getDimension(0).height, height); + reader.dispose(); } @Test - public void testGetHeightNoInput() { + public void testGetHeightNoInput() throws IOException { ImageReader reader = createReader(); int height = 0; try { height = reader.getHeight(0); - fail("height read without imput"); + fail("height read without input"); } catch (IllegalStateException ignore) { } @@ -967,10 +1023,11 @@ public abstract class ImageReaderAbstractTest { fail("Could not read image height: " + e); } assertEquals("Wrong height reported", 0, height); + reader.dispose(); } @Test - public void testGetHeightIndexOutOfBounds() { + public void testGetHeightIndexOutOfBounds() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -987,10 +1044,11 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { fail("Could not read image height: " + e); } + reader.dispose(); } @Test - public void testGetAspectRatio() { + public void testGetAspectRatio() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1004,10 +1062,11 @@ public abstract class ImageReaderAbstractTest { } Dimension d = data.getDimension(0); assertEquals("Wrong aspect aspect ratio", d.getWidth() / d.getHeight(), aspectRatio, 0.001); + reader.dispose(); } @Test - public void testGetAspectRatioNoInput() { + public void testGetAspectRatioNoInput() throws IOException { ImageReader reader = createReader(); float aspectRatio = 0f; @@ -1021,10 +1080,11 @@ public abstract class ImageReaderAbstractTest { fail("Could not read image aspect ratio" + e); } assertEquals("Wrong aspect aspect ratio", 0f, aspectRatio, 0f); + reader.dispose(); } @Test - public void testGetAspectRatioIndexOutOfBounds() { + public void testGetAspectRatioIndexOutOfBounds() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1041,16 +1101,17 @@ public abstract class ImageReaderAbstractTest { catch (IOException e) { fail("Could not read image aspect ratio" + e); } + reader.dispose(); } @Test - public void testDisposeBeforeRead() { + public void testDisposeBeforeRead() throws IOException { ImageReader reader = createReader(); reader.dispose(); // Just pass with no exceptions } @Test - public void testDisposeAfterRead() { + public void testDisposeAfterRead() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1058,19 +1119,21 @@ public abstract class ImageReaderAbstractTest { } @Test - public void testAddIIOReadProgressListener() { + public void testAddIIOReadProgressListener() throws IOException { ImageReader reader = createReader(); reader.addIIOReadProgressListener(mock(IIOReadProgressListener.class)); + reader.dispose(); } @Test - public void testAddIIOReadProgressListenerNull() { + public void testAddIIOReadProgressListenerNull() throws IOException { ImageReader reader = createReader(); reader.addIIOReadProgressListener(null); + reader.dispose(); } @Test - public void testAddIIOReadProgressListenerCallbacks() { + public void testAddIIOReadProgressListenerCallbacks() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1090,10 +1153,11 @@ public abstract class ImageReaderAbstractTest { ordered.verify(listener).imageStarted(reader, 0); ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt()); ordered.verify(listener).imageComplete(reader); + reader.dispose(); } @Test - public void testMultipleAddIIOReadProgressListenerCallbacks() { + public void testMultipleAddIIOReadProgressListenerCallbacks() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1127,26 +1191,29 @@ public abstract class ImageReaderAbstractTest { ordered.verify(listener).imageComplete(reader); ordered.verify(listenerToo).imageComplete(reader); ordered.verify(listenerThree).imageComplete(reader); + reader.dispose(); } @Test - public void testRemoveIIOReadProgressListenerNull() { + public void testRemoveIIOReadProgressListenerNull() throws IOException { ImageReader reader = createReader(); reader.removeIIOReadProgressListener(null); + reader.dispose(); } @Test - public void testRemoveIIOReadProgressListenerNone() { + public void testRemoveIIOReadProgressListenerNone() throws IOException { ImageReader reader = createReader(); reader.removeIIOReadProgressListener(mock(IIOReadProgressListener.class)); + reader.dispose(); } @Test - public void testRemoveIIOReadProgressListener() { + public void testRemoveIIOReadProgressListener() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); - + IIOReadProgressListener listener = mock(IIOReadProgressListener.class); reader.addIIOReadProgressListener(listener); reader.removeIIOReadProgressListener(listener); @@ -1160,10 +1227,11 @@ public abstract class ImageReaderAbstractTest { // Should not have called any methods... verifyZeroInteractions(listener); + reader.dispose(); } @Test - public void testRemoveIIOReadProgressListenerMultiple() { + public void testRemoveIIOReadProgressListenerMultiple() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1191,10 +1259,11 @@ public abstract class ImageReaderAbstractTest { ordered.verify(listenerToo).imageStarted(reader, 0); ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyInt()); ordered.verify(listenerToo).imageComplete(reader); + reader.dispose(); } @Test - public void testRemoveAllIIOReadProgressListeners() { + public void testRemoveAllIIOReadProgressListeners() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1213,10 +1282,11 @@ public abstract class ImageReaderAbstractTest { // Should not have called any methods... verifyZeroInteractions(listener); + reader.dispose(); } @Test - public void testRemoveAllIIOReadProgressListenersMultiple() { + public void testRemoveAllIIOReadProgressListenersMultiple() throws IOException { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1239,10 +1309,11 @@ public abstract class ImageReaderAbstractTest { // Should not have called any methods... verifyZeroInteractions(listener); verifyZeroInteractions(listenerToo); + reader.dispose(); } @Test - public void testAbort() { + public void testAbort() throws IOException { final ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1256,7 +1327,7 @@ public abstract class ImageReaderAbstractTest { // Create a listener that just makes the reader abort immediately... IIOReadProgressListener abortingListener = mock(IIOReadProgressListener.class, "Aborter"); Answer abort = new Answer() { - public Void answer(InvocationOnMock invocation) throws Throwable { + public Void answer(InvocationOnMock invocation) { reader.abort(); return null; } @@ -1275,6 +1346,7 @@ public abstract class ImageReaderAbstractTest { verify(listener).readAborted(reader); verify(listenerToo).readAborted(reader); + reader.dispose(); } @Test @@ -1284,9 +1356,6 @@ public abstract class ImageReaderAbstractTest { reader.setInput(data.getInputStream()); ImageTypeSpecifier rawType = reader.getRawImageType(0); - if (rawType == null && allowsNullRawImageType()) { - continue; - } assertNotNull(rawType); Iterator types = reader.getImageTypes(0); @@ -1306,8 +1375,10 @@ public abstract class ImageReaderAbstractTest { } } - assertTrue("ImageTypeSepcifier from getRawImageType should be in the iterator from getImageTypes", rawFound); + assertTrue("ImageTypeSpecifier from getRawImageType should be in the iterator from getImageTypes", rawFound); } + + reader.dispose(); } @Test @@ -1334,6 +1405,7 @@ public abstract class ImageReaderAbstractTest { assertSame(destination, result); } + reader.dispose(); } @Test @@ -1363,6 +1435,7 @@ public abstract class ImageReaderAbstractTest { else { System.err.println("WARNING: Test skipped due to reader.getRawImageType(0) returning null"); } + reader.dispose(); } @Test @@ -1398,7 +1471,7 @@ public abstract class ImageReaderAbstractTest { destination.getType() == BufferedImage.TYPE_BYTE_INDEXED) && message.contains("indexcolormodel")))) { failBecause( - "Wrong message: " + message + " for type " + destination.getType(), expected + "Wrong message: " + message + " for type " + destination.getType(), expected ); } } @@ -1407,6 +1480,7 @@ public abstract class ImageReaderAbstractTest { assertTrue("Wrong message: " + message, message.contains("dest")); } } + reader.dispose(); } @Test @@ -1435,6 +1509,7 @@ public abstract class ImageReaderAbstractTest { } } } + reader.dispose(); } private List createIllegalTypes(Iterator pValidTypes) { @@ -1480,9 +1555,9 @@ public abstract class ImageReaderAbstractTest { assertNotNull(image); assertEquals(reader.getWidth(0) + point.x, image.getWidth()); assertEquals(reader.getHeight(0) + point.y, image.getHeight()); + reader.dispose(); } - @SuppressWarnings("ConstantConditions") @Test public void testSetDestinationOffsetNull() throws IOException { final ImageReader reader = createReader(); @@ -1497,6 +1572,7 @@ public abstract class ImageReaderAbstractTest { catch (IllegalArgumentException e) { assertTrue(e.getMessage().toLowerCase().contains("offset")); } + reader.dispose(); } @Test @@ -1532,12 +1608,13 @@ public abstract class ImageReaderAbstractTest { assertEquals(expectedModel.getDataType(), resultModel.getDataType()); assertEquals(expectedModel.getNumBands(), resultModel.getNumBands()); assertEquals(expectedModel.getNumDataElements(), resultModel.getNumDataElements()); - assertTrue(Arrays.equals(expectedModel.getSampleSize(), resultModel.getSampleSize())); + assertArrayEquals(expectedModel.getSampleSize(), resultModel.getSampleSize()); assertEquals(expectedModel.getTransferType(), resultModel.getTransferType()); for (int i = 0; i < expectedModel.getNumBands(); i++) { assertEquals(expectedModel.getSampleSize(i), resultModel.getSampleSize(i)); } } + reader.dispose(); } @Test @@ -1545,22 +1622,83 @@ public abstract class ImageReaderAbstractTest { T reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); - + BufferedImage one = reader.read(0); BufferedImage two = reader.read(0); - + + // Test for same BufferedImage instance assertNotSame("Multiple reads return same (mutable) image", one, two); - one.setRGB(0, 0, Color.BLUE.getRGB()); - two.setRGB(0, 0, Color.RED.getRGB()); - + // Test for same backing storage (array) + one.setRGB(0, 0, Color.BLACK.getRGB()); + two.setRGB(0, 0, Color.WHITE.getRGB()); assertTrue(one.getRGB(0, 0) != two.getRGB(0, 0)); + + reader.dispose(); + } + + @Test + public void testReadThumbnails() throws IOException { + T reader = createReader(); + + if (reader.readerSupportsThumbnails()) { + for (TestData testData : getTestData()) { + try (ImageInputStream inputStream = testData.getInputStream()) { + reader.setInput(inputStream); + + int numImages = reader.getNumImages(true); + + for (int i = 0; i < numImages; i++) { + int numThumbnails = reader.getNumThumbnails(0); + + for (int t = 0; t < numThumbnails; t++) { + BufferedImage thumbnail = reader.readThumbnail(0, t); + + assertNotNull(thumbnail); + } + } + } + } + } + + reader.dispose(); + } + + @Test + public void testThumbnailProgress() throws IOException { + T reader = createReader(); + + IIOReadProgressListener listener = mock(IIOReadProgressListener.class); + reader.addIIOReadProgressListener(listener); + + if (reader.readerSupportsThumbnails()) { + for (TestData testData : getTestData()) { + try (ImageInputStream inputStream = testData.getInputStream()) { + + reader.setInput(inputStream); + + int numThumbnails = reader.getNumThumbnails(0); + for (int i = 0; i < numThumbnails; i++) { + reset(listener); + + reader.readThumbnail(0, i); + + InOrder order = inOrder(listener); + order.verify(listener).thumbnailStarted(reader, 0, i); + order.verify(listener, atLeastOnce()).thumbnailProgress(reader, 100f); + order.verify(listener).thumbnailComplete(reader); + } + } + } + } + + reader.dispose(); } @Test public void testNotBadCachingThumbnails() throws IOException { T reader = createReader(); - + if (reader.readerSupportsThumbnails()) { for (TestData data : getTestData()) { reader.setInput(data.getInputStream()); @@ -1587,7 +1725,7 @@ public abstract class ImageReaderAbstractTest { assertTrue(one.getRGB(0, 0) != two.getRGB(0, 0)); } - + if (thumbnails > 0) { // We've tested thumbnails, let's get out of here return; @@ -1597,24 +1735,23 @@ public abstract class ImageReaderAbstractTest { fail("No thumbnails tested for reader that supports thumbnails."); } + reader.dispose(); } - + @Ignore("TODO: Implement") @Test - public void testSetDestinationBands() throws IOException { + public void testSetDestinationBands() { throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement } @Ignore("TODO: Implement") @Test - public void testSetSourceBands() throws IOException { + public void testSetSourceBands() { throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement } @Test public void testProviderAndMetadataFormatNamesMatch() throws IOException { - ImageReaderSpi provider = createProvider(); - ImageReader reader = createReader(); reader.setInput(getTestData().get(0).getInputStream()); @@ -1627,6 +1764,7 @@ public abstract class ImageReaderAbstractTest { if (streamMetadata != null) { assertEquals(provider.getNativeStreamMetadataFormatName(), streamMetadata.getNativeMetadataFormatName()); } + reader.dispose(); } protected URL getClassLoaderResource(final String pName) { @@ -1734,7 +1872,7 @@ public abstract class ImageReaderAbstractTest { @Override public String toString() { - return getClass().getSimpleName() + ": " + String.valueOf(input); + return String.format("%s: %s", getClass().getSimpleName(), input); } } } diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java index 65f9a083..21256c26 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java @@ -1,13 +1,49 @@ +/* + * Copyright (c) 2014, 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.util; -import com.twelvemonkeys.lang.Validate; -import org.junit.Test; +import static org.junit.Assert.assertEquals; + +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; import javax.imageio.ImageTypeSpecifier; -import java.awt.color.ColorSpace; -import java.awt.image.*; -import static org.junit.Assert.assertEquals; +import org.junit.Test; + +import com.twelvemonkeys.lang.Validate; public class ImageTypeSpecifiersTest { @@ -31,10 +67,21 @@ public class ImageTypeSpecifiersTest { @Test public void testCreateFromBufferedImageType() { for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) { - assertEquals( - ImageTypeSpecifier.createFromBufferedImageType(type), - ImageTypeSpecifiers.createFromBufferedImageType(type) - ); + ImageTypeSpecifier expected; + + switch (type) { + // Special handling for USHORT_565 and 555, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits) + case BufferedImage.TYPE_USHORT_565_RGB: + expected = createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); + break; + case BufferedImage.TYPE_USHORT_555_RGB: + expected = createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); + break; + default: + expected = ImageTypeSpecifier.createFromBufferedImageType(type); + } + + assertEquals(expected, ImageTypeSpecifiers.createFromBufferedImageType(type)); } } @@ -89,7 +136,7 @@ public class ImageTypeSpecifiersTest { // Extra: Make sure color models bits is actually 16 (ImageTypeSpecifier equivalent returns 32) assertEquals(16, ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize()); - } + } @Test public void testCreatePacked8() { @@ -500,7 +547,7 @@ public class ImageTypeSpecifiersTest { } @Test - public void testCreatePackedGrayscale1() { + public void testCreatePackedGrayscale1BPP() { assertEquals( ImageTypeSpecifier.createGrayscale(1, DataBuffer.TYPE_BYTE, false), ImageTypeSpecifiers.createPackedGrayscale(GRAY, 1, DataBuffer.TYPE_BYTE) @@ -508,7 +555,8 @@ public class ImageTypeSpecifiersTest { } @Test - public void testCreatePackedGrayscale2() { + public void testCreatePackedGrayscale2BPP() { + // TODO: Fails on Java 11+, because IndexColorModel now has an overloaded equals that actually tests the color entries assertEquals( ImageTypeSpecifier.createGrayscale(2, DataBuffer.TYPE_BYTE, false), ImageTypeSpecifiers.createPackedGrayscale(GRAY, 2, DataBuffer.TYPE_BYTE) @@ -516,7 +564,8 @@ public class ImageTypeSpecifiersTest { } @Test - public void testCreatePackedGrayscale4() { + public void testCreatePackedGrayscale4BPP() { + // TODO: Fails on Java 11+, because IndexColorModel now has an overloaded equals that actually tests the color entries assertEquals( ImageTypeSpecifier.createGrayscale(4, DataBuffer.TYPE_BYTE, false), ImageTypeSpecifiers.createPackedGrayscale(GRAY, 4, DataBuffer.TYPE_BYTE) @@ -609,7 +658,7 @@ public class ImageTypeSpecifiersTest { for (int bits = 1; bits <= 8; bits <<= 1) { int[] colors = createIntLut(1 << bits); assertEquals( - IndexedImageTypeSpecifier.createFromIndexColorModel(new IndexColorModel(bits, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE)), + new IndexedImageTypeSpecifier(new IndexColorModel(bits, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE)), ImageTypeSpecifiers.createIndexed(colors, false, -1, bits, DataBuffer.TYPE_BYTE) ); } @@ -619,7 +668,7 @@ public class ImageTypeSpecifiersTest { public void testCreateIndexedIntArray16() { int[] colors = createIntLut(1 << 16); assertEquals( - IndexedImageTypeSpecifier.createFromIndexColorModel(new IndexColorModel(16, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT)), + new IndexedImageTypeSpecifier(new IndexColorModel(16, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT)), ImageTypeSpecifiers.createIndexed(colors, false, -1, 16, DataBuffer.TYPE_USHORT) ); @@ -631,7 +680,7 @@ public class ImageTypeSpecifiersTest { int[] colors = createIntLut(1 << bits); IndexColorModel colorModel = new IndexColorModel(bits, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); assertEquals( - IndexedImageTypeSpecifier.createFromIndexColorModel(colorModel), + new IndexedImageTypeSpecifier(colorModel), ImageTypeSpecifiers.createFromIndexColorModel(colorModel) ); } @@ -642,7 +691,7 @@ public class ImageTypeSpecifiersTest { int[] colors = createIntLut(1 << 16); IndexColorModel colorModel = new IndexColorModel(16, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT); assertEquals( - IndexedImageTypeSpecifier.createFromIndexColorModel(colorModel), + new IndexedImageTypeSpecifier(colorModel), ImageTypeSpecifiers.createFromIndexColorModel(colorModel) ); } diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java similarity index 76% rename from imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java rename to imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java index 15f9a31d..4bc19264 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java @@ -1,364 +1,377 @@ -/* - * 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 "TwelveMonkeys" 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 OWNER 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.util; - -import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; -import org.junit.Test; -import org.mockito.InOrder; - -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.event.IIOWriteProgressListener; -import javax.imageio.spi.IIORegistry; -import javax.imageio.stream.ImageOutputStream; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.RenderedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URL; -import java.util.List; - -import static org.junit.Assert.*; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.*; - -/** - * ImageReaderAbstractTestCase class description. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: ImageReaderAbstractTestCase.java,v 1.0 18.nov.2004 17:38:33 haku Exp $ - */ -public abstract class ImageWriterAbstractTestCase { - - // TODO: Move static block + getClassLoaderResource to common superclass for reader/writer test cases or delegate. - - static { - IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi()); - ImageIO.setUseCache(false); - } - - protected abstract ImageWriter createImageWriter(); - - protected abstract List getTestData(); - - protected static BufferedImage drawSomething(final BufferedImage image) { - Graphics2D g = image.createGraphics(); - try { - int width = image.getWidth(); - int height = image.getHeight(); - - g.clearRect(0, 0, width, height); - g.setPaint(new LinearGradientPaint(0, 0, width, 0, new float[] {0.2f, 1}, new Color[] {new Color(0x0, true), Color.BLUE})); - g.fillRect(0, 0, width, height); - g.setPaint(new LinearGradientPaint(0, 0, 0, height, new float[] {0.2f, 1}, new Color[] {new Color(0x0, true), Color.RED})); - g.fillRect(0, 0, width, height); - g.setPaint(new LinearGradientPaint(0, 0, 0, height, new float[] {0, 1}, new Color[] {new Color(0x00ffffff, true), Color.WHITE})); - g.fill(new Polygon(new int[] {0, width, width}, new int[] {0, height, 0}, 3)); - } - finally { - g.dispose(); - } - - return image; - } - - protected final RenderedImage getTestData(final int index) { - return getTestData().get(index); - } - - protected URL getClassLoaderResource(final String pName) { - return getClass().getResource(pName); - } - - @Test - public void testSetOutput() throws IOException { - // Should just pass with no exceptions - ImageWriter writer = createImageWriter(); - assertNotNull(writer); - writer.setOutput(ImageIO.createImageOutputStream(new ByteArrayOutputStream())); - } - - @Test - public void testSetOutputNull() { - // Should just pass with no exceptions - ImageWriter writer = createImageWriter(); - assertNotNull(writer); - writer.setOutput(null); - } - - @Test - public void testWrite() throws IOException { - ImageWriter writer = createImageWriter(); - - for (RenderedImage testData : getTestData()) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) { - writer.setOutput(stream); - writer.write(drawSomething((BufferedImage) testData)); - } - catch (IOException e) { - fail(e.getMessage()); - } - - assertTrue("No image data written", buffer.size() > 0); - } - } - - @SuppressWarnings("ConstantConditions") - @Test - public void testWriteNull() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - try { - writer.write((RenderedImage) null); - } - catch(IllegalArgumentException ignore) { - } - catch (IOException e) { - fail(e.getMessage()); - } - - assertTrue("Image data written", buffer.size() == 0); - } - - @Test(expected = IllegalStateException.class) - public void testWriteNoOutput() { - ImageWriter writer = createImageWriter(); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail(e.getMessage()); - } - } - - @Test - public void testGetDefaultWriteParam() { - ImageWriter writer = createImageWriter(); - ImageWriteParam param = writer.getDefaultWriteParam(); - assertNotNull("Default ImageWriteParam is null", param); - } - - // TODO: Test writing with params - // TODO: Source region and subsampling at least - - @Test - public void testAddIIOWriteProgressListener() { - ImageWriter writer = createImageWriter(); - writer.addIIOWriteProgressListener(mock(IIOWriteProgressListener.class)); - } - - @Test - public void testAddIIOWriteProgressListenerNull() { - ImageWriter writer = createImageWriter(); - writer.addIIOWriteProgressListener(null); - } - - @Test - public void testAddIIOWriteProgressListenerCallbacks() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listener); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail("Could not write image"); - } - - // At least imageStarted and imageComplete, plus any number of imageProgress - InOrder ordered = inOrder(listener); - ordered.verify(listener).imageStarted(writer, 0); - ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt()); - ordered.verify(listener).imageComplete(writer); - } - - @Test - public void testMultipleAddIIOWriteProgressListenerCallbacks() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); - IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class); - IIOWriteProgressListener listenerThree = mock(IIOWriteProgressListener.class); - - writer.addIIOWriteProgressListener(listener); - writer.addIIOWriteProgressListener(listenerToo); - writer.addIIOWriteProgressListener(listenerThree); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail("Could not write image"); - } - - // At least imageStarted and imageComplete, plus any number of imageProgress - InOrder ordered = inOrder(listener, listenerToo, listenerThree); - - ordered.verify(listener).imageStarted(writer, 0); - ordered.verify(listenerToo).imageStarted(writer, 0); - ordered.verify(listenerThree).imageStarted(writer, 0); - - ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt()); - ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt()); - ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyInt()); - - ordered.verify(listener).imageComplete(writer); - ordered.verify(listenerToo).imageComplete(writer); - ordered.verify(listenerThree).imageComplete(writer); - } - - @Test - public void testRemoveIIOWriteProgressListenerNull() { - ImageWriter writer = createImageWriter(); - writer.removeIIOWriteProgressListener(null); - } - - @Test - public void testRemoveIIOWriteProgressListenerNone() { - ImageWriter writer = createImageWriter(); - writer.removeIIOWriteProgressListener(mock(IIOWriteProgressListener.class)); - } - - @Test - public void testRemoveIIOWriteProgressListener() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listener); - writer.removeIIOWriteProgressListener(listener); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail("Could not write image"); - } - - // Should not have called any methods... - verifyZeroInteractions(listener); - } - - @Test - public void testRemoveIIOWriteProgressListenerMultiple() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listener); - - IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listenerToo); - - writer.removeIIOWriteProgressListener(listener); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail("Could not write image"); - } - - // Should not have called any methods... - verifyZeroInteractions(listener); - - // At least imageStarted and imageComplete, plus any number of imageProgress - InOrder ordered = inOrder(listenerToo); - ordered.verify(listenerToo).imageStarted(writer, 0); - ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt()); - ordered.verify(listenerToo).imageComplete(writer); - - } - - @Test - public void testRemoveAllIIOWriteProgressListeners() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - - IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listener); - - writer.removeAllIIOWriteProgressListeners(); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail("Could not write image"); - } - - // Should not have called any methods... - verifyZeroInteractions(listener); - } - - @Test - public void testRemoveAllIIOWriteProgressListenersMultiple() throws IOException { - ImageWriter writer = createImageWriter(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - writer.setOutput(ImageIO.createImageOutputStream(buffer)); - - - IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listener); - - IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class); - writer.addIIOWriteProgressListener(listenerToo); - - writer.removeAllIIOWriteProgressListeners(); - - try { - writer.write(getTestData(0)); - } - catch (IOException e) { - fail("Could not write image"); - } - - // Should not have called any methods... - verifyZeroInteractions(listener); - verifyZeroInteractions(listenerToo); - } +/* + * 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.imageio.util; + +import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; + +import org.junit.Test; +import org.mockito.InOrder; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.event.IIOWriteProgressListener; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.net.URL; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.*; + +/** + * ImageReaderAbstractTestCase class description. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: ImageReaderAbstractTestCase.java,v 1.0 18.nov.2004 17:38:33 haku Exp $ + */ +public abstract class ImageWriterAbstractTest { + + // TODO: Move static block + getClassLoaderResource to common superclass for reader/writer test cases or delegate. + + static { + IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi()); + ImageIO.setUseCache(false); + } + + @SuppressWarnings("unchecked") + private final Class writerClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + + protected final ImageWriterSpi provider = createProvider(); + + protected abstract ImageWriterSpi createProvider(); + + protected final T createWriter() throws IOException { + return writerClass.cast(provider.createWriterInstance(null)); + } + + protected abstract List getTestData(); + + protected static BufferedImage drawSomething(final BufferedImage image) { + Graphics2D g = image.createGraphics(); + try { + int width = image.getWidth(); + int height = image.getHeight(); + + g.clearRect(0, 0, width, height); + g.setPaint(new LinearGradientPaint(0, 0, width, 0, new float[] {0.2f, 1}, new Color[] {new Color(0x0, true), Color.BLUE})); + g.fillRect(0, 0, width, height); + g.setPaint(new LinearGradientPaint(0, 0, 0, height, new float[] {0.2f, 1}, new Color[] {new Color(0x0, true), Color.RED})); + g.fillRect(0, 0, width, height); + g.setPaint(new LinearGradientPaint(0, 0, 0, height, new float[] {0, 1}, new Color[] {new Color(0x00ffffff, true), Color.WHITE})); + g.fill(new Polygon(new int[] {0, width, width}, new int[] {0, height, 0}, 3)); + } + finally { + g.dispose(); + } + + return image; + } + + protected final RenderedImage getTestData(final int index) { + return getTestData().get(index); + } + + protected URL getClassLoaderResource(final String pName) { + return getClass().getResource(pName); + } + + @Test + public void testSetOutput() throws IOException { + // Should just pass with no exceptions + ImageWriter writer = createWriter(); + assertNotNull(writer); + writer.setOutput(ImageIO.createImageOutputStream(new ByteArrayOutputStream())); + } + + @Test + public void testSetOutputNull() throws IOException { + // Should just pass with no exceptions + ImageWriter writer = createWriter(); + assertNotNull(writer); + writer.setOutput(null); + } + + @Test + public void testWrite() throws IOException { + ImageWriter writer = createWriter(); + + for (RenderedImage testData : getTestData()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) { + writer.setOutput(stream); + writer.write(drawSomething((BufferedImage) testData)); + } + catch (IOException e) { + throw new AssertionError(e.getMessage(), e); + } + + assertTrue("No image data written", buffer.size() > 0); + } + } + + @Test + public void testWriteNull() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + try { + writer.write((RenderedImage) null); + } + catch(IllegalArgumentException ignore) { + } + catch (IOException e) { + throw new AssertionError(e.getMessage(), e); + } + + assertEquals("Image data written", 0, buffer.size()); + } + + @Test(expected = IllegalStateException.class) + public void testWriteNoOutput() throws IOException { + ImageWriter writer = createWriter(); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail(e.getMessage()); + } + } + + @Test + public void testGetDefaultWriteParam() throws IOException { + ImageWriter writer = createWriter(); + ImageWriteParam param = writer.getDefaultWriteParam(); + assertNotNull("Default ImageWriteParam is null", param); + } + + // TODO: Test writing with params + // TODO: Source region and subsampling at least + + @Test + public void testAddIIOWriteProgressListener() throws IOException { + ImageWriter writer = createWriter(); + writer.addIIOWriteProgressListener(mock(IIOWriteProgressListener.class)); + } + + @Test + public void testAddIIOWriteProgressListenerNull() throws IOException { + ImageWriter writer = createWriter(); + writer.addIIOWriteProgressListener(null); + } + + @Test + public void testAddIIOWriteProgressListenerCallbacks() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listener); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail("Could not write image"); + } + + // At least imageStarted and imageComplete, plus any number of imageProgress + InOrder ordered = inOrder(listener); + ordered.verify(listener).imageStarted(writer, 0); + ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt()); + ordered.verify(listener).imageComplete(writer); + } + + @Test + public void testMultipleAddIIOWriteProgressListenerCallbacks() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); + IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class); + IIOWriteProgressListener listenerThree = mock(IIOWriteProgressListener.class); + + writer.addIIOWriteProgressListener(listener); + writer.addIIOWriteProgressListener(listenerToo); + writer.addIIOWriteProgressListener(listenerThree); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail("Could not write image"); + } + + // At least imageStarted and imageComplete, plus any number of imageProgress + InOrder ordered = inOrder(listener, listenerToo, listenerThree); + + ordered.verify(listener).imageStarted(writer, 0); + ordered.verify(listenerToo).imageStarted(writer, 0); + ordered.verify(listenerThree).imageStarted(writer, 0); + + ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt()); + ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt()); + ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyInt()); + + ordered.verify(listener).imageComplete(writer); + ordered.verify(listenerToo).imageComplete(writer); + ordered.verify(listenerThree).imageComplete(writer); + } + + @Test + public void testRemoveIIOWriteProgressListenerNull() throws IOException { + ImageWriter writer = createWriter(); + writer.removeIIOWriteProgressListener(null); + } + + @Test + public void testRemoveIIOWriteProgressListenerNone() throws IOException { + ImageWriter writer = createWriter(); + writer.removeIIOWriteProgressListener(mock(IIOWriteProgressListener.class)); + } + + @Test + public void testRemoveIIOWriteProgressListener() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listener); + writer.removeIIOWriteProgressListener(listener); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail("Could not write image"); + } + + // Should not have called any methods... + verifyZeroInteractions(listener); + } + + @Test + public void testRemoveIIOWriteProgressListenerMultiple() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listener); + + IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listenerToo); + + writer.removeIIOWriteProgressListener(listener); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail("Could not write image"); + } + + // Should not have called any methods... + verifyZeroInteractions(listener); + + // At least imageStarted and imageComplete, plus any number of imageProgress + InOrder ordered = inOrder(listenerToo); + ordered.verify(listenerToo).imageStarted(writer, 0); + ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt()); + ordered.verify(listenerToo).imageComplete(writer); + + } + + @Test + public void testRemoveAllIIOWriteProgressListeners() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + + IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listener); + + writer.removeAllIIOWriteProgressListeners(); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail("Could not write image"); + } + + // Should not have called any methods... + verifyZeroInteractions(listener); + } + + @Test + public void testRemoveAllIIOWriteProgressListenersMultiple() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(buffer)); + + + IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listener); + + IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class); + writer.addIIOWriteProgressListener(listenerToo); + + writer.removeAllIIOWriteProgressListeners(); + + try { + writer.write(getTestData(0)); + } + catch (IOException e) { + fail("Could not write image"); + } + + // Should not have called any methods... + verifyZeroInteractions(listener); + verifyZeroInteractions(listenerToo); + } } \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifierTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifierTest.java index 043df088..0ca4e3bf 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifierTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IndexedImageTypeSpecifierTest.java @@ -1,13 +1,46 @@ +/* + * Copyright (c) 2014, 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.util; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; -import javax.imageio.ImageTypeSpecifier; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; -import static org.junit.Assert.*; +import javax.imageio.ImageTypeSpecifier; + +import org.junit.Test; /** * IndexedImageTypeSpecifierTestCase @@ -21,46 +54,43 @@ public class IndexedImageTypeSpecifierTest { public void testEquals() { IndexColorModel cm = new IndexColorModel(1, 2, new int[]{0xffffff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE); - ImageTypeSpecifier spec = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); - ImageTypeSpecifier other = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); - ImageTypeSpecifier different = IndexedImageTypeSpecifier.createFromIndexColorModel(new IndexColorModel(2, 2, new int[]{0xff00ff, 0x00, 0xff00ff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE)); + ImageTypeSpecifier spec = new IndexedImageTypeSpecifier(cm); + ImageTypeSpecifier other = new IndexedImageTypeSpecifier(cm); + ImageTypeSpecifier different = new IndexedImageTypeSpecifier(new IndexColorModel(2, 2, new int[]{0xff00ff, 0x00, 0xff00ff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE)); assertEquals(spec, other); assertEquals(other, spec); assertEquals(spec.hashCode(), other.hashCode()); - assertTrue(spec.equals(other)); - assertTrue(other.equals(spec)); - // TODO: There is still a problem that IndexColorModel does not override equals, // so any model with the same number of bits, transparency, and transfer type will be treated as equal - assertFalse(other.equals(different)); + assertNotEquals(other, different); } @Test public void testHashCode() { IndexColorModel cm = new IndexColorModel(1, 2, new int[]{0xffffff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE); - ImageTypeSpecifier spec = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); - ImageTypeSpecifier other = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); - ImageTypeSpecifier different = IndexedImageTypeSpecifier.createFromIndexColorModel(new IndexColorModel(2, 2, new int[]{0xff00ff, 0x00, 0xff00ff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE)); + ImageTypeSpecifier spec = new IndexedImageTypeSpecifier(cm); + ImageTypeSpecifier other = new IndexedImageTypeSpecifier(cm); + ImageTypeSpecifier different = new IndexedImageTypeSpecifier(new IndexColorModel(2, 2, new int[]{0xff00ff, 0x00, 0xff00ff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE)); // TODO: There is still a problem that IndexColorModel does not override hashCode, // so any model with the same number of bits, transparency, and transfer type will have same hash assertEquals(spec.hashCode(), other.hashCode()); - assertFalse(spec.hashCode() == different.hashCode()); + assertNotEquals(spec.hashCode(), different.hashCode()); } @Test(expected = IllegalArgumentException.class) public void testCreateNull() { - IndexedImageTypeSpecifier.createFromIndexColorModel(null); + new IndexedImageTypeSpecifier(null); } @Test public void testCreateBufferedImageBinary() { IndexColorModel cm = new IndexColorModel(1, 2, new int[]{0xffffff, 0x00}, 0, false, -1, DataBuffer.TYPE_BYTE); - ImageTypeSpecifier spec = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); + ImageTypeSpecifier spec = new IndexedImageTypeSpecifier(cm); BufferedImage image = spec.createBufferedImage(2, 2); @@ -72,7 +102,7 @@ public class IndexedImageTypeSpecifierTest { @Test public void testCreateBufferedImageIndexed() { IndexColorModel cm = new IndexColorModel(8, 256, new int[256], 0, false, -1, DataBuffer.TYPE_BYTE); - ImageTypeSpecifier spec = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); + ImageTypeSpecifier spec = new IndexedImageTypeSpecifier(cm); BufferedImage image = spec.createBufferedImage(2, 2); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/RasterUtilsTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/RasterUtilsTest.java new file mode 100644 index 00000000..13ec1faa --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/RasterUtilsTest.java @@ -0,0 +1,199 @@ +package com.twelvemonkeys.imageio.util; + +import org.junit.Test; + +import javax.imageio.ImageTypeSpecifier; +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * RasterUtilsTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: RasterUtilsTest.java,v 1.0 05/05/2021 haraldk Exp$ + */ +public class RasterUtilsTest { + @Test(expected = NullPointerException.class) + public void testAsByteRasterFromNull() { + RasterUtils.asByteRaster((Raster) null); + } + + @SuppressWarnings("RedundantCast") + @Test(expected = NullPointerException.class) + public void testAsByteRasterWritableFromNull() { + RasterUtils.asByteRaster((WritableRaster) null); + } + + @Test + public void testAsByteRasterPassThrough() { + WritableRaster[] rasters = new WritableRaster[] { + new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR).getRaster(), + new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR).getRaster(), + new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR_PRE).getRaster(), + new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY).getRaster(), + Raster.createBandedRaster(DataBuffer.TYPE_BYTE, 1, 1, 7, null), + Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 1, 1, 2, null), + new WritableRaster(new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, 1, 1, 1, 1, new int[1]), new Point(0, 0)) {} + }; + + for (Raster raster : rasters) { + assertSame(raster, RasterUtils.asByteRaster(raster)); + } + + for (WritableRaster raster : rasters) { + assertSame(raster, RasterUtils.asByteRaster(raster)); + } + } + + @Test + public void testAsByteRasterWritableFromTYPE_INT_RGB() { + BufferedImage image = new BufferedImage(9, 11, BufferedImage.TYPE_INT_RGB); + + WritableRaster raster = RasterUtils.asByteRaster(image.getRaster()); + + assertEquals(DataBuffer.TYPE_BYTE, raster.getTransferType()); + assertEquals(PixelInterleavedSampleModel.class, raster.getSampleModel().getClass()); + assertEquals(image.getWidth(), raster.getWidth()); + assertEquals(image.getHeight(), raster.getHeight()); + + assertEquals(3, raster.getNumBands()); + assertEquals(3, raster.getNumDataElements()); + + assertImageRasterEquals(image, raster); + } + + @Test + public void testAsByteRasterWritableFromTYPE_INT_ARGB() { + BufferedImage image = new BufferedImage(9, 11, BufferedImage.TYPE_INT_ARGB); + + WritableRaster raster = RasterUtils.asByteRaster(image.getRaster()); + + assertEquals(DataBuffer.TYPE_BYTE, raster.getTransferType()); + assertEquals(PixelInterleavedSampleModel.class, raster.getSampleModel().getClass()); + assertEquals(image.getWidth(), raster.getWidth()); + assertEquals(image.getHeight(), raster.getHeight()); + + assertEquals(4, raster.getNumBands()); + assertEquals(4, raster.getNumDataElements()); + + assertImageRasterEquals(image, raster); + } + + @Test + public void testAsByteRasterWritableFromTYPE_INT_ARGB_PRE() { + BufferedImage image = new BufferedImage(9, 11, BufferedImage.TYPE_INT_ARGB_PRE); + + WritableRaster raster = RasterUtils.asByteRaster(image.getRaster()); + + assertEquals(DataBuffer.TYPE_BYTE, raster.getTransferType()); + assertEquals(PixelInterleavedSampleModel.class, raster.getSampleModel().getClass()); + assertEquals(image.getWidth(), raster.getWidth()); + assertEquals(image.getHeight(), raster.getHeight()); + + assertEquals(4, raster.getNumBands()); + assertEquals(4, raster.getNumDataElements()); + + // We don't assert on values here, as the premultiplied values makes it hard... + } + + @Test + public void testAsByteRasterWritableFromTYPE_INT_BGR() { + BufferedImage image = new BufferedImage(9, 11, BufferedImage.TYPE_INT_BGR); + + WritableRaster raster = RasterUtils.asByteRaster(image.getRaster()); + + assertEquals(DataBuffer.TYPE_BYTE, raster.getTransferType()); + assertEquals(PixelInterleavedSampleModel.class, raster.getSampleModel().getClass()); + assertEquals(image.getWidth(), raster.getWidth()); + assertEquals(image.getHeight(), raster.getHeight()); + + assertEquals(3, raster.getNumBands()); + assertEquals(3, raster.getNumDataElements()); + + assertImageRasterEquals(image, raster); + } + + @Test + public void testAsByteRasterWritableFromTYPE_CUSTOM_GRAB() { + BufferedImage image = ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), + 0x00FF0000, + 0xFF000000, + 0x000000FF, + 0x0000FF00, + DataBuffer.TYPE_INT, false).createBufferedImage(7, 13); + + WritableRaster raster = RasterUtils.asByteRaster(image.getRaster()); + + assertEquals(DataBuffer.TYPE_BYTE, raster.getTransferType()); + assertEquals(PixelInterleavedSampleModel.class, raster.getSampleModel().getClass()); + assertEquals(image.getWidth(), raster.getWidth()); + assertEquals(image.getHeight(), raster.getHeight()); + + assertEquals(4, raster.getNumBands()); + assertEquals(4, raster.getNumDataElements()); + + assertImageRasterEquals(image, raster); + } + + @Test + public void testAsByteRasterWritableFromTYPE_CUSTOM_BxRG() { + BufferedImage image = ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), + 0x0000FF00, + 0x000000FF, + 0xFF000000, + 0, + DataBuffer.TYPE_INT, false).createBufferedImage(7, 13); + + WritableRaster raster = RasterUtils.asByteRaster(image.getRaster()); + + assertEquals(DataBuffer.TYPE_BYTE, raster.getTransferType()); + assertEquals(PixelInterleavedSampleModel.class, raster.getSampleModel().getClass()); + assertEquals(image.getWidth(), raster.getWidth()); + assertEquals(image.getHeight(), raster.getHeight()); + + assertEquals(3, raster.getNumBands()); + assertEquals(3, raster.getNumDataElements()); + + assertImageRasterEquals(image, raster); + } + + private static void assertImageRasterEquals(BufferedImage image, WritableRaster raster) { + // NOTE: This is NOT necessarily how the values are stored in the data buffer + int[] argbOffs = new int[] {16, 8, 0, 24}; + + Raster imageRaster = image.getRaster(); + + Random rng = new Random(27365481723L); + + for (int y = 0; y < raster.getHeight(); y++) { + for (int x = 0; x < raster.getWidth(); x++) { + int argb = 0; + + for (int b = 0; b < raster.getNumBands(); b++) { + int s = rng.nextInt(0xFF); + raster.setSample(x, y, b, s); + + assertEquals(s, raster.getSample(x, y, b)); + assertEquals(s, imageRaster.getSample(x, y, b)); + + argb |= (s << argbOffs[b]); + } + + if (raster.getNumBands() < 4) { + argb |= 0xFF000000; + } + + int expectedArgb = image.getRGB(x, y); + if (argb != expectedArgb) { + assertEquals(x + ", " + y + ": ", String.format("#%08x", expectedArgb), String.format("#%08x", argb)); + } + } + } + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifierTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifierTest.java index f1ffa6fa..48c37426 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifierTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/UInt32ImageTypeSpecifierTest.java @@ -1,6 +1,37 @@ +/* + * Copyright (c) 2014, 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.util; import com.twelvemonkeys.imageio.color.ColorSpaces; + import org.junit.Test; import javax.imageio.ImageTypeSpecifier; @@ -9,7 +40,8 @@ import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.PixelInterleavedSampleModel; -import static org.hamcrest.core.Is.is; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; public class UInt32ImageTypeSpecifierTest { @@ -25,13 +57,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(1, spec.getNumComponents()); assertEquals(32, spec.getBitsPerBand(0)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertFalse(spec.getColorModel().hasAlpha()); assertFalse(spec.getColorModel().isAlphaPremultiplied()); assertEquals(1, spec.getColorModel().getNumComponents()); assertEquals(1, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(1, spec.getSampleModel().getNumBands()); assertEquals(1, spec.getSampleModel().getNumDataElements()); } @@ -44,13 +76,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(32, spec.getBitsPerBand(0)); assertEquals(32, spec.getBitsPerBand(1)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertTrue(spec.getColorModel().hasAlpha()); assertFalse(spec.getColorModel().isAlphaPremultiplied()); assertEquals(2, spec.getColorModel().getNumComponents()); assertEquals(1, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(2, spec.getSampleModel().getNumBands()); assertEquals(2, spec.getSampleModel().getNumDataElements()); } @@ -65,13 +97,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(32, spec.getBitsPerBand(1)); assertEquals(32, spec.getBitsPerBand(2)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertFalse(spec.getColorModel().hasAlpha()); assertFalse(spec.getColorModel().isAlphaPremultiplied()); assertEquals(3, spec.getColorModel().getNumComponents()); assertEquals(3, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(3, spec.getSampleModel().getNumBands()); assertEquals(3, spec.getSampleModel().getNumDataElements()); } @@ -86,13 +118,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(32, spec.getBitsPerBand(2)); assertEquals(32, spec.getBitsPerBand(3)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertTrue(spec.getColorModel().hasAlpha()); assertFalse(spec.getColorModel().isAlphaPremultiplied()); assertEquals(4, spec.getColorModel().getNumComponents()); assertEquals(3, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(4, spec.getSampleModel().getNumBands()); assertEquals(4, spec.getSampleModel().getNumDataElements()); } @@ -107,13 +139,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(32, spec.getBitsPerBand(2)); assertEquals(32, spec.getBitsPerBand(3)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertTrue(spec.getColorModel().hasAlpha()); assertTrue(spec.getColorModel().isAlphaPremultiplied()); assertEquals(4, spec.getColorModel().getNumComponents()); assertEquals(3, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(4, spec.getSampleModel().getNumBands()); assertEquals(4, spec.getSampleModel().getNumDataElements()); } @@ -129,13 +161,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(32, spec.getBitsPerBand(2)); assertEquals(32, spec.getBitsPerBand(3)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertFalse(spec.getColorModel().hasAlpha()); assertFalse(spec.getColorModel().isAlphaPremultiplied()); assertEquals(4, spec.getColorModel().getNumComponents()); assertEquals(4, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(4, spec.getSampleModel().getNumBands()); assertEquals(4, spec.getSampleModel().getNumDataElements()); } @@ -151,13 +183,13 @@ public class UInt32ImageTypeSpecifierTest { assertEquals(32, spec.getBitsPerBand(3)); assertEquals(32, spec.getBitsPerBand(4)); - assertThat(spec.getColorModel(), is(ComponentColorModel.class)); + assertThat(spec.getColorModel(), instanceOf(ComponentColorModel.class)); assertTrue(spec.getColorModel().hasAlpha()); assertFalse(spec.getColorModel().isAlphaPremultiplied()); assertEquals(5, spec.getColorModel().getNumComponents()); assertEquals(4, spec.getColorModel().getNumColorComponents()); - assertThat(spec.getSampleModel(), is(PixelInterleavedSampleModel.class)); + assertThat(spec.getSampleModel(), instanceOf(PixelInterleavedSampleModel.class)); assertEquals(5, spec.getSampleModel().getNumBands()); assertEquals(5, spec.getSampleModel().getNumDataElements()); } diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/package.html b/imageio/imageio-core/src/test/resources/empty-stream.txt old mode 100755 new mode 100644 similarity index 100% rename from servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/package.html rename to imageio/imageio-core/src/test/resources/empty-stream.txt diff --git a/imageio/imageio-hdr/license.txt b/imageio/imageio-hdr/license.txt new file mode 100755 index 00000000..7c7e386c --- /dev/null +++ b/imageio/imageio-hdr/license.txt @@ -0,0 +1,27 @@ +Copyright (c) 2015, 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. diff --git a/imageio/imageio-hdr/pom.xml b/imageio/imageio-hdr/pom.xml index 985c48f7..36685051 100644 --- a/imageio/imageio-hdr/pom.xml +++ b/imageio/imageio-hdr/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-hdr TwelveMonkeys :: ImageIO :: HDR plugin @@ -12,6 +12,10 @@ ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR). + + com.twelvemonkeys.imageio.hdr + + com.twelvemonkeys.imageio @@ -21,6 +25,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test com.twelvemonkeys.imageio diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java index 170b8da0..0371bb5d 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java index 7ce3ffa3..0c66d9d1 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java index 32490da5..d1dd5d5c 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java index e24fb5fc..b1af2080 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java index 380313be..dc9fbabb 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java index cd5422eb..b783c68f 100755 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java index a11a215f..226bc4e1 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; @@ -38,7 +40,7 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; * @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$ */ final class HDRProviderInfo extends ReaderWriterProviderInfo { - protected HDRProviderInfo() { + HDRProviderInfo() { super( HDRProviderInfo.class, new String[] {"HDR", "hdr", "RGBE", "rgbe"}, diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java index b64607e1..5dd3f6cd 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java @@ -12,14 +12,15 @@ import java.util.regex.Pattern; * (RGBE_DATA_??? values control this.) Only the mimimal header reading and * writing is implemented. Each routine does error checking and will return * a status value as defined below. This code is intended as a skeleton so - * feel free to modify it to suit your needs.

    - *

    - * Ported to Java and restructured by Kenneth Russell.
    - * posted to http://www.graphics.cornell.edu/~bjw/
    - * written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
    - * based on code written by Greg Ward
    - *

    - * Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java + * feel free to modify it to suit your needs. + *

    + * Ported to Java and restructured by Kenneth Russell. + * posted to http://www.graphics.cornell.edu/~bjw/ + * written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95 + * based on code written by Greg Ward + *

    + * + * @see Source */ final class RGBE { // Flags indicating which fields in a Header are valid diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java index 1a211886..0e3e0c6a 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java @@ -4,38 +4,43 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr.tonemap; /** * DefaultToneMapper. - *

    + *

    * Normalizes values to range [0...1] using: - * - *

    Vout = Vin / (Vin + C)

    - * + *

    + *

    + * Vout = Vin / (Vin + C) + *

    + *

    * Where C is constant. + *

    * * @author Harald Kuhr * @author last modified by $Author: harald.kuhr$ diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java index 285b6955..e538714e 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java @@ -4,39 +4,44 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr.tonemap; /** * GammaToneMapper. - *

    + *

    * Normalizes values to range [0...1] using: - * - *

    Vout = A Vin\u03b3

    - * + *

    + *

    + * Vout = A Vin\u03b3 + *

    + *

    * Where A is constant and \u03b3 is the gamma. - * Values > 1 are clamped. + * Values > 1 are clamped. + *

    * * @author Harald Kuhr * @author last modified by $Author: harald.kuhr$ diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java index 33de6478..24b75be2 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java @@ -4,36 +4,39 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr.tonemap; /** * NullToneMapper. - *

    + *

    * This {@code ToneMapper} does *not* normalize or clamp values * to range [0...1], but leaves the values as-is. * Useful for applications that implements custom tone mapping. + *

    * * @author Harald Kuhr * @author last modified by $Author: harald.kuhr$ diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java index 37d8f222..840a6b7e 100644 --- a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java +++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr.tonemap; diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java index 7baae6be..dbe686f7 100755 --- a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java +++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.hdr; @@ -44,26 +46,16 @@ import java.util.List; * @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$ */ public class HDRImageReaderTest extends ImageReaderAbstractTest { - @Override - protected List getTestData() { - return Arrays.asList( - new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768)) - ); - } - @Override protected ImageReaderSpi createProvider() { return new HDRImageReaderSpi(); } @Override - protected Class getReaderClass() { - return HDRImageReader.class; - } - - @Override - protected HDRImageReader createReader() { - return new HDRImageReader(createProvider()); + protected List getTestData() { + return Collections.singletonList( + new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768)) + ); } @Override diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java index 153f3b45..684291c0 100644 --- a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java +++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2015, 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.hdr; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapperTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapperTest.java new file mode 100644 index 00000000..45ea811f --- /dev/null +++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapperTest.java @@ -0,0 +1,31 @@ +package com.twelvemonkeys.imageio.plugins.hdr.tonemap; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; + +public class DefaultToneMapperTest { + + private final DefaultToneMapper mapper = new DefaultToneMapper(); + + @Test + public void testMap0() { + float[] rgb = {0}; + mapper.map(rgb); + assertArrayEquals(new float[]{0}, rgb, 0); + } + + @Test + public void testMap1() { + float[] rgb = {1}; + mapper.map(rgb); + assertArrayEquals(new float[]{0.5f}, rgb, 0); + } + + @Test + public void testMapMax() { + float[] rgb = {Float.MAX_VALUE}; + mapper.map(rgb); + assertArrayEquals(new float[]{1}, rgb, 0); + } +} \ No newline at end of file diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapperTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapperTest.java new file mode 100644 index 00000000..0de23a8d --- /dev/null +++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapperTest.java @@ -0,0 +1,38 @@ +package com.twelvemonkeys.imageio.plugins.hdr.tonemap; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; + +public class GammaToneMapperTest { + private final GammaToneMapper mapper = new GammaToneMapper(); + + @Test + public void testMap0() { + float[] rgb = {0}; + mapper.map(rgb); + assertArrayEquals(new float[]{0}, rgb, 0); + } + + @Test + public void testMap1() { + float[] rgb = {1}; + mapper.map(rgb); + assertArrayEquals(new float[]{0.5f}, rgb, 0); + } + + @Test + public void testMap16() { + float[] rgb = {15.999999f}; + mapper.map(rgb); + assertArrayEquals(new float[]{1}, rgb, 0); + } + + @Test + public void testMapMax() { + float[] rgb = {Float.MAX_VALUE}; + mapper.map(rgb); + assertArrayEquals(new float[]{1}, rgb, 0); + } + +} \ No newline at end of file diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapperTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapperTest.java new file mode 100644 index 00000000..ab43c124 --- /dev/null +++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapperTest.java @@ -0,0 +1,31 @@ +package com.twelvemonkeys.imageio.plugins.hdr.tonemap; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; + +public class NullToneMapperTest { + private final NullToneMapper mapper = new NullToneMapper(); + + @Test + public void testMap0() { + float[] rgb = {0}; + mapper.map(rgb); + assertArrayEquals(new float[]{0}, rgb, 0); + } + + @Test + public void testMap1() { + float[] rgb = {1}; + mapper.map(rgb); + assertArrayEquals(new float[]{1}, rgb, 0); + } + + @Test + public void testMapMax() { + float[] rgb = {Float.MAX_VALUE}; + mapper.map(rgb); + assertArrayEquals(new float[]{Float.MAX_VALUE}, rgb, 0); + } + +} \ No newline at end of file diff --git a/imageio/imageio-icns/license.txt b/imageio/imageio-icns/license.txt new file mode 100755 index 00000000..74dd72ca --- /dev/null +++ b/imageio/imageio-icns/license.txt @@ -0,0 +1,27 @@ +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. diff --git a/imageio/imageio-icns/pom.xml b/imageio/imageio-icns/pom.xml index 9a76609e..6d9e2752 100644 --- a/imageio/imageio-icns/pom.xml +++ b/imageio/imageio-icns/pom.xml @@ -4,12 +4,16 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-icns TwelveMonkeys :: ImageIO :: ICNS plugin ImageIO plugin for Apple Icon Image (ICNS) format. + + com.twelvemonkeys.imageio.icns + + com.twelvemonkeys.imageio @@ -19,6 +23,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java index 723d7a51..3879a65f 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2011, Harald Kuhr + * Copyright (c) 2017, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; @@ -90,14 +92,28 @@ interface ICNS { /** 128×128 8-bit mask. */ int t8mk = ('t' << 24) + ('8' << 16) + ('m' << 8) + 'k'; - /** 256×256 JPEG 2000 or PNG icon (10.x+). */ + /** 16x16 JPEG2000 or PNG icon (10.7+). */ + int icp4 = ('i' << 24) + ('c' << 16) + ('p' << 8) + '4'; + /** 32x32 JPEG2000 or PNG icon (10.7+). */ + int icp5 = ('i' << 24) + ('c' << 16) + ('p' << 8) + '5'; + /** 64x64 JPEG2000 or PNG icon (10.7+). */ + int icp6 = ('i' << 24) + ('c' << 16) + ('p' << 8) + '6'; + /** 128x128 JPEG2000 or PNG icon (10.7+). */ + int ic07 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '7'; + /** 256×256 JPEG 2000 or PNG icon (10.5+). */ int ic08 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '8'; - - /** 512×512 JPEG 2000 or PNG icon (10.x+). */ + /** 512×512 JPEG 2000 or PNG icon (10.5+). */ int ic09 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '9'; - - /** 1024×1024 PNG icon (10.7+). */ + /** 1024×1024 JPEG2000 or PNG icon (10.7+) OR 512x512@2x "retina" (10.8+). */ int ic10 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '0'; + /** 16x16@2x "retina" JPEG2000 or PNG icon (10.8+). */ + int ic11 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '1'; + /** 32x32@2x "retina" JPEG2000 or PNG icon (10.8+). */ + int ic12 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '2'; + /** 128x128@2x "retina" JPEG2000 or PNG icon (10.8+). */ + int ic13 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '3'; + /** 256x256@2x "retina" JPEG2000 or PNG icon (10.8+). */ + int ic14 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '4'; /** Unknown (Version). */ int icnV = ('i' << 24) + ('c' << 16) + ('n' << 8) + 'V'; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS1BitColorModel.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS1BitColorModel.java index 12a42ff4..05af872d 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS1BitColorModel.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS1BitColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java index 10a90f18..362cb8d8 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java index 19d12f86..c23f92dd 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java index 719a51e5..82962c98 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java @@ -1,34 +1,37 @@ /* - * Copyright (c) 2011, Harald Kuhr + * Copyright (c) 2017, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; @@ -420,7 +423,7 @@ public final class ICNSImageReader extends ImageReaderBase { private BufferedImage readForeignFormat(int imageIndex, final ImageReadParam param, final IconResource resource) throws IOException { // TODO: Optimize by caching readers that work? - ImageInputStream stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length)); + ImageInputStream stream = new SubImageInputStream(imageInput, resource.length); try { // Try first using ImageIO @@ -439,7 +442,7 @@ public final class ICNSImageReader extends ImageReaderBase { } else { stream.close(); - stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length)); + stream = new SubImageInputStream(imageInput, resource.length); } } finally { @@ -522,10 +525,28 @@ public final class ICNSImageReader extends ImageReaderBase { } IconResource resource = IconResource.read(imageInput); -// System.err.println("resource: " + resource); + + if (resource.isTOC()) { + // TODO: IconResource.readTOC()? + int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE; + long pos = resource.start + resource.length; + + for (int i = 0; i < resourceCount; i++) { + resource = IconResource.read(pos, imageInput); + pos += resource.length; + addResource(resource); + } + } + else { + addResource(resource); + } lastResourceRead = resource; + return resource; + } + + private void addResource(final IconResource resource) { // Filter out special cases like 'icnV' or 'TOC ' resources if (resource.isMaskType()) { masks.add(resource); @@ -533,8 +554,6 @@ public final class ICNSImageReader extends ImageReaderBase { else if (!resource.isUnknownType()) { icons.add(resource); } - - return resource; } private void readeFileHeader() throws IOException { diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderSpi.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderSpi.java index d128b0b8..70f981e7 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderSpi.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderSpi.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java new file mode 100644 index 00000000..56641a40 --- /dev/null +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2017, 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.icns; + +import com.twelvemonkeys.imageio.ImageWriterBase; +import com.twelvemonkeys.imageio.stream.SubImageOutputStream; +import com.twelvemonkeys.imageio.util.ProgressListenerBase; + +import javax.imageio.*; +import javax.imageio.event.IIOWriteWarningListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +/** + * ICNSImageWriter + */ +public final class ICNSImageWriter extends ImageWriterBase { + + private int sequenceIndex = -1; + private ImageWriter pngDelegate; + + ICNSImageWriter(ImageWriterSpi provider) { + super(provider); + } + + @Override + protected void resetMembers() { + sequenceIndex = -1; + + if (pngDelegate != null) { + pngDelegate.dispose(); + pngDelegate = null; + } + } + + @Override + public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) { + return null; + } + + @Override + public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException { + prepareWriteSequence(streamMetadata); + writeToSequence(image, param); + endWriteSequence(); + } + + @Override + public boolean canWriteSequence() { + return true; + } + + @Override + public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException { + assertOutput(); + + // TODO: Allow TOC resource to be passed as stream metadata? + // - We only need number of icons to be written later + // - The contents of the TOC could be updated while adding to the sequence + + if (sequenceIndex >= 0) { + throw new IllegalStateException("writeSequence already started"); + } + + writeICNSHeader(); + sequenceIndex = 0; + } + + @Override + public void endWriteSequence() throws IOException { + assertOutput(); + + if (sequenceIndex < 0) { + throw new IllegalStateException("prepareWriteSequence not called"); + } + + // TODO: Now that we know the number of icon resources, we could move all data backwards + // and write a TOC... But I don't think the benefit will outweigh the cost. + + sequenceIndex = -1; + } + + @Override + public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException { + assertOutput(); + + if (sequenceIndex < 0) { + throw new IllegalStateException("prepareWriteSequence not called"); + } + + if (image.hasRaster()) { + throw new UnsupportedOperationException("image has a Raster"); + } + + long resourceStart = imageOutput.getStreamPosition(); + + // TODO: Allow for other formats based on param? + // - Uncompressed/RLE (only allowed up to 128x128)? + // - JPEG2000 not very likely... + + // Validate icon size, get icon resource type based on size and compression + // TODO: Allow smaller, centered in larger square? Resize? + imageOutput.writeInt(IconResource.typeFromImage(image.getRenderedImage(), "PNG")); + imageOutput.writeInt(0); // Size, update later + + processImageStarted(sequenceIndex); + + // Write icon in PNG format + ImageWriter writer = getPNGDelegate(); + writer.setOutput(new SubImageOutputStream(imageOutput)); + writer.write(null, image, copyParam(param, writer)); + + processImageComplete(); + + long resourceEnd = imageOutput.getStreamPosition(); + if (resourceEnd > Integer.MAX_VALUE) { + throw new IIOException("File too large for ICNS"); + } + + int length = (int) (resourceEnd - resourceStart); + + // Update file length field + imageOutput.seek(4); + imageOutput.writeInt((int) resourceEnd); + + // Update resource length field + imageOutput.seek(resourceStart + 4); + imageOutput.writeInt((length)); + + // Prepare for next iteration + imageOutput.seek(resourceEnd); + } + + private ImageWriteParam copyParam(final ImageWriteParam param, ImageWriter writer) { + if (param == null) { + return null; + } + + ImageWriteParam writeParam = writer.getDefaultWriteParam(); + writeParam.setSourceSubsampling(param.getSourceXSubsampling(), param.getSourceYSubsampling(), param.getSubsamplingXOffset(), param.getSubsamplingYOffset()); + writeParam.setSourceRegion(param.getSourceRegion()); + writeParam.setSourceBands(param.getSourceBands()); + + return writeParam; + } + + private ImageWriter getPNGDelegate() { + if (pngDelegate == null) { + // There's always a PNG writer... + pngDelegate = ImageIO.getImageWritersByFormatName("PNG").next(); + pngDelegate.setLocale(getLocale()); + pngDelegate.addIIOWriteProgressListener(new ProgressListenerBase() { + @Override + public void imageProgress(ImageWriter source, float percentageDone) { + processImageProgress(percentageDone); + } + + @Override + public void writeAborted(ImageWriter source) { + processWriteAborted(); + } + }); + pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() { + @Override + public void warningOccurred(ImageWriter source, int imageIndex, String warning) { + processWarningOccurred(sequenceIndex, warning); + } + }); + } + + return pngDelegate; + } + + private void writeICNSHeader() throws IOException { + if (imageOutput.getStreamPosition() != 0) { + throw new IllegalStateException("Stream already written to"); + } + + imageOutput.writeInt(ICNS.MAGIC); + imageOutput.writeInt(8); // Length of file, in bytes, must be updated while writing + } + + public static void main(String[] args) throws IOException { + boolean pngCompression = false; + int firstArg = 0; + + while (args.length > firstArg && args[firstArg].charAt(0) == '-') { + if (args[firstArg].equals("-p") || args[firstArg].equals("--png")) { + pngCompression = true; + } + + firstArg++; + } + + if (args.length - firstArg < 2) { + System.err.println("Usage: command [-p|--png] [...]"); + System.exit(1); + } + + try (ImageOutputStream out = ImageIO.createImageOutputStream(new File(args[firstArg++]))) { + ImageWriter writer = new ICNSImageWriter(null); + writer.setOutput(out); + + ImageWriteParam param = writer.getDefaultWriteParam(); + // For now, we only support PNG... +// param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); +// param.setCompressionType(pngCompression ? "BI_PNG" : "BI_RGB"); + + writer.prepareWriteSequence(null); + + for (int i = firstArg; i < args.length; i++) { + File inFile = new File(args[i]); + try (ImageInputStream input = ImageIO.createImageInputStream(inFile)) { + Iterator readers = ImageIO.getImageReaders(input); + + if (!readers.hasNext()) { + System.err.printf("Can't read %s\n", inFile.getAbsolutePath()); + } + else { + ImageReader reader = readers.next(); + reader.setInput(input); + for (int j = 0; j < reader.getNumImages(true); j++) { + IIOImage image = reader.readAll(j, null); + writer.writeToSequence(image, param); + } + } + } + } + + writer.endWriteSequence(); + writer.dispose(); + } + } + +} diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterSpi.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterSpi.java new file mode 100644 index 00000000..bf207e39 --- /dev/null +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterSpi.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, 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.icns; + +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; + +import javax.imageio.ImageTypeSpecifier; +import java.util.Locale; + +/** + * ICNSImageWriterSpi + */ +public final class ICNSImageWriterSpi extends ImageWriterSpiBase { + public ICNSImageWriterSpi() { + super(new ICNSProviderInfo()); + } + + @Override + public boolean canEncodeImage(final ImageTypeSpecifier type) { + return true; + } + + @Override + public ICNSImageWriter createWriterInstance(final Object extension) { + return new ICNSImageWriter(this); + } + + @Override + public String getDescription(final Locale locale) { + return "Apple Icon Image (icns) format Writer"; + } +} diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java index 59cb4e9e..9fe2b6a6 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2015, Harald Kuhr + * Copyright (c) 2017, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; @@ -38,7 +40,7 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; * @version $Id: ICNSProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ */ final class ICNSProviderInfo extends ReaderWriterProviderInfo { - protected ICNSProviderInfo() { + ICNSProviderInfo() { super( ICNSProviderInfo.class, new String[]{"icns", "ICNS"}, @@ -48,9 +50,12 @@ final class ICNSProviderInfo extends ReaderWriterProviderInfo { }, "com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader", new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"}, + "com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriter", + new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriterSpi"}, + false, null, null, null, null, - false, null, null, null, null, - true, null, null, null, null + true, null, + null, null, null ); } } diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java index 71fb6b06..33fc546e 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java index 8eb05ef2..d9b111a2 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java @@ -1,29 +1,31 @@ /* - * Copyright (c) 2011, Harald Kuhr + * Copyright (c) 2017, 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; @@ -32,6 +34,7 @@ import com.twelvemonkeys.lang.Validate; import javax.imageio.stream.ImageInputStream; import java.awt.*; +import java.awt.image.RenderedImage; import java.io.IOException; /** @@ -44,9 +47,9 @@ import java.io.IOException; final class IconResource { // TODO: Rewrite using subclasses/instances! - protected final long start; - protected final int type; - protected final int length; + final long start; + final int type; + final int length; private IconResource(long start, int type, int length) { validate(type, length); @@ -56,8 +59,12 @@ final class IconResource { this.length = length; } - public static IconResource read(ImageInputStream input) throws IOException { - return new IconResource(input.getStreamPosition(), input.readInt(), input.readInt()); + static IconResource read(final ImageInputStream input) throws IOException { + return read(input.getStreamPosition(), input); + } + + static IconResource read(final long offset, final ImageInputStream input) throws IOException { + return new IconResource(offset, input.readInt(), input.readInt()); } private void validate(int type, int length) { @@ -111,9 +118,17 @@ final class IconResource { case ICNS.is32: case ICNS.il32: case ICNS.it32: + case ICNS.icp4: + case ICNS.icp5: + case ICNS.icp6: + case ICNS.ic07: case ICNS.ic08: case ICNS.ic09: case ICNS.ic10: + case ICNS.ic11: + case ICNS.ic12: + case ICNS.ic13: + case ICNS.ic14: if (length > ICNS.RESOURCE_HEADER_SIZE) { break; } @@ -140,7 +155,7 @@ final class IconResource { ); } - public Dimension size() { + Dimension size() { switch (type) { case ICNS.ICON: case ICNS.ICN_: @@ -154,11 +169,14 @@ final class IconResource { case ICNS.ics8: case ICNS.is32: case ICNS.s8mk: + case ICNS.icp4: return new Dimension(16, 16); case ICNS.icl4: case ICNS.icl8: case ICNS.il32: case ICNS.l8mk: + case ICNS.icp5: + case ICNS.ic11: return new Dimension(32, 32); case ICNS.ich_: case ICNS.ich4: @@ -166,12 +184,18 @@ final class IconResource { case ICNS.ih32: case ICNS.h8mk: return new Dimension(48, 48); + case ICNS.icp6: + case ICNS.ic12: + return new Dimension(64, 64); case ICNS.it32: case ICNS.t8mk: + case ICNS.ic07: return new Dimension(128, 128); case ICNS.ic08: + case ICNS.ic13: return new Dimension(256, 256); case ICNS.ic09: + case ICNS.ic14: return new Dimension(512, 512); case ICNS.ic10: return new Dimension(1024, 1024); @@ -180,7 +204,7 @@ final class IconResource { } } - public int depth() { + int depth() { switch (type) { case ICNS.ICON: case ICNS.ICN_: @@ -206,16 +230,24 @@ final class IconResource { case ICNS.il32: case ICNS.ih32: case ICNS.it32: + case ICNS.icp4: + case ICNS.icp5: + case ICNS.icp6: + case ICNS.ic07: case ICNS.ic08: case ICNS.ic09: case ICNS.ic10: + case ICNS.ic11: + case ICNS.ic12: + case ICNS.ic13: + case ICNS.ic14: return 32; default: throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type))); } } - public boolean isUnknownType() { + boolean isUnknownType() { // Unknown types should simply be skipped when reading switch (type) { case ICNS.ICON: @@ -239,16 +271,24 @@ final class IconResource { case ICNS.il32: case ICNS.ih32: case ICNS.it32: + case ICNS.icp4: + case ICNS.icp5: + case ICNS.icp6: + case ICNS.ic07: case ICNS.ic08: case ICNS.ic09: case ICNS.ic10: + case ICNS.ic11: + case ICNS.ic12: + case ICNS.ic13: + case ICNS.ic14: return false; } return true; } - public boolean hasMask() { + boolean hasMask() { switch (type) { case ICNS.ICN_: case ICNS.icm_: @@ -260,7 +300,7 @@ final class IconResource { return false; } - public boolean isMaskType() { + boolean isMaskType() { switch (type) { case ICNS.s8mk: case ICNS.l8mk: @@ -272,7 +312,7 @@ final class IconResource { return false; } - public boolean isCompressed() { + boolean isCompressed() { switch (type) { case ICNS.is32: case ICNS.il32: @@ -289,18 +329,30 @@ final class IconResource { return false; } - public boolean isForeignFormat() { + boolean isForeignFormat() { // Recent entries contains full JPEG 2000 or PNG streams switch (type) { + case ICNS.icp4: + case ICNS.icp5: + case ICNS.icp6: + case ICNS.ic07: case ICNS.ic08: case ICNS.ic09: case ICNS.ic10: + case ICNS.ic11: + case ICNS.ic12: + case ICNS.ic13: + case ICNS.ic14: return true; } return false; } + boolean isTOC() { + return type == ICNS.TOC_; + } + @Override public int hashCode() { return (int) start ^ type; @@ -320,4 +372,62 @@ final class IconResource { public String toString() { return String.format("%s['%s' start: %d, length: %d%s]", getClass().getSimpleName(), ICNSUtil.intToStr(type), start, length, isCompressed() ? " (compressed)" : ""); } + + static int typeFromImage(final RenderedImage image, final String compression) { + int width = image.getWidth(); + int height = image.getHeight(); + + if (width == height) { + switch (compression) { + case "JPEG2000": + case "PNG": + return typeFromWidthForeign(width); + case "None": + case "RLE": + return typeFromWidthNative(width); + default: + throw new IllegalArgumentException("Unsupported compression for ICNS: " + compression); + } + } + + // Note: Strictly, the format supports an ancient 16x12 size, but I doubt we'll ever support that + throw new IllegalArgumentException(String.format("Unsupported dimensions for ICNS, only square icons supported: %dx%d", width, height)); + } + // NOTE: These also needs a mask, if there's an alpha channel + + private static int typeFromWidthNative(final int width) { + switch (width) { + case 16: + return ICNS.is32; + case 32: + return ICNS.il32; + case 48: + return ICNS.ih32; + case 128: + return ICNS.it32; + default: + throw new IllegalArgumentException(String.format("Unsupported dimensions for ICNS, only 16, 32, 48 and 128 supported: %dx%d", width, width)); + } + } + + private static int typeFromWidthForeign(final int width) { + switch (width) { + case 16: + return ICNS.icp4; + case 32: + return ICNS.icp5; + case 64: + return ICNS.icp6; + case 128: + return ICNS.ic07; + case 256: + return ICNS.ic08; + case 512: + return ICNS.ic09; + case 1024: + return ICNS.ic10; + default: + throw new IllegalArgumentException(String.format("Unsupported dimensions for ICNS, only multiples of 2 from 16 to 1024 supported: %dx%d", width, width)); + } + } } diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java index ba1704e6..9b1fb949 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; diff --git a/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi index 8e2e73e4..80a16d1d 100644 --- a/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi +++ b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -1,28 +1 @@ -# -# 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 "TwelveMonkeys" 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 OWNER 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. -# -com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi \ No newline at end of file +com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi diff --git a/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 00000000..44f82bc5 --- /dev/null +++ b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriterSpi diff --git a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java index 84131b60..0fa26a34 100644 --- a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java +++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java @@ -4,35 +4,37 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.icns; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Ignore; import org.junit.Test; -import javax.imageio.ImageReader; import javax.imageio.spi.ImageReaderSpi; import java.awt.*; import java.io.IOException; @@ -47,7 +49,12 @@ import java.util.List; * @author last modified by $Author: haraldk$ * @version $Id: ICNSImageReaderTest.java,v 1.0 25.10.11 18:44 haraldk Exp$ */ -public class ICNSImageReaderTest extends ImageReaderAbstractTest { +public class ICNSImageReaderTest extends ImageReaderAbstractTest { + @Override + protected ImageReaderSpi createProvider() { + return new ICNSImageReaderSpi(); + } + @Override protected List getTestData() { return Arrays.asList( @@ -103,21 +110,6 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTest { ); } - @Override - protected ImageReaderSpi createProvider() { - return new ICNSImageReaderSpi(); - } - - @Override - protected ImageReader createReader() { - return new ICNSImageReader(); - } - - @Override - protected Class getReaderClass() { - return ICNSImageReader.class; - } - @Override protected List getFormatNames() { return Collections.singletonList("icns"); diff --git a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterTest.java new file mode 100644 index 00000000..2f463cb6 --- /dev/null +++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2017, 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.icns; + +import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest; + +import org.junit.Test; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +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.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertTrue; + +/** + * ICNSImageWriterTest. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: ICNSImageWriterTest.java,v 1.0 25/08/2018 harald.kuhr Exp$ + */ +public class ICNSImageWriterTest extends ImageWriterAbstractTest { + + @Override + protected ImageWriterSpi createProvider() { + return new ICNSImageWriterSpi(); + } + + @Override + protected List getTestData() { + return asList( + new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(32, 32, BufferedImage.TYPE_BYTE_BINARY), + new BufferedImage(32, 32, BufferedImage.TYPE_BYTE_INDEXED), + new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB), +// new BufferedImage(48, 48, BufferedImage.TYPE_INT_ARGB), // Only supported for compression None/RLE + new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(512, 512, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB) + ); + } + + @Test(expected = IllegalArgumentException.class) + public void testWriteNonSquare() throws IOException { + // ICNS only supports square icons (except some arcane 16x12 we don't currently support) + ImageWriter writer = createWriter(); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) { + + writer.setOutput(stream); + + writer.write(new BufferedImage(32, 64, BufferedImage.TYPE_INT_ARGB)); + + } + finally { + writer.dispose(); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testWriteBadSize() throws IOException { + // ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96) + ImageWriter writer = createWriter(); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) { + + writer.setOutput(stream); + + writer.write(new BufferedImage(17, 17, BufferedImage.TYPE_INT_ARGB)); + + } + finally { + writer.dispose(); + } + } + + @Test + public void testSequencesSupported() throws IOException { + ImageWriter writer = createWriter(); + try { + assertTrue(writer.canWriteSequence()); + } + finally { + writer.dispose(); + } + } + + @Test(expected = IllegalStateException.class) + public void testWriteSequenceNotStarted() throws IOException { + // ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96) + ImageWriter writer = createWriter(); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) { + + writer.setOutput(stream); + + BufferedImage image = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB); + writer.writeToSequence(new IIOImage(image, null, null), writer.getDefaultWriteParam()); + + } + finally { + writer.dispose(); + } + } + + @Test(expected = IllegalStateException.class) + public void testEndSequenceNotStarted() throws IOException { + // ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96) + ImageWriter writer = createWriter(); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) { + + writer.setOutput(stream); + writer.endWriteSequence(); + } + finally { + writer.dispose(); + } + } + + @Test(expected = IllegalStateException.class) + public void testPrepareSequenceAlreadyStarted() throws IOException { + // ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96) + ImageWriter writer = createWriter(); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) { + + writer.setOutput(stream); + writer.prepareWriteSequence(null); + writer.prepareWriteSequence(null); + } + finally { + writer.dispose(); + } + } + + @Test + public void testWriteSequence() throws IOException { + ImageWriter writer = createWriter(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) { + writer.setOutput(stream); + + writer.prepareWriteSequence(null); + for (RenderedImage image : getTestData()) { + IIOImage iioImage = new IIOImage(image, null, null); + writer.writeToSequence(iioImage, writer.getDefaultWriteParam()); + } + writer.endWriteSequence(); + } + finally { + writer.dispose(); + } + } +} \ No newline at end of file diff --git a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java index c5772ae6..d6a065d4 100644 --- a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java +++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * 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.imageio.plugins.icns; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-iff/license.txt b/imageio/imageio-iff/license.txt index 2d8ee79c..5ae89322 100755 --- a/imageio/imageio-iff/license.txt +++ b/imageio/imageio-iff/license.txt @@ -4,22 +4,24 @@ 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 "TwelveMonkeys" nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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 OWNER 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. \ No newline at end of file +* 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. diff --git a/imageio/imageio-iff/pom.xml b/imageio/imageio-iff/pom.xml index 0c5176f1..4259b6e4 100644 --- a/imageio/imageio-iff/pom.xml +++ b/imageio/imageio-iff/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-iff TwelveMonkeys :: ImageIO :: IFF plugin @@ -13,6 +13,10 @@ type ILBM and PBM format. + + com.twelvemonkeys.imageio.iff + + com.twelvemonkeys.imageio @@ -22,6 +26,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultipaletteChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java similarity index 73% rename from imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultipaletteChunk.java rename to imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java index 372589c0..420c05fb 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultipaletteChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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. */ /* * Parts of this code is based on ilbmtoppm.c @@ -100,14 +102,15 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett } @Override - void writeChunk(DataOutput pOutput) throws IOException { + void writeChunk(DataOutput pOutput) { throw new UnsupportedOperationException("Method writeChunk not implemented"); } + @Override public ColorModel getColorModel(final IndexColorModel colorModel, final int rowIndex, final boolean laced) { if (rowIndex < lastRow || mutablePalette == null || originalPalette != null && originalPalette.get() != colorModel) { - originalPalette = new WeakReference(colorModel); + originalPalette = new WeakReference<>(colorModel); mutablePalette = new MutableIndexColorModel(colorModel); if (initialChanges != null) { diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java index a784699f..36d4b48d 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java @@ -4,38 +4,40 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; -import javax.imageio.IIOException; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import javax.imageio.IIOException; + /** * BMHDChunk - *

    * * @author Harald Kuhr * @version $Id: BMHDChunk.java,v 1.0 28.feb.2006 00:04:32 haku Exp$ @@ -128,7 +130,8 @@ final class BMHDChunk extends IFFChunk { pageHeight = Math.min(pHeight, Short.MAX_VALUE); } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) throws IOException { if (chunkLength != 20) { throw new IIOException("Unknown BMHD chunk length: " + chunkLength); } @@ -147,7 +150,8 @@ final class BMHDChunk extends IFFChunk { pageHeight = pInput.readShort(); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); @@ -166,6 +170,7 @@ final class BMHDChunk extends IFFChunk { pOutput.writeShort(pageHeight); } + @Override public String toString() { return super.toString() + " {w=" + width + ", h=" + height diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java index 42a5bbf7..e5b5aec6 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java @@ -4,37 +4,37 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; import java.io.DataInput; -import java.io.IOException; import java.io.DataOutput; /** * BODYChunk - *

    * * @author Harald Kuhr * @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$ @@ -44,11 +44,13 @@ final class BODYChunk extends IFFChunk { super(IFF.CHUNK_BODY, pChunkLength); } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) { throw new InternalError("BODY chunk should only be read from IFFImageReader"); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) { throw new InternalError("BODY chunk should only be written from IFFImageWriter"); } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java index a2e6818f..05137874 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java @@ -4,38 +4,40 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; -import javax.imageio.IIOException; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import javax.imageio.IIOException; + /** * CAMGChunk - *

    * * @author Harald Kuhr * @version $Id: CAMGChunk.java,v 1.0 28.feb.2006 02:10:07 haku Exp$ @@ -45,13 +47,14 @@ final class CAMGChunk extends IFFChunk { // #define CAMG_HAM 0x800 /* hold and modify */ // #define CAMG_EHB 0x80 /* extra halfbrite */ - private int camg; + int camg; public CAMGChunk(int pLength) { super(IFF.CHUNK_CAMG, pLength); } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) throws IOException { if (chunkLength != 4) { throw new IIOException("Unknown CAMG chunk length: " + chunkLength); } @@ -59,7 +62,8 @@ final class CAMGChunk extends IFFChunk { camg = pInput.readInt(); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) { throw new InternalError("Not implemented: writeChunk()"); } @@ -79,6 +83,7 @@ final class CAMGChunk extends IFFChunk { return (camg & 0x80) != 0; } + @Override public String toString() { return super.toString() + " {mode=" + (isHAM() ? "HAM" : isEHB() ? "EHB" : "Normal") + "}"; } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java index b1546149..82215afb 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java @@ -4,31 +4,32 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; -import javax.imageio.IIOException; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; @@ -37,9 +38,10 @@ import java.io.DataOutput; import java.io.IOException; import java.util.Arrays; +import javax.imageio.IIOException; + /** * CMAPChunk - *

    * * @author Harald Kuhr * @version $Id: CMAPChunk.java,v 1.0 28.feb.2006 00:38:05 haku Exp$ @@ -67,6 +69,7 @@ final class CMAPChunk extends IFFChunk { model = pModel; } + @Override void readChunk(final DataInput pInput) throws IOException { int numColors = chunkLength / 3; @@ -94,6 +97,7 @@ final class CMAPChunk extends IFFChunk { } } + @Override void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); @@ -111,6 +115,7 @@ final class CMAPChunk extends IFFChunk { } } + @Override public String toString() { return super.toString() + " {colorMap=" + model + "}"; } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java index 6062518f..6e458cd6 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java index bf69b27b..06dc1482 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; @@ -37,7 +39,6 @@ import java.io.IOException; /** * GRABChunk - *

    * * @author Harald Kuhr * @version $Id: GRABChunk.java,v 1.0 28.feb.2006 01:55:05 haku Exp$ diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java index 78b5f3a1..ef595fe8 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java @@ -4,40 +4,41 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; import java.io.DataInput; -import java.io.IOException; import java.io.DataOutput; +import java.io.IOException; /** - * UnknownChunk - *

    + * GenericChunk * * @author Harald Kuhr - * @version $Id: UnknownChunk.java,v 1.0 28.feb.2006 00:53:47 haku Exp$ + * @version $Id: GenericChunk.java,v 1.0 28.feb.2006 00:53:47 haku Exp$ */ final class GenericChunk extends IFFChunk { @@ -45,7 +46,7 @@ final class GenericChunk extends IFFChunk { protected GenericChunk(int pChunkId, int pChunkLength) { super(pChunkId, pChunkLength); - data = new byte[pChunkLength <= 50 ? pChunkLength : 47]; + data = new byte[chunkLength]; } protected GenericChunk(int pChunkId, byte[] pChunkData) { @@ -53,13 +54,15 @@ final class GenericChunk extends IFFChunk { data = pChunkData; } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) throws IOException { pInput.readFully(data, 0, data.length); skipData(pInput, chunkLength, data.length); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); pOutput.write(data, 0, data.length); @@ -69,6 +72,7 @@ final class GenericChunk extends IFFChunk { } } + @Override public String toString() { return super.toString() + " {value=\"" + new String(data, 0, data.length <= 50 ? data.length : 47) diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java index c9185d79..d5d194d8 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java @@ -4,33 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; /** * IFF format constants. - *

    * * @author Harald Kuhr * @version $Id: IFF.java,v 1.0 07.mar.2006 15:31:48 haku Exp$ @@ -90,6 +91,12 @@ interface IFF { /** EA IFF 85 Generic Copyright text chunk */ int CHUNK_COPY = ('(' << 24) + ('c' << 16) + (')' << 8) + ' '; + /** EA IFF 85 Generic annotation chunk (usually used for Software) */ + int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O';; + + /** Third-party defined UTF-8 text. */ + int CHUNK_UTF8 = ('U' << 24) + ('T' << 16) + ('F' << 8) + '8'; + /** color cycling */ int CHUNK_CRNG = ('C' << 24) + ('R' << 16) + ('N' << 8) + 'G'; /** color cycling */ diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java index 1c6b3982..5f27a1de 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java @@ -4,37 +4,38 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; import java.io.DataInput; -import java.io.IOException; import java.io.DataOutput; +import java.io.IOException; /** * IFFChunk - *

    * * @author Harald Kuhr * @version $Id: IFFChunk.java,v 1.0 28.feb.2006 00:00:45 haku Exp$ diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java new file mode 100644 index 00000000..7ded0a4c --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java @@ -0,0 +1,269 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import com.twelvemonkeys.imageio.AbstractMetadata; + +import javax.imageio.metadata.IIOMetadataNode; +import java.awt.*; +import java.awt.image.IndexColorModel; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static com.twelvemonkeys.imageio.plugins.iff.IFF.*; +import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.lang.Validate.notNull; + +final class IFFImageMetadata extends AbstractMetadata { + private final int formType; + private final BMHDChunk header; + private final IndexColorModel colorMap; + private final CAMGChunk viewPort; + private final List meta; + + IFFImageMetadata(int formType, BMHDChunk header, IndexColorModel colorMap, CAMGChunk viewPort, List meta) { + this.formType = isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s"); + this.header = notNull(header, "header"); + this.colorMap = colorMap; + this.viewPort = viewPort; + this.meta = meta; + } + + private boolean validFormType(int formType) { + switch (formType) { + case TYPE_ACBM: + case TYPE_DEEP: + case TYPE_ILBM: + case TYPE_PBM: + case TYPE_RGB8: + case TYPE_RGBN: + return true; + default: + return false; + } + } + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + + IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); + chroma.appendChild(csType); + + switch (header.bitplanes) { + case 8: + if (colorMap == null) { + csType.setAttribute("name", "GRAY"); + break; + } + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 24: + case 32: + csType.setAttribute("name", "RGB"); + break; + default: + csType.setAttribute("name", "Unknown"); + } + + // NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data) + IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); + chroma.appendChild(numChannels); + if (colorMap == null && header.bitplanes == 8) { + numChannels.setAttribute("value", Integer.toString(1)); + } + else if (header.bitplanes == 32) { + numChannels.setAttribute("value", Integer.toString(4)); + } + else { + numChannels.setAttribute("value", Integer.toString(3)); + } + + IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); + chroma.appendChild(blackIsZero); + blackIsZero.setAttribute("value", "TRUE"); + + // NOTE: TGA files may contain a color map, even if true color... + // Not sure if this is a good idea to expose to the meta data, + // as it might be unexpected... Then again... + if (colorMap != null) { + IIOMetadataNode palette = new IIOMetadataNode("Palette"); + chroma.appendChild(palette); + + for (int i = 0; i < colorMap.getMapSize(); i++) { + IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry"); + palette.appendChild(paletteEntry); + paletteEntry.setAttribute("index", Integer.toString(i)); + + paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i))); + paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i))); + paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i))); + } + } + + // TODO: Background color is the color of the transparent index in the color model? +// if (extensions != null && extensions.getBackgroundColor() != 0) { +// Color background = new Color(extensions.getBackgroundColor(), true); +// +// IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor"); +// chroma.appendChild(backgroundColor); +// +// backgroundColor.setAttribute("red", Integer.toString(background.getRed())); +// backgroundColor.setAttribute("green", Integer.toString(background.getGreen())); +// backgroundColor.setAttribute("blue", Integer.toString(background.getBlue())); +// } + + return chroma; + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + if (header.compressionType == BMHDChunk.COMPRESSION_NONE) { + return null; // All defaults + } + + IIOMetadataNode node = new IIOMetadataNode("Compression"); + + IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); + compressionTypeName.setAttribute("value", "RLE"); + node.appendChild(compressionTypeName); + + IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); + lossless.setAttribute("value", "TRUE"); + node.appendChild(lossless); + + return node; + } + + @Override + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data = new IIOMetadataNode("Data"); + + // PlanarConfiguration + IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); + switch (formType) { + case TYPE_PBM: + planarConfiguration.setAttribute("value", "PixelInterleaved"); + break; + case TYPE_ILBM: + planarConfiguration.setAttribute("value", "PlaneInterleaved"); + break; + default: + planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(formType)); + break; + } + data.appendChild(planarConfiguration); + + IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); + sampleFormat.setAttribute("value", colorMap != null ? "Index" : "UnsignedIntegral"); + data.appendChild(sampleFormat); + + // BitsPerSample + IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); + String value = bitsPerSampleValue(header.bitplanes); + bitsPerSample.setAttribute("value", value); + data.appendChild(bitsPerSample); + + // SignificantBitsPerSample not in format + // SampleMSB not in format + + return data; + + } + + private String bitsPerSampleValue(int bitplanes) { + switch (bitplanes) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return Integer.toString(bitplanes); + case 24: + return "8 8 8"; + case 32: + return "8 8 8 8"; + default: + throw new IllegalArgumentException("Ubknown bit count: " + bitplanes); + } + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + if (viewPort == null) { + return null; + } + + IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); + + // PixelAspectRatio + IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); + pixelAspectRatio.setAttribute("value", String.valueOf((viewPort.isHires() ? 2f : 1f) / (viewPort.isLaced() ? 2f : 1f))); + dimension.appendChild(pixelAspectRatio); + + // TODO: HorizontalScreenSize? + // TODO: VerticalScreenSize? + + return dimension; + } + + @Override + protected IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode document = new IIOMetadataNode("Document"); + + IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); + document.appendChild(formatVersion); + formatVersion.setAttribute("value", "1.0"); + + return document; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + if (meta.isEmpty()) { + return null; + } + + IIOMetadataNode text = new IIOMetadataNode("Text"); + + // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. + for (GenericChunk chunk : meta) { + IIOMetadataNode node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", IFFUtil.toChunkStr(chunk.chunkId)); + node.setAttribute("value", new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII)); + text.appendChild(node); + } + + return text; + + } + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32) { + return null; + } + + IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); + + if (header.bitplanes == 32) { + IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); + alpha.setAttribute("value", "nonpremultiplied"); + transparency.appendChild(alpha); + } + + if (colorMap != null && colorMap.getTransparency() == Transparency.BITMASK) { + IIOMetadataNode transparentIndex = new IIOMetadataNode("TransparentIndex"); + transparentIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel())); + transparency.appendChild(transparentIndex); + } + + return transparency; + } +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java index e545afcd..d3c8f6ef 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java @@ -4,39 +4,41 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; import com.twelvemonkeys.image.ResampleOp; import com.twelvemonkeys.imageio.ImageReaderBase; -import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; import javax.imageio.*; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -45,6 +47,7 @@ import java.awt.image.*; import java.io.DataInputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -54,13 +57,14 @@ import java.util.List; * format (Packed BitMap). * The IFF format (Interchange File Format) is the standard file format * supported by allmost all image software for the Amiga computer. - *

    + *

    * This reader supports the original palette-based 1-8 bit formats, including * EHB (Extra Half-Bright), HAM (Hold and Modify), and the more recent "deep" * formats, 8 bit gray, 24 bit RGB and 32 bit ARGB. * Uncompressed and ByteRun1 compressed (run length encoding) files are * supported. - *

    + *

    + *

    * Palette based images are read as {@code BufferedImage} of * {@link BufferedImage#TYPE_BYTE_INDEXED TYPE_BYTE_INDEXED} or * {@link BufferedImage#TYPE_BYTE_BINARY BufferedImage#} @@ -71,7 +75,8 @@ import java.util.List; * {@link BufferedImage#TYPE_3BYTE_BGR TYPE_3BYTE_BGR}. * 32 bit true-color images are read as * {@link BufferedImage#TYPE_4BYTE_ABGR TYPE_4BYTE_ABGR}. - *

    + *

    + *

    * Issues: HAM and HAM8 (Hold and Modify) formats are converted to RGB (24 bit), * as it seems to be very hard to create an {@code IndexColorModel} subclass * that would correctly describe these formats. @@ -80,10 +85,11 @@ import java.util.List; * HAM8 (8 bits) needs 18 bits storage/pixel, if unpacked to RGB (6 bits/gun). * See Wikipedia: HAM * for more information. - *
    + *
    * EHB palette is expanded to an {@link IndexColorModel} with 64 entries. * See Wikipedia: EHB * for more information. + *

    * * @author Harald Kuhr * @author last modified by $Author: haku $ @@ -91,7 +97,7 @@ import java.util.List; * @see Wikipedia: IFF * @see Wikipedia: IFF ILBM */ -public class IFFImageReader extends ImageReaderBase { +public final class IFFImageReader extends ImageReaderBase { // http://home.comcast.net/~erniew/lwsdk/docs/filefmts/ilbm.html // http://www.fileformat.info/format/iff/spec/7866a9f0e53c42309af667c5da3bd426/view.htm // - Contains definitions of some "new" chunks, as well as alternative FORM types @@ -106,17 +112,14 @@ public class IFFImageReader extends ImageReaderBase { private GRABChunk grab; private CAMGChunk viewPort; private MultiPalette paletteChange; + private final List meta = new ArrayList<>(); private int formType; private long bodyStart; private BufferedImage image; private DataInputStream byteRunStream; - public IFFImageReader() { - super(new IFFImageReaderSpi()); - } - - protected IFFImageReader(ImageReaderSpi pProvider) { + IFFImageReader(ImageReaderSpi pProvider) { super(pProvider); } @@ -128,6 +131,7 @@ public class IFFImageReader extends ImageReaderBase { } } + @Override protected void resetMembers() { header = null; colorMap = null; @@ -135,6 +139,7 @@ public class IFFImageReader extends ImageReaderBase { body = null; viewPort = null; formType = 0; + meta.clear(); image = null; byteRunStream = null; @@ -253,11 +258,6 @@ public class IFFImageReader extends ImageReaderBase { // System.out.println(ctbl); break; - case IFF.CHUNK_JUNK: - // Always skip junk chunks - IFFChunk.skipData(imageInput, length, 0); - break; - case IFF.CHUNK_BODY: if (body != null) { throw new IIOException("Multiple BODY chunks not allowed"); @@ -269,18 +269,32 @@ public class IFFImageReader extends ImageReaderBase { // NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method // Done reading meta return; - default: - // TODO: We probably want to store ANNO, TEXT, AUTH, COPY etc chunks as Metadata - // SHAM, ANNO, DEST, SPRT and more - IFFChunk generic = new GenericChunk(chunkId, length); + + case IFF.CHUNK_ANNO: + case IFF.CHUNK_AUTH: + case IFF.CHUNK_COPY: + case IFF.CHUNK_NAME: + case IFF.CHUNK_TEXT: + case IFF.CHUNK_UTF8: + GenericChunk generic = new GenericChunk(chunkId, length); generic.readChunk(imageInput); + meta.add(generic); // System.out.println(generic); break; + + case IFF.CHUNK_JUNK: + // Always skip junk chunks + default: + // TODO: SHAM, DEST, SPRT and more + // Everything else, we'll just skip + IFFChunk.skipData(imageInput, length, 0); + break; } } } + @Override public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException { init(pIndex); @@ -309,16 +323,26 @@ public class IFFImageReader extends ImageReaderBase { return result; } + @Override public int getWidth(int pIndex) throws IOException { init(pIndex); return header.width; } + @Override public int getHeight(int pIndex) throws IOException { init(pIndex); return header.height; } + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + init(imageIndex); + + return new IFFImageMetadata(formType, header, colorMap != null ? colorMap.getIndexColorModel(header, isEHB()) : null, viewPort, meta); + } + + @Override public Iterator getImageTypes(int pIndex) throws IOException { init(pIndex); @@ -358,12 +382,11 @@ public class IFFImageReader extends ImageReaderBase { if (colorMap != null) { IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB()); specifier = ImageTypeSpecifiers.createFromIndexColorModel(cm); - break; } else { specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); - break; } + break; } // NOTE: HAM modes falls through, as they are converted to RGB case 24: @@ -781,7 +804,7 @@ public class IFFImageReader extends ImageReaderBase { } public static void main(String[] pArgs) throws IOException { - ImageReader reader = new IFFImageReader(); + ImageReader reader = new IFFImageReader(new IFFImageReaderSpi()); boolean scale = false; for (String arg : pArgs) { @@ -795,8 +818,7 @@ public class IFFImageReader extends ImageReaderBase { continue; } - try { - ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(file)); + try (ImageInputStream input = ImageIO.createImageInputStream(file)) { boolean canRead = reader.getOriginatingProvider().canDecodeInput(input); if (canRead) { diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java index d2d445d1..92b2ec67 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java @@ -4,40 +4,42 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; -import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; - -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; import java.io.IOException; import java.util.Locale; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; + /** * IFFImageReaderSpi - *

    * * @author Harald Kuhr * @version $Id: IFFImageWriterSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$ @@ -51,6 +53,7 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase { super(new IFFProviderInfo()); } + @Override public boolean canDecodeInput(Object pSource) throws IOException { return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource); } @@ -79,10 +82,12 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase { return false; } + @Override public ImageReader createReaderInstance(Object pExtension) throws IOException { return new IFFImageReader(this); } + @Override public String getDescription(Locale pLocale) { return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image reader"; } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java index d567113a..212520ce 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java @@ -4,51 +4,61 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; + import com.twelvemonkeys.imageio.ImageWriterBase; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.io.FastByteArrayOutputStream; 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.*; -import java.awt.image.*; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; - /** * Writer for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format. * The IFF format (Interchange File Format) is the standard file format * supported by almost all image software for the Amiga computer. - *

    * * @author Harald Kuhr * @version $Id: IFFImageWriter.java,v 1.0 02.mar.2006 13:32:30 haku Exp$ @@ -56,24 +66,23 @@ import java.io.OutputStream; * @see Wikipedia: IFF * @see Wikipedia: IFF ILBM */ -public class IFFImageWriter extends ImageWriterBase { +public final class IFFImageWriter extends ImageWriterBase { - public IFFImageWriter() { - this(null); - } - - protected IFFImageWriter(ImageWriterSpi pProvider) { + IFFImageWriter(ImageWriterSpi pProvider) { super(pProvider); } + @Override public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) { throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement } + @Override public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) { throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement } + @Override public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException { assertOutput(); @@ -104,13 +113,9 @@ public class IFFImageWriter extends ImageWriterBase { // NOTE: This is much faster than imageOutput.write(pImageData.toByteArray()) // as the data array is not duplicated - OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput); - try { + try (OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput)) { pImageData.writeTo(adapter); } - finally { - adapter.close(); - } if (pImageData.size() % 2 == 0) { imageOutput.writeByte(0); // PAD @@ -179,7 +184,7 @@ public class IFFImageWriter extends ImageWriterBase { private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException { // Annotation ANNO chunk, 8 + annoData.length bytes - String annotation = "Written by " + getOriginatingProvider().getDescription(null) + " by " + getOriginatingProvider().getVendorName(); + String annotation = String.format("Written by %s IFFImageWriter %s", getOriginatingProvider().getVendorName(), getOriginatingProvider().getVersion()); GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes()); ColorModel cm = pImage.getColorModel(); diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java index 0355c768..22e11630 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java @@ -4,40 +4,42 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; -import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; - -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.ImageWriter; import java.io.IOException; import java.util.Locale; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; + +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; + /** * IFFImageWriterSpi - *

    * * @author Harald Kuhr * @version $Id: IFFImageWriterSpi.java,v 1.0 02.mar.2006 19:21:05 haku Exp$ @@ -57,10 +59,12 @@ public class IFFImageWriterSpi extends ImageWriterSpiBase { return true; } + @Override public ImageWriter createWriterInstance(Object pExtension) throws IOException { return new IFFImageWriter(this); } + @Override public String getDescription(Locale pLocale) { return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image writer"; } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java index 62f8cf89..92270714 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java index 843d2eb9..ffa62a5f 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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. */ /* * Fast 90-degree bit rotation routines. @@ -36,15 +38,16 @@ package com.twelvemonkeys.imageio.plugins.iff; /** * IFFUtil - *

    + *

    * Bit rotate methods based on Sue-Ken Yap, "A Fast 90-Degree Bitmap Rotator," * in GRAPHICS GEMS II, James Arvo ed., Academic Press, 1991, ISBN 0-12-064480-0. + *

    * * @author Unascribed (C version) * @author Harald Kuhr (Java port) * @version $Id: IFFUtil.java,v 1.0 06.mar.2006 13:31:35 haku Exp$ */ -class IFFUtil { +final class IFFUtil { /** * Creates a rotation table diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MultiPalette.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MultiPalette.java new file mode 100644 index 00000000..ac7f3dae --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MultiPalette.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, 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.iff; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; + +/** + * MultiPalette + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: MultiPalette.java,v 1.0 30.03.12 15:22 haraldk Exp$ + */ +interface MultiPalette { + ColorModel getColorModel(IndexColorModel colorModel, int rowIndex, boolean laced); +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Multipalette.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Multipalette.java deleted file mode 100644 index 6130b25c..00000000 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Multipalette.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2012, 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 "TwelveMonkeys" 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 OWNER 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.iff; - -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; - -/** - * MultiPalette - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: MultiPalette.java,v 1.0 30.03.12 15:22 haraldk Exp$ - */ -interface MultiPalette { - ColorModel getColorModel(IndexColorModel colorModel, int rowIndex, boolean laced); -} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java index 4535b580..dc381aa9 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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. */ /* * Parts of this code is based on ilbmtoppm.c diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java index 499e5690..51511882 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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. */ /* * Parts of this code is based on ilbmtoppm.c diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java index 74b8805a..5fa3c9fb 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java new file mode 100644 index 00000000..035a9cb6 --- /dev/null +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java @@ -0,0 +1,511 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import static org.junit.Assert.*; + +import java.awt.image.IndexColorModel; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; + +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.w3c.dom.Node; + +public class IFFImageMetadataTest { + @Test + public void testStandardFeatures() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + final IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + // Standard metadata format + assertTrue(metadata.isStandardMetadataFormatSupported()); + Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + assertNotNull(root); + assertTrue(root instanceof IIOMetadataNode); + + // Other formats + assertNull(metadata.getNativeMetadataFormatName()); + assertNull(metadata.getExtraMetadataFormatNames()); + assertThrows(IllegalArgumentException.class, new ThrowingRunnable() { + @Override + public void run() { + metadata.getAsTree("com_foo_bar_1.0"); + } + }); + + // Read-only + assertTrue(metadata.isReadOnly()); + assertThrows(IllegalStateException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + metadata.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, new IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName)); + } + }); + } + + @Test + public void testStandardChromaGray() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("GRAY", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("1", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); // No more children + } + + @Test + public void testStandardChromaRGB() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("RGB", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("3", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); // No more children + } + + @Test + public void testStandardChromaPalette() { + BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1); + + byte[] bw = {0, (byte) 0xff}; + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.emptyList()); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(4, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("RGB", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("3", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + IIOMetadataNode palette = (IIOMetadataNode) blackIsZero.getNextSibling(); + assertEquals("Palette", palette.getNodeName()); + assertEquals(bw.length, palette.getLength()); + + for (int i = 0; i < palette.getLength(); i++) { + IIOMetadataNode item0 = (IIOMetadataNode) palette.item(i); + assertEquals("PaletteEntry", item0.getNodeName()); + assertEquals(String.valueOf(i), item0.getAttribute("index")); + String rgb = String.valueOf(bw[i] & 0xff); + assertEquals(rgb, item0.getAttribute("red")); + assertEquals(rgb, item0.getAttribute("green")); + assertEquals(rgb, item0.getAttribute("blue")); + } + + // TODO: BackgroundIndex == 1?? + } + + @Test + public void testStandardCompressionRLE() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode compression = metadata.getStandardCompressionNode(); + assertNotNull(compression); + assertEquals("Compression", compression.getNodeName()); + assertEquals(2, compression.getLength()); + + IIOMetadataNode compressionTypeName = (IIOMetadataNode) compression.getFirstChild(); + assertEquals("CompressionTypeName", compressionTypeName.getNodeName()); + assertEquals("RLE", compressionTypeName.getAttribute("value")); + + IIOMetadataNode lossless = (IIOMetadataNode) compressionTypeName.getNextSibling(); + assertEquals("Lossless", lossless.getNodeName()); + assertEquals("TRUE", lossless.getAttribute("value")); + + assertNull(lossless.getNextSibling()); // No more children + } + + @Test + public void testStandardCompressionNone() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + assertNull(metadata.getStandardCompressionNode()); // No compression, all default... + } + + @Test + public void testStandardDataILBM_Gray() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataILBM_RGB() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataILBM_RGBA() { + BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataILBM_Palette() { + for (int i = 1; i <= 8; i++) { + BMHDChunk header = new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, rgb.length, rgb, rgb, rgb, 0), null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("Index", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals(String.valueOf(i), bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + } + + @Test + public void testStandardDataPBM_Gray() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataPBM_RGB() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + + @Test + public void testStandardDimensionNoViewport() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNull(dimension); + } + + @Test + public void testStandardDimensionNormal() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, new CAMGChunk(4), Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDimensionHires() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + CAMGChunk viewPort = new CAMGChunk(4); + viewPort.camg = 0x8000; + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("2.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDimensionInterlaced() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + CAMGChunk viewPort = new CAMGChunk(4); + viewPort.camg = 0x4; + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("0.5", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDimensionHiresInterlaced() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + CAMGChunk viewPort = new CAMGChunk(4); + viewPort.camg = 0x8004; + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDocument() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode document = metadata.getStandardDocumentNode(); + assertNotNull(document); + assertEquals("Document", document.getNodeName()); + assertEquals(1, document.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild(); + assertEquals("FormatVersion", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardText() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + String[] texts = {"annotation", "äñnótâtïøñ"}; + List meta = Arrays.asList(new GenericChunk(IFF.CHUNK_ANNO, texts[0].getBytes(StandardCharsets.US_ASCII)), + new GenericChunk(IFF.CHUNK_UTF8, texts[1].getBytes(StandardCharsets.UTF_8))); + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, meta); + + IIOMetadataNode text = metadata.getStandardTextNode(); + assertNotNull(text); + assertEquals("Text", text.getNodeName()); + assertEquals(texts.length, text.getLength()); + + for (int i = 0; i < texts.length; i++) { + IIOMetadataNode textEntry = (IIOMetadataNode) text.item(i); + assertEquals("TextEntry", textEntry.getNodeName()); + assertEquals(IFFUtil.toChunkStr(meta.get(i).chunkId), textEntry.getAttribute("keyword")); + assertEquals(texts[i], textEntry.getAttribute("value")); + } + } + + @Test + public void testStandardTransparencyRGB() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNull(transparency); // No transparency, just defaults + } + + @Test + public void testStandardTransparencyRGBA() { + BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", pixelAspectRatio.getNodeName()); + assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardTransparencyPalette() { + BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1); + + byte[] bw = {0, (byte) 0xff}; + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.emptyList()); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("TransparentIndex", pixelAspectRatio.getNodeName()); + assertEquals("1", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } +} \ No newline at end of file diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java index 90783b11..db1c1993 100755 --- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java @@ -4,31 +4,34 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Test; import javax.imageio.ImageIO; @@ -52,6 +55,12 @@ import static org.junit.Assert.*; * @version $Id: IFFImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ */ public class IFFImageReaderTest extends ImageReaderAbstractTest { + @Override + protected ImageReaderSpi createProvider() { + return new IFFImageReaderSpi(); + } + + @Override protected List getTestData() { return Arrays.asList( // 32 bit - Ok @@ -83,22 +92,17 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest ); } - protected ImageReaderSpi createProvider() { - return new IFFImageReaderSpi(); - } - - protected Class getReaderClass() { - return IFFImageReader.class; - } - + @Override protected List getFormatNames() { return Collections.singletonList("iff"); } + @Override protected List getSuffixes() { return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm"); } + @Override protected List getMIMETypes() { return Arrays.asList("image/iff", "image/x-iff"); } diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterTest.java index 1cff8e2e..427d2c01 100644 --- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterTest.java +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterTest.java @@ -4,36 +4,40 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.iff; import com.twelvemonkeys.image.MonochromeColorModel; -import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase; +import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest; + import org.junit.Test; import javax.imageio.ImageIO; import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.color.ColorSpace; @@ -54,12 +58,10 @@ import static org.junit.Assert.*; * @author last modified by $Author: haraldk$ * @version $Id: JPEG2000ImageWriterTest.java,v 1.0 20.01.12 12:19 haraldk Exp$ */ -public class IFFImageWriterTest extends ImageWriterAbstractTestCase { - private final IFFImageWriterSpi provider = new IFFImageWriterSpi(); - +public class IFFImageWriterTest extends ImageWriterAbstractTest { @Override - protected ImageWriter createImageWriter() { - return new IFFImageWriter(provider); + protected ImageWriterSpi createProvider() { + return new IFFImageWriterSpi(); } @Override @@ -79,7 +81,7 @@ public class IFFImageWriterTest extends ImageWriterAbstractTestCase { @Test public void testWriteReadCompare() throws IOException { - ImageWriter writer = createImageWriter(); + ImageWriter writer = createWriter(); List testData = getTestData(); @@ -114,9 +116,7 @@ public class IFFImageWriterTest extends ImageWriterAbstractTestCase { assertSameData(original, written); } catch (IOException e) { - AssertionError fail = new AssertionError("Failure writing test data " + i + " " + e); - fail.initCause(e); - throw fail; + throw new AssertionError("Failure writing test data " + i + " " + e, e); } } } diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java index ec666c9a..a5700e6a 100644 --- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2016, 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.iff; import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; diff --git a/imageio/imageio-jpeg-jai-interop/pom.xml b/imageio/imageio-jpeg-jai-interop/pom.xml new file mode 100644 index 00000000..883f8815 --- /dev/null +++ b/imageio/imageio-jpeg-jai-interop/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.twelvemonkeys.imageio + imageio + 3.8.0-SNAPSHOT + + imageio-jpeg-jai-interop + TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop + + Test JPEG plugin and JAI TIFF plugin interoperability + + + + com.twelvemonkeys.imageio.jaiinterop + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + com.github.jai-imageio + jai-imageio-core + 1.4.0 + + + com.twelvemonkeys.imageio + imageio-core + + + com.twelvemonkeys.imageio + imageio-core + test-jar + test + + + com.twelvemonkeys.imageio + imageio-metadata + + + com.twelvemonkeys.imageio + imageio-jpeg + + + diff --git a/imageio/imageio-jpeg-jai-interop/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/jaiinterop/JAITIFFImageReaderInteroperabilityTest.java b/imageio/imageio-jpeg-jai-interop/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/jaiinterop/JAITIFFImageReaderInteroperabilityTest.java new file mode 100644 index 00000000..dfc4af71 --- /dev/null +++ b/imageio/imageio-jpeg-jai-interop/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/jaiinterop/JAITIFFImageReaderInteroperabilityTest.java @@ -0,0 +1,121 @@ +/* + * 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.jpeg.jaiinterop; + +import com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi; +import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + +import org.junit.Ignore; +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ServiceRegistry; +import java.awt.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Tests the JAI TIFFImageReader delegating to our JPEGImageReader. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JAITIFFImageReaderInteroperabilityTest.java,v 1.0 08.05.12 15:25 haraldk Exp$ + */ +public class JAITIFFImageReaderInteroperabilityTest extends ImageReaderAbstractTest { + private static final String JAI_TIFF_PROVIDER_CLASS_NAME = "com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi"; + + @Override + protected ImageReaderSpi createProvider() { + Iterator providers = IIORegistry.getDefaultInstance().getServiceProviders(ImageReaderSpi.class, new ServiceRegistry.Filter() { + @Override + public boolean filter(final Object provider) { + return JAI_TIFF_PROVIDER_CLASS_NAME.equals(provider.getClass().getName()); + } + }, true); + + if (providers.hasNext()) { + return providers.next(); + } + + return null; + } + + @Override + protected List getTestData() { + return Arrays.asList( + new TestData(getClassLoaderResource("/tiff/foto_0001.tif"), new Dimension(1663, 2338), new Dimension(1663, 2337)), // Little endian, Old JPEG + new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)), // CMYK, JPEG compressed, with ICC profile + new TestData(getClassLoaderResource("/tiff/jpeg-lossless-8bit-gray.tif"), new Dimension(512, 512)) // Lossless JPEG Gray, 8 bit/sample + ); + } + + @Override + protected List getFormatNames() { + return Collections.emptyList(); + } + + @Override + protected List getSuffixes() { + return Collections.emptyList(); + } + + @Override + protected List getMIMETypes() { + return Collections.emptyList(); + } + + @Ignore("Fails in TIFFImageReader") + @Override + public void testSetDestinationIllegal() { + } + + @Test + public void testReallyUsingOurJPEGImageReader() { + Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + + if ((reader.getOriginatingProvider() instanceof JPEGImageReaderSpi)) { + return; + } + } + + fail("Expected Spi not registered (dependency issue?): " + JPEGImageReaderSpi.class); + } +} diff --git a/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/cmyk_jpeg.tif b/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/cmyk_jpeg.tif new file mode 100644 index 00000000..85d0bc92 Binary files /dev/null and b/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/cmyk_jpeg.tif differ diff --git a/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/foto_0001.tif b/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/foto_0001.tif new file mode 100644 index 00000000..a8f80d87 Binary files /dev/null and b/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/foto_0001.tif differ diff --git a/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/jpeg-lossless-8bit-gray.tif b/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/jpeg-lossless-8bit-gray.tif new file mode 100755 index 00000000..dfa11295 Binary files /dev/null and b/imageio/imageio-jpeg-jai-interop/src/test/resources/tiff/jpeg-lossless-8bit-gray.tif differ diff --git a/imageio/imageio-jpeg-jep262-interop/pom.xml b/imageio/imageio-jpeg-jep262-interop/pom.xml new file mode 100644 index 00000000..6f7dcefd --- /dev/null +++ b/imageio/imageio-jpeg-jep262-interop/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + com.twelvemonkeys.imageio + imageio + 3.8.0-SNAPSHOT + + imageio-jpeg-jep262-interop + TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop + + Test JPEG plugin and JEP-262 (JDK TIFF plugin) interoperability + + + + com.twelvemonkeys.imageio.jep262interop + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + com.twelvemonkeys.imageio + imageio-core + + + com.twelvemonkeys.imageio + imageio-core + test-jar + test + + + com.twelvemonkeys.imageio + imageio-metadata + + + com.twelvemonkeys.imageio + imageio-jpeg + + + diff --git a/imageio/imageio-jpeg-jep262-interop/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/jep262interop/JEP262TIFFImageReaderInteroperabilityTest.java b/imageio/imageio-jpeg-jep262-interop/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/jep262interop/JEP262TIFFImageReaderInteroperabilityTest.java new file mode 100644 index 00000000..4ca09f68 --- /dev/null +++ b/imageio/imageio-jpeg-jep262-interop/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/jep262interop/JEP262TIFFImageReaderInteroperabilityTest.java @@ -0,0 +1,123 @@ +/* + * 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.jpeg.jep262interop; + +import com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi; +import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + +import org.junit.AssumptionViolatedException; +import org.junit.Ignore; +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ServiceRegistry; +import java.awt.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static org.junit.Assert.fail; + +/** + * Tests the JEP 262 TIFFImageReader delegating to our JPEGImageReader. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: TIFFImageReaderTest.java,v 1.0 08.05.12 15:25 haraldk Exp$ + */ +public class JEP262TIFFImageReaderInteroperabilityTest extends ImageReaderAbstractTest { + private static final String JEP_262_PROVIDER_CLASS_NAME = "com.sun.imageio.plugins.tiff.TIFFImageReaderSpi"; + + @Override + protected ImageReaderSpi createProvider() { + Iterator providers = IIORegistry.getDefaultInstance().getServiceProviders(ImageReaderSpi.class, new ServiceRegistry.Filter() { + @Override + public boolean filter(final Object provider) { + return JEP_262_PROVIDER_CLASS_NAME.equals(provider.getClass().getName()) && ((ImageReaderSpi) provider).getVendorName().startsWith("Oracle"); + } + }, true); + + if (providers.hasNext()) { + return providers.next(); + } + + // Skip tests if we have no Spi (ie. pre JDK 9) + throw new AssumptionViolatedException("Provider " + JEP_262_PROVIDER_CLASS_NAME + " not found"); + } + + @Override + protected List getTestData() { + return Arrays.asList( + new TestData(getClassLoaderResource("/tiff/foto_0001.tif"), new Dimension(1663, 2338), new Dimension(1663, 2337)), // Little endian, Old JPEG + new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)), // CMYK, JPEG compressed, with ICC profile + new TestData(getClassLoaderResource("/tiff/jpeg-lossless-8bit-gray.tif"), new Dimension(512, 512)) // Lossless JPEG Gray, 8 bit/sample + ); + } + + @Override + protected List getFormatNames() { + return Collections.emptyList(); + } + + @Override + protected List getSuffixes() { + return Collections.emptyList(); + } + + @Override + protected List getMIMETypes() { + return Collections.emptyList(); + } + + @Ignore("Fails in TIFFImageReader") + @Override + public void testSetDestinationIllegal() { + } + + @Test + public void testReallyUsingOurJPEGImageReader() { + Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + + if ((reader.getOriginatingProvider() instanceof JPEGImageReaderSpi)) { + return; + } + } + + fail("Expected Spi not registered (dependency issue?): " + JPEGImageReaderSpi.class); + } +} diff --git a/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/cmyk_jpeg.tif b/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/cmyk_jpeg.tif new file mode 100644 index 00000000..85d0bc92 Binary files /dev/null and b/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/cmyk_jpeg.tif differ diff --git a/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/foto_0001.tif b/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/foto_0001.tif new file mode 100644 index 00000000..a8f80d87 Binary files /dev/null and b/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/foto_0001.tif differ diff --git a/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/jpeg-lossless-8bit-gray.tif b/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/jpeg-lossless-8bit-gray.tif new file mode 100755 index 00000000..dfa11295 Binary files /dev/null and b/imageio/imageio-jpeg-jep262-interop/src/test/resources/tiff/jpeg-lossless-8bit-gray.tif differ diff --git a/imageio/imageio-jpeg/license.txt b/imageio/imageio-jpeg/license.txt index 53188f88..0e3d0c5d 100644 --- a/imageio/imageio-jpeg/license.txt +++ b/imageio/imageio-jpeg/license.txt @@ -6,22 +6,24 @@ 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 "TwelveMonkeys" nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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 OWNER 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. +* 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. diff --git a/imageio/imageio-jpeg/pom.xml b/imageio/imageio-jpeg/pom.xml index 913c4426..b4d85920 100644 --- a/imageio/imageio-jpeg/pom.xml +++ b/imageio/imageio-jpeg/pom.xml @@ -4,7 +4,7 @@ com.twelvemonkeys.imageio imageio - 3.4-SNAPSHOT + 3.8.0-SNAPSHOT imageio-jpeg TwelveMonkeys :: ImageIO :: JPEG plugin @@ -12,6 +12,10 @@ ImageIO plugin for Joint Photographer Expert Group images (JPEG/JFIF). + + com.twelvemonkeys.imageio.jpeg + + com.twelvemonkeys.imageio @@ -21,6 +25,7 @@ com.twelvemonkeys.imageio imageio-core test-jar + test com.twelvemonkeys.imageio diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java index bae9d248..21960633 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java index 80e07237..b7de325c 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; @@ -36,7 +38,7 @@ import java.io.IOException; import java.io.InputStream; /** - * Application. + * An application (APPn) segment in the JPEG stream. * * @author Harald Kuhr * @author last modified by $Author: harald.kuhr$ @@ -76,7 +78,9 @@ class Application extends Segment { if ("JFXX".equals(identifier)) { return JFXX.read(data, length); } - // TODO: Exif? + if ("Exif".equals(identifier)) { + return EXIF.read(data, length); + } case JPEG.APP2: // ICC_PROFILE if ("ICC_PROFILE".equals(identifier)) { @@ -90,7 +94,7 @@ class Application extends Segment { default: // Generic APPn segment - byte[] bytes = new byte[length - 2]; + byte[] bytes = new byte[Math.max(0, length - 2)]; data.readFully(bytes); return new Application(marker, identifier, bytes); } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java index 50e9ea69..e6ecbda7 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIF.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIF.java new file mode 100644 index 00000000..000046ed --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIF.java @@ -0,0 +1,74 @@ +/* + * 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.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; + +import javax.imageio.stream.ImageInputStream; +import java.io.DataInput; +import java.io.EOFException; +import java.io.IOException; + +/** + * An EXIF segment. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$ + */ +final class EXIF extends Application { + EXIF(byte[] data) { + super(JPEG.APP1, "Exif", data); + } + + @Override + public String toString() { + return String.format("APP1/Exif, length: %d", data.length); + } + + ImageInputStream exifData() { + // Identifier is "Exif\0" + 1 byte pad + int offset = identifier.length() + 2; + return new ByteArrayImageInputStream(data, offset, data.length - offset); + } + + public static EXIF read(final DataInput data, int length) throws IOException { + if (length < 2 + 6) { + throw new EOFException(); + } + + byte[] bytes = new byte[length - 2]; + data.readFully(bytes); + + return new EXIF(bytes); + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnail.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnail.java new file mode 100644 index 00000000..0cdece31 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnail.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2012, 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.jpeg; + +import com.twelvemonkeys.imageio.color.YCbCrConverter; +import com.twelvemonkeys.imageio.metadata.CompoundDirectory; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.imageio.metadata.tiff.TIFF; +import com.twelvemonkeys.imageio.plugins.jpeg.ThumbnailReader.JPEGThumbnailReader; +import com.twelvemonkeys.imageio.plugins.jpeg.ThumbnailReader.UncompressedThumbnailReader; + +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * EXIFThumbnail + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: EXIFThumbnail.java,v 1.0 18.04.12 12:19 haraldk Exp$ + */ +final class EXIFThumbnail { + private EXIFThumbnail() { + } + + static ThumbnailReader from(final EXIF segment, final CompoundDirectory exif, final ImageReader jpegThumbnailReader) throws IOException { + if (segment != null && exif != null && exif.directoryCount() >= 2) { + ImageInputStream stream = segment.exifData(); // NOTE This is an in-memory stream and must not be closed... + + Directory ifd1 = exif.getDirectory(1); + + // Compression: 1 = no compression, 6 = JPEG compression (default) + Entry compressionEntry = ifd1.getEntryById(TIFF.TAG_COMPRESSION); + int compression = compressionEntry == null ? 6 : ((Number) compressionEntry.getValue()).intValue(); + + switch (compression) { + case 1: + return createUncompressedThumbnailReader(stream, ifd1); + case 6: + return createJPEGThumbnailReader(segment, jpegThumbnailReader, stream, ifd1); + default: + throw new IIOException("EXIF IFD with unknown thumbnail compression (expected 1 or 6): " + compression); + } + } + + return null; + } + + private static UncompressedThumbnailReader createUncompressedThumbnailReader(ImageInputStream stream, Directory ifd1) throws IOException { + Entry stripOffEntry = ifd1.getEntryById(TIFF.TAG_STRIP_OFFSETS); + Entry width = ifd1.getEntryById(TIFF.TAG_IMAGE_WIDTH); + Entry height = ifd1.getEntryById(TIFF.TAG_IMAGE_HEIGHT); + + if (stripOffEntry != null && width != null && height != null) { + Entry bitsPerSample = ifd1.getEntryById(TIFF.TAG_BITS_PER_SAMPLE); + Entry samplesPerPixel = ifd1.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); + Entry photometricInterpretation = ifd1.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); + + // Required + int w = ((Number) width.getValue()).intValue(); + int h = ((Number) height.getValue()).intValue(); + + if (bitsPerSample != null && !Arrays.equals((int[]) bitsPerSample.getValue(), new int[] {8, 8, 8})) { + throw new IIOException("Unknown BitsPerSample value for uncompressed EXIF thumbnail (expected [8, 8, 8]): " + bitsPerSample.getValueAsString()); + } + + if (samplesPerPixel != null && ((Number) samplesPerPixel.getValue()).intValue() != 3) { + throw new IIOException("Unknown SamplesPerPixel value for uncompressed EXIF thumbnail (expected 3): " + samplesPerPixel.getValueAsString()); + } + + int interpretation = photometricInterpretation != null ? ((Number) photometricInterpretation.getValue()).intValue() : 2; + long stripOffset = ((Number) stripOffEntry.getValue()).longValue(); + + int thumbLength = w * h * 3; + if (stripOffset >= 0 && stripOffset + thumbLength <= stream.length()) { + // Read raw image data, either RGB or YCbCr + stream.seek(stripOffset); + byte[] thumbData = new byte[thumbLength]; + stream.readFully(thumbData); + + switch (interpretation) { + case 2: + // RGB + break; + case 6: + // YCbCr + for (int i = 0; i < thumbLength; i += 3) { + YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i); + } + break; + default: + throw new IIOException("Unknown PhotometricInterpretation value for uncompressed EXIF thumbnail (expected 2 or 6): " + interpretation); + } + + return new UncompressedThumbnailReader(w, h, thumbData); + } + } + + throw new IIOException("EXIF IFD with empty or incomplete uncompressed thumbnail"); + } + + private static JPEGThumbnailReader createJPEGThumbnailReader(EXIF exif, ImageReader jpegThumbnailReader, ImageInputStream stream, Directory ifd1) throws IOException { + Entry jpegOffEntry = ifd1.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT); + if (jpegOffEntry != null) { + Entry jpegLenEntry = ifd1.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + + // Test if Exif thumbnail is contained within the Exif segment (offset + length <= segment.length) + long jpegOffset = ((Number) jpegOffEntry.getValue()).longValue(); + long jpegLength = jpegLenEntry != null ? ((Number) jpegLenEntry.getValue()).longValue() : -1; + + if (jpegLength > 0 && jpegOffset + jpegLength <= exif.data.length) { + // Verify first bytes are FFD8 + stream.seek(jpegOffset); + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + if (stream.readUnsignedShort() == JPEG.SOI) { + return new JPEGThumbnailReader(jpegThumbnailReader, stream, jpegOffset); + } + } + } + + throw new IIOException("EXIF IFD with empty or incomplete JPEG thumbnail"); + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java deleted file mode 100644 index cd70f04c..00000000 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) 2012, 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 "TwelveMonkeys" 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 OWNER 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.jpeg; - -import com.twelvemonkeys.imageio.color.YCbCrConverter; -import com.twelvemonkeys.imageio.metadata.Directory; -import com.twelvemonkeys.imageio.metadata.Entry; -import com.twelvemonkeys.imageio.metadata.tiff.TIFF; -import com.twelvemonkeys.imageio.util.IIOUtil; -import com.twelvemonkeys.lang.Validate; - -import javax.imageio.IIOException; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; -import javax.imageio.stream.MemoryCacheImageInputStream; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.SequenceInputStream; -import java.lang.ref.SoftReference; -import java.util.Arrays; - -/** - * EXIFThumbnail - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: EXIFThumbnail.java,v 1.0 18.04.12 12:19 haraldk Exp$ - */ -final class EXIFThumbnailReader extends ThumbnailReader { - private final ImageReader reader; - private final Directory ifd; - private final ImageInputStream stream; - private final int compression; - - private transient SoftReference cachedThumbnail; - - EXIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final Directory ifd, final ImageInputStream stream) { - super(progressListener, imageIndex, thumbnailIndex); - this.reader = Validate.notNull(jpegReader); - this.ifd = ifd; - this.stream = stream; - - Entry compression = ifd.getEntryById(TIFF.TAG_COMPRESSION); - - this.compression = compression != null ? ((Number) compression.getValue()).intValue() : 6; - } - - @Override - public BufferedImage read() throws IOException { - if (compression == 1) { // 1 = no compression - processThumbnailStarted(); - BufferedImage thumbnail = readUncompressed(); - processThumbnailProgress(100f); - processThumbnailComplete(); - - return thumbnail; - } - else if (compression == 6) { // 6 = JPEG compression - processThumbnailStarted(); - BufferedImage thumbnail = readJPEGCached(true); - processThumbnailProgress(100f); - processThumbnailComplete(); - - return thumbnail; - } - else { - throw new IIOException("Unsupported EXIF thumbnail compression: " + compression); - } - } - - private BufferedImage readJPEGCached(final boolean pixelsExposed) throws IOException { - BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null; - - if (thumbnail == null) { - thumbnail = readJPEG(); - } - - cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail); - - return thumbnail; - } - - private BufferedImage readJPEG() throws IOException { - // IFD1 should contain JPEG offset for JPEG thumbnail - Entry jpegOffset = ifd.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT); - - if (jpegOffset != null) { - stream.seek(((Number) jpegOffset.getValue()).longValue()); - InputStream input = IIOUtil.createStreamAdapter(stream); - - // For certain EXIF files (encoded with TIFF.TAG_YCBCR_POSITIONING = 2?), we need - // EXIF information to read the thumbnail correctly (otherwise the colors are messed up). - // Probably related to: http://bugs.sun.com/view_bug.do?bug_id=4881314 - - // HACK: Splice empty EXIF information into the thumbnail stream - byte[] fakeEmptyExif = { - // SOI (from original data) - (byte) input.read(), (byte) input.read(), - // APP1 + len (016) + 'Exif' + 0-term + pad - (byte) 0xFF, (byte) 0xE1, 0, 16, 'E', 'x', 'i', 'f', 0, 0, - // Big-endian BOM (MM), TIFF magic (042), offset (0000) - 'M', 'M', 0, 42, 0, 0, 0, 0, - }; - - input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input); - - try { - - try (MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input)) { - return readJPEGThumbnail(reader, stream); - } - } - finally { - input.close(); - } - } - - throw new IIOException("Missing JPEGInterchangeFormat tag for JPEG compressed EXIF thumbnail"); - } - - private BufferedImage readUncompressed() throws IOException { - // Read ImageWidth, ImageLength (height) and BitsPerSample (=8 8 8, always) - // PhotometricInterpretation (2=RGB, 6=YCbCr), SamplesPerPixel (=3, always), - Entry width = ifd.getEntryById(TIFF.TAG_IMAGE_WIDTH); - Entry height = ifd.getEntryById(TIFF.TAG_IMAGE_HEIGHT); - - if (width == null || height == null) { - throw new IIOException("Missing dimensions for uncompressed EXIF thumbnail"); - } - - Entry bitsPerSample = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE); - Entry samplesPerPixel = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); - Entry photometricInterpretation = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); - - // Required - int w = ((Number) width.getValue()).intValue(); - int h = ((Number) height.getValue()).intValue(); - - if (bitsPerSample != null) { - int[] bpp = (int[]) bitsPerSample.getValue(); - if (!Arrays.equals(bpp, new int[] {8, 8, 8})) { - throw new IIOException("Unknown BitsPerSample value for uncompressed EXIF thumbnail (expected [8, 8, 8]): " + bitsPerSample.getValueAsString()); - } - } - - if (samplesPerPixel != null && (Integer) samplesPerPixel.getValue() != 3) { - throw new IIOException("Unknown SamplesPerPixel value for uncompressed EXIF thumbnail (expected 3): " + samplesPerPixel.getValueAsString()); - } - - int interpretation = photometricInterpretation != null ? ((Number) photometricInterpretation.getValue()).intValue() : 2; - - // IFD1 should contain strip offsets for uncompressed images - Entry offset = ifd.getEntryById(TIFF.TAG_STRIP_OFFSETS); - if (offset != null) { - stream.seek(((Number) offset.getValue()).longValue()); - - // Read raw image data, either RGB or YCbCr - int thumbSize = w * h * 3; - byte[] thumbData = JPEGImageReader.readFully(stream, thumbSize); - - switch (interpretation) { - case 2: - // RGB - break; - case 6: - // YCbCr - for (int i = 0; i < thumbSize; i += 3) { - YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i); - } - break; - default: - throw new IIOException("Unknown PhotometricInterpretation value for uncompressed EXIF thumbnail (expected 2 or 6): " + interpretation); - } - - return ThumbnailReader.readRawThumbnail(thumbData, thumbSize, 0, w, h); - } - - throw new IIOException("Missing StripOffsets tag for uncompressed EXIF thumbnail"); - } - - @Override - public int getWidth() throws IOException { - if (compression == 1) { // 1 = no compression - Entry width = ifd.getEntryById(TIFF.TAG_IMAGE_WIDTH); - - if (width == null) { - throw new IIOException("Missing dimensions for uncompressed EXIF thumbnail"); - } - - return ((Number) width.getValue()).intValue(); - } - else if (compression == 6) { // 6 = JPEG compression - return readJPEGCached(false).getWidth(); - } - else { - throw new IIOException("Unsupported EXIF thumbnail compression (expected 1 or 6): " + compression); - } - } - - @Override - public int getHeight() throws IOException { - if (compression == 1) { // 1 = no compression - Entry height = ifd.getEntryById(TIFF.TAG_IMAGE_HEIGHT); - - if (height == null) { - throw new IIOException("Missing dimensions for uncompressed EXIF thumbnail"); - } - - return ((Number) height.getValue()).intValue(); - } - else if (compression == 6) { // 6 = JPEG compression - return readJPEGCached(false).getHeight(); - } - else { - throw new IIOException("Unsupported EXIF thumbnail compression (expected 1 or 6): " + compression); - } - } -} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java index 58a25744..4aab7fb7 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; @@ -37,8 +39,9 @@ import java.awt.image.*; /** * This class performs a pixel by pixel conversion of the source image, from CMYK to RGB. - *

    + *

    * The conversion is fast, but performed without any color space conversion. + *

    * * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -59,6 +62,7 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp { * @return {@code dest}, or a new {@link WritableRaster} if {@code dest} is {@code null}. * @throws IllegalArgumentException if {@code src} and {@code dest} refer to the same object */ + @Override public WritableRaster filter(Raster src, WritableRaster dest) { Validate.notNull(src, "src may not be null"); // TODO: Why not allow same raster, if converting to 4 byte ABGR? @@ -139,10 +143,12 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp { rgb[2] = (byte) (255 - (((cmyk[2] & 0xFF) * (255 - k) / 255) + k)); } + @Override public Rectangle2D getBounds2D(Raster src) { return src.getBounds(); } + @Override public WritableRaster createCompatibleDestRaster(final Raster src) { // WHAT?? This code no longer work for JRE 7u45+... JRE bug?! // Raster child = src.createChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, new int[] {0, 1, 2}); @@ -150,10 +156,11 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp { // This is a workaround for the above code that no longer works. // It wil use 25% more memory, but it seems to work... - WritableRaster raster = src.createCompatibleWritableRaster(); - return raster.createWritableChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, new int[] {0, 1, 2}); + return src.createCompatibleWritableRaster() + .createWritableChild(0, 0, src.getWidth(), src.getHeight(), 0, 0, new int[] {0, 1, 2}); } + @Override public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { if (dstPt == null) { dstPt = new Point2D.Double(srcPt.getX(), srcPt.getY()); @@ -165,6 +172,7 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp { return dstPt; } + @Override public RenderingHints getRenderingHints() { return null; } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java index d72dc5f3..96206fef 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java index 2e079d74..8181cc48 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java @@ -6,26 +6,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; @@ -33,27 +35,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import javax.imageio.IIOException; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; import java.io.DataInput; import java.io.IOException; final class HuffmanTable extends Segment { - private final int l[][][] = new int[4][2][16]; - private final int th[] = new int[4]; // 1: this table is present - final int v[][][][] = new int[4][2][16][200]; // tables - final int[][] tc = new int[4][2]; // 1: this table is present + private final short[][][] l = new short[4][2][16]; + private final short[][][][] v = new short[4][2][16][200]; // tables + private final boolean[][] tc = new boolean[4][2]; // 1: this table is present - static final int MSB = 0x80000000; + private static final int MSB = 0x80000000; private HuffmanTable() { super(JPEG.DHT); } - void buildHuffTables(final int[][][] HuffTab) throws IOException { + void buildHuffTables(final int[][][] huffTab) throws IOException { for (int t = 0; t < 4; t++) { for (int c = 0; c < 2; c++) { - if (tc[t][c] != 0) { - buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]); + if (tc[t][c]) { + buildHuffTable(huffTab[t][c], l[t][c], v[t][c]); } } } @@ -66,7 +68,7 @@ final class HuffmanTable extends Segment { // V[i][j] Huffman Value (length=i) // Effect: // build up HuffTab[t][c] using L and V. - private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException { + private void buildHuffTable(final int[] tab, final short[] L, final short[][] V) throws IOException { int temp = 256; int k = 0; @@ -110,7 +112,7 @@ final class HuffmanTable extends Segment { for (int t = 0; t < tc.length; t++) { for (int c = 0; c < tc[t].length; c++) { - if (tc[t][c] != 0) { + if (tc[t][c]) { if (builder.length() > 4) { builder.append(", "); } @@ -147,11 +149,10 @@ final class HuffmanTable extends Segment { throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c); } - table.th[t] = 1; - table.tc[t][c] = 1; + table.tc[t][c] = true; for (int i = 0; i < 16; i++) { - table.l[t][c][i] = data.readUnsignedByte(); + table.l[t][c][i] = (short) data.readUnsignedByte(); count++; } @@ -160,7 +161,7 @@ final class HuffmanTable extends Segment { if (count > length) { throw new IIOException("JPEG Huffman Table format error"); } - table.v[t][c][i][j] = data.readUnsignedByte(); + table.v[t][c][i][j] = (short) data.readUnsignedByte(); count++; } } @@ -172,4 +173,41 @@ final class HuffmanTable extends Segment { return table; } + + public boolean isPresent(int tableId, int tableClass) { + return tc[tableId][tableClass]; + } + + private short[] lengths(int tableId, int tableClass) { + // TODO: Consider stripping the 0s? + return l[tableId][tableClass]; + } + + private short[] tables(int tableId, int tableClass) { + // Find sum of lengths + short[] lengths = lengths(tableId, tableClass); + + int sumOfLengths = 0; + for (int length : lengths) { + sumOfLengths += length; + } + + // Flatten the tables + short[] tables = new short[sumOfLengths]; + + int pos = 0; + for (int i = 0; i < 16; i++) { + short[] table = v[tableId][tableClass][i]; + short length = lengths[i]; + + System.arraycopy(table, 0, tables, pos, length); + pos += length; + } + + return tables; + } + + JPEGHuffmanTable toNativeTable(int tableId, int tableClass) { + return new JPEGHuffmanTable(lengths(tableId, tableClass), tables(tableId, tableClass)); + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java index 28599d0e..e91679d4 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java index 7dcd8d9f..81d086f5 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; @@ -36,7 +38,7 @@ import java.io.IOException; import java.nio.ByteBuffer; /** - * JFIFSegment + * A JFIF segment. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -52,8 +54,8 @@ final class JFIF extends Application { final int yThumbnail; final byte[] thumbnail; - private JFIF(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail, byte[] data) { - super(JPEG.APP0, "JFIF", data); + JFIF(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) { + super(JPEG.APP0, "JFIF", new byte[5 + 9 + (thumbnail != null ? thumbnail.length : 0)]); this.majorVersion = majorVersion; this.minorVersion = minorVersion; @@ -96,7 +98,7 @@ final class JFIF extends Application { throw new EOFException(); } - data.readFully(new byte[5]); + data.readFully(new byte[5]); // Skip "JFIF\0" byte[] bytes = new byte[length - 2 - 5]; data.readFully(bytes); @@ -113,8 +115,7 @@ final class JFIF extends Application { buffer.getShort() & 0xffff, x = buffer.get() & 0xff, y = buffer.get() & 0xff, - getBytes(buffer, Math.min(buffer.remaining(), x * y * 3)), - bytes + getBytes(buffer, Math.min(buffer.remaining(), x * y * 3)) ); } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnail.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnail.java new file mode 100644 index 00000000..a8d5b67b --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnail.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012, 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.jpeg; + +import com.twelvemonkeys.imageio.plugins.jpeg.ThumbnailReader.UncompressedThumbnailReader; + +import javax.imageio.IIOException; +import java.io.IOException; + +/** + * JFIFThumbnail + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JFIFThumbnail.java,v 1.0 18.04.12 12:19 haraldk Exp$ + */ +final class JFIFThumbnail { + private JFIFThumbnail() { + } + + static ThumbnailReader from(final JFIF segment) throws IOException { + if (segment != null && segment.xThumbnail > 0 && segment.yThumbnail > 0) { + if (segment.thumbnail == null || segment.thumbnail.length < segment.xThumbnail * segment.yThumbnail) { + throw new IIOException("Truncated JFIF thumbnail"); + } + + return new UncompressedThumbnailReader(segment.xThumbnail, segment.yThumbnail, segment.thumbnail); + } + + return null; + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java deleted file mode 100644 index 14e71e46..00000000 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2012, 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 "TwelveMonkeys" 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 OWNER 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.jpeg; - -import java.awt.image.BufferedImage; -import java.io.IOException; - -/** - * JFIFThumbnailReader - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: JFIFThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$ - */ -final class JFIFThumbnailReader extends ThumbnailReader { - private final JFIF segment; - - JFIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex, final JFIF segment) { - super(progressListener, imageIndex, thumbnailIndex); - this.segment = segment; - } - - @Override - public BufferedImage read() { - processThumbnailStarted(); - BufferedImage thumbnail = readRawThumbnail(segment.thumbnail, segment.thumbnail.length, 0, segment.xThumbnail, segment.yThumbnail); - processThumbnailProgress(100f); - processThumbnailComplete(); - - return thumbnail; - } - - @Override - public int getWidth() throws IOException { - return segment.xThumbnail; - } - - @Override - public int getHeight() throws IOException { - return segment.yThumbnail; - } -} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java index 39a9ffb4..69117f69 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; @@ -33,7 +35,7 @@ import java.io.IOException; import java.util.Arrays; /** - * JFXXSegment + * A JFXX segment (aka JFIF extension segment). * * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -47,8 +49,8 @@ final class JFXX extends Application { final int extensionCode; final byte[] thumbnail; - private JFXX(final int extensionCode, final byte[] thumbnail, final byte[] data) { - super(com.twelvemonkeys.imageio.metadata.jpeg.JPEG.APP0, "JFXX", data); + JFXX(final int extensionCode, final byte[] thumbnail) { + super(com.twelvemonkeys.imageio.metadata.jpeg.JPEG.APP0, "JFXX", new byte[1 + (thumbnail != null ? thumbnail.length : 0)]); this.extensionCode = extensionCode; this.thumbnail = thumbnail; @@ -80,8 +82,7 @@ final class JFXX extends Application { return new JFXX( bytes[0] & 0xff, - bytes.length - 1 > 0 ? Arrays.copyOfRange(bytes, 1, bytes.length - 1) : null, - bytes + bytes.length - 1 > 0 ? Arrays.copyOfRange(bytes, 1, bytes.length - 1) : null ); } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnail.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnail.java new file mode 100644 index 00000000..accf9b67 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnail.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2012, 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.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.imageio.plugins.jpeg.ThumbnailReader.IndexedThumbnailReader; +import com.twelvemonkeys.imageio.plugins.jpeg.ThumbnailReader.JPEGThumbnailReader; +import com.twelvemonkeys.imageio.plugins.jpeg.ThumbnailReader.UncompressedThumbnailReader; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; + +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import java.io.IOException; + +/** + * JFXXThumbnailReader + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: JFXXThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$ + */ +final class JFXXThumbnail { + + private JFXXThumbnail() { + } + + static ThumbnailReader from(final JFXX segment, final ImageReader thumbnailReader) throws IOException { + if (segment != null) { + if (segment.thumbnail != null && segment.thumbnail.length > 2) { + switch (segment.extensionCode) { + case JFXX.JPEG: + if (((segment.thumbnail[0] & 0xff) << 8 | segment.thumbnail[1] & 0xff) == JPEG.SOI) { + return new JPEGThumbnailReader(thumbnailReader, new ByteArrayImageInputStream(segment.thumbnail), 0); + } + + break; + + case JFXX.INDEXED: + int w = segment.thumbnail[0] & 0xff; + int h = segment.thumbnail[1] & 0xff; + + if (segment.thumbnail.length >= 2 + 768 + w * h) { + return new IndexedThumbnailReader(w, h, segment.thumbnail, 2, segment.thumbnail, 2 + 768); + } + + break; + + case JFXX.RGB: + w = segment.thumbnail[0] & 0xff; + h = segment.thumbnail[1] & 0xff; + + if (segment.thumbnail.length >= 2 + w * h * 3) { + return new UncompressedThumbnailReader(w, h, segment.thumbnail, 2); + } + + break; + + default: + throw new IIOException(String.format("Unknown JFXX extension code: %d, ignoring thumbnail", segment.extensionCode)); + } + } + + throw new IIOException("JFXX segment truncated, ignoring thumbnail"); + } + + return null; + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java deleted file mode 100644 index 92a0b844..00000000 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2012, 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 "TwelveMonkeys" 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 OWNER 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.jpeg; - -import com.twelvemonkeys.image.InverseColorMapIndexColorModel; -import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; -import com.twelvemonkeys.lang.Validate; - -import javax.imageio.IIOException; -import javax.imageio.ImageReader; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.stream.ImageInputStream; -import java.awt.image.*; -import java.io.IOException; -import java.lang.ref.SoftReference; - -/** - * JFXXThumbnailReader - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: JFXXThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$ - */ -final class JFXXThumbnailReader extends ThumbnailReader { - - private final ImageReader reader; - private final JFXX segment; - - private transient SoftReference cachedThumbnail; - - JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXX segment) { - super(progressListener, imageIndex, thumbnailIndex); - this.reader = Validate.notNull(jpegReader); - this.segment = segment; - } - - @Override - public BufferedImage read() throws IOException { - processThumbnailStarted(); - - BufferedImage thumbnail; - switch (segment.extensionCode) { - case JFXX.JPEG: - thumbnail = readJPEGCached(true); - break; - case JFXX.INDEXED: - thumbnail = readIndexed(); - break; - case JFXX.RGB: - thumbnail = readRGB(); - break; - default: - throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode)); - } - - processThumbnailProgress(100f); - processThumbnailComplete(); - - return thumbnail; - } - - IIOMetadata readMetadata() throws IOException { - ImageInputStream input = new ByteArrayImageInputStream(segment.thumbnail); - - try { - reader.setInput(input); - - return reader.getImageMetadata(0); - } - finally { - input.close(); - } - } - - private BufferedImage readJPEGCached(boolean pixelsExposed) throws IOException { - BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null; - - if (thumbnail == null) { - ImageInputStream stream = new ByteArrayImageInputStream(segment.thumbnail); - try { - thumbnail = readJPEGThumbnail(reader, stream); - } - finally { - stream.close(); - } - } - - cachedThumbnail = pixelsExposed ? null : new SoftReference(thumbnail); - - return thumbnail; - } - - @Override - public int getWidth() throws IOException { - switch (segment.extensionCode) { - case JFXX.RGB: - case JFXX.INDEXED: - return segment.thumbnail[0] & 0xff; - case JFXX.JPEG: - return readJPEGCached(false).getWidth(); - default: - throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode)); - } - } - - @Override - public int getHeight() throws IOException { - switch (segment.extensionCode) { - case JFXX.RGB: - case JFXX.INDEXED: - return segment.thumbnail[1] & 0xff; - case JFXX.JPEG: - return readJPEGCached(false).getHeight(); - default: - throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode)); - } - } - - private BufferedImage readIndexed() { - // 1 byte: xThumb - // 1 byte: yThumb - // 768 bytes: palette - // x * y bytes: 8 bit indexed pixels - int w = segment.thumbnail[0] & 0xff; - int h = segment.thumbnail[1] & 0xff; - - int[] rgbs = new int[256]; - for (int i = 0; i < rgbs.length; i++) { - rgbs[i] = (segment.thumbnail[3 * i + 2] & 0xff) << 16 - | (segment.thumbnail[3 * i + 3] & 0xff) << 8 - | (segment.thumbnail[3 * i + 4] & 0xff); - } - - IndexColorModel icm = new InverseColorMapIndexColorModel(8, rgbs.length, rgbs, 0, false, -1, DataBuffer.TYPE_BYTE); - DataBufferByte buffer = new DataBufferByte(segment.thumbnail, segment.thumbnail.length - 770, 770); - WritableRaster raster = Raster.createPackedRaster(buffer, w, h, 8, null); - - return new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), null); - } - - private BufferedImage readRGB() { - // 1 byte: xThumb - // 1 byte: yThumb - // 3 * x * y bytes: 24 bit RGB pixels - int w = segment.thumbnail[0] & 0xff; - int h = segment.thumbnail[1] & 0xff; - - return ThumbnailReader.readRawThumbnail(segment.thumbnail, segment.thumbnail.length - 2, 2, w, h); - } -} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGColorSpace.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGColorSpace.java index 157648fc..ff82b000 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGColorSpace.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGColorSpace.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java index 2b8e49ae..fb0dc5ee 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java @@ -1,10 +1,45 @@ +/* + * Copyright (c) 2016, 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.jpeg; import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.metadata.CompoundDirectory; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.imageio.metadata.tiff.TIFF; import org.w3c.dom.Node; +import javax.imageio.IIOException; import javax.imageio.metadata.IIOMetadataNode; +import java.awt.color.ICC_Profile; import java.util.List; /** @@ -16,31 +51,89 @@ import java.util.List; */ class JPEGImage10Metadata extends AbstractMetadata { - // TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place.... + /** + * Native metadata format name + */ + static final String JAVAX_IMAGEIO_JPEG_IMAGE_1_0 = "javax_imageio_jpeg_image_1.0"; + + // TODO: Create our own native format, which is simply markerSequence from the Sun format, with the segments as-is, in sequence... + // + add special case for app segments, containing appXX + identifier (ie. to or private final List segments; - JPEGImage10Metadata(List segments) { - super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null); + private final Frame frame; + private final JFIF jfif; + private final AdobeDCT adobeDCT; + private final JFXX jfxx; + private final ICC_Profile embeddedICCProfile; + + private final CompoundDirectory exif; + + // TODO: Consider moving all the metadata stuff from the reader, over here... + JPEGImage10Metadata(final List segments, Frame frame, JFIF jfif, JFXX jfxx, ICC_Profile embeddedICCProfile, AdobeDCT adobeDCT, final CompoundDirectory exif) { + super(true, JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null); this.segments = segments; + this.frame = frame; + this.jfif = jfif; + this.adobeDCT = adobeDCT; + this.jfxx = jfxx; + this.embeddedICCProfile = embeddedICCProfile; + this.exif = exif; } @Override protected Node getNativeTree() { - IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); + IIOMetadataNode root = new IIOMetadataNode(JAVAX_IMAGEIO_JPEG_IMAGE_1_0); IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety"); - root.appendChild(jpegVariety); - // TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless + boolean isJFIF = jfif != null; + if (isJFIF) { + IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF"); + app0JFIF.setAttribute("majorVersion", Integer.toString(jfif.majorVersion)); + app0JFIF.setAttribute("minorVersion", Integer.toString(jfif.minorVersion)); + app0JFIF.setAttribute("resUnits", Integer.toString(jfif.units)); + app0JFIF.setAttribute("Xdensity", Integer.toString(jfif.xDensity)); + app0JFIF.setAttribute("Ydensity", Integer.toString(jfif.yDensity)); + + app0JFIF.setAttribute("thumbWidth", Integer.toString(jfif.xThumbnail)); + app0JFIF.setAttribute("thumbHeight", Integer.toString(jfif.yThumbnail)); + + jpegVariety.appendChild(app0JFIF); + + // Due to format oddity, add JFXX and app2ICC as subnodes here... + // ...and ignore them below, if added... + apendJFXX(app0JFIF); + appendICCProfile(app0JFIF); + } + + root.appendChild(jpegVariety); + + appendMarkerSequence(root, segments, isJFIF); + + return root; + } + + private void appendMarkerSequence(IIOMetadataNode root, List segments, boolean isJFIF) { IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence"); root.appendChild(markerSequence); for (Segment segment : segments) switch (segment.marker) { - // SOF3 is the only one supported by now + case JPEG.SOF0: + case JPEG.SOF1: + case JPEG.SOF2: case JPEG.SOF3: + case JPEG.SOF5: + case JPEG.SOF6: + case JPEG.SOF7: + case JPEG.SOF9: + case JPEG.SOF10: + case JPEG.SOF11: + case JPEG.SOF13: + case JPEG.SOF14: + case JPEG.SOF15: Frame sofSegment = (Frame) segment; IIOMetadataNode sof = new IIOMetadataNode("sof"); @@ -67,13 +160,13 @@ class JPEGImage10Metadata extends AbstractMetadata { HuffmanTable huffmanTable = (HuffmanTable) segment; IIOMetadataNode dht = new IIOMetadataNode("dht"); - // Uses fixed tables... for (int i = 0; i < 4; i++) { - for (int j = 0; j < 2; j++) { - if (huffmanTable.tc[i][j] != 0) { + for (int c = 0; c < 2; c++) { + if (huffmanTable.isPresent(i, c)) { IIOMetadataNode dhtable = new IIOMetadataNode("dhtable"); - dhtable.setAttribute("class", String.valueOf(j)); + dhtable.setAttribute("class", String.valueOf(c)); dhtable.setAttribute("htableId", String.valueOf(i)); + dhtable.setUserObject(huffmanTable.toNativeTable(i, c)); dht.appendChild(dhtable); } } @@ -83,8 +176,28 @@ class JPEGImage10Metadata extends AbstractMetadata { break; case JPEG.DQT: - markerSequence.appendChild(new IIOMetadataNode("dqt")); - // TODO: + QuantizationTable quantizationTable = (QuantizationTable) segment; + IIOMetadataNode dqt = new IIOMetadataNode("dqt"); + + for (int i = 0; i < 4; i++) { + if (quantizationTable.isPresent(i)) { + IIOMetadataNode dqtable = new IIOMetadataNode("dqtable"); + dqtable.setAttribute("elementPrecision", quantizationTable.precision(i) != 16 ? "0" : "1"); // 0 = 8 bits, 1 = 16 bits + dqtable.setAttribute("qtableId", Integer.toString(i)); + dqtable.setUserObject(quantizationTable.toNativeTable(i)); + dqt.appendChild(dqtable); + } + } + markerSequence.appendChild(dqt); + + break; + + case JPEG.DRI: + RestartInterval restartInterval = (RestartInterval) segment; + IIOMetadataNode dri = new IIOMetadataNode("dri"); + dri.setAttribute("interval", Integer.toString(restartInterval.interval)); + markerSequence.appendChild(dri); + break; case JPEG.SOS: @@ -115,6 +228,25 @@ class JPEGImage10Metadata extends AbstractMetadata { break; + case JPEG.APP0: + if (segment instanceof JFIF) { + // Either already added, or we'll ignore it anyway... + break; + } + else if (isJFIF && segment instanceof JFXX) { + // Already added + break; + } + + // Else, fall through to unknown segment + + case JPEG.APP2: + if (isJFIF && segment instanceof ICCProfile) { + // Already added + break; + } + // Else, fall through to unknown segment + case JPEG.APP14: if (segment instanceof AdobeDCT) { AdobeDCT adobe = (AdobeDCT) segment; @@ -136,32 +268,149 @@ class JPEGImage10Metadata extends AbstractMetadata { break; } + } - return root; + private void appendICCProfile(IIOMetadataNode app0JFIF) { + if (embeddedICCProfile != null) { + IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC"); + app2ICC.setUserObject(embeddedICCProfile); + + app0JFIF.appendChild(app2ICC); + } + } + + private void apendJFXX(IIOMetadataNode app0JFIF) { + if (jfxx != null) { + IIOMetadataNode jfxxNode = new IIOMetadataNode("JFXX"); + app0JFIF.appendChild(jfxxNode); + + IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX"); + app0JFXX.setAttribute("extensionCode", Integer.toString(jfxx.extensionCode)); + jfxxNode.appendChild(app0JFXX); + + switch (jfxx.extensionCode) { + case JFXX.JPEG: + IIOMetadataNode thumbJPEG = new IIOMetadataNode("JFIFthumbJPEG"); + thumbJPEG.appendChild(new IIOMetadataNode("markerSequence")); + // TODO: Insert segments in marker sequence... +// List segments = JPEGSegmentUtil.readSegments(new ByteArrayImageInputStream(jfxx.thumbnail), JPEGSegmentUtil.ALL_SEGMENTS); + // Convert to Segment as in JPEGImageReader... +// appendMarkerSequence(thumbJPEG, segments, false); + + app0JFXX.appendChild(thumbJPEG); + + break; + + case JFXX.INDEXED: + IIOMetadataNode thumbPalette = new IIOMetadataNode("JFIFthumbPalette"); + thumbPalette.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF)); + thumbPalette.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF)); + app0JFXX.appendChild(thumbPalette); + break; + + case JFXX.RGB: + IIOMetadataNode thumbRGB = new IIOMetadataNode("JFIFthumbRGB"); + thumbRGB.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF)); + thumbRGB.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF)); + app0JFXX.appendChild(thumbRGB); + break; + } + } } @Override protected IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - for (Segment segment : segments) { - if (segment instanceof Frame) { - Frame sofSegment = (Frame) segment; - IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); - colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "GRAY" : "RGB"); // TODO YCC, YCCK, CMYK etc - chroma.appendChild(colorSpaceType); + IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); + colorSpaceType.setAttribute("name", getColorSpaceType()); + chroma.appendChild(colorSpaceType); - IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); - numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame())); - chroma.appendChild(numChannels); - - break; - } - } + IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); + numChannels.setAttribute("value", String.valueOf(frame.componentsInFrame())); + chroma.appendChild(numChannels); return chroma; } + private String getColorSpaceType() { + try { + JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame); + + switch (csType) { + case Gray: + case GrayA: + return "GRAY"; + case YCbCr: + case YCbCrA: + return "YCbCr"; + case RGB: + case RGBA: + return "RGB"; + case PhotoYCC: + case PhotoYCCA: + return "PhotoYCC"; + case YCCK: + return "YCCK"; + case CMYK: + return "CMYK"; + default: + + } + } + catch (IIOException ignore) { + } + + return Integer.toString(frame.componentsInFrame(), 16) + "CLR"; + } + + private boolean hasAlpha() { + try { + JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame); + + switch (csType) { + case GrayA: + case YCbCrA: + case RGBA: + case PhotoYCCA: + return true; + default: + + } + } + catch (IIOException ignore) { + } + + return false; + } + + private boolean isLossess() { + switch (frame.marker) { + case JPEG.SOF3: + case JPEG.SOF7: + case JPEG.SOF11: + case JPEG.SOF15: + return true; + default: + return false; + } + } + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + if (hasAlpha()) { + IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); + + IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); + alpha.setAttribute("value", "nonpremultipled"); + transparency.appendChild(alpha); + + return transparency; + } + + return null; + } + @Override protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression = new IIOMetadataNode("Compression"); @@ -171,7 +420,7 @@ class JPEGImage10Metadata extends AbstractMetadata { compression.appendChild(compressionTypeName); IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); - lossless.setAttribute("value", "TRUE"); // TODO: For lossless only + lossless.setAttribute("value", isLossess() ? "TRUE" : "FALSE"); compression.appendChild(lossless); IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans"); @@ -186,12 +435,67 @@ class JPEGImage10Metadata extends AbstractMetadata { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - imageOrientation.setAttribute("value", "normal"); // TODO + imageOrientation.setAttribute("value", getExifOrientation(exif)); dimension.appendChild(imageOrientation); + if (jfif != null) { + // Aspect ratio + float xDensity = Math.max(1, jfif.xDensity); + float yDensity = Math.max(1, jfif.yDensity); + float aspectRatio = jfif.units == 0 ? xDensity / yDensity : yDensity / xDensity; + + IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); + pixelAspectRatio.setAttribute("value", Float.toString(aspectRatio)); + dimension.insertBefore(pixelAspectRatio, imageOrientation); // Keep order + + if (jfif.units != 0) { + // Pixel size + float scale = jfif.units == 1 ? 25.4F : 10.0F; // DPI or DPcm + + IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize"); + horizontalPixelSize.setAttribute("value", Float.toString(scale / xDensity)); + dimension.appendChild(horizontalPixelSize); + + IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize"); + verticalPixelSize.setAttribute("value", Float.toString(scale / yDensity)); + dimension.appendChild(verticalPixelSize); + } + } + return dimension; } + private String getExifOrientation(Directory exif) { + if (exif != null) { + Entry orientationEntry = exif.getEntryById(TIFF.TAG_ORIENTATION); + + if (orientationEntry != null) { + switch (((Number) orientationEntry.getValue()).intValue()) { + case 2: + return "FlipH"; + case 3: + return "Rotate180"; + case 4: + return "FlipV"; + case 5: + return "FlipVRotate90"; + case 6: + return "Rotate270"; + case 7: + return "FlipHRotate90"; + case 8: + return "Rotate90"; + case 0: + case 1: + default: + // Fall-through + } + } + } + + return "Normal"; + } + @Override protected IIOMetadataNode getStandardTextNode() { IIOMetadataNode text = new IIOMetadataNode("Text"); @@ -206,6 +510,10 @@ class JPEGImage10Metadata extends AbstractMetadata { } } + // TODO: Add the following from Exif (as in TIFFMetadata) + // DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright: + // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. + return text.hasChildNodes() ? text : null; } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java index ac404c0f..4843bace 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java @@ -1,7 +1,38 @@ +/* + * 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.plugins.jpeg; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.xml.XMLSerializer; + import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -22,11 +53,6 @@ import java.util.List; */ final class JPEGImage10MetadataCleaner { - /** - * Native metadata format name - */ - static final String JAVAX_IMAGEIO_JPEG_IMAGE_1_0 = "javax_imageio_jpeg_image_1.0"; - private final JPEGImageReader reader; JPEGImage10MetadataCleaner(final JPEGImageReader reader) { @@ -63,7 +89,7 @@ final class JPEGImage10MetadataCleaner { the version to the method/constructor used to obtain an IIOMetadata object.) */ - IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0); + IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0); IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0); @@ -107,7 +133,7 @@ final class JPEGImage10MetadataCleaner { IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX"); app0JFXX.setAttribute("extensionCode", String.valueOf(jfxx.extensionCode)); - JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxx); + ThumbnailReader thumbnailReader = JFXXThumbnail.from(jfxx, reader.getThumbnailReader()); IIOMetadataNode jfifThumb; switch (jfxx.extensionCode) { @@ -115,7 +141,7 @@ final class JPEGImage10MetadataCleaner { jfifThumb = new IIOMetadataNode("JFIFthumbJPEG"); // Contains it's own "markerSequence" with full DHT, DQT, SOF etc... IIOMetadata thumbMeta = thumbnailReader.readMetadata(); - Node thumbTree = thumbMeta.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0); + Node thumbTree = thumbMeta.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); jfifThumb.appendChild(thumbTree.getLastChild()); app0JFXX.appendChild(jfifThumb); break; @@ -277,11 +303,11 @@ final class JPEGImage10MetadataCleaner { } try { - imageMetadata.setFromTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree); + imageMetadata.setFromTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree); } catch (IIOInvalidTreeException e) { if (JPEGImageReader.DEBUG) { - new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0), false); + new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0), false); System.out.println("-- 8< --"); new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false); } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 52e420f1..43934b99 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -4,26 +4,28 @@ * * 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 "TwelveMonkeys" 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 OWNER 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. + * * 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.jpeg; @@ -32,13 +34,11 @@ import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.color.YCbCrConverter; import com.twelvemonkeys.imageio.metadata.CompoundDirectory; -import com.twelvemonkeys.imageio.metadata.Directory; -import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; -import com.twelvemonkeys.imageio.metadata.tiff.TIFF; import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.lang.Validate; @@ -52,20 +52,19 @@ import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; -import javax.imageio.stream.MemoryCacheImageInputStream; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_Profile; import java.awt.image.*; import java.io.*; -import java.util.*; import java.util.List; +import java.util.*; /** * A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader}, * that adds support and properly handles cases where the JRE version throws exceptions. - *

    + *
    * Main features: *