/*
* Copyright 2009 Thomas Bocek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package net.tomp2p.utils;
import io.netty.buffer.ByteBuf;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import net.tomp2p.connection2.ChannelCreator;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.message.TrackerData;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number480;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.rpc.SimpleBloomFilter;
import net.tomp2p.storage.Data;
public class Utils {
private static final Random random = new Random();
public static final int IPV4_BYTES = 4;
public static final int IPV6_BYTES = 16;
public static final int BYTE_BITS = 8;
public static final int MASK_FF = 0xff;
public static final int MASK_80 = 0x80;
public static final int INTEGER_BYTE_SIZE = 4;
public static final int LONG_BYTE_SIZE = 8;
public static final int BYTE_SIZE = 1;
public static final int SHORT_BYTE_SIZE = 2;
public static final int MASK_0F = 0xf;
public static ByteBuffer loadFile(File file) throws IOException {
FileInputStream fis = null;
FileChannel channel = null;
try {
fis = new FileInputStream(file);
channel = fis.getChannel();
return channel.map(MapMode.READ_ONLY, 0, channel.size());
} finally {
bestEffortclose(channel, fis);
}
}
public static Number160 makeSHAHash(File file) {
FileInputStream fis = null;
FileChannel channel = null;
try {
fis = new FileInputStream(file);
channel = fis.getChannel();
MessageDigest md = MessageDigest.getInstance("SHA-1");
for (long offest = 0; offest < channel.size(); offest += 10 * 1024) {
ByteBuffer buffer;
if (channel.size() - offest < 10 * 1024)
buffer = channel.map(MapMode.READ_ONLY, offest, (int) channel.size() - offest);
else
buffer = channel.map(MapMode.READ_ONLY, offest, 10 * 1024);
md.update(buffer);
}
byte[] digest = md.digest();
return new Number160(digest);
} catch (FileNotFoundException e) {
e.printStackTrace();
return Number160.ZERO;
} catch (IOException e) {
e.printStackTrace();
return Number160.ZERO;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return Number160.ZERO;
} finally {
bestEffortclose(channel, fis);
}
}
public static Number160 makeSHAHash(String strInput) {
byte[] buffer = strInput.getBytes();
return makeSHAHash(buffer);
}
public static Number160 makeSHAHash(ByteBuffer buffer) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(buffer);
byte[] digest = md.digest();
return new Number160(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return new Number160();
}
}
/**
* Calculates the SHA-1 hash of the Netty byte buffer.
* @param buffer The buffer that stores data
* @return The 160bit hash number
*/
public static Number160 makeSHAHash(final ByteBuf buffer) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
for (int i = 0; i < buffer.nioBufferCount(); i++) {
md.update(buffer.nioBuffers()[i]);
}
byte[] digest = md.digest();
return new Number160(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return new Number160();
}
}
public static Number160 makeSHAHash(byte[] buffer) {
return makeSHAHash(ByteBuffer.wrap(buffer));
}
public static Number160 makeSHAHash(byte[] buffer, int offset, int length) {
return makeSHAHash(ByteBuffer.wrap(buffer, offset, length));
}
public static Number160 createRandomNodeID() {
// TODO: this hardcoded, bad style
byte[] me = new byte[20];
random.nextBytes(me);
Number160 id = new Number160(me);
return id;
}
public static byte[] compress(byte[] input) {
// Create the compressor with highest level of compression
Deflater compressor = new Deflater();
compressor.setLevel(Deflater.BEST_SPEED);
// Give the compressor the data to compress
compressor.setInput(input);
compressor.finish();
// Create an expandable byte array to hold the compressed data.
// You cannot use an array that's the same size as the orginal because
// there is no guarantee that the compressed data will be smaller than
// the uncompressed data.
ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
// Compress the data
byte[] buf = new byte[1024];
while (!compressor.finished()) {
int count = compressor.deflate(buf);
bos.write(buf, 0, count);
}
try {
bos.close();
} catch (IOException e) {
}
// Get the compressed data
return bos.toByteArray();
}
public static byte[] uncompress(byte[] compressedData, int offset, int length) {
// Create the decompressor and give it the data to compress
Inflater decompressor = new Inflater();
decompressor.setInput(compressedData, offset, length);
// Create an expandable byte array to hold the decompressed data
ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
// Decompress the data
byte[] buf = new byte[1024];
while (!decompressor.finished()) {
try {
int count = decompressor.inflate(buf);
bos.write(buf, 0, count);
} catch (DataFormatException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// Get the decompressed data
return bos.toByteArray();
}
public static byte[] uncompress(byte[] compressedData) {
return uncompress(compressedData, 0, compressedData.length);
}
public static byte[] encodeJavaObject(Object attachement) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(attachement);
// no need to call close of flush since we use ByteArrayOutputStream
byte[] data = bos.toByteArray();
return data;
}
public static Object decodeJavaObject(ByteBuf channelBuffer) throws ClassNotFoundException, IOException {
InputStream is = new MultiByteBufferInputStream(channelBuffer);
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(is));
Object obj = ois.readObject();
ois.close();
return obj;
}
public static Object decodeJavaObject(byte[] me, int offset, int length) throws ClassNotFoundException,
IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(me, offset, length);
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = ois.readObject();
// no need to call close of flush since we use ByteArrayInputStream
return obj;
}
/**
* Stores the differences of two collections in a result collection. The result will contain items from collection1
* without those items that are in collection2.
*
* @param collection1
* The first collection (master collection) that will be iterated and checked against duplicates in
* collection2.
* @param result
* The collection to store the result
* @param collection2
* The second collection that will be searched for duplicates
* @return Returns the collection the user specified as the resulting collection
*/
public static <K> Collection<K> difference(Collection<K> collection1, Collection<K> result,
Collection<K> collection2) {
for (Iterator<K> iterator = collection1.iterator(); iterator.hasNext();) {
K item = iterator.next();
if (!collection2.contains(item)) {
result.add(item);
}
}
return result;
}
/**
* Stores the differences of multiple collections in a result collection. The result will contain items from
* collection1 without those items that are in collections2. The calling method might need to provide a
*
* @SuppressWarnings("unchecked") since generics and arrays do not mix well.
* @param collection1
* The first collection (master collection) that will be iterated and checked against duplicates in
* collection2.
* @param result
* The collection to store the result
* @param collection2
* The second collections that will be searched for duplicates
* @return Returns the collection the user specified as the resulting collection
*/
public static <K> Collection<K> difference(Collection<K> collection1, Collection<K> result,
Collection<K>... collections2) {
for (Iterator<K> iterator = collection1.iterator(); iterator.hasNext();) {
K item = iterator.next();
int size = collections2.length;
boolean found = false;
for (int i = 0; i < size; i++) {
if (collections2[i].contains(item)) {
found = true;
break;
}
}
if (!found) {
result.add(item);
}
}
return result;
}
public static void bestEffortclose(Closeable... closables) {
// best effort close;
for (Closeable closable : closables) {
if (closable != null) {
try {
closable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static final byte[] intToByteArray(int value) {
return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value };
}
public static final int byteArrayToInt(byte[] b) {
return (b[0] << 24) + ((b[1] & 0xFF) << 16) + ((b[2] & 0xFF) << 8) + (b[3] & 0xFF);
}
/**
* Returns a random element from a collection. This method is pretty slow O(n), but the Java collection framework
* does not offer a better solution. This method puts the collection into a {@link List} and fetches a random
* element using {@link List#get(int)}.
*
* @param collection
* The collection from which we want to pick a random element
* @param rnd
* The random object
* @return A random element
*/
public static <K> K pollRandom(Collection<K> collection, Random rnd) {
int size = collection.size();
if (size == 0) {
return null;
}
int index = rnd.nextInt(size);
List<K> values = new ArrayList<K>(collection);
K retVal = values.get(index);
// now we need to remove this element
collection.remove(retVal);
return retVal;
}
public static <K, V> Entry<K, V> pollRandomKey(Map<K, V> queueToAsk, Random rnd) {
int size = queueToAsk.size();
if (size == 0)
return null;
List<K> keys = new ArrayList<K>();
keys.addAll(queueToAsk.keySet());
int index = rnd.nextInt(size);
final K key = keys.get(index);
final V value = queueToAsk.remove(key);
return new Entry<K, V>() {
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
return null;
}
};
}
public static <K> Collection<K> subtract(final Collection<K> a, final Collection<K> b) {
ArrayList<K> list = new ArrayList<K>(a);
for (Iterator<K> it = b.iterator(); it.hasNext();) {
list.remove(it.next());
}
return list;
}
public static <K, V> Map<K, V> subtract(final Map<K, V> a, final Collection<K> b) {
Map<K, V> map = new HashMap<K, V>(a);
for (Iterator<K> it = b.iterator(); it.hasNext();) {
map.remove(it.next());
}
return map;
}
public static <K, V> Map<K, V> disjunction(final Map<K, V> a, final Collection<K> b) {
Map<K, V> map = new HashMap<K, V>();
for (Iterator<Entry<K, V>> it = a.entrySet().iterator(); it.hasNext();) {
Entry<K, V> entry = it.next();
if (!b.contains(entry.getKey())) {
map.put(entry.getKey(), entry.getValue());
}
}
return map;
}
public static TrackerData disjunction(TrackerData meshPeers, SimpleBloomFilter<Number160> knownPeers) {
TrackerData trackerData = new TrackerData(new HashMap<PeerAddress, Data>(), null);
for(Map.Entry<PeerAddress, Data> entry: meshPeers.getPeerAddresses().entrySet()) {
if(!knownPeers.contains(entry.getKey().getPeerId())) {
trackerData.put(entry.getKey(), entry.getValue());
}
}
return trackerData;
}
public static <K> Collection<K> limit(final Collection<K> a, final int size) {
ArrayList<K> list = new ArrayList<K>();
int i = 0;
for (Iterator<K> it = a.iterator(); it.hasNext() && i < size;) {
list.add(it.next());
}
return list;
}
public static TrackerData limit(TrackerData peers, int size) {
Map<PeerAddress, Data> map = new HashMap<PeerAddress, Data>(peers.getPeerAddresses());
int i = 0;
for (Iterator<Map.Entry<PeerAddress, Data>> it = map.entrySet().iterator(); it.hasNext() && i < size;) {
Map.Entry<PeerAddress, Data> entry = it.next();
map.put(entry.getKey(), entry.getValue());
}
TrackerData data = new TrackerData(map, peers.getReferrer());
return data;
}
public static <K, V> Map<K, V> limit(final Map<K, V> a, final int i) {
Map<K, V> map = new HashMap<K, V>(a);
int remove = a.size() - i;
for (Iterator<K> it = a.keySet().iterator(); it.hasNext() && remove >= 0;) {
map.remove(it.next());
remove--;
}
return map;
}
public static String debugArray(byte[] array, int offset, int length) {
String digits = "0123456789abcdef";
StringBuilder sb = new StringBuilder(length * 2);
for (int i = 0; i < length; i++) {
int bi = array[offset + i] & 0xff;
sb.append(digits.charAt(bi >> 4));
sb.append(digits.charAt(bi & 0xf));
}
return sb.toString();
}
public static String debugArray(byte[] array) {
return debugArray(array, 0, array.length);
}
public static boolean checkEntryProtection(Map<?, Data> dataMap) {
for (Data data : dataMap.values()) {
if (data.protectedEntry()) {
return true;
}
}
return false;
}
public static TrackerData limitRandom(TrackerData activePeers, int trackerSize) {
// TODO Auto-generated method stub
return activePeers;
}
public static <K> K getLast(List<K> list) {
if (!list.isEmpty()) {
return list.get(list.size() - 1);
}
return null;
}
/*
* Copyright (C) 2008 The Guava Authors 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.
*/
/**
* Returns an Inet4Address having the integer value specified by the argument.
*
* @param address
* {@code int}, the 32bit integer address to be converted
* @return {@link Inet4Address} equivalent of the argument
*/
public static Inet4Address fromInteger(int address) {
return getInet4Address(toByteArray(address));
}
/**
* Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to
* {@code ByteBuffer.allocate(4).putInt(value).array()} . For example, the input value {@code 0x12131415} would
* yield the byte array {@code 0x12, 0x13, 0x14, 0x15} .
* <p>
* If you need to convert and concatenate several values (possibly even of different types), use a shared
* {@link java.nio.ByteBuffer} instance, or use {@link com.google.common.io.ByteStreams#newDataOutput()} to get a
* growable buffer.
*/
private static byte[] toByteArray(int value) {
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value };
}
/**
* Returns an {@link Inet4Address}, given a byte array representation of the IPv4 address.
*
* @param bytes
* byte array representing an IPv4 address (should be of length 4).
* @return {@link Inet4Address} corresponding to the supplied byte array.
* @throws IllegalArgumentException
* if a valid {@link Inet4Address} can not be created.
*/
private static Inet4Address getInet4Address(byte[] bytes) {
if (bytes.length != 4) {
throw new IllegalArgumentException("Byte array has invalid length for an IPv4 address");
}
try {
InetAddress ipv4 = InetAddress.getByAddress(bytes);
if (!(ipv4 instanceof Inet4Address)) {
throw new UnknownHostException(String.format("'%s' is not an IPv4 address.",
ipv4.getHostAddress()));
}
return (Inet4Address) ipv4;
} catch (UnknownHostException e) {
/*
* This really shouldn't happen in practice since all our byte sequences should be valid IP addresses.
* However {@link InetAddress#getByAddress} is documented as potentially throwing this
* "if IP address is of illegal length". This is mapped to IllegalArgumentException since, presumably, the
* argument triggered some bizarre processing bug.
*/
throw new IllegalArgumentException(String.format(
"Host address '%s' is not a valid IPv4 address.", Arrays.toString(bytes)), e);
}
}
// as seen here:
// http://stackoverflow.com/questions/617414/create-a-temporary-directory-in-java
public static File createTempDir() throws IOException {
final File temp;
temp = File.createTempFile("temp", Long.toString(System.nanoTime()));
if (!(temp.delete())) {
throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
}
if (!(temp.mkdir())) {
throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
}
return (temp);
}
public static void nullCheck(Object... objects) {
int counter = 0;
for (Object object : objects) {
if (object == null) {
throw new IllegalArgumentException("Null not allowed in paramenetr nr. " + counter);
}
counter++;
}
}
public static boolean nullCheckRetVal(Object... objects) {
for (Object object : objects) {
if (object == null) {
return true;
}
}
return false;
}
public static Collection<Number160> extractContentKeys(Collection<Number480> collection) {
Collection<Number160> result = new ArrayList<Number160>(collection.size());
for (Number480 number480 : collection) {
result.add(number480.getContentKey());
}
return result;
}
/**
* Converts a byte array to a Inet4Address.
*
* @param me
* the byte array
* @param offset
* where to start in the byte array
* @return The Inet4Address
*
* @exception IndexOutOfBoundsException
* if copying would cause access of data outside array bounds for <code>src</code>.
* @exception NullPointerException
* if either <code>src</code> is <code>null</code>.
*/
public static InetAddress inet4FromBytes(final byte[] src, final int offset) {
// IPv4 is 32 bit
byte[] tmp2 = new byte[IPV4_BYTES];
System.arraycopy(src, offset, tmp2, 0, IPV4_BYTES);
try {
return Inet4Address.getByAddress(tmp2);
} catch (UnknownHostException e) {
/*
* This really shouldn't happen in practice since all our byte sequences have the right length. However
* {@link InetAddress#getByAddress} is documented as potentially throwing this
* "if IP address is of illegal length".
*/
throw new IllegalArgumentException(String.format(
"Host address '%s' is not a valid IPv4 address.", Arrays.toString(tmp2)), e);
}
}
/**
* Converts a byte array to a Inet6Address.
*
* @param me
* me the byte array
* @param offset
* where to start in the byte array
* @return The Inet6Address
*
* @exception IndexOutOfBoundsException
* if copying would cause access of data outside array bounds for <code>src</code>.
* @exception NullPointerException
* if either <code>src</code> is <code>null</code>.
*/
public static InetAddress inet6FromBytes(final byte[] me, final int offset) {
// IPv6 is 128 bit
byte[] tmp2 = new byte[IPV6_BYTES];
System.arraycopy(me, offset, tmp2, 0, IPV6_BYTES);
try {
return Inet6Address.getByAddress(tmp2);
} catch (UnknownHostException e) {
/*
* This really shouldn't happen in practice since all our byte sequences have the right length. However
* {@link InetAddress#getByAddress} is documented as potentially throwing this
* "if IP address is of illegal length".
*/
throw new IllegalArgumentException(String.format(
"Host address '%s' is not a valid IPv4 address.", Arrays.toString(tmp2)), e);
}
}
/**
* Convert a byte to a bit set. BitSet.valueOf(new byte[] {b}) is only available in 1.7, so we need to do this on
* our own.
*
* @param b
* The byte to be converted
* @return The resulting bit set
*/
public static BitSet createBitSet(final byte b) {
final BitSet bitSet = new BitSet(8);
for (int i = 0; i < Utils.BYTE_BITS; i++) {
bitSet.set(i, (b & (1 << i)) != 0);
}
return bitSet;
}
/**
* Convert a BitSet to a byte. Cannot use relayType.toByteArray()[0]; since its only available in 1.7
*
* @param bitSet
* The bit set
* @return The resulting byte
*/
public static byte createByte(final BitSet bitSet) {
byte b = 0;
for (int i = 0; i < Utils.BYTE_BITS; i++) {
if (bitSet.get(i)) {
b |= 1 << i;
}
}
return b;
}
/**
* Adds a listener to the response future and releases all aquired channels in channel creator.
*
* @param channelCreator
* The channel creator that will be shutdown and all connections will be closed
* @param baseFutures
* The futures to listen to. If all the futures finished, then the channel creator is shutdown. If null
* provided, the channel creator is shutdown immediately.
*/
public static void addReleaseListener(final ChannelCreator channelCreator,
final BaseFuture... baseFutures) {
if (baseFutures == null) {
channelCreator.shutdown();
return;
}
final int count = baseFutures.length;
final AtomicInteger finished = new AtomicInteger(0);
for (BaseFuture baseFuture : baseFutures) {
baseFuture.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(final BaseFuture future) throws Exception {
if (finished.incrementAndGet() == count) {
channelCreator.shutdown();
}
}
});
}
}
/**
* Compares if two sets have the exact same elements.
*
* @param set1
* The first set
* @param set2
* The second set
* @param <T>
* Type of the collection
* @return True if both sets have the exact same elements
*/
public static <T> boolean isSameSets(final Collection<T> set1, final Collection<T> set2) {
if (set1 == null ^ set2 == null) {
return false;
}
if (set1.size() != set2.size()) {
return false;
}
for (T obj : set1) {
if (!set2.contains(obj)) {
return false;
}
}
return true;
}
}