| /*=============================================================================# |
| # Copyright (c) 2010, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.internal.rhelp.core; |
| |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.util.List; |
| import java.util.zip.Deflater; |
| import java.util.zip.DeflaterOutputStream; |
| import java.util.zip.Inflater; |
| import java.util.zip.InflaterInputStream; |
| |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| |
| @NonNullByDefault |
| public final class FIO implements AutoCloseable { |
| |
| |
| private static final ThreadLocal<FIO> INSTANCES= new ThreadLocal<FIO>() { |
| |
| @Override |
| protected FIO initialValue() { |
| return new FIO(); |
| } |
| |
| }; |
| |
| public static FIO get(final OutputStream out) { |
| final FIO io= INSTANCES.get(); |
| io.flags= OUT; |
| io.out.init(out); |
| return io; |
| } |
| |
| public static FIO get(final InputStream in) { |
| final FIO io= INSTANCES.get(); |
| io.flags= IN; |
| io.in.init(in); |
| return io; |
| } |
| |
| public static void copy(final InputStream in, final OutputStream out) throws IOException { |
| final byte[] buffer= INSTANCES.get().ba; |
| int n; |
| while ((n = in.read(buffer, 0, buffer.length)) != -1) { |
| out.write(buffer, 0, n); |
| } |
| } |
| |
| |
| public static long decodeLong(final byte[] ba) { |
| return (((long) (ba[0] & 0xff) << 56) | |
| ((long) (ba[1] & 0xff) << 48) | |
| ((long) (ba[2] & 0xff) << 40) | |
| ((long) (ba[3] & 0xff) << 32) | |
| ((long) (ba[4] & 0xff) << 24) | |
| ((ba[5] & 0xff) << 16) | |
| ((ba[6] & 0xff) << 8) | |
| (ba[7] & 0xff) ); |
| } |
| |
| public static byte[] encodeLong(final long value) { |
| final byte[] ba= new byte[8]; |
| ba[0]= (byte) (value >>> 56); |
| ba[1]= (byte) (value >>> 48); |
| ba[2]= (byte) (value >>> 40); |
| ba[3]= (byte) (value >>> 32); |
| ba[4]= (byte) (value >>> 24); |
| ba[5]= (byte) (value >>> 16); |
| ba[6]= (byte) (value >>> 8); |
| ba[7]= (byte) (value); |
| return ba; |
| } |
| |
| |
| private static final int BB_LENGTH= 16384; |
| private static final int BA_LENGTH= BB_LENGTH; |
| private static final int BB_PART= BB_LENGTH / 4; |
| private static final int CB_LENGTH= BB_LENGTH / 2; |
| private static final int CA_LENGTH= BB_LENGTH * 4; |
| |
| private static final byte MODE_BBARRAY= 0; |
| private static final byte MODE_IPARRAY= 1; |
| |
| private static final int IN= 1 << 1; |
| private static final int OUT= 1 << 2; |
| private static final int COMPRESS= 1 << 3; |
| |
| private class OutStream extends DataOutputStream { |
| |
| |
| private final Deflater deflater; |
| |
| |
| public OutStream() { |
| super(null); |
| this.deflater= new Deflater(); |
| } |
| |
| |
| public void init(final OutputStream out) { |
| this.out= out; |
| } |
| |
| public void enableCompression() { |
| FIO.this.flags |= COMPRESS; |
| this.out= new DeflaterOutputStream(this.out, this.deflater, 4096); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if (this.out != null) { |
| super.close(); |
| this.out= null; |
| if ((FIO.this.flags & COMPRESS) != 0) { |
| this.deflater.reset(); |
| } |
| } |
| } |
| |
| } |
| |
| private class InStream extends DataInputStream { |
| |
| |
| private final Inflater inflater; |
| |
| |
| public InStream() { |
| super(null); |
| this.inflater= new Inflater(); |
| } |
| |
| |
| public void init(final InputStream in) { |
| this.in= in; |
| } |
| |
| public void enableCompression() { |
| FIO.this.flags |= COMPRESS; |
| this.in= new InflaterInputStream(this.in, this.inflater, 4096); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if (this.in != null) { |
| super.close(); |
| this.in= null; |
| if ((FIO.this.flags & COMPRESS) != 0) { |
| this.inflater.reset(); |
| } |
| } |
| } |
| |
| } |
| |
| |
| private final ByteBuffer bb; |
| private final byte[] ba; |
| private final CharBuffer cb; |
| private final char[] ca; |
| private final byte mode; |
| |
| private final InStream in; |
| private final OutStream out; |
| |
| private int flags; |
| |
| |
| public FIO() { |
| this.bb= ByteBuffer.allocateDirect(BB_LENGTH); |
| if (this.bb.hasArray()) { |
| this.mode= MODE_BBARRAY; |
| this.ba= this.bb.array(); |
| } |
| else { |
| this.mode= MODE_IPARRAY; |
| this.ba= new byte[BB_LENGTH]; |
| } |
| this.cb= this.bb.asCharBuffer(); |
| this.ca= new char[CA_LENGTH]; |
| |
| this.in= new InStream(); |
| this.out= new OutStream(); |
| } |
| |
| |
| @Override |
| public void close() throws IOException { |
| if ((this.flags & IN) != 0) { |
| this.in.close(); |
| } |
| else if ((this.flags & OUT) != 0) { |
| this.out.close(); |
| } |
| this.flags= 0; |
| } |
| |
| public void enableCompression() { |
| if ((this.flags & IN) != 0) { |
| this.flags |= COMPRESS; |
| this.in.enableCompression(); |
| } |
| else if ((this.flags & OUT) != 0) { |
| this.flags |= COMPRESS; |
| this.out.enableCompression(); |
| } |
| } |
| |
| |
| private void writeFullyBB(final int bn) throws IOException { |
| switch (this.mode) { |
| case MODE_BBARRAY: |
| this.out.write(this.ba, 0, bn); |
| return; |
| // case MODE_IPARRAY: |
| default: |
| this.bb.clear(); |
| this.bb.get(this.ba, 0, bn); |
| this.out.write(this.ba, 0, bn); |
| return; |
| } |
| } |
| |
| private void readFullyBB(final int bn) throws IOException { |
| switch (this.mode) { |
| case MODE_BBARRAY: |
| this.in.readFully(this.ba, 0, bn); |
| return; |
| // case MODE_IPARRAY: |
| default: |
| this.in.readFully(this.ba, 0, bn); |
| this.bb.clear(); |
| this.bb.put(this.ba, 0, bn); |
| return; |
| } |
| } |
| |
| |
| public void writeByte(final byte value) throws IOException { |
| this.out.writeByte(value); |
| } |
| |
| public void writeInt(final int value) throws IOException { |
| this.out.writeInt(value); |
| } |
| |
| public void writeLong(final long value) throws IOException { |
| this.out.writeLong(value); |
| } |
| |
| public void writeFloat(final float value) throws IOException { |
| this.out.writeFloat(value); |
| } |
| |
| public void writeString(final @Nullable String s) throws IOException { |
| if (s != null) { |
| final int cn= s.length(); |
| ASCII: if (cn <= BA_LENGTH) { |
| for (int ci= 0; ci < cn; ) { |
| if ((s.charAt(ci++) & 0xffffff00) != 0) { |
| break ASCII; |
| } |
| } |
| if (cn <= 8) { |
| this.out.writeInt(-cn); |
| this.out.writeBytes(s); |
| return; |
| } |
| else { |
| this.out.writeInt(-cn); |
| s.getBytes(0, cn, this.ba, 0); |
| this.out.write(this.ba, 0, cn); |
| return; |
| } |
| } |
| this.out.writeInt(cn); |
| if (cn <= 0) { |
| this.out.writeChars(s); |
| } |
| else { |
| for (int ci= 0; ci < cn; ) { |
| final int cCount= Math.min(cn - ci, CB_LENGTH); |
| s.getChars(ci, ci + cCount, this.ca, 0); |
| this.cb.clear(); |
| this.cb.put(this.ca, 0, cCount); |
| writeFullyBB(cCount << 1); |
| ci+= cCount; |
| } |
| } |
| return; |
| } |
| else { |
| this.out.writeInt(Integer.MIN_VALUE); |
| return; |
| } |
| } |
| |
| public void writeStringArray(final @Nullable String[] sa, final int length) throws IOException { |
| this.out.writeInt(length); |
| for (int i= 0; i < length; i++) { |
| final String s= sa[i]; |
| if (s != null) { |
| final int cn= s.length(); |
| ASCII: if (cn <= BA_LENGTH) { |
| for (int ci= 0; ci < cn; ) { |
| if ((s.charAt(ci++) & 0xffffff00) != 0) { |
| break ASCII; |
| } |
| } |
| if (cn <= 8) { |
| this.out.writeInt(-cn); |
| this.out.writeBytes(s); |
| continue; |
| } |
| else { |
| this.out.writeInt(-cn); |
| s.getBytes(0, cn, this.ba, 0); |
| this.out.write(this.ba, 0, cn); |
| continue; |
| } |
| } |
| this.out.writeInt(cn); |
| if (cn <= 8) { |
| this.out.writeChars(s); |
| } |
| else { |
| for (int ci= 0; ci < cn; ) { |
| final int cCount= Math.min(cn - ci, CB_LENGTH); |
| s.getChars(ci, ci + cCount, this.ca, 0); |
| this.cb.clear(); |
| this.cb.put(this.ca, 0, cCount); |
| writeFullyBB(cCount << 1); |
| ci+= cCount; |
| } |
| } |
| continue; |
| } |
| else { |
| this.out.writeInt(Integer.MIN_VALUE); |
| continue; |
| } |
| } |
| } |
| |
| public void writeStringList(final List<String> sList) throws IOException { |
| final int length= sList.size(); |
| this.out.writeInt(length); |
| for (int i= 0; i < length; i++) { |
| final String s= sList.get(i); |
| if (s != null) { |
| final int cn= s.length(); |
| ASCII: if (cn <= BA_LENGTH) { |
| for (int ci= 0; ci < cn; ) { |
| if ((s.charAt(ci++) & 0xffffff00) != 0) { |
| break ASCII; |
| } |
| } |
| if (cn <= 8) { |
| this.out.writeInt(-cn); |
| this.out.writeBytes(s); |
| continue; |
| } |
| else { |
| this.out.writeInt(-cn); |
| s.getBytes(0, cn, this.ba, 0); |
| this.out.write(this.ba, 0, cn); |
| continue; |
| } |
| } |
| this.out.writeInt(cn); |
| if (cn <= 8) { |
| this.out.writeChars(s); |
| } |
| else { |
| for (int ci= 0; ci < cn; ) { |
| final int cCount= Math.min(cn - ci, CB_LENGTH); |
| s.getChars(ci, ci + cCount, this.ca, 0); |
| this.cb.clear(); |
| this.cb.put(this.ca, 0, cCount); |
| writeFullyBB(cCount << 1); |
| ci+= cCount; |
| } |
| } |
| continue; |
| } |
| else { |
| this.out.writeInt(Integer.MIN_VALUE); |
| continue; |
| } |
| } |
| } |
| |
| public void flush() throws IOException { |
| this.out.flush(); |
| } |
| |
| |
| public byte readByte() throws IOException { |
| return this.in.readByte(); |
| } |
| |
| public int readInt() throws IOException { |
| return this.in.readInt(); |
| } |
| |
| public long readLong() throws IOException { |
| return this.in.readLong(); |
| } |
| |
| public float readFloat() throws IOException { |
| return this.in.readFloat(); |
| } |
| |
| private String readString(final int cn, final char[] ca, final DataInputStream in) throws IOException { |
| int cr= 0; |
| int position= 0; |
| final int bToComplete; |
| while (true) { |
| position+= in.read(this.ba, position, BA_LENGTH - position); |
| if (position >= BB_PART) { |
| final int icount= (position >> 1); |
| final int bcount= (icount << 1); |
| if (!this.bb.hasArray()) { |
| this.bb.clear(); |
| this.bb.put(this.ba, 0, bcount); |
| } |
| this.cb.clear(); |
| this.cb.get(ca, cr, icount); |
| cr+= icount; |
| if (position - bcount != 0) { |
| this.ca[cr++]= (char) ( |
| ((this.ba[bcount] & 0xff) << 8) | |
| (in.readUnsignedByte()) ); |
| } |
| position= 0; |
| if (cn - cr <= CB_LENGTH) { |
| bToComplete= (cn - cr) << 1; |
| break; |
| } |
| } |
| } |
| if (bToComplete > 0) { |
| in.readFully(this.ba, position, bToComplete - position); |
| if (!this.bb.hasArray()) { |
| this.bb.clear(); |
| this.bb.put(this.ba, 0, bToComplete); |
| } |
| this.cb.clear(); |
| this.cb.get(ca, cr, bToComplete >> 1); |
| } |
| return new String(ca, 0, cn); |
| } |
| |
| public @Nullable String readString() throws IOException { |
| final int cn= this.in.readInt(); |
| if (cn >= 0) { |
| if (cn == 0) { |
| return ""; |
| } |
| else if (cn <= 64) { |
| for (int ci= 0; ci < cn; ci++) { |
| this.ca[ci]= this.in.readChar(); |
| } |
| return new String(this.ca, 0, cn); |
| } |
| else if (cn <= CB_LENGTH) { |
| readFullyBB(cn << 1); |
| this.cb.clear(); |
| this.cb.get(this.ca, 0, cn); |
| return new String(this.ca, 0, cn); |
| } |
| else if (cn <= CA_LENGTH) { |
| return readString(cn, this.ca, this.in); |
| } |
| else { |
| return readString(cn, new char[cn], this.in); |
| } |
| } |
| else { |
| if (cn >= -BA_LENGTH) { |
| this.in.readFully(this.ba, 0, -cn); |
| return new String(this.ba, 0, 0, -cn); |
| } |
| else if (cn != Integer.MIN_VALUE) { |
| final byte[] bt= new byte[-cn]; |
| this.in.readFully(bt, 0, -cn); |
| return new String(bt, 0, 0, -cn); |
| } |
| else { |
| return null; |
| } |
| } |
| } |
| |
| public String readNonNullString() throws IOException { |
| final int cn= this.in.readInt(); |
| if (cn >= 0) { |
| if (cn == 0) { |
| return ""; |
| } |
| else if (cn <= 64) { |
| for (int ci= 0; ci < cn; ci++) { |
| this.ca[ci]= this.in.readChar(); |
| } |
| return new String(this.ca, 0, cn); |
| } |
| else if (cn <= CB_LENGTH) { |
| readFullyBB(cn << 1); |
| this.cb.clear(); |
| this.cb.get(this.ca, 0, cn); |
| return new String(this.ca, 0, cn); |
| } |
| else if (cn <= CA_LENGTH) { |
| return readString(cn, this.ca, this.in); |
| } |
| else { |
| return readString(cn, new char[cn], this.in); |
| } |
| } |
| else { |
| if (cn >= -BA_LENGTH) { |
| this.in.readFully(this.ba, 0, -cn); |
| return new String(this.ba, 0, 0, -cn); |
| } |
| else if (cn != Integer.MIN_VALUE) { |
| final byte[] bt= new byte[-cn]; |
| this.in.readFully(bt, 0, -cn); |
| return new String(bt, 0, 0, -cn); |
| } |
| else { |
| throw new NullPointerException(); |
| } |
| } |
| } |
| |
| public String[] readNonNullStringArray() throws IOException { |
| final int length= this.in.readInt(); |
| final String[] array= new @NonNull String[length]; |
| ARRAY: for (int i= 0; i < length; i++) { |
| final int cn= this.in.readInt(); |
| if (cn >= 0) { |
| if (cn == 0) { |
| array[i]= ""; |
| continue ARRAY; |
| } |
| else if (cn <= 64) { |
| for (int ci= 0; ci < cn; ci++) { |
| this.ca[ci]= this.in.readChar(); |
| } |
| array[i]= new String(this.ca, 0, cn); |
| continue ARRAY; |
| } |
| else if (cn <= CB_LENGTH) { |
| readFullyBB(cn << 1); |
| this.cb.clear(); |
| this.cb.get(this.ca, 0, cn); |
| array[i]= new String(this.ca, 0, cn); |
| continue ARRAY; |
| } |
| else if (cn <= CA_LENGTH) { |
| array[i]= readString(cn, this.ca, this.in); |
| continue ARRAY; |
| } |
| else { |
| array[i]= readString(cn, new char[cn], this.in); |
| continue ARRAY; |
| } |
| } |
| else { |
| if (cn >= -BA_LENGTH) { |
| this.in.readFully(this.ba, 0, -cn); |
| array[i]= new String(this.ba, 0, 0, -cn); |
| continue ARRAY; |
| } |
| else if (cn != Integer.MIN_VALUE) { |
| final byte[] bt= new byte[-cn]; |
| this.in.readFully(bt, 0, -cn); |
| array[i]= new String(bt, 0, 0, -cn); |
| continue ARRAY; |
| } |
| else { // cn == Integer.MIN_VALUE |
| throw new NullPointerException(); |
| } |
| } |
| } |
| return array; |
| } |
| |
| } |