All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.codegen.Context Maven / Gradle / Ivy

/*
 * 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;

import io.activej.codegen.expression.Expression;
import io.activej.codegen.expression.LocalVariable;
import io.activej.codegen.expression.impl.Constant;
import io.activej.codegen.util.TypeChecks;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static io.activej.codegen.expression.Expressions.*;
import static io.activej.codegen.util.TypeChecks.checkType;
import static io.activej.codegen.util.TypeChecks.isNotThrow;
import static io.activej.codegen.util.Utils.*;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.objectweb.asm.Type.*;

/**
 * Contains information about a dynamic class
 */
public final class Context {
	private static final Set OBJECT_INSTANCE_METHODS = Arrays.stream(Object.class.getMethods())
		.filter(m -> !isStatic(m.getModifiers()))
		.map(Method::getMethod)
		.collect(toSet());

	private final ClassLoader classLoader;
	private final ClassGenerator classGenerator;
	private final GeneratorAdapter g;
	private final Type selfType;
	private final Method method;

	private Set accessibleMethods;
	private final Map varLocals = new HashMap<>();
	private final Map constantMap;

	public Context(
		ClassLoader classLoader, ClassGenerator classGenerator, GeneratorAdapter g, Type selfType, Method method,
		Map constantMap
	) {
		this.classLoader = classLoader;
		this.classGenerator = classGenerator;
		this.g = g;
		this.selfType = selfType;
		this.method = method;
		this.constantMap = constantMap;
	}

	public ClassLoader getClassLoader() {
		return classLoader;
	}

	public void setConstant(String field, Constant value) {
		constantMap.put(field, value);
	}

	public GeneratorAdapter getGeneratorAdapter() {
		return g;
	}

	public Type getSelfType() {
		return selfType;
	}

	public Class getSuperclass() {
		return classGenerator.superclass;
	}

	public List> getInterfaces() {
		return classGenerator.interfaces;
	}

	public Map> getFields() {
		return classGenerator.fields;
	}

	public Map getMethods() {
		return classGenerator.methods;
	}

