mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d4e7d895e | |||
| 28b815d048 | |||
| 60016469b4 | |||
| 8899c7ae90 | |||
| e131db660b | |||
| 41fd6a7832 | |||
| f6dd5d7f83 | |||
| d0ff4f28e4 | |||
| 8e90f7bf81 | |||
| 147b805713 | |||
| e6610cf721 | |||
| d8ebedc7e4 | |||
| 008db51c3a | |||
| b2b89220c0 | |||
| d2e64b54f0 | |||
| b5c985bbe1 | |||
| ac45373f83 | |||
| 2b20d9faa3 | |||
| 5e1d996c11 | |||
| 19d71a1eb6 | |||
| d0b4589a22 | |||
| df3134e786 | |||
| ddc66e18ba | |||
| 7559ddb75e | |||
| 267be647d2 | |||
| 30a17b9506 | |||
| 2b4f96184a | |||
| 078ff4c9b4 | |||
| 1fa459552f | |||
| b8d3dcfb29 | |||
| 7cdf125100 | |||
| 0083207b2d | |||
| ba3a0ae609 | |||
| 2eb1176d73 | |||
| 815c1831c8 |
-17
@@ -1,17 +0,0 @@
|
||||
target
|
||||
*~
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
.svn
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
catalog.xml
|
||||
nbactions.xml
|
||||
nbproject
|
||||
private
|
||||
profiles.xml
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
@@ -1,527 +0,0 @@
|
||||
## Background
|
||||
|
||||
TwelveMonkeys ImageIO is a collection of plug-ins for Java's ImageIO.
|
||||
|
||||
These plugins extends 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 JDK itself.
|
||||
|
||||
Support for formats is important, both to be able to read data found
|
||||
"in the wild", as well as to maintain access to data in legacy formats.
|
||||
Because there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
|
||||
The goal is to create a set of efficient and robust ImageIO plug-ins, that can be distributed independently.
|
||||
|
||||
----
|
||||
|
||||
## Features
|
||||
|
||||
Mainstream format support
|
||||
|
||||
#### JPEG
|
||||
|
||||
* Read support for the following JPEG flavors:
|
||||
* 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'
|
||||
* JPEGs containing ICC profiles with class other than 'Display'
|
||||
* JPEGs containing ICC profiles that are incompatible with stream data
|
||||
* JPEGs with corrupted ICC profiles
|
||||
* JPEGs with corrupted `ICC_PROFILE` segments
|
||||
* JPEGs using non-standard color spaces, unsupported by Java 2D
|
||||
* 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)
|
||||
* Illegal 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).
|
||||
|
||||
#### Adobe Photoshop Document (PSD)
|
||||
|
||||
* 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)
|
||||
|
||||
#### Aldus/Adobe Tagged Image File Format (TIFF)
|
||||
|
||||
* 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
|
||||
* 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)
|
||||
* CMYK data (PhotometricInterpretation type 5/Separated)
|
||||
* YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG
|
||||
* 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 in progress
|
||||
* Will support writing most "Baseline" TIFF file types
|
||||
|
||||
#### Apple Mac Paint Picture Format (PICT)
|
||||
|
||||
* 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
|
||||
|
||||
#### Commodore Amiga/Electronic Arts Interchange File Format (IFF)
|
||||
|
||||
* Legacy format, allows reading popular image 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)
|
||||
|
||||
Icon/other formats
|
||||
|
||||
#### Apple Icon Image (ICNS)
|
||||
|
||||
* 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)
|
||||
|
||||
#### MS Windows Icon and Cursor Formats (ICO & CUR)
|
||||
|
||||
* 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
|
||||
|
||||
#### MS Windows Thumbs DB (Thumbs.db)
|
||||
|
||||
* Read support
|
||||
|
||||
Other formats, using 3rd party libraries
|
||||
|
||||
#### Scalable Vector Graphics (SVG)
|
||||
|
||||
* Read-only support using Batik
|
||||
|
||||
#### MS Windows MetaFile (WMF)
|
||||
|
||||
* Limited read-only support using Batik
|
||||
|
||||
|
||||
## 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);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
This will write the entire image into a single file, using the default settings for the given format.
|
||||
|
||||
The plugins are discovered automatically at run time. See the [FAQ](#faq) for more info on how this mechanism works.
|
||||
|
||||
## Advanced usage
|
||||
|
||||
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);
|
||||
|
||||
try {
|
||||
// Get the reader
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||
|
||||
if (!readers.hasNext()) {
|
||||
throw new IllegalArgumentException("No reader for: " + file);
|
||||
}
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
|
||||
try {
|
||||
reader.setInput(input);
|
||||
|
||||
// Optionally, listen for read warnings, progress, etc.
|
||||
reader.addIIOReadWarningListener(...);
|
||||
reader.addIIOReadProgressListener(...);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Close stream in finally block to avoid resource leaks
|
||||
input.close();
|
||||
}
|
||||
|
||||
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
|
||||
entire image into memory first.
|
||||
|
||||
It's also possible to read multiple images from the same file in a loop, using `reader.getNumImages()`.
|
||||
|
||||
|
||||
If you need more control of write parameters and the writing process, the common idiom for writing is something like:
|
||||
|
||||
// Get the writer
|
||||
Iterator<ImageWriter> 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();
|
||||
}
|
||||
|
||||
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)
|
||||
from Oracle.
|
||||
|
||||
|
||||
#### 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.
|
||||
|
||||
I 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) may occur.
|
||||
|
||||
To work around both the discovery problem and the resource leak,
|
||||
it is recommended to use the `IIOProviderContextListener` that implements
|
||||
dynamic loading and unloading of ImageIO plugins for web applications.
|
||||
|
||||
<web-app ...>
|
||||
|
||||
...
|
||||
|
||||
<listener>
|
||||
<display-name>ImageIO service provider loader/unloader</display-name>
|
||||
<listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class>
|
||||
</listener>
|
||||
|
||||
...
|
||||
|
||||
</web-app>
|
||||
|
||||
|
||||
#### 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;
|
||||
|
||||
...
|
||||
|
||||
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;
|
||||
|
||||
...
|
||||
|
||||
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)):
|
||||
|
||||
$ 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)):
|
||||
|
||||
$ mvn package
|
||||
|
||||
Currently, the only supported JDK for making a build is Oracle JDK 7.x.
|
||||
|
||||
It's possible to build using OpenJDK, but some tests will 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. To build using JDK 8, you need to pass `-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider` to revert to the color manangement system used in Java 7.
|
||||
|
||||
Because the unit tests needs quite a bit of memory to run, you might have to set the environment variable `MAVEN_OPTS`
|
||||
to give the Java process that runs Maven more memory. I suggest something like `-Xmx512m -XX:MaxPermSize=256m`.
|
||||
|
||||
Optionally, you can install the project in your local Maven repository using:
|
||||
|
||||
$ mvn install
|
||||
|
||||
## Installing
|
||||
|
||||
To install the plug-ins,
|
||||
either use Maven and add the necessary dependencies to your project,
|
||||
or manually add the needed JARs along with required dependencies in class-path.
|
||||
|
||||
The ImageIO registry and service lookup mechanism will make sure the plugins are available for use.
|
||||
|
||||
To verify that the JPEG plugin is installed and used at run-time, you could use the following code:
|
||||
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
|
||||
while (readers.hasNext()) {
|
||||
System.out.println("reader: " + readers.next());
|
||||
}
|
||||
|
||||
The first line should print:
|
||||
|
||||
reader: com.twelvemonkeys.imageio.jpeg.JPEGImageReader@somehash
|
||||
|
||||
#### Maven dependency example
|
||||
|
||||
To depend on the JPEG and TIFF plugin using Maven, add the following to your POM:
|
||||
|
||||
...
|
||||
<dependencies>
|
||||
...
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<version>3.0-rc5</version> <!-- Alternatively, build your own 3.0-something version -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>3.0-rc5</version> <!-- Alternatively, build your own 3.0-something version -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
#### 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.0-rc5.jar
|
||||
twelvemonkeys-common-io-3.0-rc5.jar
|
||||
twelvemonkeys-common-image-3.0-rc5.jar
|
||||
twelvemonkeys-imageio-core-3.0-rc5.jar
|
||||
twelvemonkeys-imageio-metadata-3.0-rc5.jar
|
||||
twelvemonkeys-imageio-jpeg-3.0-rc5.jar
|
||||
twelvemonkeys-imageio-tiff-3.0-rc5.jar
|
||||
|
||||
### Links to prebuilt binaries
|
||||
|
||||
Common dependencies
|
||||
* [common-lang-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0-rc5/common-lang-3.0-rc5.jar)
|
||||
* [common-io-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0-rc5/common-io-3.0-rc5.jar)
|
||||
* [common-image-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0-rc5/common-image-3.0-rc5.jar)
|
||||
|
||||
ImageIO dependencies
|
||||
* [imageio-core-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0-rc5/imageio-core-3.0-rc5.jar)
|
||||
* [imageio-metadata-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0-rc5/imageio-metadata-3.0-rc5.jar)
|
||||
|
||||
ImageIO plugins
|
||||
* [imageio-jpeg-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0-rc5/imageio-jpeg-3.0-rc5.jar)
|
||||
* [imageio-tiff-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0-rc5/imageio-tiff-3.0-rc5.jar)
|
||||
* [imageio-psd-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.0-rc5/imageio-psd-3.0-rc5.jar)
|
||||
* [imageio-pict-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.0-rc5/imageio-pict-3.0-rc5.jar)
|
||||
* [imageio-iff-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.0-rc5/imageio-iff-3.0-rc5.jar)
|
||||
* [imageio-icns-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0-rc5/imageio-icns-3.0-rc5.jar)
|
||||
* [imageio-ico-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0-rc5/imageio-ico-3.0-rc5.jar)
|
||||
* [imageio-thumbsdb-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0-rc5/imageio-thumbsdb-3.0-rc5.jar)
|
||||
|
||||
ImageIO plugins requiring 3rd party libs
|
||||
* [imageio-batik-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0-rc5/imageio-batik-3.0-rc5.jar)
|
||||
* [imageio-jmagick-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0-rc5/imageio-jmagick-3.0-rc5.jar)
|
||||
|
||||
Servlet support
|
||||
* [servlet-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0-rc5/servlet-3.0-rc5.jar)
|
||||
|
||||
## License
|
||||
|
||||
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
|
||||
|
||||
Copyright (c) 2008-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:
|
||||
|
||||
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 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.
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
q: How do I use it?
|
||||
|
||||
a: The easiest way is to build your own project using Maven, and just add dependencies to the specific plug-ins you need.
|
||||
If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above.
|
||||
|
||||
|
||||
q: What changes do I have to make to my code in order to use the plug-ins?
|
||||
|
||||
a: The short answer is: None. For basic usage, like ImageIO.read(...) or ImageIO.getImageReaders(...), there is no need
|
||||
to change your code. Most of the functionality is available through standard ImageIO APIs, and great care has been taken
|
||||
not to introduce extra API where none is necessary.
|
||||
|
||||
Should you want to use very specific/advanced features of some of the formats, you might have to use specific APIs, like
|
||||
setting base URL for an SVG image that consists of multiple files,
|
||||
or controlling the output compression of a TIFF file.
|
||||
|
||||
|
||||
q: How does it work?
|
||||
|
||||
a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO.
|
||||
|
||||
ImageIO uses a service lookup mechanism, to discover plug-ins at runtime.
|
||||
|
||||
TODO: Describe SPI mechanism.
|
||||
|
||||
All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath.
|
||||
|
||||
The fine print: The TwelveMonkeys service providers for TIFF and JPEG overrides the onRegistration method, and
|
||||
utilizes the pairwise partial ordering mechanism of the IIOServiceRegistry to make sure it is installed before
|
||||
the Sun/Oracle provided JPEGImageReader and the Apple provided TIFFImageReader on OS X, 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: 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.
|
||||
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.
|
||||
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
|
||||
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?
|
||||
|
||||
a: While great libraries with a wide range of formats support, the ImageMagick-based libraries has some disadvantages
|
||||
compared to ImageIO.
|
||||
- No real stream support, these libraries only work with files.
|
||||
- No easy access to pixel data through standard Java2D/BufferedImage API.
|
||||
- Not a pure Java solution, requires system specific native libs.
|
||||
|
||||
|
||||
-----
|
||||
|
||||
We did it
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>TwelveMonkeys :: Common :: Image</name>
|
||||
<description>
|
||||
The TwelveMonkeys Common Image support
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>common-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jmagick</groupId>
|
||||
<artifactId>jmagick</artifactId>
|
||||
<version>6.2.4</version>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,108 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<ImageConsumer> consumers = new ArrayList<ImageConsumer>();
|
||||
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);
|
||||
}
|
||||
@@ -1,454 +0,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 "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 javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* AreaAverageOp
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AreaAverageOp.java#2 $
|
||||
*/
|
||||
public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
final private int width;
|
||||
final private int height;
|
||||
|
||||
private Rectangle sourceRegion;
|
||||
|
||||
public AreaAverageOp(final int pWidth, final int pHeight) {
|
||||
width = pWidth;
|
||||
height = pHeight;
|
||||
}
|
||||
|
||||
public Rectangle getSourceRegion() {
|
||||
if (sourceRegion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Rectangle(sourceRegion);
|
||||
}
|
||||
|
||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||
if (pSourceRegion == null) {
|
||||
sourceRegion = null;
|
||||
}
|
||||
else {
|
||||
if (sourceRegion == null) {
|
||||
sourceRegion = new Rectangle(pSourceRegion);
|
||||
}
|
||||
else {
|
||||
sourceRegion.setBounds(pSourceRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedImage filter(BufferedImage src, BufferedImage dest) {
|
||||
BufferedImage result = dest != null ? dest : createCompatibleDestImage(src, null);
|
||||
|
||||
// TODO: src and dest can't be the same
|
||||
|
||||
// TODO: Do some type checking here..
|
||||
// Should work with
|
||||
// * all BYTE types, unless sub-byte packed rasters/IndexColorModel
|
||||
// * all INT types (even custom, as long as they use 8bit/componnet)
|
||||
// * all USHORT types (even custom)
|
||||
|
||||
// TODO: Also check if the images are really compatible!?
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// Straight-forward version
|
||||
//Image scaled = src.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
|
||||
//ImageUtil.drawOnto(result, scaled);
|
||||
//result = new BufferedImageFactory(scaled).getBufferedImage();
|
||||
|
||||
/*
|
||||
// Try: Use bilinear/bicubic and half the image down until it's less than
|
||||
// twice as big, then use bicubic for the last step?
|
||||
BufferedImage temp = null;
|
||||
AffineTransform xform = null;
|
||||
int w = src.getWidth();
|
||||
int h = src.getHeight();
|
||||
while (w / 2 > width && h / 2 > height) {
|
||||
w /= 2;
|
||||
h /= 2;
|
||||
|
||||
if (temp == null) {
|
||||
xform = AffineTransform.getScaleInstance(.5, .5);
|
||||
ColorModel cm = src.getColorModel();
|
||||
temp = new BufferedImage(cm,
|
||||
ImageUtil.createCompatibleWritableRaster(src, cm, w, h),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
|
||||
resample(src, temp, xform);
|
||||
}
|
||||
else {
|
||||
resample(temp, temp, xform);
|
||||
}
|
||||
|
||||
System.out.println("w: " + w);
|
||||
System.out.println("h: " + h);
|
||||
}
|
||||
|
||||
if (temp != null) {
|
||||
src = temp.getSubimage(0, 0, w, h);
|
||||
}
|
||||
|
||||
resample(src, result, AffineTransform.getScaleInstance(width / (double) w, height / (double) h));
|
||||
*/
|
||||
|
||||
// The real version
|
||||
filterImpl(src.getRaster(), result.getRaster());
|
||||
|
||||
long time = System.currentTimeMillis() - start;
|
||||
System.out.println("time: " + time);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void resample(final BufferedImage pSrc, final BufferedImage pDest, final AffineTransform pXform) {
|
||||
Graphics2D d = pDest.createGraphics();
|
||||
d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
try {
|
||||
d.drawImage(pSrc, pXform, null);
|
||||
}
|
||||
finally {
|
||||
d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public WritableRaster filter(Raster src, WritableRaster dest) {
|
||||
WritableRaster result = dest != null ? dest : createCompatibleDestRaster(src);
|
||||
return filterImpl(src, result);
|
||||
}
|
||||
|
||||
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
||||
//System.out.println("src: " + src);
|
||||
//System.out.println("dest: " + dest);
|
||||
if (sourceRegion != null) {
|
||||
int cx = sourceRegion.x;
|
||||
int cy = sourceRegion.y;
|
||||
int cw = sourceRegion.width;
|
||||
int ch = sourceRegion.height;
|
||||
|
||||
boolean same = src == dest;
|
||||
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
|
||||
src = same ? dest : src.createChild(cx, cy, cw, ch, 0, 0, null);
|
||||
//System.out.println("src: " + src);
|
||||
//System.out.println("dest: " + dest);
|
||||
}
|
||||
|
||||
final int width = src.getWidth();
|
||||
final int height = src.getHeight();
|
||||
|
||||
// TODO: This don't work too well..
|
||||
// The thing is that the step length and the scan length will vary, for
|
||||
// non-even (1/2, 1/4, 1/8 etc) resampling
|
||||
int widthSteps = (width + this.width - 1) / this.width;
|
||||
int heightSteps = (height + this.height - 1) / this.height;
|
||||
|
||||
final boolean oddX = width % this.width != 0;
|
||||
final boolean oddY = height % this.height != 0;
|
||||
|
||||
final int dataElements = src.getNumDataElements();
|
||||
final int bands = src.getNumBands();
|
||||
final int dataType = src.getTransferType();
|
||||
|
||||
Object data = null;
|
||||
int scanW;
|
||||
int scanH;
|
||||
|
||||
// TYPE_USHORT setup
|
||||
int[] bitMasks = null;
|
||||
int[] bitOffsets = null;
|
||||
if (src.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||
if (src.getSampleModel() instanceof SinglePixelPackedSampleModel) {
|
||||
// DIRECT
|
||||
SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) src.getSampleModel();
|
||||
bitMasks = sampleModel.getBitMasks();
|
||||
bitOffsets = sampleModel.getBitOffsets();
|
||||
}
|
||||
else {
|
||||
// GRAY
|
||||
bitMasks = new int[]{0xffff};
|
||||
bitOffsets = new int[]{0};
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < this.height; y++) {
|
||||
if (!oddY || y < this.height) {
|
||||
scanH = heightSteps;
|
||||
}
|
||||
else {
|
||||
scanH = height - (y * heightSteps);
|
||||
}
|
||||
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
if (!oddX || x < this.width) {
|
||||
scanW = widthSteps;
|
||||
}
|
||||
else {
|
||||
scanW = width - (x * widthSteps);
|
||||
}
|
||||
final int pixelCount = scanW * scanH;
|
||||
final int pixelLength = pixelCount * dataElements;
|
||||
|
||||
try {
|
||||
data = src.getDataElements(x * widthSteps, y * heightSteps, scanW, scanH, data);
|
||||
}
|
||||
catch (IndexOutOfBoundsException e) {
|
||||
// TODO: FixMe!
|
||||
// The bug is in the steps...
|
||||
//System.err.println("x: " + x);
|
||||
//System.err.println("y: " + y);
|
||||
//System.err.println("widthSteps: " + widthSteps);
|
||||
//System.err.println("heightSteps: " + heightSteps);
|
||||
//System.err.println("scanW: " + scanW);
|
||||
//System.err.println("scanH: " + scanH);
|
||||
//
|
||||
//System.err.println("width: " + width);
|
||||
//System.err.println("height: " + height);
|
||||
//System.err.println("width: " + width);
|
||||
//System.err.println("height: " + height);
|
||||
//
|
||||
//e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Might need more channels... Use an array?
|
||||
// NOTE: These are not neccessarily ARGB..
|
||||
double valueA = 0.0;
|
||||
double valueR = 0.0;
|
||||
double valueG = 0.0;
|
||||
double valueB = 0.0;
|
||||
|
||||
switch (dataType) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
// TODO: Doesn't hold for index color models...
|
||||
// For index color, the best bet is probably convert to
|
||||
// true color, then convert back to the same index color
|
||||
// model
|
||||
byte[] bytePixels = (byte[]) data;
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
valueA += bytePixels[i] & 0xff;
|
||||
if (bands > 1) {
|
||||
valueR += bytePixels[i + 1] & 0xff;
|
||||
valueG += bytePixels[i + 2] & 0xff;
|
||||
if (bands > 3) {
|
||||
valueB += bytePixels[i + 3] & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Average
|
||||
valueA /= pixelCount;
|
||||
if (bands > 1) {
|
||||
valueR /= pixelCount;
|
||||
valueG /= pixelCount;
|
||||
if (bands > 3) {
|
||||
valueB /= pixelCount;
|
||||
}
|
||||
}
|
||||
|
||||
//for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
bytePixels[0] = (byte) clamp((int) valueA);
|
||||
if (bands > 1) {
|
||||
bytePixels[1] = (byte) clamp((int) valueR);
|
||||
bytePixels[2] = (byte) clamp((int) valueG);
|
||||
if (bands > 3) {
|
||||
bytePixels[3] = (byte) clamp((int) valueB);
|
||||
}
|
||||
}
|
||||
//}
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_INT:
|
||||
int[] intPixels = (int[]) data;
|
||||
// TODO: Rewrite to use bit offsets and masks from
|
||||
// color model (see TYPE_USHORT) in case of a non-
|
||||
// 888 or 8888 colormodel?
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
valueA += (intPixels[i] & 0xff000000) >> 24;
|
||||
valueR += (intPixels[i] & 0xff0000) >> 16;
|
||||
valueG += (intPixels[i] & 0xff00) >> 8;
|
||||
valueB += (intPixels[i] & 0xff);
|
||||
}
|
||||
|
||||
// Average
|
||||
valueA /= pixelCount;
|
||||
valueR /= pixelCount;
|
||||
valueG /= pixelCount;
|
||||
valueB /= pixelCount;
|
||||
|
||||
//for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
intPixels[0] = clamp((int) valueA) << 24;
|
||||
intPixels[0] |= clamp((int) valueR) << 16;
|
||||
intPixels[0] |= clamp((int) valueG) << 8;
|
||||
intPixels[0] |= clamp((int) valueB);
|
||||
//}
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
if (bitMasks != null) {
|
||||
short[] shortPixels = (short[]) data;
|
||||
for (int i = 0; i < pixelLength; i += dataElements)
|
||||
{
|
||||
valueA += (shortPixels[i] & bitMasks[0]) >> bitOffsets[0];
|
||||
if (bitMasks.length > 1) {
|
||||
valueR += (shortPixels[i] & bitMasks[1]) >> bitOffsets[1];
|
||||
valueG += (shortPixels[i] & bitMasks[2]) >> bitOffsets[2];
|
||||
if (bitMasks.length > 3) {
|
||||
valueB += (shortPixels[i] & bitMasks[3]) >> bitOffsets[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Average
|
||||
valueA /= pixelCount;
|
||||
valueR /= pixelCount;
|
||||
valueG /= pixelCount;
|
||||
valueB /= pixelCount;
|
||||
|
||||
//for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
shortPixels[0] = (short) (((int) valueA << bitOffsets[0]) & bitMasks[0]);
|
||||
if (bitMasks.length > 1) {
|
||||
shortPixels[0] |= (short) (((int) valueR << bitOffsets[1]) & bitMasks[1]);
|
||||
shortPixels[0] |= (short) (((int) valueG << bitOffsets[2]) & bitMasks[2]);
|
||||
if (bitMasks.length > 3) {
|
||||
shortPixels[0] |= (short) (((int) valueB << bitOffsets[3]) & bitMasks[3]);
|
||||
}
|
||||
}
|
||||
//}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("TransferType not supported: " + dataType);
|
||||
|
||||
}
|
||||
|
||||
dest.setDataElements(x, y, 1, 1, data);
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
private static int clamp(final int pValue) {
|
||||
return pValue > 255 ? 255 : pValue;
|
||||
}
|
||||
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Refactor boilerplate to AbstractBufferedImageOp or use a delegate?
|
||||
// Delegate is maybe better as we won't always implement both BIOp and RasterOP
|
||||
// (but are there ever any time we want to implemnet RasterOp and not BIOp?)
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
||||
ColorModel cm = destCM != null ? destCM : src.getColorModel();
|
||||
return new BufferedImage(cm,
|
||||
ImageUtil.createCompatibleWritableRaster(src, cm, width, height),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
public WritableRaster createCompatibleDestRaster(Raster src) {
|
||||
return src.createCompatibleWritableRaster(width, height);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(Raster src) {
|
||||
return new Rectangle(width, height);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return new Rectangle(width, height);
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
// TODO: This is wrong!
|
||||
if (dstPt == null) {
|
||||
if (srcPt instanceof Point2D.Double) {
|
||||
dstPt = new Point2D.Double();
|
||||
}
|
||||
else {
|
||||
dstPt = new Point2D.Float();
|
||||
}
|
||||
}
|
||||
dstPt.setLocation(srcPt);
|
||||
|
||||
return dstPt;
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
BufferedImage image = ImageIO.read(new File("2006-Lamborghini-Gallardo-Spyder-Y-T-1600x1200.png"));
|
||||
//BufferedImage image = ImageIO.read(new File("focus-rs.jpg"));
|
||||
//BufferedImage image = ImageIO.read(new File("blauesglas_16_bitmask444.bmp"));
|
||||
//image = ImageUtil.toBuffered(image, BufferedImage.TYPE_USHORT_GRAY);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
//new PixelizeOp(10).filter(image, null);
|
||||
//new AffineTransformOp(AffineTransform.getScaleInstance(.1, .1), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
|
||||
//ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
|
||||
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_BOX).filter(image, null);
|
||||
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_QUADRATIC).filter(image, null);
|
||||
//new AreaAverageOp(image.getWidth() / 10, image.getHeight() / 10).filter(image, null);
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
//PixelizeOp pixelizer = new PixelizeOp(image.getWidth() / 10, 1);
|
||||
//pixelizer.setSourceRegion(new Rectangle(0, 2 * image.getHeight() / 3, image.getWidth(), image.getHeight() / 4));
|
||||
//PixelizeOp pixelizer = new PixelizeOp(4);
|
||||
//image = pixelizer.filter(image, image); // Filter in place, that's cool
|
||||
//image = new AffineTransformOp(AffineTransform.getScaleInstance(.25, .25), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
|
||||
//image = ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
|
||||
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_BOX).filter(image, null);
|
||||
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_QUADRATIC).filter(image, null);
|
||||
//image = new AreaAverageOp(image.getWidth() / 7, image.getHeight() / 4).filter(image, null);
|
||||
image = new AreaAverageOp(500, 600).filter(image, null);
|
||||
//image = new ResampleOp(500, 600, ResampleOp.FILTER_BOX).filter(image, null);
|
||||
long time = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println("time: " + time + " ms");
|
||||
|
||||
JFrame frame = new JFrame("Test");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setContentPane(new JScrollPane(new JLabel(new BufferedImageIcon(image))));
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
-168
@@ -1,168 +0,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 "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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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}).
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,610 +0,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 "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 java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.EventListener;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* NOTE: Does not support images with more than one {@code ColorModel} or
|
||||
* different types of pixel data. This is not very common.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java#1 $
|
||||
*/
|
||||
public final class BufferedImageFactory {
|
||||
private List<ProgressListener> listeners;
|
||||
private int percentageDone;
|
||||
|
||||
private ImageProducer producer;
|
||||
private ImageConversionException consumerException;
|
||||
private volatile boolean fetching;
|
||||
private boolean readColorModelOnly;
|
||||
|
||||
private int x = 0;
|
||||
private int y = 0;
|
||||
private int width = -1;
|
||||
private int height = -1;
|
||||
|
||||
private int xSub = 1;
|
||||
private int ySub = 1;
|
||||
|
||||
private int offset;
|
||||
private int scanSize;
|
||||
|
||||
private ColorModel sourceColorModel;
|
||||
private Hashtable sourceProperties; // ImageConsumer API dictates Hashtable
|
||||
|
||||
private Object sourcePixels;
|
||||
|
||||
private BufferedImage buffered;
|
||||
private ColorModel colorModel;
|
||||
|
||||
// NOTE: Just to not expose the inheritance
|
||||
private final Consumer consumer = new Consumer();
|
||||
|
||||
/**
|
||||
* Creates a {@code BufferedImageFactory}.
|
||||
* @param pSource the source image
|
||||
* @throws IllegalArgumentException if {@code pSource == null}
|
||||
*/
|
||||
public BufferedImageFactory(final Image pSource) {
|
||||
this(pSource != null ? pSource.getSource() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code BufferedImageFactory}.
|
||||
* @param pSource the source image producer
|
||||
* @throws IllegalArgumentException if {@code pSource == null}
|
||||
*/
|
||||
public BufferedImageFactory(final ImageProducer pSource) {
|
||||
Validate.notNull(pSource, "source");
|
||||
producer = pSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code BufferedImage} extracted from the given
|
||||
* {@code ImageSource}. Multiple requests will return the same image.
|
||||
*
|
||||
* @return the {@code BufferedImage}
|
||||
*
|
||||
* @throws ImageConversionException if the given {@code ImageSource} cannot
|
||||
* be converted for some reason.
|
||||
*/
|
||||
public BufferedImage getBufferedImage() throws ImageConversionException {
|
||||
doFetch(false);
|
||||
return buffered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code ColorModel} extracted from the
|
||||
* given {@code ImageSource}. Multiple requests will return the same model.
|
||||
*
|
||||
* @return the {@code ColorModel}
|
||||
*
|
||||
* @throws ImageConversionException if the given {@code ImageSource} cannot
|
||||
* be converted for some reason.
|
||||
*/
|
||||
public ColorModel getColorModel() throws ImageConversionException {
|
||||
doFetch(true);
|
||||
return buffered != null ? buffered.getColorModel() : colorModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees resources used by this {@code BufferedImageFactory}.
|
||||
*/
|
||||
public void dispose() {
|
||||
freeResources();
|
||||
buffered = null;
|
||||
colorModel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the image production.
|
||||
*/
|
||||
public void abort() {
|
||||
consumer.imageComplete(ImageConsumer.IMAGEABORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source region (AOI) for the new image.
|
||||
*
|
||||
* @param pRegion the source region
|
||||
*/
|
||||
public void setSourceRegion(final Rectangle pRegion) {
|
||||
// Re-fetch everything, if region changed
|
||||
if (x != pRegion.x || y != pRegion.y || width != pRegion.width || height != pRegion.height) {
|
||||
dispose();
|
||||
}
|
||||
|
||||
x = pRegion.x;
|
||||
y = pRegion.y;
|
||||
width = pRegion.width;
|
||||
height = pRegion.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source subsampling for the new image.
|
||||
*
|
||||
* @param pXSub horizontal subsampling factor
|
||||
* @param pYSub vertical subsampling factor
|
||||
*/
|
||||
public void setSourceSubsampling(int pXSub, int pYSub) {
|
||||
// Re-fetch everything, if subsampling changed
|
||||
if (xSub != pXSub || ySub != pYSub) {
|
||||
dispose();
|
||||
}
|
||||
|
||||
if (pXSub > 1) {
|
||||
xSub = pXSub;
|
||||
}
|
||||
if (pYSub > 1) {
|
||||
ySub = pYSub;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
|
||||
if (!fetching && (!pColorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
||||
// NOTE: Subsampling is only applied if extracting full image
|
||||
if (!pColorModelOnly && (xSub > 1 || ySub > 1)) {
|
||||
// If only sampling a region, the region must be scaled too
|
||||
if (width > 0 && height > 0) {
|
||||
width = (width + xSub - 1) / xSub;
|
||||
height = (height + ySub - 1) / ySub;
|
||||
|
||||
x = (x + xSub - 1) / xSub;
|
||||
y = (y + ySub - 1) / ySub;
|
||||
}
|
||||
|
||||
producer = new FilteredImageSource(producer, new SubsamplingFilter(xSub, ySub));
|
||||
}
|
||||
|
||||
// Start fetching
|
||||
fetching = true;
|
||||
readColorModelOnly = pColorModelOnly;
|
||||
|
||||
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
|
||||
|
||||
// Wait until the producer wakes us up, by calling imageComplete
|
||||
while (fetching) {
|
||||
try {
|
||||
wait(200l);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (consumerException != null) {
|
||||
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
||||
}
|
||||
|
||||
if (pColorModelOnly) {
|
||||
createColorModel();
|
||||
}
|
||||
else {
|
||||
createBuffered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createColorModel() {
|
||||
colorModel = sourceColorModel;
|
||||
|
||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||
freeResources();
|
||||
}
|
||||
|
||||
private void createBuffered() {
|
||||
if (width > 0 && height > 0) {
|
||||
if (sourceColorModel != null && sourcePixels != null) {
|
||||
// TODO: Fix pixel size / color model problem
|
||||
WritableRaster raster = ImageUtil.createRaster(width, height, sourcePixels, sourceColorModel);
|
||||
buffered = new BufferedImage(sourceColorModel, raster, sourceColorModel.isAlphaPremultiplied(), sourceProperties);
|
||||
}
|
||||
else {
|
||||
buffered = ImageUtil.createClear(width, height, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||
freeResources();
|
||||
}
|
||||
|
||||
private void freeResources() {
|
||||
sourceColorModel = null;
|
||||
sourcePixels = null;
|
||||
sourceProperties = null;
|
||||
}
|
||||
|
||||
private void processProgress(int scanline) {
|
||||
if (listeners != null) {
|
||||
int percent = 100 * scanline / height;
|
||||
|
||||
if (percent > percentageDone) {
|
||||
percentageDone = percent;
|
||||
|
||||
for (ProgressListener listener : listeners) {
|
||||
listener.progress(this, percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a progress listener to this factory.
|
||||
*
|
||||
* @param pListener the progress listener
|
||||
*/
|
||||
public void addProgressListener(ProgressListener pListener) {
|
||||
if (pListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listeners == null) {
|
||||
listeners = new CopyOnWriteArrayList<ProgressListener>();
|
||||
}
|
||||
|
||||
listeners.add(pListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a progress listener from this factory.
|
||||
*
|
||||
* @param pListener the progress listener
|
||||
*/
|
||||
public void removeProgressListener(ProgressListener pListener) {
|
||||
if (pListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listeners == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.remove(pListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all progress listeners from this factory.
|
||||
*/
|
||||
public void removeAllProgressListeners() {
|
||||
if (listeners != null) {
|
||||
listeners.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of {@code int} pixels to an array of {@code short}
|
||||
* pixels. The conversion is done, by masking out the
|
||||
* <em>higher 16 bits</em> of the {@code int}.
|
||||
*
|
||||
* For any given {@code int}, the {@code short} value is computed as
|
||||
* follows:
|
||||
* <blockquote>{@code
|
||||
* short value = (short) (intValue & 0x0000ffff);
|
||||
* }</blockquote>
|
||||
*
|
||||
* @param pPixels the pixel data to convert
|
||||
* @return an array of {@code short}s, same lenght as {@code pPixels}
|
||||
*/
|
||||
private static short[] toShortPixels(int[] pPixels) {
|
||||
short[] pixels = new short[pPixels.length];
|
||||
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
pixels[i] = (short) (pPixels[i] & 0xffff);
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface allows clients of a {@code BufferedImageFactory} to
|
||||
* receive notifications of decoding progress.
|
||||
*
|
||||
* @see BufferedImageFactory#addProgressListener
|
||||
* @see BufferedImageFactory#removeProgressListener
|
||||
*/
|
||||
public static interface ProgressListener extends EventListener {
|
||||
|
||||
/**
|
||||
* Reports progress to this listener.
|
||||
* Invoked by the {@code BufferedImageFactory} to report progress in
|
||||
* the image decoding.
|
||||
*
|
||||
* @param pFactory the factory reporting the progress
|
||||
* @param pPercentage the percentage of progress
|
||||
*/
|
||||
void progress(BufferedImageFactory pFactory, float pPercentage);
|
||||
}
|
||||
|
||||
private class Consumer implements ImageConsumer {
|
||||
/**
|
||||
* Implementation of all setPixels methods.
|
||||
* Note that this implementation assumes that all invocations for one
|
||||
* image uses the same color model, and that the pixel data has the
|
||||
* same type.
|
||||
*
|
||||
* @param pX x coordinate of pixel data region
|
||||
* @param pY y coordinate of pixel data region
|
||||
* @param pWidth width of pixel data region
|
||||
* @param pHeight height of pixel data region
|
||||
* @param pModel the color model of the pixel data
|
||||
* @param pPixels the pixel data array
|
||||
* @param pOffset the offset into the pixel data array
|
||||
* @param pScanSize the scan size of the pixel data array
|
||||
*/
|
||||
@SuppressWarnings({"SuspiciousSystemArraycopy"})
|
||||
private void setPixelsImpl(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, Object pPixels, int pOffset, int pScanSize) {
|
||||
setColorModelOnce(pModel);
|
||||
|
||||
if (pPixels == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate array if necessary
|
||||
if (sourcePixels == null) {
|
||||
// Allocate a suitable source pixel array
|
||||
// TODO: Should take pixel "width" into consideration, for byte packed rasters?!
|
||||
// OR... Is anything but single-pixel models really supported by the API?
|
||||
sourcePixels = Array.newInstance(pPixels.getClass().getComponentType(), width * height);
|
||||
scanSize = width;
|
||||
offset = 0;
|
||||
}
|
||||
else if (sourcePixels.getClass() != pPixels.getClass()) {
|
||||
throw new IllegalStateException("Only one pixel type allowed");
|
||||
}
|
||||
|
||||
// AOI stuff
|
||||
if (pY < y) {
|
||||
int diff = y - pY;
|
||||
if (diff >= pHeight) {
|
||||
return;
|
||||
}
|
||||
pOffset += pScanSize * diff;
|
||||
pY += diff;
|
||||
pHeight -= diff;
|
||||
}
|
||||
if (pY + pHeight > y + height) {
|
||||
pHeight = (y + height) - pY;
|
||||
if (pHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pX < x) {
|
||||
int diff = x - pX;
|
||||
if (diff >= pWidth) {
|
||||
return;
|
||||
}
|
||||
pOffset += diff;
|
||||
pX += diff;
|
||||
pWidth -= diff;
|
||||
}
|
||||
if (pX + pWidth > x + width) {
|
||||
pWidth = (x + width) - pX;
|
||||
if (pWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int dstOffset = offset + (pY - y) * scanSize + (pX - x);
|
||||
|
||||
// Do the pixel copying
|
||||
for (int i = pHeight; i > 0; i--) {
|
||||
System.arraycopy(pPixels, pOffset, sourcePixels, dstOffset, pWidth);
|
||||
pOffset += pScanSize;
|
||||
dstOffset += scanSize;
|
||||
}
|
||||
|
||||
processProgress(pY + pHeight);
|
||||
}
|
||||
|
||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) {
|
||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
||||
}
|
||||
|
||||
private void setColorModelOnce(final ColorModel pModel) {
|
||||
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
|
||||
// first passes the original color model through in setColorModel, then
|
||||
// later replaces it with the default RGB in the first setPixels call
|
||||
// (this is probably allowed according to the spec, but it's a waste of time and space).
|
||||
if (sourceColorModel != pModel) {
|
||||
if (/*sourceColorModel == null ||*/ sourcePixels == null) {
|
||||
sourceColorModel = pModel;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
|
||||
}
|
||||
}
|
||||
|
||||
// If color model is all we ask for, stop now
|
||||
if (readColorModelOnly) {
|
||||
consumer.imageComplete(ImageConsumer.IMAGEABORTED);
|
||||
}
|
||||
}
|
||||
|
||||
public void imageComplete(int pStatus) {
|
||||
fetching = false;
|
||||
|
||||
if (producer != null) {
|
||||
producer.removeConsumer(this);
|
||||
}
|
||||
|
||||
switch (pStatus) {
|
||||
case ImageConsumer.IMAGEERROR:
|
||||
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
||||
break;
|
||||
}
|
||||
|
||||
synchronized (BufferedImageFactory.this) {
|
||||
BufferedImageFactory.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorModel(ColorModel pModel) {
|
||||
setColorModelOnce(pModel);
|
||||
}
|
||||
|
||||
public void setDimensions(int pWidth, int pHeight) {
|
||||
if (width < 0) {
|
||||
width = pWidth - x;
|
||||
}
|
||||
if (height < 0) {
|
||||
height = pHeight - y;
|
||||
}
|
||||
|
||||
// Hmm.. Special case, but is it a good idea?
|
||||
if (width <= 0 || height <= 0) {
|
||||
imageComplete(ImageConsumer.STATICIMAGEDONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHints(int pHintflags) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
|
||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
||||
}
|
||||
|
||||
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
|
||||
if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||
// NOTE: Workaround for limitation in ImageConsumer API
|
||||
// Convert int[] to short[], to be compatible with the ColorModel
|
||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
|
||||
}
|
||||
else {
|
||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProperties(Hashtable pProperties) {
|
||||
sourceProperties = pProperties;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
Image image = Toolkit.getDefaultToolkit().createImage(args[0]);
|
||||
System.err.printf("image: %s (which is %sa buffered image)\n", image, image instanceof BufferedImage ? "" : "not ");
|
||||
|
||||
int warmUpLoops = 500;
|
||||
int testLoops = 100;
|
||||
|
||||
for (int i = 0; i < warmUpLoops; i++) {
|
||||
// Warm up...
|
||||
convertUsingFactory(image);
|
||||
convertUsingPixelGrabber(image);
|
||||
convertUsingPixelGrabberNaive(image);
|
||||
}
|
||||
|
||||
BufferedImage bufferedImage = null;
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < testLoops; i++) {
|
||||
bufferedImage = convertUsingFactory(image);
|
||||
}
|
||||
System.err.printf("Conversion time (factory): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage);
|
||||
|
||||
start = System.currentTimeMillis();
|
||||
for (int i = 0; i < testLoops; i++) {
|
||||
bufferedImage = convertUsingPixelGrabber(image);
|
||||
}
|
||||
System.err.printf("Conversion time (grabber): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage);
|
||||
|
||||
start = System.currentTimeMillis();
|
||||
for (int i = 0; i < testLoops; i++) {
|
||||
bufferedImage = convertUsingPixelGrabberNaive(image);
|
||||
}
|
||||
System.err.printf("Conversion time (naive g): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage);
|
||||
}
|
||||
|
||||
private static BufferedImage convertUsingPixelGrabberNaive(Image image) throws InterruptedException {
|
||||
// NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once
|
||||
if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) {
|
||||
System.err.printf("Could not get image dimensions for image %s\n", image.getSource());
|
||||
}
|
||||
|
||||
int w = image.getWidth(null);
|
||||
int h = image.getHeight(null);
|
||||
PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB
|
||||
grabber.grabPixels();
|
||||
|
||||
// Following casts are safe, as we force RGB in the pixel grabber
|
||||
int[] pixels = (int[]) grabber.getPixels();
|
||||
|
||||
BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
// bufferedImage.setRGB(0, 0, w, h, pixels, 0, w);
|
||||
bufferedImage.getRaster().setDataElements(0, 0, w, h, pixels);
|
||||
|
||||
return bufferedImage;
|
||||
}
|
||||
|
||||
private static BufferedImage convertUsingPixelGrabber(Image image) throws InterruptedException {
|
||||
// NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once
|
||||
if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) {
|
||||
System.err.printf("Could not get image dimensions for image %s\n", image.getSource());
|
||||
}
|
||||
|
||||
int w = image.getWidth(null);
|
||||
int h = image.getHeight(null);
|
||||
PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB
|
||||
grabber.grabPixels();
|
||||
|
||||
// Following casts are safe, as we force RGB in the pixel grabber
|
||||
// DirectColorModel cm = (DirectColorModel) grabber.getColorModel();
|
||||
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
||||
int[] pixels = (int[]) grabber.getPixels();
|
||||
|
||||
WritableRaster raster = Raster.createPackedRaster(new DataBufferInt(pixels, pixels.length), w, h, w, cm.getMasks(), null);
|
||||
|
||||
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
private static BufferedImage convertUsingFactory(Image image) {
|
||||
return new BufferedImageFactory(image).getBufferedImage();
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,90 +0,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 "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}.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,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 "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.*;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
/**
|
||||
* This class implements a convolution from the source
|
||||
* to the destination.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ConvolveWithEdgeOp.java#1 $
|
||||
*
|
||||
* @see java.awt.image.ConvolveOp
|
||||
*/
|
||||
public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
/**
|
||||
* Alias for {@link ConvolveOp#EDGE_ZERO_FILL}.
|
||||
* @see #EDGE_REFLECT
|
||||
*/
|
||||
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
|
||||
/**
|
||||
* Alias for {@link ConvolveOp#EDGE_NO_OP}.
|
||||
* @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.
|
||||
*/
|
||||
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 #EDGE_REFLECT
|
||||
*/
|
||||
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
|
||||
|
||||
private final Kernel kernel;
|
||||
private final int edgeCondition;
|
||||
|
||||
private final ConvolveOp convolve;
|
||||
|
||||
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
|
||||
// Create convolution operation
|
||||
int edge;
|
||||
|
||||
switch (pEdgeCondition) {
|
||||
case EDGE_REFLECT:
|
||||
case EDGE_WRAP:
|
||||
edge = ConvolveOp.EDGE_NO_OP;
|
||||
break;
|
||||
default:
|
||||
edge = pEdgeCondition;
|
||||
break;
|
||||
}
|
||||
|
||||
kernel = pKernel;
|
||||
edgeCondition = pEdgeCondition;
|
||||
convolve = new ConvolveOp(pKernel, edge, pHints);
|
||||
}
|
||||
|
||||
public ConvolveWithEdgeOp(final Kernel pKernel) {
|
||||
this(pKernel, EDGE_ZERO_FILL, null);
|
||||
}
|
||||
|
||||
public BufferedImage filter(BufferedImage pSource, BufferedImage pDestination) {
|
||||
if (pSource == null) {
|
||||
throw new NullPointerException("source image is null");
|
||||
}
|
||||
if (pSource == pDestination) {
|
||||
throw new IllegalArgumentException("source image cannot be the same as the destination image");
|
||||
}
|
||||
|
||||
int borderX = kernel.getWidth() / 2;
|
||||
int borderY = kernel.getHeight() / 2;
|
||||
|
||||
BufferedImage original = addBorder(pSource, borderX, borderY);
|
||||
|
||||
// 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 destination = pDestination;
|
||||
if (original.getType() == BufferedImage.TYPE_3BYTE_BGR) {
|
||||
destination = ImageUtil.createBuffered(
|
||||
pSource.getWidth(), pSource.getHeight(),
|
||||
pSource.getType(), pSource.getColorModel().getTransparency(),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
// Do the filtering (if destination is null, a new image will be created)
|
||||
destination = convolve.filter(original, destination);
|
||||
|
||||
if (pSource != original) {
|
||||
// Remove the border
|
||||
destination = destination.getSubimage(borderX, borderY, pSource.getWidth(), pSource.getHeight());
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) {
|
||||
if ((edgeCondition & 2) == 0) {
|
||||
return pOriginal;
|
||||
}
|
||||
|
||||
// TODO: Might be faster if we could clone raster and stretch 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 (edgeCondition) {
|
||||
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 " + edgeCondition);
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
return bordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edge condition.
|
||||
* @return the edge condition of this {@code ConvolveOp}.
|
||||
* @see #EDGE_NO_OP
|
||||
* @see #EDGE_ZERO_FILL
|
||||
* @see #EDGE_REFLECT
|
||||
* @see #EDGE_WRAP
|
||||
*/
|
||||
public int getEdgeCondition() {
|
||||
return edgeCondition;
|
||||
}
|
||||
|
||||
public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) {
|
||||
return convolve.filter(pSource, pDestination);
|
||||
}
|
||||
|
||||
public BufferedImage createCompatibleDestImage(final BufferedImage pSource, final ColorModel pDesinationColorModel) {
|
||||
return convolve.createCompatibleDestImage(pSource, pDesinationColorModel);
|
||||
}
|
||||
|
||||
public WritableRaster createCompatibleDestRaster(final Raster pSource) {
|
||||
return convolve.createCompatibleDestRaster(pSource);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(final BufferedImage pSource) {
|
||||
return convolve.getBounds2D(pSource);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(final Raster pSource) {
|
||||
return convolve.getBounds2D(pSource);
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
|
||||
return convolve.getPoint2D(pSourcePoint, pDestinationPoint);
|
||||
}
|
||||
|
||||
public RenderingHints getRenderingHints() {
|
||||
return convolve.getRenderingHints();
|
||||
}
|
||||
|
||||
public Kernel getKernel() {
|
||||
return convolve.getKernel();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,287 +0,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 "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.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;
|
||||
|
||||
/**
|
||||
* This BufferedImageOp simply copies pixels, converting to a
|
||||
* {@code IndexColorModel}.
|
||||
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/CopyDither.java#1 $
|
||||
*
|
||||
*/
|
||||
public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
|
||||
protected IndexColorModel indexColorModel = null;
|
||||
|
||||
/**
|
||||
* Creates a {@code CopyDither}, using the given
|
||||
* {@code IndexColorModel} for dithering into.
|
||||
*
|
||||
* @param pICM an IndexColorModel.
|
||||
*/
|
||||
public CopyDither(IndexColorModel pICM) {
|
||||
// Store colormodel
|
||||
indexColorModel = pICM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code CopyDither}, with no fixed
|
||||
* {@code IndexColorModel}. The colormodel will be generated for each
|
||||
* filtering, unless the dest image allready has an
|
||||
* {@code IndexColorModel}.
|
||||
*/
|
||||
public CopyDither() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, indexColorModel);
|
||||
}
|
||||
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
|
||||
*
|
||||
* @return a {@code WritableRaster}
|
||||
*/
|
||||
public final WritableRaster createCompatibleDestRaster(Raster pSrc) {
|
||||
return createCompatibleDestRaster(pSrc, getICM(pSrc));
|
||||
}
|
||||
|
||||
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 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 destiantion 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
|
||||
* @param pDest
|
||||
*
|
||||
* @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 | IndexImage.COLOR_SELECTION_QUALITY));
|
||||
}
|
||||
|
||||
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 | IndexImage.COLOR_SELECTION_QUALITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a single-input/single-output pixel copy operation.
|
||||
*
|
||||
* @param pSource
|
||||
* @param pDest
|
||||
* @param pColorModel
|
||||
*
|
||||
* @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();
|
||||
|
||||
if (pDest == null) {
|
||||
pDest = createCompatibleDestRaster(pSource, pColorModel);
|
||||
}
|
||||
|
||||
// temp buffers
|
||||
final int[] inRGB = new int[4];
|
||||
Object pixel = null;
|
||||
|
||||
// TODO: Use getPixels instead of getPixel for better performance?
|
||||
|
||||
// Loop through image data
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// Get rgb from original raster
|
||||
// DON'T KNOW IF THIS WILL WORK FOR ALL TYPES..?
|
||||
pSource.getPixel(x, y, inRGB);
|
||||
|
||||
// Get pixel value...
|
||||
// It is VERY important that we are using an IndexColorModel that
|
||||
// support reverse color lookup for speed.
|
||||
pixel = pColorModel.getDataElements(toIntARGB(inRGB), pixel);
|
||||
|
||||
// And set it
|
||||
pDest.setDataElements(x, y, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
return pDest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,460 +0,0 @@
|
||||
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.
|
||||
* <P/>
|
||||
* The weights used are 7/16, 3/16, 5/16 and 1/16, distributed like this:
|
||||
* <!-- - -
|
||||
* | |x|7|
|
||||
* - - - -
|
||||
* |3|5|1|
|
||||
* - - -->
|
||||
* <P/>
|
||||
* <TABLE border="1" cellpadding="4" cellspacing="0">
|
||||
* <TR><TD bgcolor="#000000"> </TD><TD class="TableHeadingColor"
|
||||
* align="center">X</TD><TD>7/16</TD></TR>
|
||||
* <TR><TD>3/16</TD><TD>5/16</TD><TD>1/16</TD></TR>
|
||||
* </TABLE>
|
||||
* <P/>
|
||||
* See <A href="http://www.awprofessional.com/bookstore/product.asp?isbn=0201848406&rl=1">Computer Graphics (Foley et al.)</a>
|
||||
* for more information.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,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 "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 <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,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 "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 represents a 256 color fixed grayscale IndexColorModel.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/GrayColorModel.java#1 $
|
||||
*
|
||||
*/
|
||||
public class GrayColorModel extends IndexColorModel {
|
||||
|
||||
private final static byte[] sGrays = createGrayScale();
|
||||
|
||||
public GrayColorModel() {
|
||||
super(8, sGrays.length, sGrays, sGrays, sGrays);
|
||||
}
|
||||
|
||||
private static byte[] createGrayScale() {
|
||||
byte[] grays = new byte[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
grays[i] = (byte) i;
|
||||
}
|
||||
return grays;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,129 +0,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 "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.
|
||||
* <P/>
|
||||
* Uses ITU standard conversion: (222 * Red + 707 * Green + 71 * Blue) / 1000.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,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 "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;
|
||||
|
||||
/**
|
||||
* This class wraps IllegalArgumentException, and is thrown by the ImageUtil
|
||||
* class, when trying to convert images read from {@code null}-sources etc.
|
||||
*
|
||||
* @author Harald Kuhr
|
||||
*
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageConversionException.java#1 $
|
||||
*/
|
||||
public class ImageConversionException extends ImageFilterException {
|
||||
|
||||
public ImageConversionException(String pMessage) {
|
||||
super(pMessage);
|
||||
}
|
||||
|
||||
public ImageConversionException(String pMessage, Throwable pCause) {
|
||||
super(pMessage, pCause);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,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 "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;
|
||||
|
||||
/**
|
||||
* This class wraps IllegalArgumentException as thrown by the
|
||||
* BufferedImageOp interface for more fine-grained control.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageFilterException.java#1 $
|
||||
*/
|
||||
public class ImageFilterException extends IllegalArgumentException {
|
||||
public ImageFilterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ImageFilterException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ImageFilterException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,211 +0,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 "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.
|
||||
* <p/>
|
||||
* Based on "Efficient Inverse Color Map Computation" by Spencer W. Thomas
|
||||
* in "Graphics Gems Volume II"
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
-303
@@ -1,303 +0,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 "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.StringUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* A faster implementation of {@code IndexColorModel}, that is backed by an
|
||||
* inverse color-map, for fast look-ups.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java#1 $
|
||||
*
|
||||
*/
|
||||
public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
|
||||
protected int rgbs[];
|
||||
protected int mapSize;
|
||||
|
||||
protected InverseColorMap inverseMap = null;
|
||||
private final static int ALPHA_THRESHOLD = 0x80;
|
||||
|
||||
private int whiteIndex = -1;
|
||||
private final static int WHITE = 0x00FFFFFF;
|
||||
private final static int RGB_MASK = 0x00FFFFFF;
|
||||
|
||||
/**
|
||||
* Creates an {@code InverseColorMapIndexColorModel} from an existing
|
||||
* {@code IndexColorModel}.
|
||||
*
|
||||
* @param pColorModel the color model to create from.
|
||||
* @throws IllegalArgumentException if {@code pColorModel} is {@code null}
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) {
|
||||
this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel));
|
||||
}
|
||||
|
||||
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
|
||||
// times. What is wrong with protected?!
|
||||
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
|
||||
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels());
|
||||
|
||||
rgbs = pRGBs;
|
||||
mapSize = rgbs.length;
|
||||
|
||||
inverseMap = new InverseColorMap(rgbs);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a defensive copy of the RGB color map in the given
|
||||
* {@code IndexColorModel}.
|
||||
*
|
||||
* @param pColorModel the indexed color model to get RGB values from
|
||||
* @return the RGB color map
|
||||
*/
|
||||
private static int[] getRGBs(IndexColorModel pColorModel) {
|
||||
int[] rgb = new int[pColorModel.getMapSize()];
|
||||
pColorModel.getRGBs(rgb);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code InverseColorMapIndexColorModel} from the given array
|
||||
* of RGB components, plus one transparent index.
|
||||
*
|
||||
* @param pNumBits the number of bits each pixel occupies
|
||||
* @param pSize the size of the color component arrays
|
||||
* @param pRGBs the array of packed RGB color components
|
||||
* @param pStart the starting offset of the first color component
|
||||
* @param pAlpha indicates whether alpha values are contained in {@code pRGBs}
|
||||
* @param pTransparentIndex the index of the transparent pixel
|
||||
* @param pTransferType the data type of the array used to represent pixels
|
||||
*
|
||||
* @throws IllegalArgumentException if bits is less than 1 or greater than 16,
|
||||
* or if size is less than 1
|
||||
*
|
||||
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs, int pStart, boolean pAlpha, int pTransparentIndex, int pTransferType) {
|
||||
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
|
||||
rgbs = getRGBs(this);
|
||||
mapSize = rgbs.length;
|
||||
|
||||
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code InverseColorMapIndexColorModel} from the given arrays
|
||||
* of red, green, and blue components, plus one transparent index.
|
||||
*
|
||||
* @param pNumBits the number of bits each pixel occupies
|
||||
* @param pSize the size of the color component arrays
|
||||
* @param pReds the array of red color components
|
||||
* @param pGreens the array of green color components
|
||||
* @param pBlues the array of blue color components
|
||||
* @param pTransparentIndex the index of the transparent pixel
|
||||
*
|
||||
* @throws IllegalArgumentException if bits is less than 1 or greater than 16,
|
||||
* or if size is less than 1
|
||||
*
|
||||
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues, int pTransparentIndex) {
|
||||
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
|
||||
rgbs = getRGBs(this);
|
||||
mapSize = rgbs.length;
|
||||
|
||||
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code InverseColorMapIndexColorModel} from the given arrays
|
||||
* of red, green, and blue components.
|
||||
*
|
||||
* @param pNumBits the number of bits each pixel occupies
|
||||
* @param pSize the size of the color component arrays
|
||||
* @param pReds the array of red color components
|
||||
* @param pGreens the array of green color components
|
||||
* @param pBlues the array of blue color components
|
||||
*
|
||||
* @throws IllegalArgumentException if bits is less than 1 or greater than 16,
|
||||
* or if size is less than 1
|
||||
*
|
||||
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues) {
|
||||
super(pNumBits, pSize, pReds, pGreens, pBlues);
|
||||
rgbs = getRGBs(this);
|
||||
mapSize = rgbs.length;
|
||||
|
||||
inverseMap = new InverseColorMap(rgbs);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
private int getWhiteIndex() {
|
||||
for (int i = 0; i < rgbs.length; i++) {
|
||||
int color = rgbs[i];
|
||||
if ((color & RGB_MASK) == WHITE) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code IndexColorModel} optimized for the given {@code Image}.
|
||||
*
|
||||
* @param pImage the {@code Image} containing the RGB samples
|
||||
* @param pNumCols the maximum number of colors in the {@code IndexColorModel}
|
||||
* @param pFlags flags
|
||||
*
|
||||
* @return a new optimized {@code IndexColorModel}
|
||||
*/
|
||||
public static IndexColorModel create(Image pImage, int pNumCols, int pFlags) {
|
||||
// TODO: Inline and deprecate IndexImage.getIndexColorModel!?
|
||||
IndexColorModel icm = IndexImage.getIndexColorModel(pImage, pNumCols, pFlags);
|
||||
|
||||
InverseColorMapIndexColorModel cm;
|
||||
if (icm instanceof InverseColorMapIndexColorModel) {
|
||||
cm = (InverseColorMapIndexColorModel) icm;
|
||||
}
|
||||
else {
|
||||
cm = new InverseColorMapIndexColorModel(icm);
|
||||
}
|
||||
|
||||
return cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a data element array representation of a pixel in this
|
||||
* ColorModel, given an integer pixel representation in the
|
||||
* default RGB color model. This array can then be passed to the
|
||||
* {@link java.awt.image.WritableRaster#setDataElements(int, int, Object) setDataElements}
|
||||
* method of a {@link java.awt.image.WritableRaster} object. If the pixel variable is
|
||||
* {@code null}, a new array is allocated. If {@code pixel}
|
||||
* is not {@code null}, it must be
|
||||
* a primitive array of type {@code transferType}; otherwise, a
|
||||
* {@code ClassCastException} is thrown. An
|
||||
* {@code ArrayIndexOutOfBoundsException} is
|
||||
* thrown if {@code pixel} is not large enough to hold a pixel
|
||||
* value for this {@code ColorModel}. The pixel array is returned.
|
||||
* <p>
|
||||
* Since {@code OpaqueIndexColorModel} can be subclassed, subclasses
|
||||
* inherit the implementation of this method and if they don't
|
||||
* override it then they throw an exception if they use an
|
||||
* unsupported {@code transferType}.
|
||||
*
|
||||
* #param rgb the integer pixel representation in the default RGB
|
||||
* color model
|
||||
* #param pixel the specified pixel
|
||||
* #return an array representation of the specified pixel in this
|
||||
* {@code OpaqueIndexColorModel}.
|
||||
* #throws ClassCastException if {@code pixel}
|
||||
* is not a primitive array of type {@code transferType}
|
||||
* #throws ArrayIndexOutOfBoundsException if
|
||||
* {@code pixel} is not large enough to hold a pixel value
|
||||
* for this {@code ColorModel}
|
||||
* #throws UnsupportedOperationException if {@code transferType}
|
||||
* is invalid
|
||||
* @see java.awt.image.WritableRaster#setDataElements
|
||||
* @see java.awt.image.SampleModel#setDataElements
|
||||
*
|
||||
*/
|
||||
public Object getDataElements(int rgb, Object pixel) {
|
||||
int alpha = (rgb>>>24);
|
||||
|
||||
int pix;
|
||||
if (alpha < ALPHA_THRESHOLD && getTransparentPixel() != -1) {
|
||||
pix = getTransparentPixel();
|
||||
}
|
||||
else {
|
||||
int color = rgb & RGB_MASK;
|
||||
if (color == WHITE && whiteIndex != -1) {
|
||||
pix = whiteIndex;
|
||||
}
|
||||
else {
|
||||
pix = inverseMap.getIndexNearest(color);
|
||||
}
|
||||
}
|
||||
|
||||
return installpixel(pixel, pix);
|
||||
}
|
||||
|
||||
private Object installpixel(Object pixel, int pix) {
|
||||
switch (transferType) {
|
||||
case DataBuffer.TYPE_INT:
|
||||
int[] intObj;
|
||||
if (pixel == null) {
|
||||
pixel = intObj = new int[1];
|
||||
}
|
||||
else {
|
||||
intObj = (int[]) pixel;
|
||||
}
|
||||
intObj[0] = pix;
|
||||
break;
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
byte[] byteObj;
|
||||
if (pixel == null) {
|
||||
pixel = byteObj = new byte[1];
|
||||
}
|
||||
else {
|
||||
byteObj = (byte[]) pixel;
|
||||
}
|
||||
byteObj[0] = (byte) pix;
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
short[] shortObj;
|
||||
if (pixel == null) {
|
||||
pixel = shortObj = new short[1];
|
||||
}
|
||||
else {
|
||||
shortObj = (short[]) pixel;
|
||||
}
|
||||
shortObj[0] = (short) pix;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
|
||||
}
|
||||
return pixel;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
// Just a workaround to ease debugging
|
||||
return StringUtil.replace(super.toString(), "IndexColorModel: ", getClass().getName() + ": ");
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
/**
|
||||
* Magick
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/Magick.java#1 $
|
||||
*/
|
||||
final class Magick {
|
||||
static final boolean DEBUG = useDebug();
|
||||
|
||||
private static boolean useDebug() {
|
||||
try {
|
||||
return "TRUE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.magick.debug"));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// Most probably in case of a SecurityManager
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Magick() {}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
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.
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,611 +0,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 "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.
|
||||
* <p/>
|
||||
* <em>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. :-)</em>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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}.
|
||||
* <p/>
|
||||
* The conversion depends on {@code pImage}'s {@code ImageType}:
|
||||
* <dl>
|
||||
* <dt>{@code ImageType.BilevelType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}</dd>
|
||||
*
|
||||
* <dt>{@code ImageType.GrayscaleType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}</dd>
|
||||
* <dt>{@code ImageType.GrayscaleMatteType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}</dd>
|
||||
*
|
||||
* <dt>{@code ImageType.PaletteType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
|
||||
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
|
||||
* <dt>{@code ImageType.PaletteMatteType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
|
||||
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
|
||||
*
|
||||
* <dt>{@code ImageType.TrueColorType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}</dd>
|
||||
* <dt>{@code ImageType.TrueColorPaletteType}</dt>
|
||||
* <dd>{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}</dd>
|
||||
*
|
||||
* @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}.
|
||||
* <p/>
|
||||
* The conversion depends on {@code pImage}'s {@code ColorModel}:
|
||||
* <dl>
|
||||
* <dt>{@code IndexColorModel} with 1 bit b/w</dt>
|
||||
* <dd>{@code MagickImage} of type {@code ImageType.BilevelType}</dd>
|
||||
* <dt>{@code IndexColorModel} > 1 bit,</dt>
|
||||
* <dd>{@code MagickImage} of type {@code ImageType.PaletteType}
|
||||
* or {@code MagickImage} of type {@code ImageType.PaletteMatteType}
|
||||
* depending on <tt>ColorModel.getAlpha()</dd>
|
||||
*
|
||||
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}</dt>
|
||||
* <dd>{@code MagickImage} of type {@code ImageType.GrayscaleType}
|
||||
* or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType}
|
||||
* depending on <tt>ColorModel.getAlpha()</dd>
|
||||
*
|
||||
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}</dt>
|
||||
* <dd>{@code MagickImage} of type {@code ImageType.TrueColorType}
|
||||
* or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}</dd>
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,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 "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.*;
|
||||
|
||||
/**
|
||||
* Monochrome B/W color model.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
public class MonochromeColorModel extends IndexColorModel {
|
||||
|
||||
private final static int[] MONO_PALETTE = {0x00000000, 0x00FFFFFF};
|
||||
|
||||
private static MonochromeColorModel sInstance = new MonochromeColorModel();
|
||||
|
||||
private MonochromeColorModel() {
|
||||
super(1, 2, MONO_PALETTE, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
|
||||
public static IndexColorModel getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public synchronized Object getDataElements(int pRGB, Object pPixel) {
|
||||
// Get color components
|
||||
int r = pRGB >> 16 & 0xFF;
|
||||
int g = pRGB >> 8 & 0xFF;
|
||||
int b = pRGB & 0xFF;
|
||||
|
||||
// ITU standard: Gray scale=(222*Red+707*Green+71*Blue)/1000
|
||||
int gray = (222 * r + 707 * g + 71 * b) / 1000;
|
||||
|
||||
byte[] pixel;
|
||||
if (pPixel != null) {
|
||||
pixel = (byte[]) pPixel;
|
||||
}
|
||||
else {
|
||||
pixel = new byte[1];
|
||||
}
|
||||
|
||||
if (gray <= 0x80) {
|
||||
pixel[0] = 0;
|
||||
}
|
||||
else {
|
||||
pixel[0] = 1;
|
||||
}
|
||||
|
||||
return pixel;
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,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 "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 javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* PixelizeOp
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/PixelizeOp.java#2 $
|
||||
*/
|
||||
public class PixelizeOp implements BufferedImageOp, RasterOp {
|
||||
// TODO: support more raster types/color models
|
||||
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
|
||||
|
||||
final private int pixelSizeX;
|
||||
final private int pixelSizeY;
|
||||
|
||||
private Rectangle sourceRegion;
|
||||
|
||||
public PixelizeOp(final int pPixelSize) {
|
||||
this(pPixelSize, pPixelSize);
|
||||
}
|
||||
|
||||
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
|
||||
pixelSizeX = pPixelSizeX;
|
||||
pixelSizeY = pPixelSizeY;
|
||||
}
|
||||
|
||||
public Rectangle getSourceRegion() {
|
||||
if (sourceRegion == null) {
|
||||
return null;
|
||||
}
|
||||
return new Rectangle(sourceRegion);
|
||||
}
|
||||
|
||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||
if (pSourceRegion == null) {
|
||||
sourceRegion = null;
|
||||
}
|
||||
else {
|
||||
if (sourceRegion == null) {
|
||||
sourceRegion = new Rectangle(pSourceRegion);
|
||||
}
|
||||
else {
|
||||
sourceRegion.setBounds(pSourceRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedImage filter(BufferedImage src, BufferedImage dest) {
|
||||
BufferedImage result = dest != null ? dest : createCompatibleDestImage(src, null);
|
||||
|
||||
// TODO: Do some type checking here..
|
||||
// Should work with
|
||||
// * all BYTE types, unless sub-byte packed rasters/IndexColorModel
|
||||
// * all INT types (even custom, as long as they use 8bit/componnet)
|
||||
// * all USHORT types (even custom)
|
||||
|
||||
// TODO: Also check if the images are really compatible!?
|
||||
|
||||
filterImpl(src.getRaster(), result.getRaster());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public WritableRaster filter(Raster src, WritableRaster dest) {
|
||||
WritableRaster result = dest != null ? dest : createCompatibleDestRaster(src);
|
||||
return filterImpl(src, result);
|
||||
}
|
||||
|
||||
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
||||
//System.out.println("src: " + src);
|
||||
//System.out.println("dest: " + dest);
|
||||
if (sourceRegion != null) {
|
||||
int cx = sourceRegion.x;
|
||||
int cy = sourceRegion.y;
|
||||
int cw = sourceRegion.width;
|
||||
int ch = sourceRegion.height;
|
||||
|
||||
boolean same = src == dest;
|
||||
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
|
||||
src = same ? dest : src.createChild(cx, cy, cw, ch, 0, 0, null);
|
||||
//System.out.println("src: " + src);
|
||||
//System.out.println("dest: " + dest);
|
||||
}
|
||||
|
||||
final int width = src.getWidth();
|
||||
final int height = src.getHeight();
|
||||
int w = (width + pixelSizeX - 1) / pixelSizeX;
|
||||
int h = (height + pixelSizeY - 1) / pixelSizeY;
|
||||
|
||||
final boolean oddX = width % w != 0;
|
||||
final boolean oddY = height % h != 0;
|
||||
|
||||
final int dataElements = src.getNumDataElements();
|
||||
final int bands = src.getNumBands();
|
||||
final int dataType = src.getTransferType();
|
||||
|
||||
Object data = null;
|
||||
int scanW;
|
||||
int scanH;
|
||||
|
||||
|
||||
// TYPE_USHORT setup
|
||||
int[] bitMasks = null;
|
||||
int[] bitOffsets = null;
|
||||
if (src.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||
if (src.getSampleModel() instanceof SinglePixelPackedSampleModel) {
|
||||
// DIRECT
|
||||
SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) src.getSampleModel();
|
||||
bitMasks = sampleModel.getBitMasks();
|
||||
bitOffsets = sampleModel.getBitOffsets();
|
||||
}
|
||||
else {
|
||||
// GRAY
|
||||
bitMasks = new int[] {0xffff};
|
||||
bitOffsets = new int[] {0};
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
if (!oddY || y + 1 < h) {
|
||||
scanH = pixelSizeY;
|
||||
}
|
||||
else {
|
||||
scanH = height - (y * pixelSizeY);
|
||||
}
|
||||
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (!oddX || x + 1 < w) {
|
||||
scanW = pixelSizeX;
|
||||
}
|
||||
else {
|
||||
scanW = width - (x * pixelSizeX);
|
||||
}
|
||||
final int pixelCount = scanW * scanH;
|
||||
final int pixelLength = pixelCount * dataElements;
|
||||
|
||||
data = src.getDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
|
||||
|
||||
// NOTE: These are not neccessarily ARGB..
|
||||
double valueA = 0.0;
|
||||
double valueR = 0.0;
|
||||
double valueG = 0.0;
|
||||
double valueB = 0.0;
|
||||
|
||||
switch (dataType) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
// TODO: Doesn't hold for index color models...
|
||||
byte[] bytePixels = (byte[]) data;
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
valueA += bytePixels[i] & 0xff;
|
||||
if (bands > 1) {
|
||||
valueR += bytePixels[i + 1] & 0xff;
|
||||
valueG += bytePixels[i + 2] & 0xff;
|
||||
if (bands > 3) {
|
||||
valueB += bytePixels[i + 3] & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Average
|
||||
valueA /= pixelCount;
|
||||
if (bands > 1) {
|
||||
valueR /= pixelCount;
|
||||
valueG /= pixelCount;
|
||||
if (bands > 3) {
|
||||
valueB /= pixelCount;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
bytePixels[i] = (byte) clamp((int) valueA);
|
||||
if (bands > 1) {
|
||||
bytePixels[i + 1] = (byte) clamp((int) valueR);
|
||||
bytePixels[i + 2] = (byte) clamp((int) valueG);
|
||||
if (bands > 3) {
|
||||
bytePixels[i + 3] = (byte) clamp((int) valueB);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_INT:
|
||||
int[] intPixels = (int[]) data;
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
valueA += (intPixels[i] & 0xff000000) >> 24;
|
||||
valueR += (intPixels[i] & 0xff0000) >> 16;
|
||||
valueG += (intPixels[i] & 0xff00) >> 8;
|
||||
valueB += (intPixels[i] & 0xff);
|
||||
}
|
||||
|
||||
// Average
|
||||
valueA /= pixelCount;
|
||||
valueR /= pixelCount;
|
||||
valueG /= pixelCount;
|
||||
valueB /= pixelCount;
|
||||
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
intPixels[i] = clamp((int) valueA) << 24;
|
||||
intPixels[i] |= clamp((int) valueR) << 16;
|
||||
intPixels[i] |= clamp((int) valueG) << 8;
|
||||
intPixels[i] |= clamp((int) valueB);
|
||||
}
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
if (bitMasks != null) {
|
||||
short[] shortPixels = (short[]) data;
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
valueA += (shortPixels[i] & bitMasks[0]) >> bitOffsets[0];
|
||||
if (bitMasks.length > 1) {
|
||||
valueR += (shortPixels[i] & bitMasks[1]) >> bitOffsets[1];
|
||||
valueG += (shortPixels[i] & bitMasks[2]) >> bitOffsets[2];
|
||||
if (bitMasks.length > 3) {
|
||||
valueB += (shortPixels[i] & bitMasks[3]) >> bitOffsets[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Average
|
||||
valueA /= pixelCount;
|
||||
valueR /= pixelCount;
|
||||
valueG /= pixelCount;
|
||||
valueB /= pixelCount;
|
||||
|
||||
for (int i = 0; i < pixelLength; i += dataElements) {
|
||||
shortPixels[i] = (short) (((int) valueA << bitOffsets[0]) & bitMasks[0]);
|
||||
if (bitMasks.length > 1) {
|
||||
shortPixels[i] |= (short) (((int) valueR << bitOffsets[1]) & bitMasks[1]);
|
||||
shortPixels[i] |= (short) (((int) valueG << bitOffsets[2]) & bitMasks[2]);
|
||||
if (bitMasks.length > 3) {
|
||||
shortPixels[i] |= (short) (((int) valueB << bitOffsets[3]) & bitMasks[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("TransferType not supported: " + dataType);
|
||||
|
||||
}
|
||||
|
||||
dest.setDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
|
||||
}
|
||||
}
|
||||
/*/
|
||||
// This is a very naive way of pixelizing (but it works)...
|
||||
// Thanks to the awsome speed of AffineTransformOp, it's also fast
|
||||
double sx = w / (double) src.getWidth();
|
||||
double sy = h / (double) src.getHeight();
|
||||
|
||||
WritableRaster temp = src.createCompatibleWritableRaster(w, h);
|
||||
|
||||
new AffineTransformOp(AffineTransform.getScaleInstance(sx, sy), 3)
|
||||
.filter(src, temp);
|
||||
new AffineTransformOp(AffineTransform.getScaleInstance(1 / sx, 1 / sy),
|
||||
AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
|
||||
.filter(temp, dest);
|
||||
//*/
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
private static int clamp(final int pValue) {
|
||||
return pValue > 255 ? 255 : pValue;
|
||||
}
|
||||
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Refactor boilerplate to AbstractBufferedImageOp or use a delegate?
|
||||
// Delegate is maybe better as we won't always implement both BIOp and RasterOP
|
||||
// (but are there ever any time we want to implemnet RasterOp and not BIOp?)
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
||||
ColorModel cm = destCM != null ? destCM : src.getColorModel();
|
||||
return new BufferedImage(cm,
|
||||
ImageUtil.createCompatibleWritableRaster(src, cm, src.getWidth(), src.getHeight()),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
public WritableRaster createCompatibleDestRaster(Raster src) {
|
||||
return src.createCompatibleWritableRaster();
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(Raster src) {
|
||||
return new Rectangle(src.getWidth(), src.getHeight());
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return new Rectangle(src.getWidth(), src.getHeight());
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
if (dstPt == null) {
|
||||
if (srcPt instanceof Point2D.Double) {
|
||||
dstPt = new Point2D.Double();
|
||||
}
|
||||
else {
|
||||
dstPt = new Point2D.Float();
|
||||
}
|
||||
}
|
||||
dstPt.setLocation(srcPt);
|
||||
return dstPt;
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
BufferedImage image = ImageIO.read(new File("2006-Lamborghini-Gallardo-Spyder-Y-T-1600x1200.png"));
|
||||
//BufferedImage image = ImageIO.read(new File("focus-rs.jpg"));
|
||||
//BufferedImage image = ImageIO.read(new File("blauesglas_16_bitmask444.bmp"));
|
||||
//image = ImageUtil.toBuffered(image, BufferedImage.TYPE_USHORT_GRAY);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
//new PixelizeOp(10).filter(image, null);
|
||||
//new AffineTransformOp(AffineTransform.getScaleInstance(.1, .1), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
|
||||
//ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
|
||||
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_BOX).filter(image, null);
|
||||
new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_QUADRATIC).filter(image, null);
|
||||
}
|
||||
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
//PixelizeOp pixelizer = new PixelizeOp(image.getWidth() / 10, 1);
|
||||
//pixelizer.setSourceRegion(new Rectangle(0, 2 * image.getHeight() / 3, image.getWidth(), image.getHeight() / 4));
|
||||
//PixelizeOp pixelizer = new PixelizeOp(4);
|
||||
//image = pixelizer.filter(image, image); // Filter in place, that's cool
|
||||
//image = new AffineTransformOp(AffineTransform.getScaleInstance(.25, .25), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
|
||||
//image = ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
|
||||
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_BOX).filter(image, null);
|
||||
image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_QUADRATIC).filter(image, null);
|
||||
long time = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println("time: " + time + " ms");
|
||||
|
||||
JFrame frame = new JFrame("Test");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setContentPane(new JScrollPane(new JLabel(new BufferedImageIcon(image))));
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,77 +0,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 "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.
|
||||
* <p/>
|
||||
* 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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Classes for image manipulation.
|
||||
* <p/>
|
||||
* See the class {@link com.twelvemonkeys.image.ImageUtil}.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
package com.twelvemonkeys.image;
|
||||
-366
@@ -1,366 +0,0 @@
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ImageProducer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* BufferedImageFactoryTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateNullImage() {
|
||||
new BufferedImageFactory((Image) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateNullProducer() {
|
||||
new BufferedImageFactory((ImageProducer) null);
|
||||
}
|
||||
|
||||
// NPE in Toolkit, ok
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testGetBufferedImageErrorSourceByteArray() {
|
||||
Image source = Toolkit.getDefaultToolkit().createImage((byte[]) null);
|
||||
|
||||
new BufferedImageFactory(source);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetBufferedImageErrorSourceImageProducer() {
|
||||
Image source = Toolkit.getDefaultToolkit().createImage((ImageProducer) null);
|
||||
|
||||
new BufferedImageFactory(source);
|
||||
}
|
||||
|
||||
// TODO: This is a quite serious bug, however, the bug is in the Toolkit, allowing such images in the first place...
|
||||
// In any case, there's not much we can do, except until someone is bored and kills the app/thread... :-P
|
||||
@Ignore("Bug in Toolkit")
|
||||
@Test(timeout = 1000, expected = ImageConversionException.class)
|
||||
public void testGetBufferedImageErrorSourceString() {
|
||||
Image source = Toolkit.getDefaultToolkit().createImage((String) null);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
factory.getBufferedImage();
|
||||
}
|
||||
|
||||
// This is a little random, and it would be nicer if we could throw an IllegalArgumentException on create.
|
||||
// Unfortunately, the API doesn't allow this...
|
||||
@Test(timeout = 1000, expected = ImageConversionException.class)
|
||||
public void testGetBufferedImageErrorSourceURL() {
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(getClass().getResource("/META-INF/MANIFEST.MF"));
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
factory.getBufferedImage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBufferedImageJPEG() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
BufferedImage image = factory.getBufferedImage();
|
||||
|
||||
assertEquals(187, image.getWidth());
|
||||
assertEquals(283, image.getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorModelJPEG() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
ColorModel colorModel = factory.getColorModel();
|
||||
|
||||
assertNotNull(colorModel);
|
||||
assertEquals(3, colorModel.getNumColorComponents()); // getNumComponents may include alpha, we don't care
|
||||
assertEquals(ColorSpace.getInstance(ColorSpace.CS_sRGB), colorModel.getColorSpace());
|
||||
|
||||
for (int i = 0; i < colorModel.getNumComponents(); i++) {
|
||||
assertEquals(8, colorModel.getComponentSize(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBufferedImageGIF() {
|
||||
URL resource = getClass().getResource("/tux.gif");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
BufferedImage image = factory.getBufferedImage();
|
||||
|
||||
assertEquals(250, image.getWidth());
|
||||
assertEquals(250, image.getHeight());
|
||||
|
||||
assertEquals(Transparency.BITMASK, image.getTransparency());
|
||||
|
||||
// All corners of image should be fully transparent
|
||||
assertEquals(0, image.getRGB(0, 0) >>> 24);
|
||||
assertEquals(0, image.getRGB(249, 0) >>> 24);
|
||||
assertEquals(0, image.getRGB(0, 249) >>> 24);
|
||||
assertEquals(0, image.getRGB(249, 249) >>> 24);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorModelGIF() {
|
||||
URL resource = getClass().getResource("/tux.gif");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
ColorModel colorModel = factory.getColorModel();
|
||||
|
||||
assertNotNull(colorModel);
|
||||
|
||||
assertEquals(3, colorModel.getNumColorComponents());
|
||||
assertEquals(ColorSpace.getInstance(ColorSpace.CS_sRGB), colorModel.getColorSpace());
|
||||
assertTrue(colorModel instanceof IndexColorModel);
|
||||
|
||||
assertTrue(colorModel.hasAlpha());
|
||||
assertEquals(4, colorModel.getNumComponents());
|
||||
assertTrue(((IndexColorModel) colorModel).getTransparentPixel() >= 0);
|
||||
assertEquals(Transparency.BITMASK, colorModel.getTransparency());
|
||||
|
||||
for (int i = 0; i < colorModel.getNumComponents(); i++) {
|
||||
assertEquals(8, colorModel.getComponentSize(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBufferedImageSubsampled() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
BufferedImage original = factory.getBufferedImage();
|
||||
|
||||
factory.setSourceSubsampling(2, 2);
|
||||
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
|
||||
|
||||
// Values rounded up
|
||||
assertEquals(94, image.getWidth());
|
||||
assertEquals(142, image.getHeight());
|
||||
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(x * 2, y * 2), image.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBufferedImageSourceRegion() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
BufferedImage original = factory.getBufferedImage();
|
||||
|
||||
factory.setSourceRegion(new Rectangle(40, 40, 40, 40));
|
||||
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
|
||||
|
||||
assertEquals(40, image.getWidth());
|
||||
assertEquals(40, image.getHeight());
|
||||
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(40 + x, 40 + y), image.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBufferedImageSubsampledSourceRegion() throws Exception{
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
BufferedImage original = factory.getBufferedImage();
|
||||
|
||||
factory.setSourceRegion(new Rectangle(40, 40, 40, 40));
|
||||
factory.setSourceSubsampling(2, 2);
|
||||
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
|
||||
|
||||
assertEquals(20, image.getWidth());
|
||||
assertEquals(20, image.getHeight());
|
||||
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(40 + x * 2, 40 + y * 2), image.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbort() throws Exception {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
final BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
|
||||
// Listener should abort ASAP
|
||||
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
||||
if (pPercentage > 5) {
|
||||
pFactory.abort();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BufferedImage image = factory.getBufferedImage();
|
||||
|
||||
assertEquals(187, image.getWidth());
|
||||
assertEquals(283, image.getHeight());
|
||||
|
||||
// Upper right should be loaded
|
||||
assertEquals((image.getRGB(186, 0) & 0xFF0000) >> 16 , 0x68, 10);
|
||||
assertEquals((image.getRGB(186, 0) & 0xFF00) >> 8, 0x91, 10);
|
||||
assertEquals(image.getRGB(186, 0) & 0xFF, 0xE0, 10);
|
||||
|
||||
// Lower right should be blank
|
||||
assertEquals(image.getRGB(186, 282) & 0xFFFFFF, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListener() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
|
||||
VerifyingListener listener = new VerifyingListener(factory);
|
||||
factory.addProgressListener(listener);
|
||||
factory.getBufferedImage();
|
||||
|
||||
listener.verify(100f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveListener() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
|
||||
VerifyingListener listener = new VerifyingListener(factory);
|
||||
factory.addProgressListener(listener);
|
||||
factory.removeProgressListener(listener);
|
||||
factory.getBufferedImage();
|
||||
|
||||
listener.verify(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveNullListener() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
|
||||
VerifyingListener listener = new VerifyingListener(factory);
|
||||
factory.addProgressListener(listener);
|
||||
factory.removeProgressListener(null);
|
||||
factory.getBufferedImage();
|
||||
|
||||
listener.verify(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveNotAdddedListener() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
|
||||
VerifyingListener listener = new VerifyingListener(factory);
|
||||
factory.addProgressListener(listener);
|
||||
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
||||
}
|
||||
});
|
||||
factory.getBufferedImage();
|
||||
|
||||
listener.verify(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAllListeners() {
|
||||
URL resource = getClass().getResource("/sunflower.jpg");
|
||||
assertNotNull(resource);
|
||||
Image source = Toolkit.getDefaultToolkit().createImage(resource);
|
||||
assertNotNull(source);
|
||||
|
||||
BufferedImageFactory factory = new BufferedImageFactory(source);
|
||||
|
||||
VerifyingListener listener = new VerifyingListener(factory);
|
||||
VerifyingListener listener2 = new VerifyingListener(factory);
|
||||
factory.addProgressListener(listener);
|
||||
factory.addProgressListener(listener);
|
||||
factory.addProgressListener(listener2);
|
||||
factory.removeAllProgressListeners();
|
||||
factory.getBufferedImage();
|
||||
|
||||
listener.verify(0);
|
||||
listener2.verify(0);
|
||||
}
|
||||
|
||||
private static class VerifyingListener implements BufferedImageFactory.ProgressListener {
|
||||
private final BufferedImageFactory factory;
|
||||
private float progress;
|
||||
|
||||
public VerifyingListener(BufferedImageFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
||||
assertEquals(factory, pFactory);
|
||||
assertTrue(pPercentage >= progress && pPercentage <= 100f);
|
||||
|
||||
progress = pPercentage;
|
||||
}
|
||||
|
||||
|
||||
public void verify(final float expectedProgress) {
|
||||
assertEquals(expectedProgress, progress, .1f); // Sanity test that the listener was invoked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
|
||||
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());
|
||||
|
||||
// Hmmm...
|
||||
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
|
||||
|| bufferedScaled.getPropertyNames() == null
|
||||
|| bufferedScaled.getPropertyNames().length == 0);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ImagingOpException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ResampleOpTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 {
|
||||
|
||||
protected BufferedImage createImage(final int pWidth, final int pHeigth) {
|
||||
return createImage(pWidth, pHeigth, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
protected BufferedImage createImage(final int pWidth, final int pHeigth, final int pType) {
|
||||
BufferedImage image = new BufferedImage(pWidth, pHeigth, pType);
|
||||
Graphics2D g = image.createGraphics();
|
||||
try {
|
||||
g.setPaint(new GradientPaint(0, 0, Color.RED, pWidth, pHeigth, new Color(0x00000000, true)));
|
||||
g.fillRect(0, 0, pWidth, pHeigth);
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateImage() {
|
||||
// Sanity test the create method
|
||||
BufferedImage image = createImage(79, 84);
|
||||
assertNotNull(image);
|
||||
assertEquals(79, image.getWidth());
|
||||
assertEquals(84, image.getHeight());
|
||||
}
|
||||
|
||||
private void assertResample(final BufferedImage pImage, final int pWidth, final int pHeight, final int pFilterType) {
|
||||
BufferedImage result = new ResampleOp(pWidth, pHeight, pFilterType).filter(pImage, null);
|
||||
assertNotNull(result);
|
||||
assertEquals(pWidth, result.getWidth());
|
||||
assertEquals(pHeight, result.getHeight());
|
||||
|
||||
result = new ResampleOp(pImage.getWidth(), pImage.getHeight(), pFilterType).filter(createImage(pWidth, pHeight), pImage);
|
||||
assertNotNull(result);
|
||||
assertEquals(pImage.getType(), result.getType());
|
||||
assertSame(pImage, result);
|
||||
assertEquals(pImage.getWidth(), result.getWidth());
|
||||
assertEquals(pImage.getHeight(), result.getHeight());
|
||||
|
||||
result = new ResampleOp(pImage.getWidth(), pImage.getHeight(), pFilterType).filter(createImage(pWidth, pHeight), createImage(pWidth, pHeight, pImage.getType()));
|
||||
assertNotNull(result);
|
||||
assertEquals(pImage.getType(), result.getType());
|
||||
assertEquals(pWidth, result.getWidth());
|
||||
assertEquals(pHeight, result.getHeight());
|
||||
}
|
||||
|
||||
private void assertResampleBufferedImageTypes(final int pFilterType) {
|
||||
List<String> exceptions = new ArrayList<String>();
|
||||
|
||||
// Test all image types in BufferedImage
|
||||
for (int type = BufferedImage.TYPE_INT_ARGB; type <= BufferedImage.TYPE_BYTE_INDEXED; type++) {
|
||||
// TODO: Does not currently work with TYPE_BYTE_GRAY or TYPE_USHORT_GRAY
|
||||
// TODO: FixMe!
|
||||
if ((pFilterType == ResampleOp.FILTER_POINT || pFilterType == ResampleOp.FILTER_TRIANGLE) &&
|
||||
(type == BufferedImage.TYPE_BYTE_GRAY || type == BufferedImage.TYPE_USHORT_GRAY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferedImage image = createImage(10, 10, type);
|
||||
try {
|
||||
assertResample(image, 15, 5, pFilterType);
|
||||
}
|
||||
catch (ImagingOpException e) {
|
||||
// NOTE: It is currently allowed for filters to throw this exception and it is PLATFORM DEPENDENT..
|
||||
System.err.println("WARNING: " + e.getMessage() + ", image: " + image);
|
||||
//e.printStackTrace();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
exceptions.add(t.toString() + ": " + image.toString());
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("Filter threw exceptions: ", Collections.EMPTY_LIST, exceptions);
|
||||
}
|
||||
|
||||
// 1x1
|
||||
@Test
|
||||
public void testResample1x1Point() {
|
||||
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Box() {
|
||||
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Triangle() {
|
||||
assertResample(createImage(1, 1), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Lanczos() {
|
||||
assertResample(createImage(1, 1), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Gaussian() {
|
||||
assertResample(createImage(1, 1), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Sinc() {
|
||||
assertResample(createImage(1, 1), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 2x2
|
||||
@Test
|
||||
public void testResample2x2Point() {
|
||||
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Box() {
|
||||
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Triangle() {
|
||||
assertResample(createImage(2, 2), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Lanczos() {
|
||||
assertResample(createImage(2, 2), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Gaussian() {
|
||||
assertResample(createImage(2, 2), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Sinc() {
|
||||
assertResample(createImage(2, 2), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 3x3
|
||||
@Test
|
||||
public void testResample3x3Point() {
|
||||
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Box() {
|
||||
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Triangle() {
|
||||
assertResample(createImage(3, 3), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Lanczos() {
|
||||
assertResample(createImage(3, 3), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Gaussian() {
|
||||
assertResample(createImage(3, 3), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Sinc() {
|
||||
assertResample(createImage(3, 3), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 4x4
|
||||
@Test
|
||||
public void testResample4x4Point() {
|
||||
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Box() {
|
||||
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Triangle() {
|
||||
assertResample(createImage(4, 4), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Lanczos() {
|
||||
assertResample(createImage(4, 4), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Gaussian() {
|
||||
assertResample(createImage(4, 4), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Sinc() {
|
||||
assertResample(createImage(4, 4), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 20x20
|
||||
@Test
|
||||
public void testResample20x20Point() {
|
||||
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Box() {
|
||||
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Triangle() {
|
||||
assertResample(createImage(20, 20), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Lanczos() {
|
||||
assertResample(createImage(20, 20), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Gaussian() {
|
||||
assertResample(createImage(20, 20), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Sinc() {
|
||||
assertResample(createImage(20, 20), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 200x160
|
||||
@Test
|
||||
public void testResample200x160Point() {
|
||||
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Box() {
|
||||
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Triangle() {
|
||||
assertResample(createImage(200, 160), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Lanczos() {
|
||||
assertResample(createImage(200, 160), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Gaussian() {
|
||||
assertResample(createImage(200, 160), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Sinc() {
|
||||
assertResample(createImage(200, 160), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// Test 10x10 -> 15x5 with different algorithms and types
|
||||
@Test
|
||||
public void testResamplePoint() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResampleBox() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResampleTriangle() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResampleLanczos() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Ignore("Not for general unit testing")
|
||||
@Test
|
||||
public void testTime() {
|
||||
int iterations = 1000;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
assertResample(createImage(50, 50), 33, 33, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
assertResample(createImage(512, 512), 145, 145, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.printf("time: %d ms, avg %s ms%n", end - start, (end - start) / (double) iterations);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.7 KiB |
@@ -1,6 +0,0 @@
|
||||
TODO:
|
||||
Remove compile-time dependency on JMagick:
|
||||
- Extract interface for MagickAccelerator
|
||||
- Move implementation to separate module
|
||||
- Instantiate impl via reflection
|
||||
DONE:
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>TwelveMonkeys :: Common :: IO</name>
|
||||
<description>
|
||||
The TwelveMonkeys Common IO support
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Represents a cached seekable stream, that reads through a cache.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 $
|
||||
*/
|
||||
abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
/** The backing stream */
|
||||
protected final InputStream stream;
|
||||
|
||||
/** The stream positon in the backing stream (stream) */
|
||||
protected long streamPosition;
|
||||
|
||||
private StreamCache cache;
|
||||
|
||||
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
|
||||
Validate.notNull(pStream, "stream");
|
||||
Validate.notNull(pCache, "cache");
|
||||
|
||||
stream = pStream;
|
||||
cache = pCache;
|
||||
}
|
||||
|
||||
protected final StreamCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long avail = streamPosition - position + stream.available();
|
||||
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
checkOpen();
|
||||
int read;
|
||||
|
||||
if (position == streamPosition) {
|
||||
// TODO: Read more bytes here!
|
||||
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
|
||||
// Read a byte from the stream
|
||||
read = stream.read();
|
||||
|
||||
if (read >= 0) {
|
||||
streamPosition++;
|
||||
cache.write(read);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// ..or read byte from the cache
|
||||
syncPosition();
|
||||
read = cache.read();
|
||||
}
|
||||
|
||||
// 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 = stream.read(pBytes, pOffset, pLength);
|
||||
|
||||
if (length > 0) {
|
||||
streamPosition += length;
|
||||
cache.write(pBytes, pOffset, length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// ...or read bytes from the cache
|
||||
syncPosition();
|
||||
length = cache.read(pBytes, pOffset, pLength);
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (length > 0) {
|
||||
position += length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
protected final void syncPosition() throws IOException {
|
||||
if (cache.getPosition() != position) {
|
||||
cache.seek(position); // Assure EOF is correctly thrown
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean isCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract boolean isCachedMemory();
|
||||
|
||||
public abstract boolean isCachedFile();
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
if (streamPosition < pPosition) {
|
||||
// Make sure we append at end of cache
|
||||
if (cache.getPosition() != streamPosition) {
|
||||
cache.seek(streamPosition);
|
||||
}
|
||||
|
||||
// Read diff from stream into cache
|
||||
long left = pPosition - streamPosition;
|
||||
|
||||
// TODO: Use fixed buffer, instead of allocating here...
|
||||
int bufferLen = left > 1024 ? 1024 : (int) left;
|
||||
byte[] buffer = new byte[bufferLen];
|
||||
|
||||
while (left > 0) {
|
||||
int length = buffer.length < left ? buffer.length : (int) left;
|
||||
int read = stream.read(buffer, 0, length);
|
||||
|
||||
if (read > 0) {
|
||||
cache.write(buffer, 0, read);
|
||||
streamPosition += read;
|
||||
left -= read;
|
||||
}
|
||||
else if (read < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else /*if (streamPosition >= pPosition) */ {
|
||||
// Seek backwards into the cache
|
||||
cache.seek(pPosition);
|
||||
}
|
||||
|
||||
// System.out.println("pPosition: " + pPosition);
|
||||
// System.out.println("position: " + position);
|
||||
// System.out.println("streamPosition: " + streamPosition);
|
||||
// System.out.println("cache.position: " + cache.getPosition());
|
||||
|
||||
// NOTE: If position == pPosition then we're good to go
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) {
|
||||
cache.flush(pPosition);
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
cache.flush(position);
|
||||
cache = null;
|
||||
stream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract stream cache.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* Creates a {@code StreamCache}.
|
||||
*/
|
||||
protected StreamCache() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single byte at the current read/write position. The read/write position will be increased by one.
|
||||
*
|
||||
* @param pByte the byte value to write.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
abstract void write(int pByte) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a series of bytes at the current read/write position. The read/write position will be increased by
|
||||
* {@code pLength}.
|
||||
* <p/>
|
||||
* 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.
|
||||
* @param pLength the number of bytes to write from the buffer.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
write(pBuffer[pOffset + i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single byte a the current read/write position. The read/write position will be increased by one.
|
||||
*
|
||||
* @return the value read, or {@code -1} to indicate EOF.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
abstract int read() throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a series of bytes at the current read/write position. The read/write position will be increased by
|
||||
* {@code pLength}.
|
||||
* <p/>
|
||||
* 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.
|
||||
* @param pLength the number of bytes to write from the buffer.
|
||||
* @return the number of bytes read, or {@code -1} to indicate EOF.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
int count = 0;
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
int read = read();
|
||||
if (read >= 0) {
|
||||
pBuffer[pOffset + i] = (byte) read;
|
||||
count++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions the current cache read/write position to the given position.
|
||||
*
|
||||
* @param pPosition the new read/write position
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
abstract void seek(long pPosition) throws IOException;
|
||||
|
||||
/**
|
||||
* Optionally flushes any data prior to the given position.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
*/
|
||||
void flush(final long pPosition) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current cache read/write position.
|
||||
*
|
||||
* @return the current cache read/write postion.
|
||||
*
|
||||
* @throws IOException if the position can't be determined because of a problem in the cache backing mechanism.
|
||||
*/
|
||||
abstract long getPosition() throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<Reader> 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<Reader> 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<Reader>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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("");
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,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 "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}.
|
||||
* <p/>
|
||||
* 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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,130 +0,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 "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}.
|
||||
* <p/>
|
||||
* @see FileCacheSeekableStream
|
||||
* @see MemoryCacheSeekableStream
|
||||
* @see RandomAccessFile
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,242 +0,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 "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.
|
||||
* <p/>
|
||||
* The file name masks are used as a filter input and is given to the class via
|
||||
* the string array property:<br>
|
||||
* <dd>{@code filenameMasksForInclusion} - Filename mask for exclusion of
|
||||
* files (default if both properties are defined)
|
||||
* <dd>{@code filenameMasksForExclusion} - Filename mask for exclusion of
|
||||
* files.
|
||||
* <p/>
|
||||
* 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 <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -1,451 +0,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 "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.
|
||||
* <p/>
|
||||
* The standard {@code java.io.DataInputStream} class
|
||||
* which this class imitates reads big endian quantities.
|
||||
* <p/>
|
||||
* <em>Warning:
|
||||
* <!-- Beware of little indians! -->
|
||||
* 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.
|
||||
* </em>
|
||||
*
|
||||
* @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
|
||||
* @see java.io.DataInputStream
|
||||
* @see java.io.DataInput
|
||||
* @see java.io.DataOutput
|
||||
*
|
||||
* @author Elliotte Rusty Harold
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 <b>big</b> 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}.
|
||||
* <p>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
-339
@@ -1,339 +0,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 "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.
|
||||
* <p/>
|
||||
* The standard {@code java.io.DataOutputStream} class which this class
|
||||
* imitates uses big endian integers.
|
||||
* <p/>
|
||||
* <em>Warning:
|
||||
* <!-- Beware of little indians! -->
|
||||
* 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.
|
||||
* </em>
|
||||
*
|
||||
* @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 <b>big</b> 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;
|
||||
}
|
||||
}
|
||||
-626
@@ -1,626 +0,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 "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.
|
||||
* <p/>
|
||||
* <em>Warning:
|
||||
* <!-- Beware of little indians! -->
|
||||
* 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.
|
||||
* </em>
|
||||
*
|
||||
* @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 <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @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 <b>big</b> 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 <b>big</b> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @see FileCacheSeekableStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<byte[]> cache = new ArrayList<byte[]>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 {
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @see java.io.RandomAccessFile
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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.
|
||||
* <p/>
|
||||
* <em>Note that read access is NOT synchronized.</em>
|
||||
*
|
||||
* @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.
|
||||
* <p/>
|
||||
* <em>Note that write access is NOT synchronized.</em>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,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 "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.
|
||||
* <p/>
|
||||
* @see SeekableInputStream
|
||||
* @see SeekableOutputStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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.
|
||||
* <p/>
|
||||
* An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller
|
||||
* than the flushed position (as returned by {@link #getFlushedPosition()}).
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* Calls to reset without a corresponding call to mark will either:
|
||||
* <ul>
|
||||
* <li>throw an {@code IOException}</li>
|
||||
* <li>or, reset to the beginning of the stream.</li>
|
||||
* </ul>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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;
|
||||
}
|
||||
@@ -1,236 +0,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 "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.
|
||||
* <p/>
|
||||
* @see SeekableOutputStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<Long> markedPositions = new Stack<Long>();
|
||||
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,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 "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.
|
||||
* <p/>
|
||||
* @see SeekableInputStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<Long> markedPositions = new Stack<Long>();
|
||||
|
||||
/// 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;
|
||||
}
|
||||
@@ -1,187 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,135 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 <em>not</em> 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;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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";
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,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 "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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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";
|
||||
}
|
||||
}
|
||||
@@ -1,473 +0,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 "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.
|
||||
* <p/>
|
||||
* This class is based on example code from
|
||||
* <a href="http://www.oreilly.com/catalog/swinghks/index.html">Swing Hacks</a>,
|
||||
* By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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.
|
||||
* <p/>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,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 "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}.
|
||||
* <p/>
|
||||
* <em>Instances of this class are not thread-safe.</em>
|
||||
* <p/>
|
||||
* <em>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.
|
||||
* </em>
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,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 "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;
|
||||
|
||||
/**
|
||||
* Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java#1 $
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
abstract class AbstractRLEDecoder implements Decoder {
|
||||
protected final byte[] row;
|
||||
protected final int width;
|
||||
protected int srcX;
|
||||
protected int srcY;
|
||||
protected int dstX;
|
||||
protected int dstY;
|
||||
|
||||
/**
|
||||
* Creates an RLEDecoder. As RLE encoded BMPs may contain x and y deltas,
|
||||
* etc, we need to know height and width of the image.
|
||||
*
|
||||
* @param pWidth width of the image
|
||||
* @param pHeight height of the image
|
||||
*/
|
||||
AbstractRLEDecoder(final int pWidth, final int pHeight) {
|
||||
width = pWidth;
|
||||
int bytesPerRow = width;
|
||||
int mod = bytesPerRow % 4;
|
||||
|
||||
if (mod != 0) {
|
||||
bytesPerRow += 4 - mod;
|
||||
}
|
||||
|
||||
row = new byte[bytesPerRow];
|
||||
|
||||
srcX = 0;
|
||||
srcY = pHeight - 1;
|
||||
|
||||
dstX = srcX;
|
||||
dstY = srcY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes one full row of image data.
|
||||
*
|
||||
* @param pStream the input stream containing RLE data
|
||||
*
|
||||
* @throws IOException if an I/O related exception occurs while reading
|
||||
*/
|
||||
protected abstract void decodeRow(final InputStream pStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Decodes as much data as possible, from the stream into the buffer.
|
||||
*
|
||||
* @param stream the input stream containing RLE data
|
||||
* @param buffer the buffer to decode the data to
|
||||
*
|
||||
* @return the number of bytes decoded from the stream, to the buffer
|
||||
*
|
||||
* @throws IOException if an I/O related exception ocurs while reading
|
||||
*/
|
||||
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
while (buffer.hasRemaining() && dstY >= 0) {
|
||||
// NOTE: Decode only full rows, don't decode if y delta
|
||||
if (dstX == 0 && srcY == dstY) {
|
||||
decodeRow(stream);
|
||||
}
|
||||
|
||||
int length = Math.min(row.length - dstX, buffer.remaining());
|
||||
// System.arraycopy(row, dstX, buffer, decoded, length);
|
||||
buffer.put(row, 0, length);
|
||||
dstX += length;
|
||||
// decoded += length;
|
||||
|
||||
if (dstX == row.length) {
|
||||
dstX = 0;
|
||||
dstY--;
|
||||
|
||||
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
|
||||
// gap with zero-bytes
|
||||
if (dstY > srcY) {
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
row[i] = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a read byte for EOF marker.
|
||||
*
|
||||
* @param pByte the byte to check
|
||||
* @return the value of {@code pByte} if positive.
|
||||
*
|
||||
* @throws EOFException if {@code pByte} is negative
|
||||
*/
|
||||
protected static int checkEOF(final int pByte) throws EOFException {
|
||||
if (pByte < 0) {
|
||||
throw new EOFException("Premature end of file");
|
||||
}
|
||||
|
||||
return pByte;
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,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 "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.
|
||||
* <p/>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2045"RFC 2045</a>
|
||||
*
|
||||
* @see Base64Encoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,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 "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.
|
||||
* <p/>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2045"RFC 2045</a>
|
||||
*
|
||||
* @see Base64Decoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,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 "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}.
|
||||
* <p/>
|
||||
* Important note: Decoder implementations are typically not synchronized.
|
||||
* <p/>
|
||||
* @see Encoder
|
||||
* @see DecoderStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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
|
||||
*/
|
||||
int decode(InputStream stream, ByteBuffer buffer) throws IOException;
|
||||
}
|
||||
@@ -1,198 +0,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 "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.
|
||||
* <p/>
|
||||
* @see EncoderStream
|
||||
* @see Decoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,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 "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}.
|
||||
* <p/>
|
||||
* Important note: Encoder implementations are typically not synchronized.
|
||||
*
|
||||
* @see Decoder
|
||||
* @see EncoderStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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()?
|
||||
}
|
||||
@@ -1,135 +0,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 "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.
|
||||
* <p/>
|
||||
* @see DecoderStream
|
||||
* @see Encoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,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 "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 16 bit-chunked Apple PackBits-like run-length
|
||||
* encoding.
|
||||
* <p/>
|
||||
* This version of the decoder decodes chunk of 16 bit, instead of 8 bit.
|
||||
* This format is used in certain PICT files.
|
||||
*
|
||||
* @see PackBitsDecoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java#2 $
|
||||
*/
|
||||
public final class PackBits16Decoder implements Decoder {
|
||||
// TODO: Refactor this into an option for the PackBitsDecoder?
|
||||
private final boolean disableNoop;
|
||||
|
||||
private int leftOfRun;
|
||||
private boolean splitRun;
|
||||
private boolean reachedEOF;
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}.
|
||||
*/
|
||||
public PackBits16Decoder() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}.
|
||||
* <p/>
|
||||
* 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 pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||
*/
|
||||
public PackBits16Decoder(final boolean pDisableNoop) {
|
||||
disableNoop = pDisableNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
final int max = buffer.capacity();
|
||||
|
||||
while (read < max) {
|
||||
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 && 2 * (n + 1) + read > max) {
|
||||
leftOfRun = n;
|
||||
splitRun = true;
|
||||
break;
|
||||
}
|
||||
else if (n < 0 && 2 * (-n + 1) + read > max) {
|
||||
leftOfRun = n;
|
||||
splitRun = true;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
if (n >= 0) {
|
||||
// Copy next n + 1 shorts literally
|
||||
int len = 2 * (n + 1);
|
||||
readFully(stream, buffer, len);
|
||||
read += len;
|
||||
}
|
||||
// Allow -128 for compatibility, see above
|
||||
else if (disableNoop || n != -128) {
|
||||
// Replicate the next short -n + 1 times
|
||||
byte value1 = readByte(stream);
|
||||
byte value2 = readByte(stream);
|
||||
|
||||
for (int i = -n + 1; i > 0; i--) {
|
||||
buffer.put(value1);
|
||||
buffer.put(value2);
|
||||
}
|
||||
}
|
||||
// else NOOP (-128)
|
||||
}
|
||||
catch (IndexOutOfBoundsException e) {
|
||||
throw new DecodeException("Error in PackBits decompression, data seems corrupt", e);
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException {
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
|
||||
while (read < pLength) {
|
||||
int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + read, pLength - read);
|
||||
|
||||
if (count < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
}
|
||||
|
||||
read += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,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 "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.
|
||||
* <p/>
|
||||
* <small>From Wikipedia, the free encyclopedia</small><br/>
|
||||
* PackBits is a fast, simple compression scheme for run-length encoding of
|
||||
* data.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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).
|
||||
* <p/>
|
||||
* <table><tr><th>Header byte</th><th>Data</th></tr>
|
||||
* <tr><td>0 to 127</td> <td>1 + <i>n</i> literal bytes of data</td></tr>
|
||||
* <tr><td>0 to -127</td> <td>One byte of data, repeated 1 - <i>n</i> times in
|
||||
* the decompressed output</td></tr>
|
||||
* <tr><td>-128</td> <td>No operation</td></tr></table>
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* See <a href="http://developer.apple.com/technotes/tn/tn1023.html">Understanding PackBits</a>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 int leftOfRun;
|
||||
private boolean splitRun;
|
||||
private boolean reachedEOF;
|
||||
|
||||
/** Creates a {@code PackBitsDecoder}. */
|
||||
public PackBitsDecoder() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
|
||||
* <p/>
|
||||
* 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 pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||
*/
|
||||
public PackBitsDecoder(final boolean pDisableNoop) {
|
||||
disableNoop = pDisableNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, n + 1);
|
||||
}
|
||||
// Allow -128 for compatibility, see above
|
||||
else if (disableNoop || n != -128) {
|
||||
// Replicate the next byte -n + 1 times
|
||||
byte value = readByte(stream);
|
||||
|
||||
for (int i = -n + 1; i > 0; i--) {
|
||||
buffer.put(value);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,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 "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.
|
||||
* <p/>
|
||||
* From Wikipedia, the free encyclopedia<br/>
|
||||
* PackBits is a fast, simple compression scheme for run-length encoding of
|
||||
* data.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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).
|
||||
* <p/>
|
||||
* <table><tr><th>Header byte</th><th>Data</th></tr>
|
||||
* <tr><td>0 to 127</td> <td>1 + <i>n</i> literal bytes of data</td></tr>
|
||||
* <tr><td>0 to -127</td> <td>One byte of data, repeated 1 - <i>n</i> times in
|
||||
* the decompressed output</td></tr>
|
||||
* <tr><td>-128</td> <td>No operation</td></tr></table>
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* See <a href="http://developer.apple.com/technotes/tn/tn1023.html">Understanding PackBits</a>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,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 "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.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements 4 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE4Decoder.java#1 $
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
|
||||
public RLE4Decoder(final int pWidth, final int pHeight) {
|
||||
super((pWidth + 1) / 2, pHeight);
|
||||
}
|
||||
|
||||
protected void decodeRow(final InputStream pInput) throws IOException {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
|
||||
while (srcY >= 0) {
|
||||
int byte1 = pInput.read();
|
||||
int byte2 = checkEOF(pInput.read());
|
||||
|
||||
if (byte1 == 0x00) {
|
||||
switch (byte2) {
|
||||
case 0x00:
|
||||
// End of line
|
||||
// NOTE: Some BMPs have double EOLs..
|
||||
if (srcX != 0) {
|
||||
srcX = row.length;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
// End of bitmap
|
||||
srcX = row.length;
|
||||
srcY = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Delta
|
||||
deltaX = srcX + pInput.read();
|
||||
deltaY = srcY - checkEOF(pInput.read());
|
||||
srcX = row.length;
|
||||
break;
|
||||
default:
|
||||
// Absolute mode
|
||||
// Copy the next byte2 (3..255) bytes from file to output
|
||||
// Two samples are packed into one byte
|
||||
// If the number of bytes used to pack is not a mulitple of 2,
|
||||
// an additional padding byte is in the stream and must be skipped
|
||||
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
||||
while (byte2 > 1) {
|
||||
int packed = checkEOF(pInput.read());
|
||||
row[srcX++] = (byte) packed;
|
||||
byte2 -= 2;
|
||||
}
|
||||
if (byte2 == 1) {
|
||||
// TODO: Half byte alignment? Seems to be ok...
|
||||
int packed = checkEOF(pInput.read());
|
||||
row[srcX++] = (byte) (packed & 0xf0);
|
||||
}
|
||||
if (paddingByte) {
|
||||
checkEOF(pInput.read());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Encoded mode
|
||||
// Replicate the two samples in byte2 as many times as byte1 says
|
||||
while (byte1 > 1) {
|
||||
row[srcX++] = (byte) byte2;
|
||||
byte1 -= 2;
|
||||
}
|
||||
|
||||
if (byte1 == 1) {
|
||||
// TODO: Half byte alignment? Seems to be ok...
|
||||
row[srcX++] = (byte) (byte2 & 0xf0);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're done with a complete row, copy the data
|
||||
if (srcX == row.length) {
|
||||
// Move to new position, either absolute (delta) or next line
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
srcX = (deltaX + 1) / 2;
|
||||
|
||||
if (deltaY > srcY) {
|
||||
srcY = deltaY;
|
||||
break;
|
||||
}
|
||||
|
||||
deltaX = 0;
|
||||
deltaY = 0;
|
||||
}
|
||||
else {
|
||||
srcX = 0;
|
||||
srcY--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,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 "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.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE8Decoder.java#1 $
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
final class RLE8Decoder extends AbstractRLEDecoder {
|
||||
|
||||
public RLE8Decoder(final int pWidth, final int pHeight) {
|
||||
super(pWidth, pHeight);
|
||||
}
|
||||
|
||||
protected void decodeRow(final InputStream pInput) throws IOException {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
|
||||
while (srcY >= 0) {
|
||||
int byte1 = pInput.read();
|
||||
int byte2 = checkEOF(pInput.read());
|
||||
|
||||
if (byte1 == 0x00) {
|
||||
switch (byte2) {
|
||||
case 0x00:
|
||||
// End of line
|
||||
// NOTE: Some BMPs have double EOLs..
|
||||
if (srcX != 0) {
|
||||
srcX = row.length;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
// End of bitmap
|
||||
srcX = row.length;
|
||||
srcY = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Delta
|
||||
deltaX = srcX + pInput.read();
|
||||
deltaY = srcY - checkEOF(pInput.read());
|
||||
srcX = row.length;
|
||||
break;
|
||||
default:
|
||||
// Absolute mode
|
||||
// Copy the next byte2 (3..255) bytes from file to output
|
||||
boolean paddingByte = (byte2 % 2) != 0;
|
||||
while (byte2-- > 0) {
|
||||
row[srcX++] = (byte) checkEOF(pInput.read());
|
||||
}
|
||||
if (paddingByte) {
|
||||
checkEOF(pInput.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Encoded mode
|
||||
// Replicate byte2 as many times as byte1 says
|
||||
byte value = (byte) byte2;
|
||||
while (byte1-- > 0) {
|
||||
row[srcX++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're done with a complete row, copy the data
|
||||
if (srcX == row.length) {
|
||||
|
||||
// Move to new position, either absolute (delta) or next line
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
srcX = deltaX;
|
||||
if (deltaY != srcY) {
|
||||
srcY = deltaY;
|
||||
break;
|
||||
}
|
||||
deltaX = 0;
|
||||
deltaY = 0;
|
||||
}
|
||||
else {
|
||||
srcX = 0;
|
||||
srcY--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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..
|
||||
*
|
||||
* @see com.twelvemonkeys.io.enc.DecoderStream
|
||||
* @see com.twelvemonkeys.io.enc.EncoderStream
|
||||
* @see com.twelvemonkeys.io.enc.Decoder
|
||||
* @see com.twelvemonkeys.io.enc.Encoder
|
||||
* @see com.twelvemonkeys.io.enc.DecodeException
|
||||
*
|
||||
* @version 2.0
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
package com.twelvemonkeys.io.enc;
|
||||
@@ -1,764 +0,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 "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.
|
||||
* <p/>
|
||||
* <!-- TODO: Consider really detaching the entries, as this is hard for users to enforce... -->
|
||||
* <em>NOTE: This class is not synchronized. Accessing the document or its
|
||||
* entries from different threads, will need synchronization on the document
|
||||
* instance.</em>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @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<Entry> getEntries(final int pDirectoryId, final Entry pParent)
|
||||
throws IOException {
|
||||
return getEntriesRecursive(pDirectoryId, pParent, new TreeSet<Entry>());
|
||||
}
|
||||
|
||||
private SortedSet<Entry> getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet<Entry> 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.
|
||||
* <p/>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,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 "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 java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown when an OLE 2 compound document is considered corrupt.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/CorruptDocumentException.java#3 $
|
||||
* @see com.twelvemonkeys.io.ole2.CompoundDocument
|
||||
*/
|
||||
public class CorruptDocumentException extends IOException {
|
||||
public CorruptDocumentException() {
|
||||
this("Corrupt OLE 2 Compound Document");
|
||||
}
|
||||
|
||||
public CorruptDocumentException(final String pMessage) {
|
||||
super(pMessage);
|
||||
}
|
||||
|
||||
public CorruptDocumentException(final Throwable pCause) {
|
||||
super(pCause.getMessage());
|
||||
initCause(pCause);
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,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 "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 <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @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<Entry> {
|
||||
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<Entry> 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<Entry> NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet<Entry>());
|
||||
|
||||
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).
|
||||
* <p/>
|
||||
* 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).
|
||||
* <p/>
|
||||
* 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<Entry> 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);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,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 "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 java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* SIdChain
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java#1 $
|
||||
*/
|
||||
final class SIdChain {
|
||||
int[] chain;
|
||||
int size = 0;
|
||||
int next = 0;
|
||||
|
||||
public SIdChain() {
|
||||
chain = new int[16];
|
||||
}
|
||||
|
||||
void addSID(int pSID) {
|
||||
ensureCapacity();
|
||||
chain[size++] = pSID;
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if (chain.length == size) {
|
||||
int[] temp = new int[size << 1];
|
||||
System.arraycopy(chain, 0, temp, 0, size);
|
||||
chain = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getChain() {
|
||||
int[] result = new int[size];
|
||||
System.arraycopy(chain, 0, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
next = 0;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return next < size;
|
||||
}
|
||||
|
||||
public int next() {
|
||||
if (next >= size) {
|
||||
throw new NoSuchElementException("No element");
|
||||
}
|
||||
return chain[next++];
|
||||
}
|
||||
|
||||
public int get(final int pIndex) {
|
||||
return chain[pIndex];
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(size * 5);
|
||||
buf.append('[');
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i != 0) {
|
||||
buf.append(',');
|
||||
}
|
||||
buf.append(chain[i]);
|
||||
}
|
||||
buf.append(']');
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Contains classes for reading the contents of the
|
||||
* Microsoft OLE 2 compound document format.
|
||||
*
|
||||
* @see com.twelvemonkeys.io.ole2.CompoundDocument
|
||||
* @see <a href="http://sc.openoffice.org/compdocfileformat.pdf">OpenOffice.org's documentation</a>
|
||||
*
|
||||
* @version 2.0
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
package com.twelvemonkeys.io.ole2;
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Provides for system input and output through data streams, serialization and the file system.
|
||||
*/
|
||||
package com.twelvemonkeys.io;
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "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.DateUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* HTTPUtil
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: HTTPUtil.java,v 1.0 08.09.13 13:57 haraldk Exp$
|
||||
*/
|
||||
public class HTTPUtil {
|
||||
/**
|
||||
* RFC 1123 date format, as recommended by RFC 2616 (HTTP/1.1), sec 3.3
|
||||
* NOTE: All date formats are private, to ensure synchronized access.
|
||||
*/
|
||||
private static final SimpleDateFormat HTTP_RFC1123_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
|
||||
static {
|
||||
HTTP_RFC1123_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 850 date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3
|
||||
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
|
||||
*/
|
||||
private static final SimpleDateFormat HTTP_RFC850_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US);
|
||||
/**
|
||||
* ANSI C asctime() date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3.
|
||||
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
|
||||
*/
|
||||
private static final SimpleDateFormat HTTP_ASCTIME_FORMAT = new SimpleDateFormat("EEE MMM d HH:mm:ss yy", Locale.US);
|
||||
|
||||
private static long sNext50YearWindowChange = DateUtil.currentTimeDay();
|
||||
static {
|
||||
HTTP_RFC850_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
HTTP_ASCTIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3:
|
||||
// - HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
|
||||
// which appears to be more than 50 years in the future is in fact
|
||||
// in the past (this helps solve the "year 2000" problem).
|
||||
update50YearWindowIfNeeded();
|
||||
}
|
||||
|
||||
private static void update50YearWindowIfNeeded() {
|
||||
// Avoid class synchronization
|
||||
long next = sNext50YearWindowChange;
|
||||
|
||||
if (next < System.currentTimeMillis()) {
|
||||
// Next check in one day
|
||||
next += DateUtil.DAY;
|
||||
sNext50YearWindowChange = next;
|
||||
|
||||
Date startDate = new Date(next - (50l * DateUtil.CALENDAR_YEAR));
|
||||
//System.out.println("next test: " + new Date(next) + ", 50 year start: " + startDate);
|
||||
synchronized (HTTP_RFC850_FORMAT) {
|
||||
HTTP_RFC850_FORMAT.set2DigitYearStart(startDate);
|
||||
}
|
||||
synchronized (HTTP_ASCTIME_FORMAT) {
|
||||
HTTP_ASCTIME_FORMAT.set2DigitYearStart(startDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HTTPUtil() {}
|
||||
|
||||
/**
|
||||
* Formats the time to a HTTP date, using the RFC 1123 format, as described
|
||||
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
|
||||
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
|
||||
*
|
||||
* @param pTime the time
|
||||
* @return a {@code String} representation of the time
|
||||
*/
|
||||
public static String formatHTTPDate(long pTime) {
|
||||
return formatHTTPDate(new Date(pTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the time to a HTTP date, using the RFC 1123 format, as described
|
||||
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
|
||||
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
|
||||
*
|
||||
* @param pTime the time
|
||||
* @return a {@code String} representation of the time
|
||||
*/
|
||||
public static String formatHTTPDate(Date pTime) {
|
||||
synchronized (HTTP_RFC1123_FORMAT) {
|
||||
return HTTP_RFC1123_FORMAT.format(pTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a HTTP date string into a {@code long} representing milliseconds
|
||||
* since January 1, 1970 GMT.
|
||||
* <p>
|
||||
* Use this method with headers that contain dates, such as
|
||||
* {@code If-Modified-Since} or {@code Last-Modified}.
|
||||
* <p>
|
||||
* The date string may be in either RFC 1123, RFC 850 or ANSI C asctime()
|
||||
* format, as described in
|
||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
|
||||
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>
|
||||
*
|
||||
* @param pDate the date to parse
|
||||
*
|
||||
* @return a {@code long} value representing the date, expressed as the
|
||||
* number of milliseconds since January 1, 1970 GMT,
|
||||
* @throws NumberFormatException if the date parameter is not parseable.
|
||||
* @throws IllegalArgumentException if the date paramter is {@code null}
|
||||
*/
|
||||
public static long parseHTTPDate(String pDate) throws NumberFormatException {
|
||||
return parseHTTPDateImpl(pDate).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* ParseHTTPDate implementation
|
||||
*
|
||||
* @param pDate the date string to parse
|
||||
*
|
||||
* @return a {@code Date}
|
||||
* @throws NumberFormatException if the date parameter is not parseable.
|
||||
* @throws IllegalArgumentException if the date paramter is {@code null}
|
||||
*/
|
||||
private static Date parseHTTPDateImpl(final String pDate) throws NumberFormatException {
|
||||
if (pDate == null) {
|
||||
throw new IllegalArgumentException("date == null");
|
||||
}
|
||||
|
||||
if (StringUtil.isEmpty(pDate)) {
|
||||
throw new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
|
||||
}
|
||||
|
||||
DateFormat format;
|
||||
|
||||
if (pDate.indexOf('-') >= 0) {
|
||||
format = HTTP_RFC850_FORMAT;
|
||||
update50YearWindowIfNeeded();
|
||||
}
|
||||
else if (pDate.indexOf(',') < 0) {
|
||||
format = HTTP_ASCTIME_FORMAT;
|
||||
update50YearWindowIfNeeded();
|
||||
}
|
||||
else {
|
||||
format = HTTP_RFC1123_FORMAT;
|
||||
// NOTE: RFC1123 always uses 4-digit years
|
||||
}
|
||||
|
||||
Date date;
|
||||
try {
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (format) {
|
||||
date = format.parse(pDate);
|
||||
}
|
||||
}
|
||||
catch (ParseException e) {
|
||||
NumberFormatException nfe = new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
|
||||
nfe.initCause(e);
|
||||
throw nfe;
|
||||
}
|
||||
|
||||
if (date == null) {
|
||||
throw new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
}
|
||||
@@ -1,312 +0,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 "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.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/net/MIMEUtil.java#5 $
|
||||
*
|
||||
* @see <A href="http://www.iana.org/assignments/media-types/">MIME Media Types</A>
|
||||
*/
|
||||
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<String, List<String>> sExtToMIME = new HashMap<String, List<String>>();
|
||||
private static Map<String, List<String>> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME);
|
||||
|
||||
private static Map<String, List<String>> sMIMEToExt = new HashMap<String, List<String>>();
|
||||
private static Map<String, List<String>> 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<String> extensions =
|
||||
Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, ")));
|
||||
|
||||
String typeStr = StringUtil.toLowerCase((String) entry.getValue());
|
||||
List<String> 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<String> 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<String> getMIMETypes(final String pFileExt) {
|
||||
List<String> 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<String, List<String>> 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<String> 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<String> getExtensions(final String pMIME) {
|
||||
String mime = bareMIME(StringUtil.toLowerCase(pMIME));
|
||||
if (mime.endsWith("/*")) {
|
||||
return getExtensionForWildcard(mime);
|
||||
}
|
||||
List<String> extensions = sMIMEToExt.get(mime);
|
||||
return maskNull(extensions);
|
||||
}
|
||||
|
||||
// Gets all extensions for a wildcard MIME type
|
||||
private static List<String> getExtensionForWildcard(final String pMIME) {
|
||||
final String family = pMIME.substring(0, pMIME.length() - 1);
|
||||
Set<String> extensions = new LinkedHashSet<String>();
|
||||
for (Map.Entry<String, List<String>> mimeToExt : sMIMEToExt.entrySet()) {
|
||||
if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) {
|
||||
extensions.addAll(mimeToExt.getValue());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(new ArrayList<String>(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<String, List<String>> 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<String> maskNull(List<String> pTypes) {
|
||||
return (pTypes == null) ? Collections.<String>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();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Provides classes for net access.
|
||||
*/
|
||||
package com.twelvemonkeys.net;
|
||||
@@ -1,182 +0,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 "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.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.bootstrap.DOMImplementationRegistry;
|
||||
import org.w3c.dom.ls.DOMImplementationLS;
|
||||
import org.w3c.dom.ls.LSOutput;
|
||||
import org.w3c.dom.ls.LSSerializer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* {@code DOMImplementationLS} backed implementation.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java#2 $
|
||||
*/
|
||||
public final class DOMSerializer {
|
||||
|
||||
private static final String PARAM_PRETTY_PRINT = "format-pretty-print";
|
||||
private static final String PARAM_XML_DECLARATION = "xml-declaration";
|
||||
|
||||
private final LSSerializer serializer;
|
||||
private final LSOutput output;
|
||||
|
||||
private DOMSerializer() {
|
||||
DOMImplementationLS domImpl = Support.getImplementation();
|
||||
serializer = domImpl.createLSSerializer();
|
||||
output = domImpl.createLSOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a serializer using the given byte stream and encoding.
|
||||
*
|
||||
* @param pStream the byte stream.
|
||||
* @param pEncoding the encoding.
|
||||
* @throws IllegalStateException if no {@code DOMImplementation} with the right features can be instantiated.
|
||||
*/
|
||||
public DOMSerializer(final OutputStream pStream, final String pEncoding) {
|
||||
this();
|
||||
|
||||
output.setByteStream(pStream);
|
||||
output.setEncoding(pEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a serializer using the given character stream and encoding.
|
||||
*
|
||||
* @param pStream the characted stream.
|
||||
* @throws IllegalStateException if no {@code DOMImplementation} with the right features can be instantiated.
|
||||
*/
|
||||
public DOMSerializer(final Writer pStream) {
|
||||
this();
|
||||
|
||||
output.setCharacterStream(pStream);
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: Is it useful?
|
||||
public void setNewLine(final String pNewLine) {
|
||||
serializer.setNewLine(pNewLine);
|
||||
}
|
||||
|
||||
public String getNewLine() {
|
||||
return serializer.getNewLine();
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specifies wether the serializer should use indentation and optimize for
|
||||
* readability.
|
||||
* <p/>
|
||||
* Note: This is a hint, and may be ignored by DOM implemenations.
|
||||
*
|
||||
* @param pPrettyPrint {@code true} to enable pretty printing
|
||||
*/
|
||||
public void setPrettyPrint(final boolean pPrettyPrint) {
|
||||
DOMConfiguration configuration = serializer.getDomConfig();
|
||||
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
|
||||
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getPrettyPrint() {
|
||||
return Boolean.TRUE.equals(serializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private void setXMLDeclaration(boolean pXMLDeclaration) {
|
||||
serializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire document.
|
||||
*
|
||||
* @param pDocument the document.
|
||||
*/
|
||||
public void serialize(final Document pDocument) {
|
||||
serializeImpl(pDocument, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given node, along with any subnodes.
|
||||
* Will not emit XML declaration.
|
||||
*
|
||||
* @param pNode the top node.
|
||||
*/
|
||||
public void serialize(final Node pNode) {
|
||||
serializeImpl(pNode, false);
|
||||
}
|
||||
|
||||
private void serializeImpl(final Node pNode, final boolean pOmitDecl) {
|
||||
setXMLDeclaration(pOmitDecl);
|
||||
serializer.write(pNode, output);
|
||||
}
|
||||
|
||||
private static class Support {
|
||||
private final static DOMImplementationRegistry DOM_REGISTRY = createDOMRegistry();
|
||||
|
||||
static DOMImplementationLS getImplementation() {
|
||||
DOMImplementationLS implementation = (DOMImplementationLS) DOM_REGISTRY.getDOMImplementation("LS 3.0");
|
||||
if (implementation == null) {
|
||||
|
||||
DOMImplementationList list = DOM_REGISTRY.getDOMImplementationList("");
|
||||
System.err.println("DOM implementations (" + list.getLength() + "):");
|
||||
for (int i = 0; i < list.getLength(); i++) {
|
||||
System.err.println(" " + list.item(i));
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Could not create DOM Implementation (no LS support found)");
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
private static DOMImplementationRegistry createDOMRegistry() {
|
||||
try {
|
||||
return DOMImplementationRegistry.newInstance();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,621 +0,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 "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.xml;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* XMLSerializer
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java#1 $
|
||||
*/
|
||||
public class XMLSerializer {
|
||||
// TODO: Replace with DOMSerializer? Test performance, pretty printing etc...
|
||||
// Main problem: Sun's Java 5 does not have LS 3.0 support
|
||||
// This class has no dependencies, which probably makes it more useful
|
||||
|
||||
// TODO: Don't insert initial and ending line-break for text-nodes
|
||||
// TODO: Support not inserting line-breaks, to preserve space
|
||||
// TODO: Support line breaking (at configurable width)
|
||||
// TODO: Support standalone?
|
||||
// TODO: Support more than version 1.0?
|
||||
// TODO: Consider using IOException to communicate trouble, rather than RTE,
|
||||
// to be more compatible...
|
||||
|
||||
private final OutputStream output;
|
||||
private final Charset encoding;
|
||||
private final SerializationContext context;
|
||||
|
||||
public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
|
||||
output = pOutput;
|
||||
encoding = Charset.forName(pEncoding);
|
||||
context = new SerializationContext();
|
||||
}
|
||||
|
||||
public final XMLSerializer indentation(String pIndent) {
|
||||
// TODO: Verify that indent value is only whitespace?
|
||||
context.indent = pIndent != null ? pIndent : "\t";
|
||||
return this;
|
||||
}
|
||||
|
||||
public final XMLSerializer stripComments(boolean pStrip) {
|
||||
context.stripComments = pStrip;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire document, along with the XML declaration
|
||||
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||
*
|
||||
* @param pDocument the document to serialize.
|
||||
*/
|
||||
public void serialize(final Document pDocument) {
|
||||
serialize(pDocument, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire sub tree starting at {@code pRootNode}, along with an optional XML declaration
|
||||
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||
*
|
||||
* @param pRootNode the root node to serialize.
|
||||
* @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}.
|
||||
*/
|
||||
public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) {
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(output, encoding));
|
||||
try {
|
||||
if (pWriteXMLDeclaration) {
|
||||
writeXMLDeclaration(out);
|
||||
}
|
||||
writeXML(out, pRootNode, context.copy());
|
||||
}
|
||||
finally {
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeXMLDeclaration(final PrintWriter pOut) {
|
||||
pOut.print("<?xml version=\"1.0\" encoding=\"");
|
||||
pOut.print(encoding.name());
|
||||
pOut.println("\"?>");
|
||||
}
|
||||
|
||||
private void writeXML(final PrintWriter pOut, final Node pDocument, final SerializationContext pContext) {
|
||||
writeNodeRecursive(pOut, pDocument, pContext);
|
||||
}
|
||||
|
||||
private void writeNodeRecursive(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
if (pNode.getNodeType() != Node.TEXT_NODE) {
|
||||
indentToLevel(pOut, pContext);
|
||||
}
|
||||
|
||||
switch (pNode.getNodeType()) {
|
||||
case Node.DOCUMENT_NODE:
|
||||
case Node.DOCUMENT_FRAGMENT_NODE:
|
||||
writeDocument(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.DOCUMENT_TYPE_NODE:
|
||||
writeDoctype(pOut, (DocumentType) pNode);
|
||||
break;
|
||||
case Node.ELEMENT_NODE:
|
||||
boolean preserveSpace = pContext.preserveSpace;
|
||||
updatePreserveSpace(pNode, pContext);
|
||||
writeElement(pOut, (Element) pNode, pContext);
|
||||
pContext.preserveSpace = preserveSpace;
|
||||
break;
|
||||
case Node.CDATA_SECTION_NODE:
|
||||
writeCData(pOut, pNode);
|
||||
break;
|
||||
case Node.TEXT_NODE:
|
||||
writeText(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.COMMENT_NODE:
|
||||
writeComment(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.PROCESSING_INSTRUCTION_NODE:
|
||||
writeProcessingInstruction(pOut, (ProcessingInstruction) pNode);
|
||||
break;
|
||||
case Node.ATTRIBUTE_NODE:
|
||||
throw new IllegalArgumentException("Malformed input Document: Attribute nodes should only occur inside Element nodes");
|
||||
case Node.ENTITY_NODE:
|
||||
// '<!ENTITY ' + getNodeName + ... + '>'
|
||||
case Node.ENTITY_REFERENCE_NODE:
|
||||
// ( '&' | '%' ) + getNodeName + ';'
|
||||
case Node.NOTATION_NODE:
|
||||
// '<!NOTATION ' + getNodeName + ( ExternalID | PublicID ) + '>'
|
||||
default:
|
||||
throw new InternalError("Lazy programmer never implemented serialization of " + pNode.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeProcessingInstruction(final PrintWriter pOut, final ProcessingInstruction pNode) {
|
||||
pOut.print("\n<?");
|
||||
pOut.print(pNode.getTarget());
|
||||
String value = pNode.getData();
|
||||
|
||||
if (value != null) {
|
||||
pOut.print(" ");
|
||||
pOut.print(value);
|
||||
}
|
||||
|
||||
pOut.println("?>");
|
||||
}
|
||||
|
||||
private void writeText(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
// TODO: Is this really as specified?
|
||||
String value = pNode.getNodeValue();
|
||||
if (pContext.preserveSpace) {
|
||||
pOut.print(maybeEscapeElementValue(value));
|
||||
}
|
||||
else if (!StringUtil.isEmpty(value)) {
|
||||
String escapedValue = maybeEscapeElementValue(value.trim());
|
||||
//if (escapedValue.length() + (pContext.level * pContext.indent.length()) > 78) {
|
||||
indentToLevel(pOut, pContext);
|
||||
//}
|
||||
pOut.println(escapedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCData(final PrintWriter pOut, final Node pNode) {
|
||||
pOut.print("<![CDATA[");
|
||||
pOut.print(validateCDataValue(pNode.getNodeValue()));
|
||||
pOut.println("]]>");
|
||||
}
|
||||
|
||||
private static void updatePreserveSpace(final Node pNode, final SerializationContext pContext) {
|
||||
NamedNodeMap attributes = pNode.getAttributes();
|
||||
if (attributes != null) {
|
||||
Node space = attributes.getNamedItem("xml:space");
|
||||
if (space != null) {
|
||||
if ("preserve".equals(space.getNodeValue())) {
|
||||
pContext.preserveSpace = true;
|
||||
}
|
||||
else if ("default".equals(space.getNodeValue())) {
|
||||
pContext.preserveSpace = false;
|
||||
}
|
||||
// No other values are allowed per spec, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void indentToLevel(final PrintWriter pOut, final SerializationContext pContext) {
|
||||
for (int i = 0; i < pContext.level; i++) {
|
||||
pOut.print(pContext.indent);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeComment(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
if (pContext.stripComments) {
|
||||
return;
|
||||
}
|
||||
|
||||
String value = pNode.getNodeValue();
|
||||
validateCommentValue(value);
|
||||
|
||||
if (value.startsWith(" ")) {
|
||||
pOut.print("<!--");
|
||||
}
|
||||
else {
|
||||
pOut.print("<!-- ");
|
||||
}
|
||||
pOut.print(value);
|
||||
if (value.endsWith(" ")) {
|
||||
pOut.println("-->");
|
||||
}
|
||||
else {
|
||||
pOut.println(" -->");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an escaped version of the input string. The string is guaranteed
|
||||
* to not contain illegal XML characters ({@code &<>}).
|
||||
* If no escaping is needed, the input string is returned as is.
|
||||
*
|
||||
* @param pValue the input string that might need escaping.
|
||||
* @return an escaped version of the input string.
|
||||
*/
|
||||
static String maybeEscapeElementValue(final String pValue) {
|
||||
int startEscape = needsEscapeElement(pValue);
|
||||
|
||||
if (startEscape < 0) {
|
||||
// If no escaping is needed, simply return original
|
||||
return pValue;
|
||||
}
|
||||
else {
|
||||
// Otherwise, start replacing
|
||||
StringBuilder builder = new StringBuilder(pValue.substring(0, startEscape));
|
||||
builder.ensureCapacity(pValue.length() + 30);
|
||||
|
||||
int pos = startEscape;
|
||||
for (int i = pos; i < pValue.length(); i++) {
|
||||
switch (pValue.charAt(i)) {
|
||||
case '&':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, "&");
|
||||
break;
|
||||
case '<':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, "<");
|
||||
break;
|
||||
case '>':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, ">");
|
||||
break;
|
||||
//case '\'':
|
||||
//case '"':
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(pValue.substring(pos));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
||||
pBuilder.append(pString.substring(pStart, pEnd));
|
||||
pBuilder.append(pEntity);
|
||||
return pEnd + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an the first index from the input string that should be escaped
|
||||
* if escaping is needed, otherwise {@code -1}.
|
||||
*
|
||||
* @param pString the input string that might need escaping.
|
||||
* @return the first index from the input string that should be escaped,
|
||||
* or {@code -1}.
|
||||
*/
|
||||
private static int needsEscapeElement(final String pString) {
|
||||
for (int i = 0; i < pString.length(); i++) {
|
||||
switch (pString.charAt(i)) {
|
||||
case '&':
|
||||
case '<':
|
||||
case '>':
|
||||
//case '\'':
|
||||
//case '"':
|
||||
return i;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String maybeEscapeAttributeValue(final String pValue) {
|
||||
int startEscape = needsEscapeAttribute(pValue);
|
||||
|
||||
if (startEscape < 0) {
|
||||
return pValue;
|
||||
}
|
||||
else {
|
||||
StringBuilder builder = new StringBuilder(pValue.substring(0, startEscape));
|
||||
builder.ensureCapacity(pValue.length() + 16);
|
||||
|
||||
int pos = startEscape;
|
||||
for (int i = pos; i < pValue.length(); i++) {
|
||||
switch (pValue.charAt(i)) {
|
||||
case '&':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, "&");
|
||||
break;
|
||||
case '"':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, """);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(pValue.substring(pos));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an the first index from the input string that should be escaped
|
||||
* if escaping is needed, otherwise {@code -1}.
|
||||
*
|
||||
* @param pString the input string that might need escaping.
|
||||
* @return the first index from the input string that should be escaped,
|
||||
* or {@code -1}.
|
||||
*/
|
||||
private static int needsEscapeAttribute(final String pString) {
|
||||
for (int i = 0; i < pString.length(); i++) {
|
||||
switch (pString.charAt(i)) {
|
||||
case '&':
|
||||
//case '<':
|
||||
//case '>':
|
||||
//case '\'':
|
||||
case '"':
|
||||
return i;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String validateCDataValue(final String pValue) {
|
||||
if (pValue.contains("]]>")) {
|
||||
throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'");
|
||||
}
|
||||
return pValue;
|
||||
}
|
||||
|
||||
private static String validateCommentValue(final String pValue) {
|
||||
if (pValue.contains("--")) {
|
||||
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
|
||||
}
|
||||
return pValue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeElement(final PrintWriter pOut, final Element pNode, final SerializationContext pContext) {
|
||||
pOut.print("<");
|
||||
pOut.print(pNode.getTagName());
|
||||
|
||||
// TODO: Attributes should probably include namespaces, so that it works
|
||||
// even if the document was created using attributes instead of namespaces...
|
||||
// In that case, prefix will be null...
|
||||
|
||||
// Handle namespace
|
||||
String namespace = pNode.getNamespaceURI();
|
||||
if (namespace != null && !namespace.equals(pContext.defaultNamespace)) {
|
||||
String prefix = pNode.getPrefix();
|
||||
if (prefix == null) {
|
||||
pContext.defaultNamespace = namespace;
|
||||
pOut.print(" xmlns");
|
||||
}
|
||||
else {
|
||||
pOut.print(" xmlns:");
|
||||
pOut.print(prefix);
|
||||
}
|
||||
pOut.print("=\"");
|
||||
pOut.print(namespace);
|
||||
pOut.print("\"");
|
||||
}
|
||||
|
||||
// Iterate attributes if any
|
||||
if (pNode.hasAttributes()) {
|
||||
NamedNodeMap attributes = pNode.getAttributes();
|
||||
for (int i = 0; i < attributes.getLength(); i++) {
|
||||
Attr attribute = (Attr) attributes.item(i);
|
||||
String name = attribute.getName();
|
||||
if (!(name.startsWith("xmlns") && (name.length() == 5 || name.charAt(5) == ':'))) {
|
||||
pOut.print(" ");
|
||||
pOut.print(name);
|
||||
pOut.print("=\"");
|
||||
pOut.print(maybeEscapeAttributeValue(attribute.getValue()));
|
||||
pOut.print("\"");
|
||||
}
|
||||
//else {
|
||||
// System.err.println("attribute.getName(): " + name);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider not indenting/newline if the first child is a text node
|
||||
// Iterate children if any
|
||||
if (pNode.hasChildNodes()) {
|
||||
pOut.print(">");
|
||||
if (!pContext.preserveSpace) {
|
||||
pOut.println();
|
||||
}
|
||||
|
||||
NodeList children = pNode.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
writeNodeRecursive(pOut, children.item(i), pContext.push());
|
||||
}
|
||||
|
||||
if (!pContext.preserveSpace) {
|
||||
indentToLevel(pOut, pContext);
|
||||
}
|
||||
|
||||
pOut.print("</");
|
||||
pOut.print(pNode.getTagName());
|
||||
pOut.println(">");
|
||||
}
|
||||
else {
|
||||
pOut.println("/>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writeDoctype(final PrintWriter pOut, final DocumentType pDoctype) {
|
||||
// NOTE: The DOMImplementationLS LSSerializer actually inserts SYSTEM or
|
||||
// PUBLIC identifiers even if they are empty strings. The result is, it
|
||||
// will create invalid documents.
|
||||
// Testing for empty strings seems to be more compatible.
|
||||
if (pDoctype != null) {
|
||||
pOut.print("<!DOCTYPE ");
|
||||
pOut.print(pDoctype.getName());
|
||||
|
||||
String publicId = pDoctype.getPublicId();
|
||||
if (!StringUtil.isEmpty(publicId)) {
|
||||
pOut.print(" PUBLIC ");
|
||||
pOut.print(publicId);
|
||||
}
|
||||
|
||||
String systemId = pDoctype.getSystemId();
|
||||
if (!StringUtil.isEmpty(systemId)) {
|
||||
if (StringUtil.isEmpty(publicId)) {
|
||||
pOut.print(" SYSTEM \"");
|
||||
}
|
||||
else {
|
||||
pOut.print(" \"");
|
||||
}
|
||||
pOut.print(systemId);
|
||||
pOut.print("\"");
|
||||
}
|
||||
|
||||
String internalSubset = pDoctype.getInternalSubset();
|
||||
if (!StringUtil.isEmpty(internalSubset)) {
|
||||
pOut.print(" [ ");
|
||||
pOut.print(internalSubset);
|
||||
pOut.print(" ]");
|
||||
}
|
||||
pOut.println(">");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException, SAXException {
|
||||
// Build XML tree (Document) and write
|
||||
// Find the implementation
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
DocumentBuilder builder;
|
||||
|
||||
try {
|
||||
builder = factory.newDocumentBuilder();
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
//noinspection ThrowableInstanceNeverThrown BOGUS
|
||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
||||
}
|
||||
|
||||
DOMImplementation dom = builder.getDOMImplementation();
|
||||
|
||||
Document document = dom.createDocument("http://www.twelvemonkeys.com/xml/test", "test", dom.createDocumentType("test", null, null));
|
||||
|
||||
Element root = document.getDocumentElement();
|
||||
|
||||
// This is probably not the correct way of setting a default namespace
|
||||
//root.setAttribute("xmlns", "http://www.twelvemonkeys.com/xml/test");
|
||||
|
||||
// Create and insert the normal Properties headers as XML comments
|
||||
document.insertBefore(document.createComment(new Date().toString()), root);
|
||||
|
||||
Element test = document.createElement("sub");
|
||||
root.appendChild(test);
|
||||
Element more = document.createElementNS("http://more.com/1999/namespace", "more:more");
|
||||
more.setAttribute("foo", "test");
|
||||
more.setAttribute("bar", "'really' \"legal\" & ok");
|
||||
test.appendChild(more);
|
||||
more.appendChild(document.createTextNode("Simply some text."));
|
||||
more.appendChild(document.createCDATASection("&something escaped;"));
|
||||
more.appendChild(document.createTextNode("More & <more>!"));
|
||||
more.appendChild(document.createTextNode("\"<<'&'>>\""));
|
||||
Element another = document.createElement("another");
|
||||
test.appendChild(another);
|
||||
Element yet = document.createElement("yet-another");
|
||||
yet.setAttribute("this-one", "with-params");
|
||||
test.appendChild(yet);
|
||||
|
||||
Element pre = document.createElementNS("http://www.twelvemonkeys.com/xml/test", "pre");
|
||||
pre.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
|
||||
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
|
||||
test.appendChild(pre);
|
||||
|
||||
Element pre2 = document.createElementNS("http://www.twelvemonkeys.com/xml/test", "tight");
|
||||
pre2.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
|
||||
pre2.appendChild(document.createTextNode("no-space-around-me"));
|
||||
test.appendChild(pre2);
|
||||
|
||||
// Create serializer and output document
|
||||
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
|
||||
System.out.println("XMLSerializer:");
|
||||
XMLSerializer serializer = new XMLSerializer(System.out, "UTF-8");
|
||||
serializer.serialize(document);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("DOMSerializer:");
|
||||
DOMSerializer serializerD = new DOMSerializer(System.out, "UTF-8");
|
||||
serializerD.setPrettyPrint(true);
|
||||
serializerD.serialize(document);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("\n");
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
XMLSerializer serializer2 = new XMLSerializer(out, "UTF-8");
|
||||
serializer2.serialize(document);
|
||||
|
||||
ByteArrayOutputStream outD = new ByteArrayOutputStream();
|
||||
DOMSerializer serializer2D = new DOMSerializer(outD, "UTF-8");
|
||||
serializer2D.serialize(document);
|
||||
|
||||
Document document2 = builder.parse(new ByteArrayInputStream(out.toByteArray()));
|
||||
System.out.println("XMLSerializer reparsed XMLSerializer:");
|
||||
serializer.serialize(document2);
|
||||
System.out.println();
|
||||
System.out.println("DOMSerializer reparsed XMLSerializer:");
|
||||
serializerD.serialize(document2);
|
||||
System.out.println();
|
||||
|
||||
|
||||
Document documentD = builder.parse(new ByteArrayInputStream(outD.toByteArray()));
|
||||
System.out.println("XMLSerializer reparsed DOMSerializer:");
|
||||
serializer.serialize(documentD);
|
||||
System.out.println();
|
||||
System.out.println("DOMSerializer reparsed DOMSerializer:");
|
||||
serializerD.serialize(documentD);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
static class SerializationContext implements Cloneable {
|
||||
String indent = "\t";
|
||||
int level = 0;
|
||||
boolean preserveSpace = false;
|
||||
boolean stripComments = false;
|
||||
String defaultNamespace;
|
||||
|
||||
public SerializationContext copy() {
|
||||
try {
|
||||
return (SerializationContext) clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SerializationContext push() {
|
||||
SerializationContext context = copy();
|
||||
context.level++;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Provides XML support classes.
|
||||
*/
|
||||
package com.twelvemonkeys.xml;
|
||||
@@ -1,111 +0,0 @@
|
||||
##############################################################################
|
||||
#
|
||||
# MIME type mappings for MIMEUtil.
|
||||
# $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/resources/com/twelvemonkeys/net/MIMEUtil.properties#2 $
|
||||
#
|
||||
##############################################################################
|
||||
#
|
||||
# Format:
|
||||
# <default ext>[','<alt ext>','<alt ext>..]=<default mime>[';'<alt mime>';'<alt mime>..]
|
||||
#
|
||||
# If possible, the "default ext" and "default mime" must be the official file
|
||||
# extension and MIME type respectively.
|
||||
#
|
||||
# See http://www.iana.org/assignments/media-types/ for the complete list.
|
||||
#
|
||||
# TODO: Change to this format: http://java.sun.com/javase/6/docs/api/javax/activation/MimetypesFileTypeMap.html
|
||||
|
||||
# Application types
|
||||
doc,dot=application/msword
|
||||
bin,exe,arc,lha,lhx,lzx,zoo=application/octet-stream
|
||||
arj=application/arj
|
||||
pdf=application/pdf
|
||||
ai,eps,ps=application/postscript
|
||||
ppt=application/vnd.ms-powerpoint;application/x-powerpoint;application/powerpoint
|
||||
csh=application/x-csh
|
||||
gtar=application/x-gtar
|
||||
gz,gzip,z=application/x-gzip;application/x-compressed
|
||||
class=application/x-java-vm
|
||||
ser=application/x-java-serialized-object
|
||||
jar=application/x-java-archive
|
||||
tex=application/x-tex;application/x-latex
|
||||
texinfo,texi=application/x-texinfo
|
||||
t,tr,roff=application/x-troff
|
||||
sh=application/x-sh
|
||||
tar=application/x-tar
|
||||
zip=application/zip
|
||||
hqx=application/binhex
|
||||
swf=application/x-shockwave-flash
|
||||
xls=application/vnd.ms-excel
|
||||
prc,pdb,pqa,oprc=application/vnd.palm
|
||||
ogg=application/ogg
|
||||
|
||||
# Audio types
|
||||
au,snd=audio/basic
|
||||
wav=audio/x-wav;audio/wav
|
||||
aiff,aif,aifc=audio/x-aiff
|
||||
midi,mid=audio/x-midi
|
||||
gsm,gsd=audio/x-gsm
|
||||
mp3=audio/x-mpeg-3;audio/mpeg3
|
||||
ram,ra=audio/x-pn-realaudio;audio/x-realaudio
|
||||
|
||||
# Image types
|
||||
bmp=image/bmp;image/x-bmp;image/x-win-bmp
|
||||
cgm=image/cgm
|
||||
ras=image/cmu-raster;image/x-cmu-raster
|
||||
gif=image/gif
|
||||
ico=image/ico;image/x-icon
|
||||
ief=image/ief
|
||||
iff,ilbm=image/x-iff;image/iff
|
||||
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
|
||||
jpm=image/jpm
|
||||
png=image/png;image/x-png
|
||||
# NOTE: image/svg-xml is an old recommendation, should not be used
|
||||
svg,svgz=image/svg+xml;image/svg-xml;image/x-svg
|
||||
tga=image/targa;image/x-targa
|
||||
tif,tiff=image/tiff;image/x-tiff
|
||||
ras,rast=image/x-cmu-raster;image/cmu-raster
|
||||
pict,pct,pic=image/x-pict;image/pict
|
||||
pcd=image/x-pcd;image/pcd
|
||||
pcx=image/x-pcx
|
||||
#pdb=image/x-palm-db;image/palm-db
|
||||
pnm=image/x-portable-anymap
|
||||
pbm=image/x-portable-bitmap
|
||||
pgm=image/x-portable-graymap
|
||||
ppm=image/x-portable-pixmap
|
||||
psd=image/x-psd;image/psd
|
||||
wbmp=image/vnd.wap.wbmp;image/x-wbmp
|
||||
wmf,emf=image/x-wmf;image/wmf;windows/metafile
|
||||
xbm=image/x-xbitmap;image/x-xbm;image/xbm
|
||||
xpm=image/x-xpixmap
|
||||
xwd=image/x-windowdump
|
||||
|
||||
# Message types
|
||||
# No known file extensions
|
||||
|
||||
# Model types
|
||||
vrml;wrl;wrz=model/vrml;x-world/x-vrml
|
||||
|
||||
# Multipart types
|
||||
# No known file extensions
|
||||
|
||||
# Text types
|
||||
txt=text/plain
|
||||
html,htm=text/html
|
||||
xml=text/xml;application/xml
|
||||
css=text/css
|
||||
js=text/javascript
|
||||
rtf,rtx=text/richtext;application/rtf;application/x-rtf
|
||||
xhtml,xht=application/xhtml+xml
|
||||
sgml,sgm=text/sgml;text/x-sgml
|
||||
wml=vnd.wap.wml
|
||||
wmls=vnd.wap.wmlscript
|
||||
ics=text/calendar
|
||||
|
||||
# Video types
|
||||
mpg,mpeg,mpe=video/mpeg
|
||||
qt,mov=video/quicktime
|
||||
avi=video/x-msvideo;video/msvideo;video/avi
|
||||
movie=video/x-sgi-movie
|
||||
scm=video/x-scm
|
||||
rv=video/vnd.rn-realvideo
|
||||
@@ -1,68 +0,0 @@
|
||||
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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<Reader> readers = new ArrayList<Reader>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
-36
@@ -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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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"));
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* FileCacheSeekableStreamTestCase
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* MemoryCacheSeekableStreamTestCase
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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]);
|
||||
}
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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]);
|
||||
}
|
||||
}}
|
||||
-208
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "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 org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* LittleEndianDataInputStreamTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: LittleEndianDataInputStreamTest.java,v 1.0 15.02.13 11:04 haraldk Exp$
|
||||
*/
|
||||
public class LittleEndianDataInputStreamTest {
|
||||
@Test
|
||||
public void testReadBoolean() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(new byte[] {0, 1, 0x7f, (byte) 0xff}));
|
||||
assertFalse(data.readBoolean());
|
||||
assertTrue(data.readBoolean());
|
||||
assertTrue(data.readBoolean());
|
||||
assertTrue(data.readBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByte() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||
new byte[] {
|
||||
(byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x00,
|
||||
(byte) 0xff, (byte) 0xff,
|
||||
(byte) 0x00, (byte) 0x80,
|
||||
(byte) 0xff, (byte) 0x7f,
|
||||
(byte) 0x00, (byte) 0x01,
|
||||
}
|
||||
|
||||
));
|
||||
|
||||
assertEquals(0, data.readByte());
|
||||
assertEquals(0, data.readByte());
|
||||
assertEquals(1, data.readByte());
|
||||
assertEquals(0, data.readByte());
|
||||
assertEquals(-1, data.readByte());
|
||||
assertEquals(-1, data.readByte());
|
||||
assertEquals(0, data.readByte());
|
||||
assertEquals(Byte.MIN_VALUE, data.readByte());
|
||||
assertEquals(-1, data.readByte());
|
||||
assertEquals(Byte.MAX_VALUE, data.readByte());
|
||||
assertEquals(0, data.readByte());
|
||||
assertEquals(1, data.readByte());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadUnsignedByte() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||
new byte[] {
|
||||
(byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x00,
|
||||
(byte) 0xff, (byte) 0xff,
|
||||
(byte) 0x00, (byte) 0x80,
|
||||
(byte) 0xff, (byte) 0x7f,
|
||||
(byte) 0x00, (byte) 0x01,
|
||||
}
|
||||
|
||||
));
|
||||
|
||||
assertEquals(0, data.readUnsignedByte());
|
||||
assertEquals(0, data.readUnsignedByte());
|
||||
assertEquals(1, data.readUnsignedByte());
|
||||
assertEquals(0, data.readUnsignedByte());
|
||||
assertEquals(255, data.readUnsignedByte());
|
||||
assertEquals(255, data.readUnsignedByte());
|
||||
assertEquals(0, data.readUnsignedByte());
|
||||
assertEquals(128, data.readUnsignedByte());
|
||||
assertEquals(255, data.readUnsignedByte());
|
||||
assertEquals(Byte.MAX_VALUE, data.readUnsignedByte());
|
||||
assertEquals(0, data.readUnsignedByte());
|
||||
assertEquals(1, data.readUnsignedByte());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||
new byte[] {
|
||||
(byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x00,
|
||||
(byte) 0xff, (byte) 0xff,
|
||||
(byte) 0x00, (byte) 0x80,
|
||||
(byte) 0xff, (byte) 0x7f,
|
||||
(byte) 0x00, (byte) 0x01,
|
||||
}
|
||||
|
||||
));
|
||||
|
||||
assertEquals(0, data.readShort());
|
||||
assertEquals(1, data.readShort());
|
||||
assertEquals(-1, data.readShort());
|
||||
assertEquals(Short.MIN_VALUE, data.readShort());
|
||||
assertEquals(Short.MAX_VALUE, data.readShort());
|
||||
assertEquals(256, data.readShort());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadUnsignedShort() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||
new byte[] {
|
||||
(byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x00,
|
||||
(byte) 0xff, (byte) 0xff,
|
||||
(byte) 0x00, (byte) 0x80,
|
||||
(byte) 0xff, (byte) 0x7f,
|
||||
(byte) 0x00, (byte) 0x01,
|
||||
}
|
||||
|
||||
));
|
||||
|
||||
assertEquals(0, data.readUnsignedShort());
|
||||
assertEquals(1, data.readUnsignedShort());
|
||||
assertEquals(Short.MAX_VALUE * 2 + 1, data.readUnsignedShort());
|
||||
assertEquals(Short.MAX_VALUE + 1, data.readUnsignedShort());
|
||||
assertEquals(Short.MAX_VALUE, data.readUnsignedShort());
|
||||
assertEquals(256, data.readUnsignedShort());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||
new byte[] {
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
|
||||
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
|
||||
(byte) 0xff, (byte) 0x00, (byte) 0xff, (byte) 0x00,
|
||||
(byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0xff,
|
||||
(byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca,
|
||||
(byte) 0xca, (byte) 0xfe, (byte) 0xd0, (byte) 0x0d,
|
||||
}
|
||||
|
||||
));
|
||||
|
||||
assertEquals(0, data.readInt());
|
||||
assertEquals(1, data.readInt());
|
||||
assertEquals(-1, data.readInt());
|
||||
assertEquals(Integer.MIN_VALUE, data.readInt());
|
||||
assertEquals(Integer.MAX_VALUE, data.readInt());
|
||||
assertEquals(16777216, data.readInt());
|
||||
assertEquals(0xff00ff, data.readInt());
|
||||
assertEquals(0xff00ff00, data.readInt());
|
||||
assertEquals(0xCafeBabe, data.readInt());
|
||||
assertEquals(0x0dd0feca, data.readInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||
new byte[] {
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
|
||||
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
|
||||
(byte) 0x0d, (byte) 0xd0, (byte) 0xfe, (byte) 0xca, (byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca,
|
||||
}
|
||||
|
||||
));
|
||||
|
||||
assertEquals(0, data.readLong());
|
||||
assertEquals(1, data.readLong());
|
||||
assertEquals(-1, data.readLong());
|
||||
assertEquals(Long.MIN_VALUE, data.readLong());
|
||||
assertEquals(Long.MAX_VALUE, data.readLong());
|
||||
assertEquals(72057594037927936L, data.readLong());
|
||||
assertEquals(0xCafeBabeL << 32 | 0xCafeD00dL, data.readLong());
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* MemoryCacheSeekableStreamTestCase
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
-254
@@ -1,254 +0,0 @@
|
||||
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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* SeekableAbstractTestCase
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
-488
@@ -1,488 +0,0 @@
|
||||
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
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
/**
|
||||
* SeekableInterfaceTestCase
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInterfaceTest.java#1 $
|
||||
*/
|
||||
public interface SeekableInterfaceTest {
|
||||
void testSeekable();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user