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

import net.jqwik.api.providers.*;

public class GenericsClassContext {

	static final GenericsClassContext NULL = new GenericsClassContext(null) {
		@Override
		protected TypeResolution resolveInSupertypes(TypeResolution typeResolution) {
			return typeResolution.unchanged();
		}

		@Override
		public String toString() {
			return "GenericsContext(null)";
		}
	};

	private final TypeUsage contextType;
	private final Map resolutions = new HashMap<>();

	GenericsClassContext(TypeUsage contextType) {
		this.contextType = contextType;
	}

	public TypeUsage contextType() {
		return contextType;
	}

	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)", contextType.toString());
	}

	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.type() instanceof TypeVariable) {
			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) {
		TypeVariable typeVariable = (TypeVariable) typeVariableResolution.type();
		LookupTypeVariable variable = new LookupTypeVariable(typeVariable);
		TypeResolution localResolution = resolveLocally(typeVariableResolution, variable);
		TypeResolution supertypeResolution = resolveInSupertypes(localResolution);
		if (supertypeResolution.typeHasChanged()) {
			return resolveType(supertypeResolution);
		}
		if (localResolution.typeHasChanged()) {
			return resolveType(localResolution);
		}
		return typeVariableResolution;
	}

	private TypeResolution resolveLocally(TypeResolution typeResolution, LookupTypeVariable variable) {
		return resolutions.getOrDefault(variable, typeResolution.unchanged());
	}

	protected TypeResolution resolveInSupertypes(TypeResolution typeResolution) {
		return supertypeContexts()
				   .map(context -> context.resolveType(typeResolution))
				   .filter(TypeResolution::typeHasChanged)
				   .findFirst()
				   .orElse(typeResolution.unchanged());
	}

	private Stream supertypeContexts() {
		Stream superclassStream = contextType.getSuperclass().map(Stream::of).orElseGet(Stream::empty);
		Stream interfacesStream = contextType.getInterfaces().stream();
		return Stream.concat(superclassStream, interfacesStream).map(GenericsSupport::contextFor);
	}

	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() {
			int result = name.hashCode();
			result = 31 * result + declaration.hashCode();
			return result;
		}

		@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
		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