blob: 071b233b14c22cd39e99f6e9ec55e143fbaf484e [file] [log] [blame]
/*=============================================================================#
# 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;
}
}