package com.intellij.flex.uiDesigner.io; import gnu.trove.TIntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.OutputStream; public class PrimitiveAmfOutputStream extends OutputStream { private static final int UINT29_MASK = 0x1FFFFFFF; private static final int INT28_MAX_VALUE = 0x0FFFFFFF; private static final int INT28_MIN_VALUE = 0xF0000000; protected AbstractByteArrayOutputStream out; public PrimitiveAmfOutputStream(@NotNull AbstractByteArrayOutputStream out) { this.out = out; } @Override public void close() throws IOException { flush(); out.close(); } public void reset() { resetSizeAndPosition(); } public void writeTo(PrimitiveAmfOutputStream out) { ((ByteArrayOutputStreamEx)this.out).writeTo(out); } void resetSizeAndPosition() { out.reset(); } public AbstractByteArrayOutputStream getByteOut() { return out; } public ByteArrayOutputStreamEx getByteArrayOut() { return (ByteArrayOutputStreamEx)out; } public BlockDataOutputStream getBlockOut() { return (BlockDataOutputStream)out; } public int allocateShort() { return out.allocateDirty(2); } public int allocateClearShort() { return out.allocateClearShort(); } public void write(Enum value) { write(value.ordinal()); } // Represent smaller integers with fewer bytes using the most significant bit of each byte. The worst case uses 32-bits // to represent a 29-bit number, which is what we would have done with no compression. public final void writeUInt29(int v) { if (v < 0x80) { out.write(v); } else if (v < 0x4000) { int count = out.size(); final byte[] bytes = out.getBuffer(2); bytes[count++] = (byte)(((v >> 7) & 0x7F) | 0x80); bytes[count] = (byte)(v & 0x7F); } else if (v < 0x200000) { int count = out.size(); final byte[] bytes = out.getBuffer(3); bytes[count++] = (byte)(((v >> 14) & 0x7F) | 0x80); bytes[count++] = (byte)(((v >> 7) & 0x7F) | 0x80); bytes[count] = (byte)(v & 0x7F); } else if (v < 0x40000000) { int count = out.size(); final byte[] bytes = out.getBuffer(4); bytes[count++] = (byte)(((v >> 22) & 0x7F) | 0x80); bytes[count++] = (byte)(((v >> 15) & 0x7F) | 0x80); bytes[count++] = (byte)(((v >> 8) & 0x7F) | 0x80); bytes[count] = (byte)(v & 0xFF); } else { throw new IllegalArgumentException("Integer out of range: " + v); } } public void writeNullableString(@Nullable final CharSequence s) { if (s == null) { write(0); } else { writeAmfUtf(s, false); } } public void writeAmfUtf(@NotNull CharSequence s) { writeAmfUtf(s, false); } public final void writeAmfUtf(@NotNull CharSequence s, final boolean shiftLength) { writeAmfUtf(s, shiftLength, 0, s.length()); } public final void writeAmfUtf(@NotNull CharSequence s, final boolean shiftLength, final int beginIndex, final int endIndex) { int utfLen = 0; int c; for (int i = beginIndex; i < endIndex; i++) { c = s.charAt(i); if (c >= 0x0001 && c <= 0x007F) { utfLen++; } else if (c > 0x07FF) { utfLen += 3; } else { utfLen += 2; } } writeUInt29(shiftLength ? ((utfLen << 1) | 1) : utfLen); int count = out.size(); final byte[] bytes = out.getBuffer(utfLen); for (int i = beginIndex; i < endIndex; i++) { c = s.charAt(i); if (c <= 0x007F) { bytes[count++] = (byte)c; } else if (c > 0x07FF) { bytes[count++] = (byte)(0xE0 | ((c >> 12) & 0x0F)); bytes[count++] = (byte)(0x80 | ((c >> 6) & 0x3F)); bytes[count++] = (byte)(0x80 | (c & 0x3F)); } else { bytes[count++] = (byte)(0xC0 | ((c >> 6) & 0x1F)); bytes[count++] = (byte)(0x80 | (c & 0x3F)); } } } public final void writeAmfInt(int v) { if (v >= INT28_MIN_VALUE && v <= INT28_MAX_VALUE) { write(Amf3Types.INTEGER); writeUInt29(v & UINT29_MASK); } else { writeAmfDouble(v); } } public void writeAmfInt(String v) { final int radix; if (v.charAt(0) == '#') { radix = 16; v = v.substring(1); } else if (v.charAt(0) == '0' && v.length() > 2 && v.charAt(1) == 'x') { v = v.substring(2); radix = 16; } else { radix = 10; } if (v.length() > 6) { writeAmfUInt(Long.parseLong(v, radix)); } else { writeAmfUInt(Integer.parseInt(v, radix)); } } public void writeAmfUInt(int v) { writeAmfInt(v < 0 ? (v + 16777216) : v); } public void writeAmfUInt(long v) { if (v < 0) { v += 16777216; } if (v >= INT28_MIN_VALUE && v <= INT28_MAX_VALUE) { write(Amf3Types.INTEGER); writeUInt29((int)v & UINT29_MASK); } else { writeAmfDouble(v); } } @SuppressWarnings({"UnusedDeclaration"}) public void writeAmfUInt(String v) { writeAmfUInt(Integer.parseInt(v, 10)); } public void writeAmfDouble(double v) { write(Amf3Types.DOUBLE); writeDouble(v); } public final void writeAmfDouble(String v) { boolean startWithSharp; if ((startWithSharp = v.startsWith("#")) || v.startsWith("0x")) { v = v.substring(startWithSharp ? 1 : 2); writeAmfDouble(Integer.parseInt(v, 16)); } else { writeAmfDouble(Double.parseDouble(v)); } } @Override public final void write(int b) { out.write(b); } @Override public final void write(byte[] b) { out.write(b, 0, b.length); } @Override public final void write(byte[] b, int off, int len) { out.write(b, off, len); } @Override public void flush() throws IOException { out.flush(); } public void writeAmfBoolean(CharSequence v) { write(v.length() > 0 && v.charAt(0) == 't' ? Amf3Types.TRUE : Amf3Types.FALSE); } public final void write(boolean v) { out.write(v ? 1 : 0); } public final void writeShort(int v) { final int offset = out.size(); IOUtil.writeShort(v, out.getBuffer(2), offset); } public final void putByte(int v, int position) { out.getBuffer()[position] = (byte)v; } public final void putShort(int v, int position) { IOUtil.writeShort(v, out.getBuffer(), position); } public final void writeInt(int v) { final int offset = out.size(); IOUtil.writeInt(v, out.getBuffer(4), offset); } public final void writeLong(long v) { int count = out.size(); final byte[] bytes = out.getBuffer(8); bytes[count++] = (byte)(v >>> 56); bytes[count++] = (byte)(v >>> 48); bytes[count++] = (byte)(v >>> 40); bytes[count++] = (byte)(v >>> 32); bytes[count++] = (byte)(v >>> 24); bytes[count++] = (byte)(v >>> 16); bytes[count++] = (byte)(v >>> 8); bytes[count] = (byte)(v); } public final void writeDouble(double v) { writeLong(Double.doubleToLongBits(v)); } public void write(TIntArrayList array) { write(Amf3Types.VECTOR_INT); writeUInt29((array.size() << 1) | 1); write(true); array.forEach(value -> { writeInt(value); return true; }); } public final int size() { return out.size(); } }