Mainly new code standard.

A few changes that should have been committed earlier.. :-/
This commit is contained in:
Harald Kuhr
2011-02-17 12:40:49 +01:00
parent 41b8080683
commit 20b87d155d
40 changed files with 951 additions and 593 deletions

View File

@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata;
import com.twelvemonkeys.lang.Validate;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* AbstractEntry
@@ -69,6 +70,45 @@ public abstract class AbstractEntry implements Entry {
}
public String getValueAsString() {
if (valueCount() > 1) {
if (valueCount() < 16) {
Class<?> type = mValue.getClass().getComponentType();
if (type.isPrimitive()) {
if (type.equals(boolean.class)) {
return Arrays.toString((boolean[]) mValue);
}
else if (type.equals(byte.class)) {
return Arrays.toString((byte[]) mValue);
}
else if (type.equals(char.class)) {
return new String((char[]) mValue);
}
else if (type.equals(double.class)) {
return Arrays.toString((double[]) mValue);
}
else if (type.equals(float.class)) {
return Arrays.toString((float[]) mValue);
}
else if (type.equals(int.class)) {
return Arrays.toString((int[]) mValue);
}
else if (type.equals(long.class)) {
return Arrays.toString((long[]) mValue);
}
else if (type.equals(short.class)) {
return Arrays.toString((short[]) mValue);
}
// Fall through should never happen
}
else {
return Arrays.toString((Object[]) mValue);
}
}
return String.valueOf(mValue) + " (" + valueCount() + ")";
}
return String.valueOf(mValue);
}

View File

