blob: 1fd21b0f923bc21a709975807741db1c6f9f099e [file] [log] [blame]
/*
* Copyright (c) 2007-2013, 2015 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.net4j.util.io;
import org.eclipse.net4j.internal.util.bundle.OM;
import org.eclipse.net4j.util.StringUtil;
import org.eclipse.net4j.util.WrappedException;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
/**
* @author Eike Stepper
*/
public final class ExtendedIOUtil
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, ExtendedIOUtil.class);
private static final int UTF_HEADER_SIZE = 2;
private static final int MAX_16_BIT = (1 << 16) - 1;
private static final int MAX_UTF_LENGTH = MAX_16_BIT - UTF_HEADER_SIZE;
private static final int MAX_UTF_CHARS = MAX_UTF_LENGTH / 3;
private static final int MAX_ENUM_LITERALS = Byte.MAX_VALUE - Byte.MIN_VALUE;
private static final byte NO_ENUM_LITERAL = Byte.MIN_VALUE;
private static final int BYTE_BUFFER_SIZE = 8192;
private static final int CHARACTER_BUFFER_SIZE = 4096;
private ExtendedIOUtil()
{
}
public static void writeByteArray(DataOutput out, byte[] b) throws IOException
{
if (b != null)
{
out.writeInt(b.length);
out.write(b);
}
else
{
out.writeInt(-1);
}
}
public static byte[] readByteArray(DataInput in) throws IOException
{
int length = in.readInt();
if (length < 0)
{
return null;
}
byte[] b;
try
{
b = new byte[length];
}
catch (Throwable t)
{
throw new IOException("Unable to allocate " + length + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$
}
in.readFully(b);
return b;
}
public static void writeObject(final DataOutput out, Object object) throws IOException
{
ObjectOutput objectOutput = null;
if (out instanceof ObjectOutput)
{
objectOutput = (ObjectOutput)out;
}
else
{
@SuppressWarnings("resource")
ObjectOutputStream wrapper = new ObjectOutputStream(new OutputStream()
{
@Override
public void write(int b) throws IOException
{
out.writeByte((b & 0xff) + Byte.MIN_VALUE);
}
});
objectOutput = wrapper;
}
objectOutput.writeObject(object);
}
public static Object readObject(final DataInput in) throws IOException
{
return readObject(in, (ClassResolver)null);
}
public static Object readObject(final DataInput in, ClassLoader classLoader) throws IOException
{
return readObject(in, new ClassLoaderClassResolver(classLoader));
}
public static Object readObject(final DataInput in, final ClassResolver classResolver) throws IOException
{
ObjectInput objectInput = null;
if (in instanceof ObjectInput)
{
objectInput = (ObjectInput)in;
}
else
{
@SuppressWarnings("resource")
ObjectInputStream wrapper = new ObjectInputStream(new InputStream()
{
@Override
public int read() throws IOException
{
return in.readByte() - Byte.MIN_VALUE;
}
})
{
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException
{
if (classResolver != null)
{
if (TRACER.isEnabled())
{
TRACER.format("Deserializing class {0}", desc.getName()); //$NON-NLS-1$
}
Class<?> c = classResolver.resolveClass(desc);
if (c != null)
{
return c;
}
}
return super.resolveClass(desc);
}
};
objectInput = wrapper;
}
try
{
return objectInput.readObject();
}
catch (ClassNotFoundException ex)
{
throw WrappedException.wrap(ex);
}
}
public static void writeString(DataOutput out, String str) throws IOException
{
if (str != null)
{
int size = str.length();
int start = 0;
do
{
out.writeBoolean(true);
int chunk = Math.min(size, MAX_UTF_CHARS);
int end = start + chunk;
out.writeUTF(str.substring(start, end));
start = end;
size -= chunk;
} while (size > 0);
}
out.writeBoolean(false);
}
public static String readString(DataInput in) throws IOException
{
boolean more = in.readBoolean();
if (!more)
{
return null;
}
StringBuilder builder = new StringBuilder();
do
{
String chunk = in.readUTF();
builder.append(chunk);
more = in.readBoolean();
} while (more);
return builder.toString();
}
/**
* @since 3.3
*/
public static long writeBinaryStream(DataOutput out, InputStream inputStream) throws IOException
{
long length = 0;
byte[] buffer = new byte[BYTE_BUFFER_SIZE];
for (;;)
{
int n = inputStream.read(buffer);
if (n == IOUtil.EOF)
{
out.writeShort(0);
break;
}
out.writeShort(n);
out.write(buffer, 0, n);
length += n;
}
return length;
}
/**
* @since 3.3
*/
public static long readBinaryStream(DataInput in, OutputStream outputStream) throws IOException
{
long length = 0;
byte[] buffer = new byte[BYTE_BUFFER_SIZE];
for (;;)
{
int n = in.readShort();
if (n == 0)
{
break;
}
in.readFully(buffer, 0, n);
outputStream.write(buffer, 0, n);
length += n;
}
return length;
}
/**
* @since 3.3
*/
public static long writeCharacterStream(DataOutput out, Reader reader) throws IOException
{
long length = 0;
char[] buffer = new char[CHARACTER_BUFFER_SIZE];
for (;;)
{
int n = reader.read(buffer);
if (n == IOUtil.EOF)
{
out.writeShort(0);
break;
}
out.writeShort(n);
for (int i = 0; i < n; i++)
{
out.writeChar(buffer[i]);
}
length += n;
}
return length;
}
/**
* @since 3.3
*/
public static long readCharacterStream(DataInput in, Writer writer) throws IOException
{
long length = 0;
char[] buffer = new char[CHARACTER_BUFFER_SIZE];
for (;;)
{
int n = in.readShort();
if (n == 0)
{
break;
}
for (int i = 0; i < n; i++)
{
buffer[i] = in.readChar();
}
writer.write(buffer, 0, n);
length += n;
}
return length;
}
/**
* @since 3.0
*/
public static void writeEnum(DataOutput out, Enum<?> literal) throws IOException
{
if (literal == null)
{
out.writeByte(NO_ENUM_LITERAL);
}
else
{
getEnumLiterals(literal.getDeclaringClass()); // Check valid size
int ordinal = literal.ordinal();
out.writeByte(ordinal + Byte.MIN_VALUE + 1);
}
}
/**
* @since 3.0
*/
public static <T extends Enum<?>> T readEnum(DataInput in, Class<T> type) throws IOException
{
T[] literals = getEnumLiterals(type);
int ordinal = in.readByte();
if (ordinal == NO_ENUM_LITERAL)
{
return null;
}
return literals[ordinal - Byte.MIN_VALUE - 1];
}
/**
* @since 3.4
*/
public static void writeException(DataOutput out, Throwable t) throws IOException
{
String message = StringUtil.formatException(t);
writeString(out, message);
byte[] bytes = ExtendedIOUtil.serializeThrowable(t);
writeByteArray(out, bytes);
}
/**
* @since 3.4
*/
public static Throwable readException(DataInput in) throws IOException
{
String message = readString(in);
byte[] bytes = readByteArray(in);
try
{
return ExtendedIOUtil.deserializeThrowable(bytes);
}
catch (Throwable couldNotLoadExceptionClass)
{
return new RuntimeException(message);
}
}
/**
* @since 3.4
*/
public static byte[] serializeThrowable(Throwable t)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
writeObject(dos, t);
return baos.toByteArray();
}
catch (Exception ex)
{
return null;
}
}
/**
* @since 3.4
*/
public static Throwable deserializeThrowable(byte[] bytes)
{
try
{
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
DataInputStream dis = new DataInputStream(bais);
return (Throwable)readObject(dis, OM.class.getClassLoader());
}
catch (IOException ex)
{
return null;
}
}
private static <T> T[] getEnumLiterals(Class<T> type)
{
T[] literals = type.getEnumConstants();
int size = literals.length;
if (size > MAX_ENUM_LITERALS)
{
throw new AssertionError("Enum too large: " + size + " literals");
}
return literals;
}
/**
* @author Eike Stepper
*/
public interface ClassResolver
{
public Class<?> resolveClass(ObjectStreamClass v) throws ClassNotFoundException;
}
/**
* @author Eike Stepper
*/
public static class ClassLoaderClassResolver implements ClassResolver
{
private static final String STACK_TRACE_ELEMENT = StackTraceElement[].class.getName();
private ClassLoader classLoader;
public ClassLoaderClassResolver(ClassLoader classLoader)
{
this.classLoader = classLoader;
}
public Class<?> resolveClass(ObjectStreamClass v) throws ClassNotFoundException
{
String className = v.getName();
try
{
return classLoader.loadClass(className);
}
catch (ClassNotFoundException ex)
{
if (!STACK_TRACE_ELEMENT.equals(className))
{
if (TRACER.isEnabled())
{
TRACER.trace("Exception in resolver", ex); //$NON-NLS-1$
}
}
return null;
}
}
}
}