/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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 org.elasticsearch.common.io.stream; import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.file.NoSuchFileException; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Token; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexFormatTooNewException; import org.apache.lucene.index.IndexFormatTooOldException; import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.text.Text; import org.elasticsearch.tasks.Task; import org.joda.time.ReadableInstant; /** * */ public abstract class StreamOutput extends OutputStream { private Version version = Version.CURRENT; public Version getVersion() { return this.version; } public StreamOutput setVersion(Version version) { this.version = version; return this; } public long position() throws IOException { throw new UnsupportedOperationException(); } public void seek(long position) throws IOException { throw new UnsupportedOperationException(); } /** * Writes a single byte. */ public abstract void writeByte(byte b) throws IOException; /** * Writes an array of bytes. * * @param b the bytes to write */ public void writeBytes(byte[] b) throws IOException { writeBytes(b, 0, b.length); } /** * Writes an array of bytes. * * @param b the bytes to write * @param length the number of bytes to write */ public void writeBytes(byte[] b, int length) throws IOException { writeBytes(b, 0, length); } /** * Writes an array of bytes. * * @param b the bytes to write * @param offset the offset in the byte array * @param length the number of bytes to write */ public abstract void writeBytes(byte[] b, int offset, int length) throws IOException; /** * Writes an array of bytes. * * @param b the bytes to write */ public void writeByteArray(byte[] b) throws IOException { writeVInt(b.length); writeBytes(b, 0, b.length); } /** * Writes the bytes reference, including a length header. */ public void writeBytesReference(@Nullable BytesReference bytes) throws IOException { if (bytes == null) { writeVInt(0); return; } writeVInt(bytes.length()); bytes.writeTo(this); } public void writeBytesRef(BytesRef bytes) throws IOException { if (bytes == null) { writeVInt(0); return; } writeVInt(bytes.length); write(bytes.bytes, bytes.offset, bytes.length); } public final void writeShort(short v) throws IOException { writeByte((byte) (v >> 8)); writeByte((byte) v); } /** * Writes an int as four bytes. */ public void writeInt(int i) throws IOException { writeByte((byte) (i >> 24)); writeByte((byte) (i >> 16)); writeByte((byte) (i >> 8)); writeByte((byte) i); } /** * Writes an int in a variable-length format. Writes between one and * five bytes. Smaller values take fewer bytes. Negative numbers * will always use all 5 bytes and are therefore better serialized * using {@link #writeInt} */ public void writeVInt(int i) throws IOException { while ((i & ~0x7F) != 0) { writeByte((byte) ((i & 0x7f) | 0x80)); i >>>= 7; } writeByte((byte) i); } /** * Writes a long as eight bytes. */ public void writeLong(long i) throws IOException { writeInt((int) (i >> 32)); writeInt((int) i); } /** * Writes an long in a variable-length format. Writes between one and nine * bytes. Smaller values take fewer bytes. Negative numbers are not * supported. */ public void writeVLong(long i) throws IOException { assert i >= 0; while ((i & ~0x7F) != 0) { writeByte((byte) ((i & 0x7f) | 0x80)); i >>>= 7; } writeByte((byte) i); } public void writeOptionalString(@Nullable String str) throws IOException { if (str == null) { writeBoolean(false); } else { writeBoolean(true); writeString(str); } } public void writeOptionalVInt(@Nullable Integer integer) throws IOException { if (integer == null) { writeBoolean(false); } else { writeBoolean(true); writeVInt(integer); } } public void writeOptionalText(@Nullable Text text) throws IOException { if (text == null) { writeInt(-1); } else { writeText(text); } } private final BytesRefBuilder spare = new BytesRefBuilder(); public void writeText(Text text) throws IOException { if (!text.hasBytes()) { final String string = text.string(); spare.copyChars(string); writeInt(spare.length()); write(spare.bytes(), 0, spare.length()); } else { BytesReference bytes = text.bytes(); writeInt(bytes.length()); bytes.writeTo(this); } } public void writeString(String str) throws IOException { int charCount = str.length(); writeVInt(charCount); int c; for (int i = 0; i < charCount; i++) { c = str.charAt(i); if (c <= 0x007F) { writeByte((byte) c); } else if (c > 0x07FF) { writeByte((byte) (0xE0 | c >> 12 & 0x0F)); writeByte((byte) (0x80 | c >> 6 & 0x3F)); writeByte((byte) (0x80 | c >> 0 & 0x3F)); } else { writeByte((byte) (0xC0 | c >> 6 & 0x1F)); writeByte((byte) (0x80 | c >> 0 & 0x3F)); } } } public void writeFloat(float v) throws IOException { writeInt(Float.floatToIntBits(v)); } public void writeDouble(double v) throws IOException { writeLong(Double.doubleToLongBits(v)); } private static byte ZERO = 0; private static byte ONE = 1; private static byte TWO = 2; /** * Writes a boolean. */ public void writeBoolean(boolean b) throws IOException { writeByte(b ? ONE : ZERO); } public void writeOptionalBoolean(@Nullable Boolean b) throws IOException { if (b == null) { writeByte(TWO); } else { writeByte(b ? ONE : ZERO); } } /** * Forces any buffered output to be written. */ @Override public abstract void flush() throws IOException; /** * Closes this stream to further operations. */ @Override public abstract void close() throws IOException; public abstract void reset() throws IOException; @Override public void write(int b) throws IOException { writeByte((byte) b); } @Override public void write(byte[] b, int off, int len) throws IOException { writeBytes(b, off, len); } public void writeStringArray(String[] array) throws IOException { writeVInt(array.length); for (String s : array) { writeString(s); } } /** * Writes a string array, for nullable string, writes it as 0 (empty string). */ public void writeStringArrayNullable(@Nullable String[] array) throws IOException { if (array == null) { writeVInt(0); } else { writeVInt(array.length); for (String s : array) { writeString(s); } } } public void writeMap(@Nullable Map<String, Object> map) throws IOException { writeGenericValue(map); } public void writeGenericValue(@Nullable Object value) throws IOException { if (value == null) { writeByte((byte) -1); return; } Class type = value.getClass(); if (type == String.class) { writeByte((byte) 0); writeString((String) value); } else if (type == Integer.class) { writeByte((byte) 1); writeInt((Integer) value); } else if (type == Long.class) { writeByte((byte) 2); writeLong((Long) value); } else if (type == Float.class) { writeByte((byte) 3); writeFloat((Float) value); } else if (type == Double.class) { writeByte((byte) 4); writeDouble((Double) value); } else if (type == Boolean.class) { writeByte((byte) 5); writeBoolean((Boolean) value); } else if (type == byte[].class) { writeByte((byte) 6); writeVInt(((byte[]) value).length); writeBytes(((byte[]) value)); } else if (value instanceof List) { writeByte((byte) 7); List list = (List) value; writeVInt(list.size()); for (Object o : list) { writeGenericValue(o); } } else if (value instanceof Object[]) { writeByte((byte) 8); Object[] list = (Object[]) value; writeVInt(list.length); for (Object o : list) { writeGenericValue(o); } } else if (value instanceof Map) { if (value instanceof LinkedHashMap) { writeByte((byte) 9); } else { writeByte((byte) 10); } @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) value; writeVInt(map.size()); for (Map.Entry<String, Object> entry : map.entrySet()) { writeString(entry.getKey()); writeGenericValue(entry.getValue()); } } else if (type == Byte.class) { writeByte((byte) 11); writeByte((Byte) value); } else if (type == Date.class) { writeByte((byte) 12); writeLong(((Date) value).getTime()); } else if (value instanceof ReadableInstant) { writeByte((byte) 13); writeString(((ReadableInstant) value).getZone().getID()); writeLong(((ReadableInstant) value).getMillis()); } else if (value instanceof BytesReference) { writeByte((byte) 14); writeBytesReference((BytesReference) value); } else if (value instanceof Text) { writeByte((byte) 15); writeText((Text) value); } else if (type == Short.class) { writeByte((byte) 16); writeShort((Short) value); } else if (type == int[].class) { writeByte((byte) 17); writeIntArray((int[]) value); } else if (type == long[].class) { writeByte((byte) 18); writeLongArray((long[]) value); } else if (type == float[].class) { writeByte((byte) 19); writeFloatArray((float[]) value); } else if (type == double[].class) { writeByte((byte) 20); writeDoubleArray((double[]) value); } else if (value instanceof BytesRef) { writeByte((byte) 21); writeBytesRef((BytesRef) value); } else if (type == GeoPoint.class) { writeByte((byte) 22); writeGeoPoint((GeoPoint) value); } else if (value instanceof Token) { writeByte((byte) 64); IPartitioner p = DatabaseDescriptor.getPartitioner(); ByteBuffer b = p.getTokenFactory().toByteArray((Token) value); writeVInt(b.array().length); writeBytes(b.array()); } else { throw new IOException("Can't write type [" + type + "]"); } } public void writeIntArray(int[] values) throws IOException { writeVInt(values.length); for (int value : values) { writeInt(value); } } public void writeVIntArray(int[] values) throws IOException { writeVInt(values.length); for (int value : values) { writeVInt(value); } } public void writeLongArray(long[] values) throws IOException { writeVInt(values.length); for (long value : values) { writeLong(value); } } public void writeVLongArray(long[] values) throws IOException { writeVInt(values.length); for (long value : values) { writeVLong(value); } } public void writeFloatArray(float[] values) throws IOException { writeVInt(values.length); for (float value : values) { writeFloat(value); } } public void writeDoubleArray(double[] values) throws IOException { writeVInt(values.length); for (double value : values) { writeDouble(value); } } /** * Serializes a potential null value. */ public void writeOptionalStreamable(@Nullable Streamable streamable) throws IOException { if (streamable != null) { writeBoolean(true); streamable.writeTo(this); } else { writeBoolean(false); } } public void writeThrowable(Throwable throwable) throws IOException { if (throwable == null) { writeBoolean(false); } else { writeBoolean(true); boolean writeCause = true; boolean writeMessage = true; if (throwable instanceof CorruptIndexException) { writeVInt(1); writeOptionalString(((CorruptIndexException)throwable).getOriginalMessage()); writeOptionalString(((CorruptIndexException)throwable).getResourceDescription()); writeMessage = false; } else if (throwable instanceof IndexFormatTooNewException) { writeVInt(2); writeOptionalString(((IndexFormatTooNewException)throwable).getResourceDescription()); writeInt(((IndexFormatTooNewException)throwable).getVersion()); writeInt(((IndexFormatTooNewException)throwable).getMinVersion()); writeInt(((IndexFormatTooNewException)throwable).getMaxVersion()); writeMessage = false; writeCause = false; } else if (throwable instanceof IndexFormatTooOldException) { writeVInt(3); IndexFormatTooOldException t = (IndexFormatTooOldException) throwable; writeOptionalString(t.getResourceDescription()); if (t.getVersion() == null) { writeBoolean(false); writeOptionalString(t.getReason()); } else { writeBoolean(true); writeInt(t.getVersion()); writeInt(t.getMinVersion()); writeInt(t.getMaxVersion()); } writeMessage = false; writeCause = false; } else if (throwable instanceof NullPointerException) { writeVInt(4); writeCause = false; } else if (throwable instanceof NumberFormatException) { writeVInt(5); writeCause = false; } else if (throwable instanceof IllegalArgumentException) { writeVInt(6); } else if (throwable instanceof AlreadyClosedException) { writeVInt(7); } else if (throwable instanceof EOFException) { writeVInt(8); writeCause = false; } else if (throwable instanceof SecurityException) { writeVInt(9); } else if (throwable instanceof StringIndexOutOfBoundsException) { writeVInt(10); writeCause = false; } else if (throwable instanceof ArrayIndexOutOfBoundsException) { writeVInt(11); writeCause = false; } else if (throwable instanceof AssertionError) { writeVInt(12); } else if (throwable instanceof FileNotFoundException) { writeVInt(13); writeCause = false; } else if (throwable instanceof NoSuchFileException) { writeVInt(14); writeOptionalString(((NoSuchFileException) throwable).getFile()); writeOptionalString(((NoSuchFileException) throwable).getOtherFile()); writeOptionalString(((NoSuchFileException) throwable).getReason()); writeCause = false; } else if (throwable instanceof OutOfMemoryError) { writeVInt(15); writeCause = false; } else if (throwable instanceof IllegalStateException) { writeVInt(16); } else if (throwable instanceof LockObtainFailedException) { writeVInt(17); } else if (throwable instanceof InterruptedException) { writeVInt(18); writeCause = false; } else { ElasticsearchException ex; if (throwable instanceof ElasticsearchException && ElasticsearchException.isRegistered(throwable.getClass())) { ex = (ElasticsearchException) throwable; } else { ex = new NotSerializableExceptionWrapper(throwable); } writeVInt(0); writeVInt(ElasticsearchException.getId(ex.getClass())); ex.writeTo(this); return; } if (writeMessage) { writeOptionalString(throwable.getMessage()); } if (writeCause) { writeThrowable(throwable.getCause()); } ElasticsearchException.writeStackTraces(throwable, this); } } /** * Writes a {@link NamedWriteable} to the current stream, by first writing its name and then the object itself */ void writeNamedWriteable(NamedWriteable namedWriteable) throws IOException { writeString(namedWriteable.getWriteableName()); namedWriteable.writeTo(this); } /** * Writes a {@link Task.Status} to the current stream. */ public void writeTaskStatus(Task.Status status) throws IOException { writeNamedWriteable(status); } /** * Writes the given {@link GeoPoint} to the stream */ public void writeGeoPoint(GeoPoint geoPoint) throws IOException { writeDouble(geoPoint.lat()); writeDouble(geoPoint.lon()); } /** * Writes a list of {@link Writeable} objects */ public <T extends Writeable<T>> void writeList(List<T> list) throws IOException { writeVInt(list.size()); for (T obj: list) { obj.writeTo(this); } } }