@@ -43,16 +43,35 @@ final class EXIFEntry extends AbstractEntry {
EXIFEntry(final int pIdentifier, final Object pValue, final short pType) {
super(pIdentifier, pValue);
if (pType < 1 || pType > TIFF.TYPE_NAMES.length) {
throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType));
}
// if (pType < 1 || pType > TIFF.TYPE_NAMES.length) {
// throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType));
// }
mType = pType;
}
public short getType() {
return mType;
}
@Override
public String getFieldName() {
switch ((Integer) getIdentifier()) {
case TIFF.TAG_EXIF_IFD:
return "EXIF";
case TIFF.TAG_XMP:
return "XMP";
case TIFF.TAG_IPTC:
return "IPTC";
case TIFF.TAG_PHOTOSHOP:
return "Adobe";
case TIFF.TAG_ICC_PROFILE:
return "ICC Profile";
case TIFF.TAG_IMAGE_WIDTH:
return "ImageWidth";
case TIFF.TAG_IMAGE_HEIGHT:
return "ImageHeight";
case TIFF.TAG_COMPRESSION:
return "Compression";
case TIFF.TAG_ORIENTATION:
@@ -82,6 +101,7 @@ final class EXIFEntry extends AbstractEntry {
return "PixelXDimension";
case EXIF.TAG_PIXEL_Y_DIMENSION:
return "PixelYDimension";
// TODO: More field names
}

View File

@@ -34,10 +34,15 @@ import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
@@ -48,6 +53,7 @@ import java.util.List;
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
*/
public final class EXIFReader extends MetadataReader {
static final Collection<Integer> KNOWN_IFDS = Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD);
@Override
public Directory read(final ImageInputStream pInput) throws IOException {
@@ -56,10 +62,15 @@ public final class EXIFReader extends MetadataReader {
if (bom[0] == 'I' && bom[1] == 'I') {
pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
else if (bom[0] == 'M' && bom[1] == 'M') {
pInput.setByteOrder(ByteOrder.BIG_ENDIAN);
}
else {
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
}
// TODO: BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
int magic = pInput.readUnsignedShort();
if (magic != TIFF.TIFF_MAGIC) {
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
@@ -72,7 +83,6 @@ public final class EXIFReader extends MetadataReader {
private EXIFDirectory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException {
List<Entry> entries = new ArrayList<Entry>();
pInput.seek(pOffset);
int entryCount = pInput.readUnsignedShort();
@@ -82,6 +92,7 @@ public final class EXIFReader extends MetadataReader {
long nextOffset = pInput.readUnsignedInt();
// Read linked IFDs
if (nextOffset != 0) {
EXIFDirectory next = readDirectory(pInput, nextOffset);
@@ -90,50 +101,158 @@ public final class EXIFReader extends MetadataReader {
}
}
// TODO: Make what sub-IFDs to parse optional? Or leave this to client code? At least skip the non-TIFF data?
readSubdirectories(pInput, entries,
Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD
// , TIFF.TAG_IPTC, TIFF.TAG_XMP
// , TIFF.TAG_ICC_PROFILE
// , TIFF.TAG_PHOTOSHOP
// ,TIFF.TAG_MODI_OLE_PROPERTY_SET
)
);
return new EXIFDirectory(entries);
}
// private Directory readForeignMetadata(final MetadataReader reader, final byte[] bytes) throws IOException {
// return reader.read(ImageIO.createImageInputStream(new ByteArrayInputStream(bytes)));
// }
// TODO: Might be better to leave this for client code, as it's tempting go really overboard and support any possible embedded format..
private void readSubdirectories(ImageInputStream input, List<Entry> entries, List<Integer> subIFDs) throws IOException {
if (subIFDs == null || subIFDs.isEmpty()) {
return;
}
for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
EXIFEntry entry = (EXIFEntry) entries.get(i);
int tagId = (Integer) entry.getIdentifier();
if (subIFDs.contains(tagId)) {
try {
Object directory;
/*
if (tagId == TIFF.TAG_IPTC) {
directory = readForeignMetadata(new IPTCReader(), (byte[]) entry.getValue());
}
else if (tagId == TIFF.TAG_XMP) {
directory = readForeignMetadata(new XMPReader(), (byte[]) entry.getValue());
}
else if (tagId == TIFF.TAG_PHOTOSHOP) {
// TODO: This is waaay too fragile.. Might need registry-based meta data parsers?
try {
Class cl = Class.forName("com.twelvemonkeys.imageio.plugins.psd.PSDImageResource");
Method method = cl.getMethod("read", ImageInputStream.class);
method.setAccessible(true);
directory = method.invoke(null, ImageIO.createImageInputStream(new ByteArrayInputStream((byte[]) entry.getValue())));
}
catch (Exception ignore) {
continue;
}
}
else if (tagId == TIFF.TAG_ICC_PROFILE) {
directory = ICC_Profile.getInstance((byte[]) entry.getValue());
}
else if (tagId == TIFF.TAG_MODI_OLE_PROPERTY_SET) {
// TODO: Encapsulate in something more useful?
directory = new CompoundDocument(new ByteArrayInputStream((byte[]) entry.getValue())).getRootEntry();
}
else*/ if (KNOWN_IFDS.contains(tagId)) {
directory = readDirectory(input, getPointerOffset(entry));
}
else {
continue;
}
// Replace the entry with parsed data
entries.set(i, new EXIFEntry(tagId, directory, entry.getType()));
}
catch (IIOException e) {
// TODO: Issue warning without crashing...?
e.printStackTrace();
}
}
}
}
private long getPointerOffset(final Entry entry) throws IIOException {
long offset;
Object value = entry.getValue();
if (value instanceof Byte) {
offset = ((Byte) value & 0xff);
}
else if (value instanceof Short) {
offset = ((Short) value & 0xffff);
}
else if (value instanceof Integer) {
offset = ((Integer) value & 0xffffffffL);
}
else if (value instanceof Long) {
offset = (Long) value;
}
else {
throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null)));
}
return offset;
}
private EXIFEntry readEntry(final ImageInputStream pInput) throws IOException {
// TODO: BigTiff entries are different
int tagId = pInput.readUnsignedShort();
short type = pInput.readShort();
int count = pInput.readInt(); // Number of values
Object value;
if (count < 0) {
throw new IIOException(String.format("Illegal count %d for tag %s type %s @%08x", count, tagId, type, pInput.getStreamPosition()));
}
Object value;
int valueLength = getValueLength(type, count);
if (type < 0 || type > 13) {
// Invalid tag, this is just for debugging
System.err.printf("offset: %08x%n", pInput.getStreamPosition() - 8l);
System.err.println("tagId: " + tagId);
System.err.println("type: " + type + " (INVALID)");
System.err.println("count: " + count);
if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) {
// Parse sub IFDs
long offset = pInput.readUnsignedInt();
pInput.mark();
pInput.seek(pInput.getStreamPosition() - 8);
try {
value = readDirectory(pInput, offset);
byte[] bytes = new byte[8 + Math.max(20, valueLength)];
pInput.readFully(bytes);
System.err.print("data: " + HexDump.dump(bytes));
System.err.println(bytes.length < valueLength ? "..." : "");
}
finally {
pInput.reset();
}
}
else {
int valueLength = getValueLength(type, count);
if (valueLength > 0 && valueLength <= 4) {
value = readValueInLine(pInput, type, count);
pInput.skipBytes(4 - valueLength);
}
else {
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
value = readValue(pInput, valueOffset, type, count);
}
// TODO: For BigTiff allow size <= 8
if (valueLength > 0 && valueLength <= 4) {
value = readValueInLine(pInput, type, count);
pInput.skipBytes(4 - valueLength);
}
else {
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
value = readValueAt(pInput, valueOffset, type, count);
}
return new EXIFEntry(tagId, value, type);
}
private Object readValue(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
long pos = pInput.getStreamPosition();
try {
pInput.seek(pOffset);
return readValueInLine(pInput, pType, pCount);
return readValue(pInput, pType, pCount);
}
finally {
pInput.seek(pos);
@@ -141,53 +260,82 @@ public final class EXIFReader extends MetadataReader {
}
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
return readValueDirect(pInput, pType, pCount);
return readValue(pInput, pType, pCount);
}
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
// TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code?
long pos = pInput.getStreamPosition();
switch (pType) {
case 2:
// TODO: This might be UTF-8 or ISO-8859-1, even though spec says ASCII
case 2: // ASCII
// TODO: This might be UTF-8 or ISO-8859-x, even though spec says ASCII
byte[] ascii = new byte[pCount];
pInput.readFully(ascii);
return StringUtil.decode(ascii, 0, ascii.length, "UTF-8"); // UTF-8 is ASCII compatible
case 1:
case 1: // BYTE
if (pCount == 1) {
return pInput.readUnsignedByte();
}
case 6:
// else fall through
case 6: // SBYTE
if (pCount == 1) {
return pInput.readByte();
}
case 7:
// else fall through
case 7: // UNDEFINED
byte[] bytes = new byte[pCount];
pInput.readFully(bytes);
// NOTE: We don't change (unsigned) BYTE array wider Java type, as most often BYTE array means
// binary data and we want to keep that as a byte array for clients to parse futher
return bytes;
case 3:
case 3: // SHORT
if (pCount == 1) {
return pInput.readUnsignedShort();
}
case 8:
case 8: // SSHORT
if (pCount == 1) {
return pInput.readShort();
}
short[] shorts = new short[pCount];
pInput.readFully(shorts, 0, shorts.length);
if (pType == 3) {
int[] ints = new int[pCount];
for (int i = 0; i < pCount; i++) {
ints[i] = shorts[i] & 0xffff;
}
return ints;
}
return shorts;
case 4:
case 13: // IFD
case 4: // LONG
if (pCount == 1) {
return pInput.readUnsignedInt();
}
case 9:
case 9: // SLONG
if (pCount == 1) {
return pInput.readInt();
}
int[] ints = new int[pCount];
pInput.readFully(ints, 0, ints.length);
if (pType == 4 || pType == 13) {
long[] longs = new long[pCount];
for (int i = 0; i < pCount; i++) {
longs[i] = ints[i] & 0xffffffffL;
}
return longs;
}
return ints;
case 11:
case 11: // FLOAT
if (pCount == 1) {
return pInput.readFloat();
}
@@ -195,7 +343,7 @@ public final class EXIFReader extends MetadataReader {
float[] floats = new float[pCount];
pInput.readFully(floats, 0, floats.length);
return floats;
case 12:
case 12: // DOUBLE
if (pCount == 1) {
return pInput.readDouble();
}
@@ -204,7 +352,7 @@ public final class EXIFReader extends MetadataReader {
pInput.readFully(doubles, 0, doubles.length);
return doubles;
case 5:
case 5: // RATIONAL
if (pCount == 1) {
return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
}
@@ -215,7 +363,7 @@ public final class EXIFReader extends MetadataReader {
}
return rationals;
case 10:
case 10: // SRATIONAL
if (pCount == 1) {
return new Rational(pInput.readInt(), pInput.readInt());
}
@@ -227,8 +375,32 @@ public final class EXIFReader extends MetadataReader {
return srationals;
// BigTiff:
case 16: // LONG8
case 17: // SLONG8
case 18: // IFD8
// TODO: Assert BigTiff (version == 43)
if (pCount == 1) {
long val = pInput.readLong();
if (pType != 17 && val < 0) {
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
}
return val;
}
long[] longs = new long[pCount];
for (int i = 0; i < pCount; i++) {
longs[i] = pInput.readLong();
}
return longs;
default:
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
// Spec says skip unknown values:
// TODO: Rather just return null, UNKNOWN_TYPE or new Unknown(type, count, offset) for value?
return new Unknown(pType, pCount, pos);
// throw new IIOException(String.format("Unknown EXIF type '%s' at pos %d", pType, pInput.getStreamPosition()));
}
}
@@ -239,4 +411,115 @@ public final class EXIFReader extends MetadataReader {
return -1;
}
public static void main(String[] args) throws IOException {
EXIFReader reader = new EXIFReader();
ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0]));
long pos = 0;
if (args.length > 1) {
if (args[1].startsWith("0x")) {
pos = Integer.parseInt(args[1].substring(2), 16);
}
else {
pos = Long.parseLong(args[1]);
}
stream.setByteOrder(pos < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
pos = Math.abs(pos);
stream.seek(pos);
}
try {
Directory directory;
if (args.length > 1) {
directory = reader.readDirectory(stream, pos);
}
else {
directory = reader.read(stream);
}
for (Entry entry : directory) {
System.err.println(entry);
Object value = entry.getValue();
if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
System.err.println(HexDump.dump(bytes, 0, Math.min(bytes.length, 128)));
}
}
}
finally {
stream.close();
}
}
//////////////////////
// TODO: Stream based hex dump util?
public static class HexDump {
private HexDump() {}
private static final int WIDTH = 32;
public static String dump(byte[] bytes) {
return dump(bytes, 0, bytes.length);
}
public static String dump(byte[] bytes, int off, int len) {
StringBuilder builder = new StringBuilder();
int i;
for (i = 0; i < len; i++) {
if (i % WIDTH == 0) {
if (i > 0 ) {
builder.append("\n");
}
builder.append(String.format("%08x: ", i + off));
}
else if (i > 0 && i % 2 == 0) {
builder.append(" ");
}
builder.append(String.format("%02x", bytes[i + off]));
int next = i + 1;
if (next % WIDTH == 0 || next == len) {
int leftOver = (WIDTH - (next % WIDTH)) % WIDTH;
if (leftOver != 0) {
// Pad: 5 spaces for every 2 bytes... Special care if padding is non-even.
int pad = leftOver / 2;
if (len % 2 != 0) {
builder.append(" ");
}
for (int j = 0; j < pad; j++) {
builder.append(" ");
}
}
builder.append(" ");
builder.append(toAsciiString(bytes, next - (WIDTH - leftOver) + off, next + off));
}
}
return builder.toString();
}
private static String toAsciiString(final byte[] bytes, final int from, final int to) {
byte[] range = Arrays.copyOfRange(bytes, from, to);
for (int i = 0; i < range.length; i++) {
if (range[i] < 32 || range[i] > 126) {
range[i] = '.'; // Unreadable char
}
}
return new String(range, Charset.forName("ascii"));
}
}
}

View File

@@ -47,7 +47,7 @@ public interface TIFF {
5 = RATIONAL Two LONGs: the first represents the numerator of a
fraction; the second, the denominator.
TIFF 6.0 and above:
TIFF 6.0 and above:
6 = SBYTE An 8-bit signed (twos-complement) integer.
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
the definition of the field.
@@ -57,21 +57,39 @@ public interface TIFF {
fraction, the second the denominator.
11 = FLOAT Single precision (4-byte) IEEE format.
12 = DOUBLE Double precision (8-byte) IEEE format.
TODO: Verify IFD type
See http://www.awaresystems.be/imaging/tiff/tifftags/subifds.html
13 = IFD, same as LONG
TODO: BigTiff specifies more types
See http://www.awaresystems.be/imaging/tiff/bigtiff.html, http://www.remotesensing.org/libtiff/bigtiffdesign.html
(what about 14-15??)
16 = TIFF_LONG8, being unsigned 8byte integer
17 = TIFF_SLONG8, being signed 8byte integer
18 = TIFF_IFD8, being a new unsigned 8byte IFD offset.
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
*/
String[] TYPE_NAMES = {
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
"IFD",
null, null,
"LONG8", "SLONG8", "IFD8"
};
int[] TYPE_LENGTHS = {
1, 1, 2, 4, 8,
1, 1, 2, 4, 8, 4, 8,
4,
-1, -1,
8, 8, 8
};
int IFD_EXIF = 0x8769;
int IFD_GPS = 0x8825;
int IFD_INTEROP = 0xA005;
/// EXIF defined TIFF tags
int TAG_EXIF_IFD = 34665;
int TAG_GPS_IFD = 34853;
int TAG_INTEROP_IFD = 40965;
/// A. Tags relating to image data structure:
@@ -114,4 +132,22 @@ public interface TIFF {
int TAG_SOFTWARE = 305;
int TAG_ARTIST = 315;
int TAG_COPYRIGHT = 33432;
int TAG_SUB_IFD = 330;
int TAG_XMP = 700;
int TAG_IPTC = 33723;
int TAG_PHOTOSHOP = 34377;
int TAG_ICC_PROFILE = 34675;
// Microsoft Office Document Imaging (MODI)
// http://msdn.microsoft.com/en-us/library/aa167596%28office.11%29.aspx
int TAG_MODI_BLC = 34718;
int TAG_MODI_VECTOR = 34719;
int TAG_MODI_PTC = 34720;
// http://blogs.msdn.com/b/openspecification/archive/2009/12/08/details-of-three-tiff-tag-extensions-that-microsoft-office-document-imaging-modi-software-may-write-into-the-tiff-files-it-generates.aspx
int TAG_MODI_PLAIN_TEXT = 37679;
int TAG_MODI_OLE_PROPERTY_SET = 37680;
int TAG_MODI_TEXT_POS_INFO = 37681;
}

View File

@@ -0,0 +1,45 @@
package com.twelvemonkeys.imageio.metadata.exif;
/**
* Unknown
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: Unknown.java,v 1.0 Oct 8, 2010 3:38:45 PM haraldk Exp$
*/
final class Unknown {
private final short type;
private final int count;
private final long pos;
public Unknown(final short type, final int count, final long pos) {
this.type = type;
this.count = count;
this.pos = pos;
}
@Override
public int hashCode() {
return (int) (pos ^ (pos >>> 32)) + count * 37 + type * 97;
}
@Override
public boolean equals(Object other) {
if (other != null && other.getClass() == getClass()){
Unknown unknown = (Unknown) other;
return pos == unknown.pos && type == unknown.type && count == unknown.count;
}
return false;
}
@Override
public String toString() {
if (count == 1) {
return String.format("Unknown(%d)@%08x", type, pos);
}
else {
return String.format("Unknown(%d)[%d]@%08x", type, count, pos);
}
}
}

View File

@@ -49,12 +49,12 @@ public final class JPEGSegmentUtil {
private JPEGSegmentUtil() {}
// TODO: Allow for multiple images (multiple SOI markers), using specified index?
public static List<Segment> readSegments(final ImageInputStream stream, final int appMarker, final String segmentName) throws IOException {
// TODO: Allow for multiple images (multiple SOI markers), using specified index, or document that stream must be placed before SOI of wanted image
public static List<Segment> readSegments(final ImageInputStream stream, final int imageIndex, final int appMarker, final String segmentName) throws IOException {
return readSegments(stream, Collections.singletonMap(appMarker, Collections.singletonList(segmentName)));
}
public static List<Segment> readSegments(final ImageInputStream stream, Map<Integer, List<String>> segmentIdentifiers) throws IOException {
public static List<Segment> readSegments(final ImageInputStream stream, final Map<Integer, List<String>> segmentIdentifiers) throws IOException {
readSOI(stream);
List<Segment> segments = Collections.emptyList();

View File

@@ -69,7 +69,7 @@ public class XMPScannerTestCase extends TestCase {
}
public void testScanForUTF8singleQuote() throws IOException {
InputStream stream = createXMPStream(XMP, "UTF-8".replace("\"", "'"));
InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-8");
Reader reader = XMPScanner.scanForXMPPacket(stream);
@@ -85,7 +85,7 @@ public class XMPScannerTestCase extends TestCase {
}
public void testScanForUTF16BEsingleQuote() throws IOException {
InputStream stream = createXMPStream(XMP, "UTF-16BE".replace("\"", "'"));
InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-16BE");
Reader reader = XMPScanner.scanForXMPPacket(stream);
@@ -101,7 +101,7 @@ public class XMPScannerTestCase extends TestCase {
}
public void testScanForUTF16LEsingleQuote() throws IOException {
InputStream stream = createXMPStream(XMP, "UTF-16LE".replace("\"", "'"));
InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-16LE");
Reader reader = XMPScanner.scanForXMPPacket(stream);