Index: trunk/src/com/drew/lang/BufferBoundsException.java =================================================================== --- trunk/src/com/drew/lang/BufferBoundsException.java (revision 6127) +++ trunk/src/com/drew/lang/BufferBoundsException.java (revision 6127) @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2012 Drew Noakes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * http://drewnoakes.com/code/exif/ + * http://code.google.com/p/metadata-extractor/ + */ + +package com.drew.lang; + +import com.drew.lang.annotations.NotNull; + +import java.io.IOException; + +/** + * A checked replacement for IndexOutOfBoundsException. Used by BufferReader. + * + * @author Drew Noakes http://drewnoakes.com + */ +public final class BufferBoundsException extends Exception +{ + private static final long serialVersionUID = 2911102837808946396L; + + public BufferBoundsException(@NotNull byte[] buffer, int index, int bytesRequested) + { + super(getMessage(buffer, index, bytesRequested)); + } + + public BufferBoundsException(final String message) + { + super(message); + } + + public BufferBoundsException(final String message, final IOException innerException) + { + super(message, innerException); + } + + private static String getMessage(@NotNull byte[] buffer, int index, int bytesRequested) + { + if (index < 0) + return String.format("Attempt to read from buffer using a negative index (%s)", index); + + return String.format("Attempt to read %d byte%s from beyond end of buffer (requested index: %d, max index: %d)", + bytesRequested, bytesRequested==1?"":"s", index, buffer.length - 1); + } +} Index: trunk/src/com/drew/lang/BufferReader.java =================================================================== --- trunk/src/com/drew/lang/BufferReader.java (revision 6127) +++ trunk/src/com/drew/lang/BufferReader.java (revision 6127) @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2012 Drew Noakes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * http://drewnoakes.com/code/exif/ + * http://code.google.com/p/metadata-extractor/ + */ + +package com.drew.lang; + +import com.drew.lang.annotations.NotNull; + +public interface BufferReader +{ + /** + * Returns the length of the buffer. This value represents the total number of bytes in the underlying source. + * + * @return The number of bytes in the buffer. + */ + long getLength(); + + /** + * Sets the endianness of this reader. + *
true
for Motorola (or big) endiannessfalse
for Intel (or little) endiannesstrue
for motorola/big endian, false
for intel/little endian
+ */
+ void setMotorolaByteOrder(boolean motorolaByteOrder);
+
+ /**
+ * Gets the endianness of this reader.
+ * true
for Motorola (or big) endiannessfalse
for Intel (or little) endiannessbyte=='\0'
or where length==maxLength
.
+ *
+ * @param index The index within the buffer at which to start reading the string.
+ * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit,
+ * reading will stop and the string will be truncated to this length.
+ * @return The read string.
+ * @throws BufferBoundsException The buffer does not contain enough bytes to satisfy this request.
+ */
+ @NotNull
+ String getNullTerminatedString(int index, int maxLengthBytes) throws BufferBoundsException;
+}
Index: trunk/src/com/drew/lang/ByteArrayReader.java
===================================================================
--- trunk/src/com/drew/lang/ByteArrayReader.java (revision 6127)
+++ trunk/src/com/drew/lang/ByteArrayReader.java (revision 6127)
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2002-2012 Drew Noakes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * http://drewnoakes.com/code/exif/
+ * http://code.google.com/p/metadata-extractor/
+ */
+
+package com.drew.lang;
+
+import com.drew.lang.annotations.NotNull;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Provides methods to read specific values from a byte array, with a consistent, checked exception structure for
+ * issues.
+ *
+ * By default, the reader operates with Motorola byte order (big endianness). This can be changed by calling
+ * {@see setMotorolaByteOrder(boolean)}.
+ *
+ * @author Drew Noakes http://drewnoakes.com
+ * */
+public class ByteArrayReader implements BufferReader
+{
+ @NotNull
+ private final byte[] _buffer;
+ private boolean _isMotorolaByteOrder = true;
+
+ @SuppressWarnings({ "ConstantConditions" })
+ @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent")
+ public ByteArrayReader(@NotNull byte[] buffer)
+ {
+ if (buffer == null)
+ throw new NullPointerException();
+
+ _buffer = buffer;
+ }
+
+ @Override
+ public long getLength()
+ {
+ return _buffer.length;
+ }
+
+
+ @Override
+ public void setMotorolaByteOrder(boolean motorolaByteOrder)
+ {
+ _isMotorolaByteOrder = motorolaByteOrder;
+ }
+
+ @Override
+ public boolean isMotorolaByteOrder()
+ {
+ return _isMotorolaByteOrder;
+ }
+
+ @Override
+ public short getUInt8(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 1);
+
+ return (short) (_buffer[index] & 255);
+ }
+
+ @Override
+ public byte getInt8(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 1);
+
+ return _buffer[index];
+ }
+
+ @Override
+ public int getUInt16(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 2);
+
+ if (_isMotorolaByteOrder) {
+ // Motorola - MSB first
+ return (_buffer[index ] << 8 & 0xFF00) |
+ (_buffer[index + 1] & 0xFF);
+ } else {
+ // Intel ordering - LSB first
+ return (_buffer[index + 1] << 8 & 0xFF00) |
+ (_buffer[index ] & 0xFF);
+ }
+ }
+
+ @Override
+ public short getInt16(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 2);
+
+ if (_isMotorolaByteOrder) {
+ // Motorola - MSB first
+ return (short) (((short)_buffer[index ] << 8 & (short)0xFF00) |
+ ((short)_buffer[index + 1] & (short)0xFF));
+ } else {
+ // Intel ordering - LSB first
+ return (short) (((short)_buffer[index + 1] << 8 & (short)0xFF00) |
+ ((short)_buffer[index ] & (short)0xFF));
+ }
+ }
+
+ @Override
+ public long getUInt32(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 4);
+
+ if (_isMotorolaByteOrder) {
+ // Motorola - MSB first (big endian)
+ return (((long)_buffer[index ]) << 24 & 0xFF000000L) |
+ (((long)_buffer[index + 1]) << 16 & 0xFF0000L) |
+ (((long)_buffer[index + 2]) << 8 & 0xFF00L) |
+ (((long)_buffer[index + 3]) & 0xFFL);
+ } else {
+ // Intel ordering - LSB first (little endian)
+ return (((long)_buffer[index + 3]) << 24 & 0xFF000000L) |
+ (((long)_buffer[index + 2]) << 16 & 0xFF0000L) |
+ (((long)_buffer[index + 1]) << 8 & 0xFF00L) |
+ (((long)_buffer[index ]) & 0xFFL);
+ }
+ }
+
+ @Override
+ public int getInt32(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 4);
+
+ if (_isMotorolaByteOrder) {
+ // Motorola - MSB first (big endian)
+ return (_buffer[index ] << 24 & 0xFF000000) |
+ (_buffer[index + 1] << 16 & 0xFF0000) |
+ (_buffer[index + 2] << 8 & 0xFF00) |
+ (_buffer[index + 3] & 0xFF);
+ } else {
+ // Intel ordering - LSB first (little endian)
+ return (_buffer[index + 3] << 24 & 0xFF000000) |
+ (_buffer[index + 2] << 16 & 0xFF0000) |
+ (_buffer[index + 1] << 8 & 0xFF00) |
+ (_buffer[index ] & 0xFF);
+ }
+ }
+
+ @Override
+ public long getInt64(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 8);
+
+ if (_isMotorolaByteOrder) {
+ // Motorola - MSB first
+ return ((long)_buffer[index ] << 56 & 0xFF00000000000000L) |
+ ((long)_buffer[index + 1] << 48 & 0xFF000000000000L) |
+ ((long)_buffer[index + 2] << 40 & 0xFF0000000000L) |
+ ((long)_buffer[index + 3] << 32 & 0xFF00000000L) |
+ ((long)_buffer[index + 4] << 24 & 0xFF000000L) |
+ ((long)_buffer[index + 5] << 16 & 0xFF0000L) |
+ ((long)_buffer[index + 6] << 8 & 0xFF00L) |
+ ((long)_buffer[index + 7] & 0xFFL);
+ } else {
+ // Intel ordering - LSB first
+ return ((long)_buffer[index + 7] << 56 & 0xFF00000000000000L) |
+ ((long)_buffer[index + 6] << 48 & 0xFF000000000000L) |
+ ((long)_buffer[index + 5] << 40 & 0xFF0000000000L) |
+ ((long)_buffer[index + 4] << 32 & 0xFF00000000L) |
+ ((long)_buffer[index + 3] << 24 & 0xFF000000L) |
+ ((long)_buffer[index + 2] << 16 & 0xFF0000L) |
+ ((long)_buffer[index + 1] << 8 & 0xFF00L) |
+ ((long)_buffer[index ] & 0xFFL);
+ }
+ }
+
+ @Override
+ public float getS15Fixed16(int index) throws BufferBoundsException
+ {
+ checkBounds(index, 4);
+
+ if (_isMotorolaByteOrder) {
+ float res = (_buffer[index ] & 255) << 8 |
+ (_buffer[index + 1] & 255);
+ int d = (_buffer[index + 2] & 255) << 8 |
+ (_buffer[index + 3] & 255);
+ return (float)(res + d/65536.0);
+ } else {
+ // this particular branch is untested
+ float res = (_buffer[index + 3] & 255) << 8 |
+ (_buffer[index + 2] & 255);
+ int d = (_buffer[index + 1] & 255) << 8 |
+ (_buffer[index ] & 255);
+ return (float)(res + d/65536.0);
+ }
+ }
+
+ @Override
+ public float getFloat32(int index) throws BufferBoundsException
+ {
+ return Float.intBitsToFloat(getInt32(index));
+ }
+
+ @Override
+ public double getDouble64(int index) throws BufferBoundsException
+ {
+ return Double.longBitsToDouble(getInt64(index));
+ }
+
+ @Override
+ @NotNull
+ public byte[] getBytes(int index, int count) throws BufferBoundsException
+ {
+ checkBounds(index, count);
+
+ byte[] bytes = new byte[count];
+ System.arraycopy(_buffer, index, bytes, 0, count);
+ return bytes;
+ }
+
+ @Override
+ @NotNull
+ public String getString(int index, int bytesRequested) throws BufferBoundsException
+ {
+ return new String(getBytes(index, bytesRequested));
+ }
+
+ @Override
+ @NotNull
+ public String getString(int index, int bytesRequested, String charset) throws BufferBoundsException
+ {
+ byte[] bytes = getBytes(index, bytesRequested);
+ try {
+ return new String(bytes, charset);
+ } catch (UnsupportedEncodingException e) {
+ return new String(bytes);
+ }
+ }
+
+ @Override
+ @NotNull
+ public String getNullTerminatedString(int index, int maxLengthBytes) throws BufferBoundsException
+ {
+ // NOTE currently only really suited to single-byte character strings
+
+ checkBounds(index, maxLengthBytes);
+
+ // Check for null terminators
+ int length = 0;
+ while ((index + length) < _buffer.length && _buffer[index + length] != '\0' && length < maxLengthBytes)
+ length++;
+
+ byte[] bytes = getBytes(index, length);
+ return new String(bytes);
+ }
+
+ private void checkBounds(final int index, final int bytesRequested) throws BufferBoundsException
+ {
+ if (bytesRequested < 0 || index < 0 || (long)index + (long)bytesRequested - 1L >= (long)_buffer.length)
+ throw new BufferBoundsException(_buffer, index, bytesRequested);
+ }
+}
Index: trunk/src/com/drew/lang/CompoundException.java
===================================================================
--- trunk/src/com/drew/lang/CompoundException.java (revision 4231)
+++ trunk/src/com/drew/lang/CompoundException.java (revision 6127)
@@ -1,17 +1,26 @@
/*
- * This is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses. I do ask that you leave this header in tact.
+ * Copyright 2002-2012 Drew Noakes
*
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * If you make use of this code, I'd appreciate hearing about it.
- * drew@drewnoakes.com
- * Latest version of this software kept at
- * http://drewnoakes.com/
+ * 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.
+ *
+ * More information about this project is available at:
+ *
+ * http://drewnoakes.com/code/exif/
+ * http://code.google.com/p/metadata-extractor/
*/
package com.drew.lang;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
import java.io.PrintStream;
@@ -22,58 +31,65 @@
* unavailable in previous versions. This class allows support
* of these previous JDK versions.
+ *
+ * @author Drew Noakes http://drewnoakes.com
*/
public class CompoundException extends Exception
{
- private final Throwable _innnerException;
+ private static final long serialVersionUID = -9207883813472069925L;
- public CompoundException(String msg)
+ @Nullable
+ private final Throwable _innerException;
+
+ public CompoundException(@Nullable String msg)
{
this(msg, null);
}
- public CompoundException(Throwable exception)
+ public CompoundException(@Nullable Throwable exception)
{
this(null, exception);
}
- public CompoundException(String msg, Throwable innerException)
+ public CompoundException(@Nullable String msg, @Nullable Throwable innerException)
{
super(msg);
- _innnerException = innerException;
+ _innerException = innerException;
}
+ @Nullable
public Throwable getInnerException()
{
- return _innnerException;
+ return _innerException;
}
+ @NotNull
public String toString()
{
- StringBuffer sbuffer = new StringBuffer();
- sbuffer.append(super.toString());
- if (_innnerException != null) {
- sbuffer.append("\n");
- sbuffer.append("--- inner exception ---");
- sbuffer.append("\n");
- sbuffer.append(_innnerException.toString());
+ StringBuilder string = new StringBuilder();
+ string.append(super.toString());
+ if (_innerException != null) {
+ string.append("\n");
+ string.append("--- inner exception ---");
+ string.append("\n");
+ string.append(_innerException.toString());
}
- return sbuffer.toString();
+ return string.toString();
}
- public void printStackTrace(PrintStream s)
+ public void printStackTrace(@NotNull PrintStream s)
{
super.printStackTrace(s);
- if (_innnerException != null) {
+ if (_innerException != null) {
s.println("--- inner exception ---");
- _innnerException.printStackTrace(s);
+ _innerException.printStackTrace(s);
}
}
- public void printStackTrace(PrintWriter s)
+ public void printStackTrace(@NotNull PrintWriter s)
{
super.printStackTrace(s);
- if (_innnerException != null) {
+ if (_innerException != null) {
s.println("--- inner exception ---");
- _innnerException.printStackTrace(s);
+ _innerException.printStackTrace(s);
}
}
@@ -82,7 +98,7 @@
{
super.printStackTrace();
- if (_innnerException != null) {
+ if (_innerException != null) {
System.err.println("--- inner exception ---");
- _innnerException.printStackTrace();
+ _innerException.printStackTrace();
}
}
Index: trunk/src/com/drew/lang/GeoLocation.java
===================================================================
--- trunk/src/com/drew/lang/GeoLocation.java (revision 6127)
+++ trunk/src/com/drew/lang/GeoLocation.java (revision 6127)
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2002-2012 Drew Noakes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * http://drewnoakes.com/code/exif/
+ * http://code.google.com/p/metadata-extractor/
+ */
+
+package com.drew.lang;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
+
+/**
+ * Represents a latitude and longitude pair, giving a position on earth in spherical coordinates.
+ * Values of latitude and longitude are given in degrees.
+ * This type is immutable.
+ */
+public final class GeoLocation
+{
+ private final double _latitude;
+ private final double _longitude;
+
+ /**
+ * Instantiates a new instance of {@link GeoLocation}.
+ *
+ * @param latitude the latitude, in degrees
+ * @param longitude the longitude, in degrees
+ */
+ public GeoLocation(double latitude, double longitude)
+ {
+ _latitude = latitude;
+ _longitude = longitude;
+ }
+
+ /**
+ * @return the latitudinal angle of this location, in degrees.
+ */
+ public double getLatitude()
+ {
+ return _latitude;
+ }
+
+ /**
+ * @return the longitudinal angle of this location, in degrees.
+ */
+ public double getLongitude()
+ {
+ return _longitude;
+ }
+
+ /**
+ * @return true, if both latitude and longitude are equal to zero
+ */
+ public boolean isZero()
+ {
+ return _latitude == 0 && _longitude == 0;
+ }
+
+ /**
+ * Converts a decimal degree angle into its corresponding DMS (degrees-minutes-seconds) representation as a string,
+ * of format: {@code -1° 23' 4.56"}
+ */
+ @NotNull
+ public static String decimalToDegreesMinutesSecondsString(double decimal)
+ {
+ double[] dms = decimalToDegreesMinutesSeconds(decimal);
+ return dms[0] + "° " + dms[1] + "' " + dms[2] + '"';
+ }
+
+ /**
+ * Converts a decimal degree angle into its corresponding DMS (degrees-minutes-seconds) component values, as
+ * a double array.
+ */
+ @NotNull
+ public static double[] decimalToDegreesMinutesSeconds(double decimal)
+ {
+ int d = (int)decimal;
+ double m = Math.abs((decimal % 1) * 60);
+ double s = (m % 1) * 60;
+ return new double[] { d, (int)m, s};
+ }
+
+ /**
+ * Converts DMS (degrees-minutes-seconds) rational values, as given in {@link com.drew.metadata.exif.GpsDirectory},
+ * into a single value in degrees, as a double.
+ */
+ @Nullable
+ public static Double degreesMinutesSecondsToDecimal(@NotNull final Rational degs, @NotNull final Rational mins, @NotNull final Rational secs, final boolean isNegative)
+ {
+ double decimal = Math.abs(degs.doubleValue())
+ + mins.doubleValue() / 60.0d
+ + secs.doubleValue() / 3600.0d;
+
+ if (Double.isNaN(decimal))
+ return null;
+
+ if (isNegative)
+ decimal *= -1;
+
+ return decimal;
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ GeoLocation that = (GeoLocation) o;
+ if (Double.compare(that._latitude, _latitude) != 0) return false;
+ if (Double.compare(that._longitude, _longitude) != 0) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ temp = _latitude != +0.0d ? Double.doubleToLongBits(_latitude) : 0L;
+ result = (int) (temp ^ (temp >>> 32));
+ temp = _longitude != +0.0d ? Double.doubleToLongBits(_longitude) : 0L;
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ /**
+ * @return a string representation of this location, of format: {@code 1.23, 4.56}
+ */
+ @Override
+ @NotNull
+ public String toString()
+ {
+ return _latitude + ", " + _longitude;
+ }
+
+ /**
+ * @return a string representation of this location, of format: {@code -1° 23' 4.56", 54° 32' 1.92"}
+ */
+ @NotNull
+ public String toDMSString()
+ {
+ return decimalToDegreesMinutesSecondsString(_latitude) + ", " + decimalToDegreesMinutesSecondsString(_longitude);
+ }
+}
Index: trunk/src/com/drew/lang/NullOutputStream.java
===================================================================
--- trunk/src/com/drew/lang/NullOutputStream.java (revision 4231)
+++ trunk/src/com/drew/lang/NullOutputStream.java (revision 6127)
@@ -1,17 +1,21 @@
-/**
- * This is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses. I do ask that you leave this header in tact.
+/*
+ * Copyright 2002-2012 Drew Noakes
*
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * If you make use of this code, I'd appreciate hearing about it.
- * drew@drewnoakes.com
- * Latest version of this software kept at
- * http://drewnoakes.com/
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Created by dnoakes on Dec 15, 2002 3:30:59 PM using IntelliJ IDEA.
+ * 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.
+ *
+ * More information about this project is available at:
+ *
+ * http://drewnoakes.com/code/exif/
+ * http://code.google.com/p/metadata-extractor/
*/
package com.drew.lang;
@@ -20,4 +24,9 @@
import java.io.OutputStream;
+/**
+ * An implementation of OutputSteam that ignores write requests by doing nothing. This class may be useful in tests.
+ *
+ * @author Drew Noakes http://drewnoakes.com
+ */
public class NullOutputStream extends OutputStream
{
Index: trunk/src/com/drew/lang/Rational.java
===================================================================
--- trunk/src/com/drew/lang/Rational.java (revision 4231)
+++ trunk/src/com/drew/lang/Rational.java (revision 6127)
@@ -1,35 +1,27 @@
/*
- * Rational.java
- *
- * This class is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses. Similarly, I release this Java version under the
- * same license, though I do ask that you leave this header in tact.
- *
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
- *
- * If you make use of this code, I'd appreciate hearing about it.
- * drew.noakes@drewnoakes.com
- * Latest version of this software kept at
- * http://drewnoakes.com/
- *
- * Created on 6 May 2002, 18:06
- * Updated 26 Aug 2002 by Drew
- * - Added toSimpleString() method, which returns a simplified and hopefully more
- * readable version of the Rational. i.e. 2/10 -> 1/5, and 10/2 -> 5
- * Modified 29 Oct 2002 (v1.2)
- * - Improved toSimpleString() to factor more complex rational numbers into
- * a simpler form
- * i.e.
- * 10/15 -> 2/3
- * - toSimpleString() now accepts a boolean flag, 'allowDecimals' which will
- * display the rational number in decimal form if it fits within 5 digits
- * i.e.
- * 3/4 -> 0.75 when allowDecimal == true
+ * Copyright 2002-2012 Drew Noakes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * http://drewnoakes.com/code/exif/
+ * http://code.google.com/p/metadata-extractor/
*/
package com.drew.lang;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
import java.io.Serializable;
@@ -38,20 +30,16 @@
* Immutable class for holding a rational number without loss of precision. Provides
* a familiar representation via toString() in form numerator/denominator
.
- *
- * @author Drew Noakes http://drewnoakes.com
+ *
+ * @author Drew Noakes http://drewnoakes.com
*/
public class Rational extends java.lang.Number implements Serializable
{
- /**
- * Holds the numerator.
- */
- private final int numerator;
-
- /**
- * Holds the denominator.
- */
- private final int denominator;
-
- private int maxSimplificationCalculations = 1000;
+ private static final long serialVersionUID = 510688928138848770L;
+
+ /** Holds the numerator. */
+ private final long _numerator;
+
+ /** Holds the denominator. */
+ private final long _denominator;
/**
@@ -60,8 +48,8 @@
* with them!
*/
- public Rational(int numerator, int denominator)
- {
- this.numerator = numerator;
- this.denominator = denominator;
+ public Rational(long numerator, long denominator)
+ {
+ _numerator = numerator;
+ _denominator = denominator;
}
@@ -70,10 +58,10 @@
* This may involve rounding.
*
- * @return the numeric value represented by this object after conversion
- * to type double
.
+ * @return the numeric value represented by this object after conversion
+ * to type double
.
*/
public double doubleValue()
{
- return (double)numerator / (double)denominator;
+ return (double) _numerator / (double) _denominator;
}
@@ -82,10 +70,10 @@
* This may involve rounding.
*
- * @return the numeric value represented by this object after conversion
- * to type float
.
+ * @return the numeric value represented by this object after conversion
+ * to type float
.
*/
public float floatValue()
{
- return (float)numerator / (float)denominator;
+ return (float) _numerator / (float) _denominator;
}
@@ -95,10 +83,10 @@
* casts the result of doubleValue()
to byte
.
*
- * @return the numeric value represented by this object after conversion
- * to type byte
.
+ * @return the numeric value represented by this object after conversion
+ * to type byte
.
*/
public final byte byteValue()
{
- return (byte)doubleValue();
+ return (byte) doubleValue();
}
@@ -108,10 +96,10 @@
* casts the result of doubleValue()
to int
.
*
- * @return the numeric value represented by this object after conversion
- * to type int
.
+ * @return the numeric value represented by this object after conversion
+ * to type int
.
*/
public final int intValue()
{
- return (int)doubleValue();
+ return (int) doubleValue();
}
@@ -121,10 +109,10 @@
* casts the result of doubleValue()
to long
.
*
- * @return the numeric value represented by this object after conversion
- * to type long
.
+ * @return the numeric value represented by this object after conversion
+ * to type long
.
*/
public final long longValue()
{
- return (long)doubleValue();
+ return (long) doubleValue();
}
@@ -134,74 +122,66 @@
* casts the result of doubleValue()
to short
.
*
- * @return the numeric value represented by this object after conversion
- * to type short
.
+ * @return the numeric value represented by this object after conversion
+ * to type short
.
*/
public final short shortValue()
{
- return (short)doubleValue();
- }
-
-
- /**
- * Returns the denominator.
- */
- public final int getDenominator()
- {
- return this.denominator;
- }
-
- /**
- * Returns the numerator.
- */
- public final int getNumerator()
- {
- return this.numerator;
- }
-
- /**
- * Returns the reciprocal value of this obejct as a new Rational.
+ return (short) doubleValue();
+ }
+
+
+ /** Returns the denominator. */
+ public final long getDenominator()
+ {
+ return this._denominator;
+ }
+
+ /** Returns the numerator. */
+ public final long getNumerator()
+ {
+ return this._numerator;
+ }
+
+ /**
+ * Returns the reciprocal value of this object as a new Rational.
+ *
* @return the reciprocal in a new object
*/
+ @NotNull
public Rational getReciprocal()
{
- return new Rational(this.denominator, this.numerator);
- }
-
- /**
- * Checks if this rational number is an Integer, either positive or negative.
- */
+ return new Rational(this._denominator, this._numerator);
+ }
+
+ /** Checks if this rational number is an Integer, either positive or negative. */
public boolean isInteger()
{
- if (denominator == 1 ||
- (denominator != 0 && (numerator % denominator == 0)) ||
- (denominator == 0 && numerator == 0)
- ) {
- return true;
- } else {
- return false;
- }
+ return _denominator == 1 ||
+ (_denominator != 0 && (_numerator % _denominator == 0)) ||
+ (_denominator == 0 && _numerator == 0);
}
/**
* Returns a string representation of the object of form numerator/denominator
.
- * @return a string representation of the object.
- */
+ *
+ * @return a string representation of the object.
+ */
+ @NotNull
public String toString()
{
- return numerator + "/" + denominator;
- }
-
- /**
- * Returns the simplest represenation of this Rational's value possible.
- */
+ return _numerator + "/" + _denominator;
+ }
+
+ /** Returns the simplest representation of this Rational's value possible. */
+ @NotNull
public String toSimpleString(boolean allowDecimal)
{
- if (denominator == 0 && numerator != 0) {
+ if (_denominator == 0 && _numerator != 0) {
return toString();
} else if (isInteger()) {
return Integer.toString(intValue());
- } else if (numerator != 1 && denominator % numerator == 0) {
+ } else if (_numerator != 1 && _denominator % _numerator == 0) {
// common factor between denominator and numerator
- int newDenominator = denominator / numerator;
+ long newDenominator = _denominator / _numerator;
return new Rational(1, newDenominator).toSimpleString(allowDecimal);
} else {
@@ -220,9 +200,11 @@
* Decides whether a brute-force simplification calculation should be avoided
* by comparing the maximum number of possible calculations with some threshold.
+ *
* @return true if the simplification should be performed, otherwise false
*/
private boolean tooComplexForSimplification()
{
- double maxPossibleCalculations = (((double)(Math.min(denominator, numerator) - 1) / 5d) + 2);
+ double maxPossibleCalculations = (((double) (Math.min(_denominator, _numerator) - 1) / 5d) + 2);
+ final int maxSimplificationCalculations = 1000;
return maxPossibleCalculations > maxSimplificationCalculations;
}
@@ -231,15 +213,22 @@
* Compares two Rational
instances, returning true if they are mathematically
* equivalent.
+ *
* @param obj the Rational to compare this instance to.
* @return true if instances are mathematically equivalent, otherwise false. Will also
* return false if obj
is not an instance of Rational
.
*/
- public boolean equals(Object obj)
- {
- if (!(obj instanceof Rational)) {
+ @Override
+ public boolean equals(@Nullable Object obj)
+ {
+ if (obj==null || !(obj instanceof Rational))
return false;
- }
- Rational that = (Rational)obj;
+ Rational that = (Rational) obj;
return this.doubleValue() == that.doubleValue();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (23 * (int)_denominator) + (int)_numerator;
}
@@ -252,5 +241,5 @@
* To reduce a rational, need to see if both numerator and denominator are divisible
* by a common factor. Using the prime number series in ascending order guarantees
- * the minimun number of checks required.
* However, generating the prime number series seems to be a hefty task. Perhaps @@ -265,12 +254,14 @@ * -- * ------------------------------------ + 2 * 10 2 - * + *
* Math.min(denominator, numerator) - 1 * = ------------------------------------ + 2 * 5 * - * @return a simplified instance, or if the Rational could not be simpliffied, + * + * @return a simplified instance, or if the Rational could not be simplified, * returns itself (unchanged) */ + @NotNull public Rational getSimplifiedInstance() { @@ -278,11 +269,11 @@ return this; } - for (int factor = 2; factor <= Math.min(denominator, numerator); factor++) { + for (int factor = 2; factor <= Math.min(_denominator, _numerator); factor++) { if ((factor % 2 == 0 && factor > 2) || (factor % 5 == 0 && factor > 5)) { continue; } - if (denominator % factor == 0 && numerator % factor == 0) { + if (_denominator % factor == 0 && _numerator % factor == 0) { // found a common factor - return new Rational(numerator / factor, denominator / factor); + return new Rational(_numerator / factor, _denominator / factor); } } Index: trunk/src/com/drew/lang/StringUtil.java =================================================================== --- trunk/src/com/drew/lang/StringUtil.java (revision 6127) +++ trunk/src/com/drew/lang/StringUtil.java (revision 6127) @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2012 Drew Noakes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * http://drewnoakes.com/code/exif/ + * http://code.google.com/p/metadata-extractor/ + */ + +package com.drew.lang; + +import com.drew.lang.annotations.NotNull; + +import java.util.Iterator; + +/** @author Drew Noakes http://drewnoakes.com */ +public class StringUtil +{ + public static String join(@NotNull Iterable extends CharSequence> strings, @NotNull String delimiter) + { + int capacity = 0; + int delimLength = delimiter.length(); + + Iterator extends CharSequence> iter = strings.iterator(); + if (iter.hasNext()) + capacity += iter.next().length() + delimLength; + + StringBuilder buffer = new StringBuilder(capacity); + iter = strings.iterator(); + if (iter.hasNext()) { + buffer.append(iter.next()); + while (iter.hasNext()) { + buffer.append(delimiter); + buffer.append(iter.next()); + } + } + return buffer.toString(); + } + + public static