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.codegen.util;
  
  import io.activej.codegen.Context;
  import io.activej.types.Primitives;
  import org.jetbrains.annotations.Nullable;
  import org.objectweb.asm.Type;
  import org.objectweb.asm.commons.GeneratorAdapter;
  import org.objectweb.asm.commons.Method;
  
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.util.HashMap;
  import java.util.Map;
  
  import static java.lang.String.format;
  import static org.objectweb.asm.Type.*;
  import static org.objectweb.asm.commons.Method.getMethod;
  
  @SuppressWarnings("WeakerAccess")
  public final class Utils {
  	private static final Type OBJECT_TYPE = Type.getType(Object.class);
  	private static final Map<String, Type> WRAPPER_TO_PRIMITIVE = new HashMap<>();
  
  	static {
  		for (Class<?> primitiveType : Primitives.allPrimitiveTypes()) {
  			Class<?> wrappedType = Primitives.wrap(primitiveType);
  			WRAPPER_TO_PRIMITIVE.put(wrappedType.getName(), getType(primitiveType));
  		}
  	}
  
  	private static final Type WRAPPED_BOOLEAN_TYPE = getType(Boolean.class);
  	private static final Type WRAPPED_CHAR_TYPE = getType(Character.class);
  	private static final Type WRAPPED_BYTE_TYPE = getType(Byte.class);
  	private static final Type WRAPPED_SHORT_TYPE = getType(Short.class);
  	private static final Type WRAPPED_INT_TYPE = getType(Integer.class);
  	private static final Type WRAPPED_FLOAT_TYPE = getType(Float.class);
  	private static final Type WRAPPED_LONG_TYPE = getType(Long.class);
  	private static final Type WRAPPED_DOUBLE_TYPE = getType(Double.class);
  	private static final Type WRAPPED_VOID_TYPE = getType(Void.class);
  
  	public static boolean isEqualType(@Nullable Type type1, @Nullable Type type2) {
  		if (type1 == null && type2 == null) return true;
  		if (type1 == null || type2 == null) return false;
  		int sort = type1.getSort();
  		if (sort != type2.getSort()) return false;
  		if (sort <= DOUBLE) return true;
  		return type1.equals(type2);
  	}
  
  	public static boolean isPrimitiveType(Type type) {
  		return type.getSort() > VOID && type.getSort() <= DOUBLE;
  	}
  
  	public static boolean isWrapperType(Type type) {
  		return
  			type.getSort() == OBJECT &&
  			WRAPPER_TO_PRIMITIVE.containsKey(type.getClassName());
  	}
  
  	public static Method unwrapToPrimitive(Type primitiveType) {
  		return switch (primitiveType.getSort()) {
  			case BOOLEAN -> getMethod("boolean booleanValue()");
  			case CHAR -> getMethod("char charValue()");
  			case BYTE -> getMethod("byte byteValue()");
  			case SHORT -> getMethod("short shortValue()");
  			case INT -> getMethod("int intValue()");
  			case FLOAT -> getMethod("float floatValue()");
  			case LONG -> getMethod("long longValue()");
  			case DOUBLE -> getMethod("double doubleValue()");
  			default ->
  				throw new IllegalArgumentException(format("No primitive value method for %s ", primitiveType.getClassName()));
  		};
  	}
  
  	public static Type wrap(Type type) {
  		return switch (type.getSort()) {
  			case BOOLEAN -> WRAPPED_BOOLEAN_TYPE;
  			case CHAR -> WRAPPED_CHAR_TYPE;
  			case BYTE -> WRAPPED_BYTE_TYPE;
  			case SHORT -> WRAPPED_SHORT_TYPE;
  			case INT -> WRAPPED_INT_TYPE;
  			case FLOAT -> WRAPPED_FLOAT_TYPE;
  			case LONG -> WRAPPED_LONG_TYPE;
  			case DOUBLE -> WRAPPED_DOUBLE_TYPE;
  			case VOID -> WRAPPED_VOID_TYPE;
  			default -> throw new IllegalArgumentException(format("%s is not primitive", type.getClassName()));
  		};
  	}
  
  	public static Type unwrap(Type type) {
  		if (type.getSort() != OBJECT)
  			throw new IllegalArgumentException("Cannot unwrap type that is not an object reference");
  		@Nullable Type reference = WRAPPER_TO_PRIMITIVE.get(type.getClassName());
  		if (reference == null)
  			throw new IllegalArgumentException("Not a wrapper type: " + type.getClassName());
  		return reference;
  	}
  
  	public static void invokeVirtualOrInterface(Context context, Type owner, Method method) {
  		GeneratorAdapter g = context.getGeneratorAdapter();
  		Class<?> ownerClass = context.toJavaType(owner);
  		if (!ownerClass.isInterface()) {
  			g.invokeVirtual(owner, method);
  			return;
  		}
  
  		if (!hasMethod(context, ownerClass, method) && hasMethod(context, Object.class, method)) {
  			g.invokeVirtual(OBJECT_TYPE, method);
  		} else {
  			g.invokeInterface(owner, method);
  		}
  	}
  
  	private static boolean hasMethod(Context context, Class<?> owner, Method method) {
  		Type[] argumentTypes = method.getArgumentTypes();
  		Class<?>[] parameterTypes = new Class[argumentTypes.length];
  		for (int i = 0; i < argumentTypes.length; i++) {
  			parameterTypes[i] = context.toJavaType(argumentTypes[i]);
  		}
  
  		try {
  			owner.getMethod(method.getName(), parameterTypes);
  		} catch (NoSuchMethodException e) {
  			return false;
  		}
  
  		return true;
  	}
  
  	public static String exceptionInGeneratedClass(Context ctx) {
  		return format("Thrown in generated class %s in method %s",
  			ctx.getSelfType().getClassName(),
  			ctx.getMethod()
  		);
  	}
  
  	public static boolean isValidCast(Type from, Type to) {
  		return
  			from.getSort() != to.getSort() &&
  			!(from.getSort() < BOOLEAN || from.getSort() > DOUBLE || to.getSort() < BOOLEAN || to.getSort() > DOUBLE);
  	}
  
  	public static String getStringSetting(Class<?> cls, String key, String defaultValue) {
  		return System.getProperty(cls.getSimpleName() + '.' + key, defaultValue);
  	}
  
  	public static Path getPathSetting(Class<?> cls, String key, Path defaultValue) {
  		String setting = getStringSetting(cls, key, null);
  		return setting == null ? defaultValue : Paths.get(setting);
  	}
  
  	public static boolean isJvmPrimitive(Object value) {
  		return value.getClass() == String.class || Primitives.isWrapperType(value.getClass());
  	}
  }
  