	public Set getAccessibleMethods() {
		if (accessibleMethods != null) {
			return accessibleMethods;
		}
		accessibleMethods = new HashSet<>(classGenerator.methods.keySet());
		Class superclass = classGenerator.superclass;
		while (superclass != null) {
			for (java.lang.reflect.Method method : superclass.getDeclaredMethods()) {
				int modifiers = method.getModifiers();
				if (!Modifier.isStatic(modifiers) &&
					(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))
				) {
					accessibleMethods.add(Method.getMethod(method));
				}
			}
			superclass = superclass.getSuperclass();
		}
		return accessibleMethods;
	}

	public Map getStaticMethods() {
		return classGenerator.staticMethods;
	}

	public Method getMethod() {
		return method;
	}

	public LocalVariable newLocal(Type type) {
		if (type == Type.VOID_TYPE) return localVoid();
		int local = getGeneratorAdapter().newLocal(type);
		return local(local);
	}

	public LocalVariable ensureLocal(Object key, Expression expression) {
		LocalVariable varLocal = varLocals.get(key);
		if (varLocal == null) {
			Type type = expression.load(this);
			checkType(type, isNotThrow());

			if (type == Type.VOID_TYPE) {
				varLocal = localVoid();
			} else {
				int local = getGeneratorAdapter().newLocal(type);
				varLocal = local(local);
				g.storeLocal(local);
			}
			varLocals.put(key, varLocal);
		}
		return varLocal;
	}

	public @Nullable Type unifyTypes(@Nullable Type type1, @Nullable Type type2) {
		if (type1 == null) return type2;
		if (type2 == null) return type1;
		int sort1 = type1.getSort();
		int sort2 = type2.getSort();
		if (sort1 == ARRAY && sort2 == ARRAY) {
			if (type1.equals(type2))
				return type1;
		}
		if (sort1 == OBJECT && sort2 == OBJECT) {
			if (type1.equals(type2))
				return type1;
			Class class1 = toJavaType(type1);
			Class class2 = toJavaType(type2);
			if (class1.isAssignableFrom(class2)) {
				return type1;
			}
			if (class2.isAssignableFrom(class1)) {
				return type2;
			}
		}
		if (sort1 == sort2) {
			return type1;
		}
		throw new IllegalArgumentException();
	}

	private SelfOrClass toSelfOrClass(Type type) {
		return type.equals(getSelfType()) ?
			new SelfOrClass(classGenerator.superclass, classGenerator.interfaces) :
			new SelfOrClass(toJavaType(type), Collections.emptyList());
	}

	public Class toJavaType(Type type) {
		if (type.equals(getSelfType()))
			throw new IllegalArgumentException();
		int sort = type.getSort();
		return switch (sort) {
			case BOOLEAN -> boolean.class;
			case CHAR -> char.class;
			case BYTE -> byte.class;
			case SHORT -> short.class;
			case INT -> int.class;
			case FLOAT -> float.class;
			case LONG -> long.class;
			case DOUBLE -> double.class;
			case VOID -> void.class;
			case OBJECT -> {
				try {
					yield classLoader.loadClass(type.getClassName());
				} catch (ClassNotFoundException e) {
					throw new IllegalArgumentException(format("No class %s in class loader", type.getClassName()), e);
				}
			}
			case ARRAY -> {
				Class result;
				if (type.equals(getType(Object[].class))) {
					result = Object[].class;
				} else {
					String className = type.getDescriptor().replace('/', '.');
					try {
						result = Class.forName(className);
					} catch (ClassNotFoundException e) {
						throw new IllegalArgumentException(format("No class %s in Class.forName", className), e);
					}
				}
				yield result;
			}
			default -> throw new IllegalArgumentException(format("No Java type for %s", type.getClassName()));
		};
	}

	public void cast(Type typeFrom, Type typeTo) {
		GeneratorAdapter g = getGeneratorAdapter();

		if (typeFrom.equals(typeTo)) {
			return;
		}

		if (typeTo == VOID_TYPE) {
			if (typeFrom.getSize() == 1)
				g.pop();
			if (typeFrom.getSize() == 2)
				g.pop2();
			return;
		}

		if (typeFrom == VOID_TYPE) {
			throw new RuntimeException(format("Can't cast VOID_TYPE type to %s. %s",
				typeTo.getClassName(),
				exceptionInGeneratedClass(this)));
		}

		if (typeFrom.equals(getSelfType())) {
			SelfOrClass fromSelfOrClass = toSelfOrClass(typeFrom);
			SelfOrClass toSelfOrClass = toSelfOrClass(typeTo);
			if (toSelfOrClass.isAssignableFrom(fromSelfOrClass)) {
				return;
			}
			throw new RuntimeException(format("Can't cast self %s type to %s, %s",
				typeFrom.getClassName(),
				typeTo.getClassName(),
				exceptionInGeneratedClass(this)));
		}

		if (!typeFrom.equals(getSelfType()) && !typeTo.equals(getSelfType()) &&
			toJavaType(typeTo).isAssignableFrom(toJavaType(typeFrom))
		) {
			return;
		}

		if (typeTo.equals(getType(Object.class)) && isPrimitiveType(typeFrom)) {
			g.box(typeFrom);
//			g.cast(wrap(typeFrom), getType(Object.class));
			return;
		}

		if (!isPrimitiveType(typeFrom) && !isWrapperType(typeFrom) && isPrimitiveType(typeTo)) {
			Type typeToWrapped = wrap(typeTo);
			g.checkCast(typeToWrapped);
			typeFrom = typeToWrapped;
		}

		if ((isPrimitiveType(typeFrom) || isWrapperType(typeFrom)) &&
			(isPrimitiveType(typeTo) || isWrapperType(typeTo))
		) {
			Type targetTypePrimitive = isPrimitiveType(typeTo) ? typeTo : unwrap(typeTo);

			if (isWrapperType(typeFrom)) {
				g.invokeVirtual(typeFrom, unwrapToPrimitive(targetTypePrimitive));
				return;
			}

			assert isPrimitiveType(typeFrom);

			if (isValidCast(typeFrom, targetTypePrimitive)) {
				g.cast(typeFrom, targetTypePrimitive);
			}

			if (isWrapperType(typeTo)) {
				g.valueOf(targetTypePrimitive);
			}

			return;
		}

		g.checkCast(typeTo);
	}

	public Type invoke(Expression owner, String methodName, Expression... arguments) {
		return invoke(owner, methodName, List.of(arguments));
	}

	public Type invoke(Expression owner, String methodName, List arguments) {
		Type ownerType = owner.load(this);
		checkType(ownerType, TypeChecks.isAssignable());

		Type[] argumentTypes = getArgumentTypes(arguments);
		return invoke(ownerType, methodName, argumentTypes);
	}

	public Type invoke(Type ownerType, String methodName, Type... argumentTypes) {
		SelfOrClass[] arguments = Stream.of(argumentTypes).map(this::toSelfOrClass).toArray(SelfOrClass[]::new);
		Method foundMethod;
		if (ownerType.equals(getSelfType())) {
			foundMethod = findMethod(
				getAccessibleMethods().stream(),
				methodName,
				arguments);
			if (foundMethod == null) {
				throw new IllegalArgumentException(
					"Method not found: " + ownerType.getClassName() + '#' + methodName +
					Arrays.stream(arguments)
						.map(SelfOrClass::toString)
						.collect(joining(",", "(", ")")));
			}
			g.invokeVirtual(ownerType, foundMethod);
		} else {
			Class javaOwnerType = toJavaType(ownerType);
			foundMethod = findMethod(
				Arrays.stream(javaOwnerType.getMethods())
					.filter(m -> !isStatic(m.getModifiers()))
					.map(Method::getMethod),
				methodName,
				arguments);
			if (foundMethod == null) {
				throw new IllegalArgumentException(
					"Method not found: " + ownerType.getClassName() + '#' + methodName +
					Arrays.stream(arguments)
						.map(SelfOrClass::toString)
						.collect(joining(",", "(", ")")));
			}
			invokeVirtualOrInterface(this, ownerType, foundMethod);
		}
		return foundMethod.getReturnType();
	}

	public Type invokeStatic(Type ownerType, String methodName, Expression... arguments) {
		return invokeStatic(ownerType, methodName, List.of(arguments));
	}

	public Type invokeStatic(Type ownerType, String methodName, List arguments) {
		Type[] argumentTypes = getArgumentTypes(arguments);
		return invokeStatic(ownerType, methodName, argumentTypes);
	}

	public Type invokeStatic(Type ownerType, String methodName, Type... argumentTypes) {
		SelfOrClass[] arguments = Stream.of(argumentTypes).map(this::toSelfOrClass).toArray(SelfOrClass[]::new);
		Method foundMethod;
		if (ownerType.equals(getSelfType())) {
			foundMethod = findMethod(
				getStaticMethods().keySet().stream(),
				methodName,
				arguments);
		} else {
			foundMethod = findMethod(
				Arrays.stream(toJavaType(ownerType).getMethods())
					.filter(m -> isStatic(m.getModifiers()))
					.map(Method::getMethod),
				methodName,
				arguments);
		}
		if (foundMethod == null) {
			throw new IllegalArgumentException(
				"Static method not found: " + ownerType.getClassName() + '.' + methodName +
				Arrays.stream(arguments)
					.map(SelfOrClass::toString)
					.collect(joining(",", "(", ")")));
		}
		g.invokeStatic(ownerType, foundMethod);
		return foundMethod.getReturnType();
	}

	public Type invokeConstructor(Type ownerType, Expression... arguments) {
		return invokeConstructor(ownerType, List.of(arguments));
	}

	public Type invokeConstructor(Type ownerType, List arguments) {
		g.newInstance(ownerType);
		g.dup();

		Type[] argumentTypes = getArgumentTypes(arguments);
		return invokeConstructor(ownerType, argumentTypes);
	}

	public Type invokeConstructor(Type ownerType, Type... argumentTypes) {
		if (ownerType.equals(getSelfType()))
			throw new IllegalArgumentException();
		SelfOrClass[] arguments = Stream.of(argumentTypes).map(this::toSelfOrClass).toArray(SelfOrClass[]::new);
		Method foundMethod = findMethod(
			Arrays.stream(toJavaType(ownerType).getConstructors()).map(Method::getMethod),
			"",
			arguments);
		if (foundMethod == null) {
			throw new IllegalArgumentException(
				"Constructor not found:" + ownerType.getClassName() +
				Arrays.stream(arguments)
					.map(SelfOrClass::toString)
					.collect(joining(",", "(", ")")));
		}
		g.invokeConstructor(ownerType, foundMethod);
		return ownerType;
	}

	public Type invokeSuperConstructor(List arguments) {
		g.loadThis();
		Type[] argumentTypes = getArgumentTypes(arguments);
		SelfOrClass[] argumentClasses = Stream.of(argumentTypes).map(this::toSelfOrClass).toArray(SelfOrClass[]::new);
		Method foundMethod = findMethod(
			Arrays.stream(classGenerator.superclass.getDeclaredConstructors()).map(Method::getMethod),
			"",
			argumentClasses);
		if (foundMethod == null) {
			throw new IllegalArgumentException(
				"Parent constructor not found: " + classGenerator.superclass.getSimpleName() +
				" with arguments " +
				Arrays
					.stream(argumentClasses)
					.map(SelfOrClass::toString)
					.collect(joining(",", "(", ")")));
		}
		g.invokeConstructor(getType(classGenerator.superclass), foundMethod);

		for (String field : classGenerator.fieldExpressions.keySet()) {
			if (classGenerator.fieldsStatic.contains(field)) continue;
			Expression expression = classGenerator.fieldExpressions.get(field);
			set(property(self(), field), expression).load(this);
		}

		return VOID_TYPE;
	}

	public Type invokeSuperMethod(String methodName, Expression[] arguments) {
		return invokeSuperMethod(methodName, List.of(arguments));
	}

	public Type invokeSuperMethod(String methodName, List arguments) {
		g.loadThis();
		Type[] argumentTypes = getArgumentTypes(arguments);
		SelfOrClass[] argumentClasses = Stream.of(argumentTypes).map(this::toSelfOrClass).toArray(SelfOrClass[]::new);
		Method foundMethod = findMethod(
			getAccessibleMethods().stream(),
			methodName,
			argumentClasses);
		if (foundMethod == null) {
			throw new IllegalArgumentException(
				"Parent method of " + classGenerator.superclass.getSimpleName() +
				" with name'" + methodName +
				"' and arguments " +
				Arrays.stream(argumentClasses)
					.map(SelfOrClass::toString)
					.collect(joining(",", "(", ")")) +
				" not found");
		}
		String typeName = getType(classGenerator.superclass).getInternalName();
		g.visitMethodInsn(Opcodes.INVOKESPECIAL, typeName, methodName, method.getDescriptor(), false);
		return foundMethod.getReturnType();
	}

	@SuppressWarnings("StatementWithEmptyBody")
	private @Nullable Method findMethod(Stream methods, String name, SelfOrClass[] arguments) {
		Set methodSet = methods.collect(toSet());

		methodSet.addAll(OBJECT_INSTANCE_METHODS);

		Method foundMethod = null;
		SelfOrClass[] foundMethodArguments = null;

		for (Method method : methodSet) {
			if (!name.equals(method.getName())) continue;
			SelfOrClass[] methodArguments = Stream.of(method.getArgumentTypes()).map(this::toSelfOrClass).toArray(SelfOrClass[]::new);
			if (!isAssignable(methodArguments, arguments)) {
				continue;
			}
			if (foundMethod == null) {
				foundMethod = method;
				foundMethodArguments = methodArguments;
			} else {
				if (isAssignable(foundMethodArguments, methodArguments)) {
					foundMethod = method;
					foundMethodArguments = methodArguments;
				} else if (isAssignable(methodArguments, foundMethodArguments)) {
					// do nothing
				} else {
					throw new IllegalArgumentException("Ambiguous method: " + method + " " + Arrays.toString(arguments));
				}
			}
		}

		return foundMethod;
	}

	private static boolean isAssignable(SelfOrClass[] to, SelfOrClass[] from) {
		if (to.length != from.length) return false;
		return IntStream.range(0, from.length)
			.allMatch(i -> to[i].isAssignableFrom(from[i]));
	}

	public static final class SelfOrClass {
		final Class implementation;
		final List> interfaces;

		private SelfOrClass(Class implementation, List> interfaces) {
			this.implementation = implementation;
			this.interfaces = interfaces;
		}

		boolean isAssignableFrom(Class cls) {
			if (implementation.isAssignableFrom(cls)) return true;
			for (Class anInterface : interfaces) {
				if (anInterface.isAssignableFrom(cls)) return true;
			}
			return false;
		}

		boolean isAssignableFrom(SelfOrClass selfOrClass) {
			if (!selfOrClass.isSelf()) {
				return isAssignableFrom(selfOrClass.implementation);
			}

			if (isSelf()) {
				// same classes
				assert implementation == selfOrClass.implementation;
				assert interfaces.equals(selfOrClass.interfaces);

				return true;
			}

			if (implementation.isAssignableFrom(selfOrClass.implementation)) return true;
			for (Class anInterface : selfOrClass.interfaces) {
				if (implementation.isAssignableFrom(anInterface)) return true;
			}
			return false;
		}

		boolean isSelf() {
			return !interfaces.isEmpty();
		}

		@Override
		public String toString() {
			if (interfaces.isEmpty()) return implementation.getName();

			StringBuilder sb = new StringBuilder(implementation.getName());
			for (Class anInterface : interfaces) {
				sb.append('|').append(anInterface.getName());
			}
			return sb.toString();
		}
	}

	private Type[] getArgumentTypes(List arguments) {
		Type[] argumentTypes = new Type[arguments.size()];
		for (int i = 0; i < arguments.size(); i++) {
			Expression argument = arguments.get(i);
			Type argumentType = argument.load(this);
			checkType(argumentType, TypeChecks.isAssignable());
			argumentTypes[i] = argumentType;
		}
		return argumentTypes;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy