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

net.jqwik.engine.support.GenericsClassContext Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
package net.jqwik.engine.support;

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;

public class GenericsClassContext {

	static final GenericsClassContext NULL = new GenericsClassContext(null) {
		@Override
		public String toString() {
			return "GenericsContext(null)";
		}
	};

	private final Class contextClass;
	private final Map resolutions = new LinkedHashMap<>();

	GenericsClassContext(Class contextClass) {
		this.contextClass = contextClass;
	}

	public Class contextClass() {
		return contextClass;
	}

	void addResolution(TypeVariable typeVariable, Type resolvedType, AnnotatedType annotatedType) {
		LookupTypeVariable genericVariable = new LookupTypeVariable(typeVariable);
		resolutions.put(genericVariable, new TypeResolution(resolvedType, annotatedType, true));
	}

	@Override
	public String toString() {
		return String.format("GenericsContext(%s)", contextClass);
	}

	public TypeResolution resolveParameter(Parameter parameter) {
		TypeResolution initial = new TypeResolution(parameter.getParameterizedType(), parameter.getAnnotatedType(), false);
		return resolveType(initial);
	}

	public TypeResolution resolveReturnType(Method method) {
		TypeResolution initial = new TypeResolution(method.getGenericReturnType(), method.getAnnotatedReturnType(), false);
		return resolveType(initial);
	}

	private TypeResolution resolveType(TypeResolution typeResolution) {
		if (typeResolution.isVariable()) {
			return resolveVariable(typeResolution);
		}
		if (typeResolution.type() instanceof ParameterizedType) {
			return resolveParameterizedType(typeResolution);
		}
		return typeResolution;
	}

	private TypeResolution resolveParameterizedType(TypeResolution parameterizedTypeResolution) {
		ParameterizedType type = (ParameterizedType) parameterizedTypeResolution.type();
		Type[] actualTypeArguments = type.getActualTypeArguments();

		AnnotatedParameterizedType annotatedType = (AnnotatedParameterizedType) parameterizedTypeResolution.annotatedType();
		// Sometimes a type resolution does not have an annotated type
		AnnotatedType[] annotatedActualTypeArguments = annotatedType == null
														   ? new AnnotatedType[0]
														   : annotatedType.getAnnotatedActualTypeArguments();

		int numberOfArguments = Math.min(annotatedActualTypeArguments.length, actualTypeArguments.length);
		List resolvedTypeArguments = new ArrayList<>();
		for (int i = 0; i < numberOfArguments; i++) {
			Type typeArgument = actualTypeArguments[i];
			AnnotatedType annotatedTypeArgument = annotatedActualTypeArguments[i];
			TypeResolution typeResolution = resolveType(new TypeResolution(typeArgument, annotatedTypeArgument, false));
			resolvedTypeArguments.add(typeResolution);
		}

		if (resolvedTypeArguments.stream().noneMatch(TypeResolution::typeHasChanged)) {
			return parameterizedTypeResolution;
		}
		ParameterizedTypeWrapper resolvedType = new ParameterizedTypeWrapper(type) {
			@Override
			public Type[] getActualTypeArguments() {
				return resolvedTypeArguments.stream().map(TypeResolution::type).toArray(Type[]::new);
			}
		};
		AnnotatedParameterizedType resolvedAnnotatedType = new AnnotatedParameterizedTypeWrapper(annotatedType) {
			@Override
			public Type getType() {
				return resolvedType;
			}

			@Override
			public AnnotatedType[] getAnnotatedActualTypeArguments() {
				return resolvedTypeArguments.stream().map(TypeResolution::annotatedType).toArray(AnnotatedType[]::new);
			}
		};
		return new TypeResolution(resolvedType, resolvedAnnotatedType, true);
	}

	private TypeResolution resolveVariable(TypeResolution typeVariableResolution) {
		TypeResolution localResolution = resolveVariableLocally(typeVariableResolution);
		if (localResolution.isVariable()) {
			TypeResolution supertypeResolution = resolveVariableInSupertypesOf(localResolution, contextClass);
			if (supertypeResolution.typeHasChanged()) {
				return resolveType(supertypeResolution);
			}
		}
		if (localResolution.typeHasChanged()) {
			return resolveType(localResolution);
		}
		return typeVariableResolution;
	}

