content: /*
   * Copyright (C) 2020 ActiveJ LLC.
   *
   * Licensed 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 io.activej.serializer.def;
  
  import io.activej.codegen.expression.Expression;
  import io.activej.codegen.expression.Variable;
  import io.activej.serializer.util.BinaryOutputUtils;
  
  import static io.activej.codegen.expression.Expressions.*;
  
  /**
   * Provides methods for writing primitives
   * and Strings to byte arrays
   */
  public final class SerializerExpressions {
  	private static final Class<?> JDK_UNSAFE;
  	private static final int BYTE_ARRAY_BASE_OFFSET;
  	private static final boolean BIG_ENDIAN;
  
  	static {
  		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  
  		Class<?> jdkUnsafe;
  		int byteArrayBaseOffset = 0;
  		boolean bigEndian = false;
  		try {
  			jdkUnsafe = classLoader.loadClass("jdk.internal.misc.Unsafe");
  			Object unsafe = jdkUnsafe.getMethod("getUnsafe").invoke(null);
  			byteArrayBaseOffset = (int) jdkUnsafe.getMethod("arrayBaseOffset", Class.class).invoke(unsafe, byte[].class);
  			int byteArrayIndexScale = (int) jdkUnsafe.getMethod("arrayIndexScale", Class.class).invoke(unsafe, byte[].class);
  			bigEndian = (boolean) jdkUnsafe.getMethod("isBigEndian").invoke(unsafe);
  			if (byteArrayIndexScale != 1) jdkUnsafe = null;
  		} catch (Exception e) {
  			jdkUnsafe = null;
  		}
  		JDK_UNSAFE = jdkUnsafe;
  		BYTE_ARRAY_BASE_OFFSET = byteArrayBaseOffset;
  		BIG_ENDIAN = bigEndian;
  	}
  
  	private static Expression getUnsafe() {
  		return staticCall(JDK_UNSAFE, "getUnsafe");
  	}
  
  	public static Expression writeBytes(Expression buf, Variable pos, Expression bytes) {
  		return writeBytes(buf, pos, bytes, value(0), length(bytes));
  	}
  
  	public static Expression writeBytes(Expression buf, Variable pos, Expression bytes, Expression bytesOff, Expression bytesLen) {
  		return sequence(
  			staticCall(System.class, "arraycopy", bytes, bytesOff, buf, pos, bytesLen),
  			set(pos, add(pos, bytesLen)));
  	}
  
  	public static Expression writeByte(Expression buf, Variable pos, Expression value) {
  		return sequence(
  			arraySet(buf, pos, value),
  			set(pos, add(pos, value(1))));
  	}
  
  	public static Expression writeBoolean(Expression buf, Variable pos, Expression value) {
  		return writeByte(buf, pos, value);
  	}
  
  	public static Expression writeShort(Expression buf, Variable pos, Expression value, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			putUnaligned(buf, pos, value, "putShortUnaligned", Short.class, 2, bigEndian) :
  			set(pos, staticCall(BinaryOutputUtils.class, "writeShort" + (bigEndian ? "" : "LE"), buf, pos, cast(value, short.class)));
  	}
  
  	public static Expression writeChar(Expression buf, Variable pos, Expression value, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			putUnaligned(buf, pos, value, "putCharUnaligned", Character.class, 2, bigEndian) :
  			set(pos, staticCall(BinaryOutputUtils.class, "writeChar" + (bigEndian ? "" : "LE"), buf, pos, cast(value, char.class)));
  	}
  
  	public static Expression writeInt(Expression buf, Variable pos, Expression value, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			putUnaligned(buf, pos, value, "putIntUnaligned", Integer.class, 4, bigEndian) :
  			set(pos, staticCall(BinaryOutputUtils.class, "writeInt" + (bigEndian ? "" : "LE"), buf, pos, cast(value, int.class)));
  	}
  
  	public static Expression writeLong(Expression buf, Variable pos, Expression value, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			putUnaligned(buf, pos, value, "putLongUnaligned", Long.class, 8, bigEndian) :
  			set(pos, staticCall(BinaryOutputUtils.class, "writeLong" + (bigEndian ? "" : "LE"), buf, pos, cast(value, long.class)));
  	}
  
  	private static Expression putUnaligned(Expression buf, Variable pos, Expression value, String name, Class<?> numericType, int size, boolean bigEndian) {
  		return ensureRemaining(buf, pos, size, sequence(
  			call(getUnsafe(), name,
  				buf, cast(add(value(BYTE_ARRAY_BASE_OFFSET), pos), long.class), convEndian(value, numericType, bigEndian)),
  			set(pos, add(pos, value(size)))
  		));
  	}
  
  	public static Expression writeVarInt(Expression buf, Variable pos, Expression value) {
  		return set(pos,
  			staticCall(BinaryOutputUtils.class, "writeVarInt", buf, pos, cast(value, int.class)));
  	}
  
  	public static Expression writeVarLong(Expression buf, Variable pos, Expression value) {
  		return set(pos,
  			staticCall(BinaryOutputUtils.class, "writeVarLong", buf, pos, cast(value, long.class)));
  	}
  
  	public static Expression writeFloat(Expression buf, Variable pos, Expression value, boolean bigEndian) {
  		return writeInt(buf, pos, staticCall(Float.class, "floatToIntBits", cast(value, float.class)), bigEndian);
  	}
  
  	public static Expression writeDouble(Expression buf, Variable pos, Expression value, boolean bigEndian) {
  		return writeLong(buf, pos, staticCall(Double.class, "doubleToLongBits", cast(value, double.class)), bigEndian);
  	}
  
  	public static Expression ensureRemaining(Expression buf, Variable pos, int size, Expression next) {
  		return ensureRemaining(buf, pos, value(size), next);
  	}
  
  	public static Expression ensureRemaining(Expression buf, Variable pos, Expression size, Expression next) {
  		return ifGt(add(pos, size), length(buf),
  			throwException(ArrayIndexOutOfBoundsException.class),
  			next);
  	}
  
  	public static Expression array(Expression in) {
  		return call(in, "array");
  	}
  
  	public static Expression pos(Expression in) {
  		return call(in, "pos");
  	}
  
  	public static Expression pos(Expression in, Expression newPos) {
  		return call(in, "pos", newPos);
  	}
  
  	public static Expression move(Expression in, Expression bytes) {
  		return call(in, "move", bytes);
  	}
  
  	private static Expression move(Expression in, int bytes) {
  		return call(in, "move", value(bytes));
  	}
  
  	public static Expression readBytes(Expression in, Expression buf) {
  		return call(in, "read", buf);
  	}
  
  	public static Expression readBytes(Expression in, Expression buf, Expression off, Expression len) {
  		return call(in, "read", buf, off, len);
  	}
  
  	public static Expression readByte(Expression in) {
  		return call(in, "readByte");
  	}
  
  	public static Expression readBoolean(Expression in) {
  		return call(in, "readBoolean");
  	}
  
  	public static Expression readShort(Expression in, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			getUnaligned(in, "getShortUnaligned", Short.class, 2, bigEndian) :
  			call(in, "readShort" + (bigEndian ? "" : "LE"));
  	}
  
  	public static Expression readChar(Expression in, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			getUnaligned(in, "getCharUnaligned", Character.class, 2, bigEndian) :
  			call(in, "readChar" + (bigEndian ? "" : "LE"));
  	}
  
  	public static Expression readInt(Expression in, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			getUnaligned(in, "getIntUnaligned", Integer.class, 4, bigEndian) :
  			call(in, "readInt" + (bigEndian ? "" : "LE"));
  	}
  
  	public static Expression readLong(Expression in, boolean bigEndian) {
  		return JDK_UNSAFE != null ?
  			getUnaligned(in, "getLongUnaligned", Long.class, 8, bigEndian) :
  			call(in, "readLong" + (bigEndian ? "" : "LE"));
  	}
  
  	private static Expression getUnaligned(Expression in, String name, Class<?> numericType, int size, boolean bigEndian) {
  		return let(
  			convEndian(
  				call(getUnsafe(), name,
  					array(in), cast(add(value(BYTE_ARRAY_BASE_OFFSET), pos(in)), long.class)),
  				numericType,
  				bigEndian),
  			result -> sequence(
  				move(in, size),
  				result)
  		);
  	}
  
  	private static Expression convEndian(Expression numericValue, Class<?> numericType, boolean bigEndian) {
  		return BIG_ENDIAN == bigEndian ?
  			numericValue :
  			staticCall(numericType, "reverseBytes", numericValue);
  	}
  
  	public static Expression readVarInt(Expression in) {
  		return call(in, "readVarInt");
  	}
  
  	public static Expression readVarLong(Expression in) {
  		return call(in, "readVarLong");
  	}
  
  	public static Expression readFloat(Expression in, boolean bigEndian) {
  		return staticCall(Float.class, "intBitsToFloat", readInt(in, bigEndian));
  	}
  
  	public static Expression readDouble(Expression in, boolean bigEndian) {
  		return staticCall(Double.class, "longBitsToDouble", readLong(in, bigEndian));
  	}
  
  }
  