	private TypeResolution resolveVariableLocally(TypeResolution typeResolution) {
		TypeVariable typeVariable = (TypeVariable) typeResolution.type();
		LookupTypeVariable variable = new LookupTypeVariable(typeVariable);
		return resolutions.getOrDefault(variable, typeResolution.unchanged());
	}

	private static TypeResolution resolveVariableInSupertypesOf(TypeResolution variableResolution, Class clazz) {
		return resolveVariableInTypes(variableResolution, supertypesOf(clazz));
	}

	private static Collection> supertypesOf(Class clazz) {
		if (clazz == null) {
			return Collections.emptySet();
		}
		Set> supertypes = new LinkedHashSet<>();
		supertypes.add(clazz.getSuperclass());
		supertypes.addAll(Arrays.asList(clazz.getInterfaces()));
		return supertypes;
	}

	private static TypeResolution resolveVariableInTypes(TypeResolution variableResolution, Collection> superTypes) {
		for (Class superType : superTypes) {
			GenericsClassContext context = GenericsSupport.contextFor(superType);
			TypeResolution resolved = context.resolveVariableLocally(variableResolution);
			if (resolved.typeHasChanged()) {
				return resolved;
			}
		}
		for (Class superType : superTypes) {
			TypeResolution typeResolution = resolveVariableInSupertypesOf(variableResolution, superType);
			if (typeResolution.typeHasChanged()) {
				return typeResolution;
			}
		}
		return variableResolution.unchanged();
	}

	private static class LookupTypeVariable {
		private final String name;
		private final GenericDeclaration declaration;

		private LookupTypeVariable(TypeVariable typeVariable) {
			this.name = typeVariable.getName();
			this.declaration = typeVariable.getGenericDeclaration();
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;

			LookupTypeVariable that = (LookupTypeVariable) o;

			if (!name.equals(that.name)) return false;
			return declaration.equals(that.declaration);
		}

		@Override
		public int hashCode() {
			return Objects.hash(name, declaration);
		}

		@Override
		public String toString() {
			return String.format("<%s>", name);
		}
	}

	private static class ParameterizedTypeWrapper implements ParameterizedType {

		private final ParameterizedType wrapped;

		private ParameterizedTypeWrapper(ParameterizedType wrapped) {
			this.wrapped = wrapped;
		}

		@Override
		public Type[] getActualTypeArguments() {
			return wrapped.getActualTypeArguments();
		}

		@Override
		public Type getRawType() {
			return wrapped.getRawType();
		}

		@Override
		public Type getOwnerType() {
			return wrapped.getOwnerType();
		}

		@Override
		public String toString() {
			String baseString = JqwikStringSupport.displayString(getRawType());
			String typeArgumentsString = Arrays.stream(getActualTypeArguments()) //
											   .map(JqwikStringSupport::displayString) //
											   .collect(Collectors.joining(", "));
			return String.format("%s<%s>", baseString, typeArgumentsString);
		}
	}

	private static class AnnotatedParameterizedTypeWrapper implements AnnotatedParameterizedType {

		private final AnnotatedParameterizedType annotatedType;

		private AnnotatedParameterizedTypeWrapper(AnnotatedParameterizedType annotatedType) {
			this.annotatedType = annotatedType;
		}

		@Override
		public AnnotatedType[] getAnnotatedActualTypeArguments() {
			return annotatedType.getAnnotatedActualTypeArguments();
		}

		@Override
		public Type getType() {
			return annotatedType.getType();
		}

		@Override
		public  T getAnnotation(Class annotationClass) {
			return annotatedType.getAnnotation(annotationClass);
		}

		@Override
		public Annotation[] getAnnotations() {
			return annotatedType.getAnnotations();
		}

		@Override
		public Annotation[] getDeclaredAnnotations() {
			return annotatedType.getDeclaredAnnotations();
		}

		// For compatibility with JDK >= 9. A breaking change in the JDK :-(
		// @Override
		@SuppressWarnings("Since15")
		public AnnotatedType getAnnotatedOwnerType() {
			// TODO: Return annotatedType.getAnnotatedOwnerType() as soon as Java >= 9 is being used
			return null;
		}

		@Override
		public String toString() {
			String typeString = JqwikStringSupport.displayString(getType());
			String annotationsString = Arrays.stream(getAnnotations())
											 .map(JqwikStringSupport::displayString)
											 .collect(Collectors.joining(", "));
			return String.format("%s %s", annotationsString, typeString);
		}

	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy