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

com.navercorp.fixturemonkey.tree.ObjectNode Maven / Gradle / Ivy

The newest version!
/*
 * Fixture Monkey
 *
 * Copyright (c) 2021-present NAVER Corp.
 *
 * 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 com.navercorp.fixturemonkey.tree;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nullable;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

import com.navercorp.fixturemonkey.api.arbitrary.CombinableArbitrary;
import com.navercorp.fixturemonkey.api.container.ConcurrentLruCache;
import com.navercorp.fixturemonkey.api.generator.ArbitraryContainerInfo;
import com.navercorp.fixturemonkey.api.generator.ArbitraryGenerator;
import com.navercorp.fixturemonkey.api.generator.ArbitraryGeneratorContext;
import com.navercorp.fixturemonkey.api.generator.ArbitraryProperty;
import com.navercorp.fixturemonkey.api.generator.ContainerPropertyGenerator;
import com.navercorp.fixturemonkey.api.generator.ObjectProperty;
import com.navercorp.fixturemonkey.api.generator.ObjectPropertyGenerator;
import com.navercorp.fixturemonkey.api.generator.ObjectPropertyGeneratorContext;
import com.navercorp.fixturemonkey.api.lazy.LazyArbitrary;
import com.navercorp.fixturemonkey.api.option.FixtureMonkeyOptions;
import com.navercorp.fixturemonkey.api.property.CandidateConcretePropertyResolver;
import com.navercorp.fixturemonkey.api.property.CompositeTypeDefinition;
import com.navercorp.fixturemonkey.api.property.DefaultCandidateConcretePropertyResolver;
import com.navercorp.fixturemonkey.api.property.DefaultTypeDefinition;
import com.navercorp.fixturemonkey.api.property.ElementPropertyGenerator;
import com.navercorp.fixturemonkey.api.property.LazyPropertyGenerator;
import com.navercorp.fixturemonkey.api.property.MapEntryElementProperty;
import com.navercorp.fixturemonkey.api.property.Property;
import com.navercorp.fixturemonkey.api.property.PropertyGenerator;
import com.navercorp.fixturemonkey.api.property.PropertyPath;
import com.navercorp.fixturemonkey.api.property.RootProperty;
import com.navercorp.fixturemonkey.api.property.TypeDefinition;
import com.navercorp.fixturemonkey.api.tree.ObjectTreeNode;
import com.navercorp.fixturemonkey.api.tree.TreeProperty;
import com.navercorp.fixturemonkey.api.type.Types;
import com.navercorp.fixturemonkey.customizer.ContainerInfoManipulator;
import com.navercorp.fixturemonkey.customizer.NodeManipulator;

@API(since = "0.4.0", status = Status.MAINTAINED)
public final class ObjectNode implements ObjectTreeNode {
	private static final ConcurrentLruCache> CANDIDATE_CONCRETE_PROPERTIES_BY_PROPERTY =
		new ConcurrentLruCache<>(1024);

	private final RootProperty rootProperty;

	@Nullable
	private final Property resolvedParentProperty;
	private TypeDefinition resolvedTypeDefinition;
	private final TreeProperty treeProperty;
	private final TraverseContext traverseContext;

	@Nullable
	private ObjectNode parent = null;
	private List children;
	@Nullable
	private CombinableArbitrary arbitrary;
	private double nullInject;
	@Nullable
	private TypeDefinition expandedTypeDefinition = null;

	private final List manipulators = new ArrayList<>();
	private final List containerInfoManipulators = new ArrayList<>();
	@SuppressWarnings("rawtypes")
	private final List arbitraryFilters = new ArrayList<>();
	private final List, CombinableArbitrary>> arbitraryCustomizers =
		new ArrayList<>();

	private final LazyArbitrary childNotCacheable = LazyArbitrary.lazy(() -> {
		for (ObjectNode child : this.resolveChildren()) {
			if (child.manipulated() || child.childNotCacheable.getValue() || child.treeProperty.isContainer()) {
				return true;
			}
		}

		return false;
	});
	private final LazyArbitrary lazyPropertyPath = LazyArbitrary.lazy(() -> {
		Property resolvedProperty = this.resolvedTypeDefinition.getResolvedProperty();
		if (parent == null) {
			return new PropertyPath(resolvedProperty, null, 1);
		}

		PropertyPath parentPropertyPath = parent.getLazyPropertyPath().getValue();
		return new PropertyPath(
			resolvedProperty,
			parentPropertyPath,
			parentPropertyPath.getDepth() + 1
		);
	});

	ObjectNode(
		RootProperty rootProperty,
		@Nullable Property resolvedParentProperty,
		TypeDefinition resolvedTypeDefinition,
		TreeProperty treeProperty,
		double nullInject,
		TraverseContext traverseContext
	) {
		this.rootProperty = rootProperty;
		this.resolvedParentProperty = resolvedParentProperty;
		this.resolvedTypeDefinition = resolvedTypeDefinition;
		this.treeProperty = treeProperty;
		this.nullInject = nullInject;
		this.traverseContext = traverseContext;
	}

	@Nullable
	public Property getResolvedParentProperty() {
		return resolvedParentProperty;
	}

	/**
	 * The resolved property refers to the concrete type of {@link #getOriginalProperty()},
	 * it can't be an abstract class or interface unless it's an anonymous object.
	 *
	 * @return the resolvedProperty
	 */
	public Property getResolvedProperty() {
		return this.resolvedTypeDefinition.getResolvedProperty();
	}

	public TypeDefinition getResolvedTypeDefinition() {
		return resolvedTypeDefinition;
	}

	public void setResolvedTypeDefinition(TypeDefinition typeDefinition) {
		this.resolvedTypeDefinition = typeDefinition;
	}

	public TreeProperty getTreeProperty() {
		return this.treeProperty;
	}

	/**
	 * The original property refers to the class-time property, which means it is the type in a class file.
	 * It can be an abstract class or interface, unlike the {@link #getResolvedProperty()}
	 *
	 * @return the original property
	 */
	public Property getOriginalProperty() {
		return this.getTreeProperty().getObjectProperty().getProperty();
	}

	/**
	 * expands by the resolved property and gets the expanded child nodes.
	 * It may not return empty if {@link TypeDefinition#getPropertyGenerator()} generates the child nodes.
	 *
	 * @return expanded child node.
	 */
	public List resolveChildren() {
		this.expand(this.resolvedTypeDefinition);
		return this.children;
	}

	/**
	 * Gets the expanded child nodes, it may be return empty if not expanded.
	 *
	 * @return already expanded child nodes
	 */
	public List getChildren() {
		return this.children;
	}

	public void setChildren(List children) {
		this.children = children;
		for (ObjectNode child : this.children) {
			child.parent = this;
		}
	}

	/**
	 * The ArbitraryProperty remains for backward compatibility with {@link ArbitraryGeneratorContext}.
	 *
	 * @return the ArbitraryProperty transformed by ObjectNode's {@link #treeProperty}
	 */
	public ArbitraryProperty getArbitraryProperty() {
		return this.treeProperty.toArbitraryProperty(this.nullInject);
	}

	@Nullable
	public CombinableArbitrary getArbitrary() {
		return this.arbitrary;
	}

	public void setArbitrary(@Nullable CombinableArbitrary arbitrary) {
		this.arbitrary = arbitrary;
	}

	public void addContainerManipulator(ContainerInfoManipulator containerInfoManipulator) {
		this.traverseContext.addContainerInfoManipulator(containerInfoManipulator);
		this.containerInfoManipulators.add(containerInfoManipulator);
	}

	public void addManipulator(NodeManipulator nodeManipulator) {
		this.manipulators.add(nodeManipulator);
	}

	@SuppressWarnings("rawtypes")
	public List getArbitraryFilters() {
		return arbitraryFilters;
	}

	@SuppressWarnings("rawtypes")
	public void addArbitraryFilter(Predicate filter) {
		this.arbitraryFilters.add(filter);
	}

	public void addGeneratedArbitraryCustomizer(
		Function, CombinableArbitrary> arbitraryCustomizer
	) {
		this.arbitraryCustomizers.add(arbitraryCustomizer);
	}

	public List, CombinableArbitrary>> getGeneratedArbitraryCustomizers() {
		return arbitraryCustomizers;
	}

	public void addArbitraryCustomizer(Function, CombinableArbitrary> arbitraryCustomizer) {
		this.arbitraryCustomizers.add(arbitraryCustomizer);
	}

	public double getNullInject() {
		return nullInject;
	}

	public void setNullInject(double nullInject) {
		this.nullInject = nullInject;
	}

	public boolean manipulated() {
		return !manipulators.isEmpty() || !containerInfoManipulators.isEmpty();
	}

	public boolean cacheable() {
		return !manipulated() && !treeProperty.isContainer() && !childNotCacheable.getValue();
	}

	@Nullable
	public ObjectNode getParent() {
		return parent;
	}

	@Nullable
	public ContainerInfoManipulator getAppliedContainerInfoManipulator() {
		if (containerInfoManipulators.isEmpty()) {
			return null;
		}

		return containerInfoManipulators.get(containerInfoManipulators.size() - 1);
	}

	public LazyArbitrary getLazyPropertyPath() {
		return lazyPropertyPath;
	}

	public void expand(TypeDefinition typeDefinition) {
		if (this.expandedTypeDefinition == resolvedTypeDefinition) {
			return;
		}

		List newChildren;
		if (this.getTreeProperty().isContainer()) {
			newChildren = expandContainerNode(typeDefinition, this.traverseContext).collect(Collectors.toList());
		} else {
			newChildren = this.generateChildrenNodes(
				typeDefinition.getResolvedProperty(),
				typeDefinition.getPropertyGenerator()
					.generateChildProperties(typeDefinition.getResolvedProperty()),
				this.nullInject,
				this.traverseContext
			);
		}

		this.setChildren(newChildren);
		this.expandedTypeDefinition = resolvedTypeDefinition;
	}

	@Override
	public void expand() {
		if (this.expandedTypeDefinition == resolvedTypeDefinition) {
			return;
		}

		this.setChildren(
			this.getTreeProperty().getTypeDefinitions().stream()
				.flatMap(
					typeDefinition -> {
						if (this.getTreeProperty().isContainer()) {
							return expandContainerNode(typeDefinition, this.traverseContext);
						}

						return this.generateChildrenNodes(
							typeDefinition.getResolvedProperty(),
							typeDefinition.getPropertyGenerator()
								.generateChildProperties(typeDefinition.getResolvedProperty()),
							this.nullInject,
							this.traverseContext
						).stream();
					}
				)
				.collect(Collectors.toList())
		);
		this.expandedTypeDefinition = resolvedTypeDefinition;
	}

	@Override
	public void forceExpand() {
		List newChildren = this.getTreeProperty().getTypeDefinitions().stream()
			.flatMap(
				typeDefinition -> {
					if (this.getTreeProperty().isContainer()) {
						return this.expandContainerNode(
							typeDefinition,
							traverseContext.withParentProperties()
						);
					}

					return this.generateChildrenNodes(
						typeDefinition.getResolvedProperty(),
						typeDefinition.getPropertyGenerator()
							.generateChildProperties(typeDefinition.getResolvedProperty()),
						this.nullInject,
						traverseContext.withParentProperties()
					).stream();
				}
			).collect(Collectors.toList());

		this.setChildren(newChildren);
		this.expandedTypeDefinition = resolvedTypeDefinition;
	}

	public void forceExpand(TypeDefinition typeDefinition) {
		List newChildren;
		if (this.getTreeProperty().isContainer()) {
			newChildren = expandContainerNode(
				typeDefinition,
				this.traverseContext.withParentProperties()
			)
				.collect(Collectors.toList());
		} else {
			newChildren = this.generateChildrenNodes(
				typeDefinition.getResolvedProperty(),
				typeDefinition.getPropertyGenerator()
					.generateChildProperties(typeDefinition.getResolvedProperty()),
				this.nullInject,
				this.traverseContext.withParentProperties()
			);
		}

		this.setChildren(newChildren);
		this.expandedTypeDefinition = resolvedTypeDefinition;
	}

	private Stream expandContainerNode(TypeDefinition typeDefinition, TraverseContext traverseContext) {
		ContainerInfoManipulator appliedContainerInfoManipulator =
			this.getAppliedContainerInfoManipulator();

		ArbitraryContainerInfo containerInfo = appliedContainerInfoManipulator != null
			? appliedContainerInfoManipulator.getContainerInfo()
			: null;

		PropertyGenerator propertyGenerator = typeDefinition.getPropertyGenerator();
		if (propertyGenerator instanceof LazyPropertyGenerator) {
			propertyGenerator = ((LazyPropertyGenerator)propertyGenerator).getDelegate();
		}

		if (propertyGenerator instanceof ElementPropertyGenerator) {
			((ElementPropertyGenerator)propertyGenerator).updateContainerInfo(containerInfo);
		}

		List elementProperties = propertyGenerator.generateChildProperties(
			typeDefinition.getResolvedProperty()
		);

		return this.generateChildrenNodes(
			typeDefinition.getResolvedProperty(),
			elementProperties,
			this.nullInject,
			traverseContext
		).stream();
	}

	static ObjectNode generateRootNode(
		RootProperty rootProperty,
		TraverseContext traverseContext
	) {
		return ObjectNode.generateObjectNode(
			rootProperty,
			null,
			rootProperty,
			null,
			0.0d,
			traverseContext
		);
	}

	static ObjectNode generateObjectNode(
		RootProperty rootProperty,
		@Nullable Property resolvedParentProperty,
		Property property,
		@Nullable Integer propertySequence,
		double parentNullInject,
		TraverseContext context
	) {
		FixtureMonkeyOptions fixtureMonkeyOptions = context.getMonkeyContext().getFixtureMonkeyOptions();
		ContainerPropertyGenerator containerPropertyGenerator =
			fixtureMonkeyOptions.getContainerPropertyGenerator(property);
		boolean container = containerPropertyGenerator != null;

		ObjectPropertyGenerator objectPropertyGenerator =
			fixtureMonkeyOptions.getObjectPropertyGenerator(property);

		TreeProperty parentTreeProperty = context.getLastTreeProperty();

		ArbitraryProperty parentArbitraryProperty = parentTreeProperty != null
			? parentTreeProperty.toArbitraryProperty(parentNullInject)
			: null;

		ObjectPropertyGeneratorContext objectPropertyGeneratorContext = new ObjectPropertyGeneratorContext(
			property,
			resolveIndex(
				resolvedParentProperty,
				parentTreeProperty,
				propertySequence,
				fixtureMonkeyOptions
			),
			parentArbitraryProperty,
			container,
			fixtureMonkeyOptions.getPropertyNameResolver(property)
		);

		ObjectProperty objectProperty = objectPropertyGenerator.generate(objectPropertyGeneratorContext);

		List candidateProperties = resolveCandidateProperties(
			property,
			fixtureMonkeyOptions
		);

		List objectProperties =
			context.getTreeProperties().stream()
				.map(TreeProperty::getObjectProperty).collect(Collectors.toList());
		objectProperties.add(objectProperty);

		ContainerInfoManipulator appliedContainerInfoManipulator = resolveAppliedContainerInfoManipulator(
			container,
			context.getContainerInfoManipulators(),
			objectProperties
		);

		List typeDefinitions = candidateProperties.stream()
			.map(concreteProperty -> {
				if (!container) {
					LazyPropertyGenerator lazyPropertyGenerator =
						new LazyPropertyGenerator(
							getPropertyGenerator(context.getPropertyConfigurers(), fixtureMonkeyOptions)
						);

					return new DefaultTypeDefinition(
						concreteProperty,
						lazyPropertyGenerator
					);
				}

				PropertyGenerator containerElementPropertyGenerator = new ElementPropertyGenerator(
					property,
					containerPropertyGenerator,
					fixtureMonkeyOptions.getArbitraryContainerInfoGenerator(property),
					null
				);

				LazyPropertyGenerator lazyPropertyGenerator =
					new LazyPropertyGenerator(containerElementPropertyGenerator);

				return new DefaultTypeDefinition(
					concreteProperty,
					lazyPropertyGenerator
				);
			})
			.collect(Collectors.toList());

		double nullInject = fixtureMonkeyOptions.getNullInjectGenerator(property)
			.generate(objectPropertyGeneratorContext);

		TreeProperty treeProperty = new TreeProperty(
			objectProperty,
			container,
			typeDefinitions
		);

		TraverseContext nextTraverseContext = context.appendArbitraryProperty(treeProperty);

		ObjectNode newObjectNode = new ObjectNode(
			rootProperty,
			resolvedParentProperty,
			new CompositeTypeDefinition(typeDefinitions).getResolvedTypeDefinition(),
			treeProperty,
			nullInject,
			nextTraverseContext
		);

		if (appliedContainerInfoManipulator != null) {
			newObjectNode.addContainerManipulator(appliedContainerInfoManipulator);
		}
		return newObjectNode;
	}

	@Nullable
	private static Integer resolveIndex(
		@Nullable Property resolvedParentProperty,
		@Nullable TreeProperty parentTreeProperty,
		@Nullable Integer propertySequence,
		FixtureMonkeyOptions fixtureMonkeyOptions
	) {
		if (resolvedParentProperty == null || parentTreeProperty == null) {
			return null;
		}

		boolean parentContainer =
			fixtureMonkeyOptions.getContainerPropertyGenerator(resolvedParentProperty) != null;
		if (!parentContainer) {
			return null;
		}

		if (propertySequence == null) {
			return null;
		}

		int index = propertySequence;
		if (parentTreeProperty.getObjectProperty().getProperty() instanceof MapEntryElementProperty) {
			index /= 2;
		}
		return index;
	}

	private List generateChildrenNodes(
		Property resolvedParentProperty,
		List childProperties,
		double parentNullInject,
		TraverseContext context
	) {
		List children = new ArrayList<>();

		for (int sequence = 0; sequence < childProperties.size(); sequence++) {
			Property childProperty = childProperties.get(sequence);

			if (context.isTraversed(childProperty)
				&& !(resolvedParentProperty instanceof MapEntryElementProperty)) {
				continue;
			}

			ObjectNode childNode = generateObjectNode(
				rootProperty,
				resolvedParentProperty,
				childProperty,
				sequence,
				parentNullInject,
				context
			);
			children.add(childNode);
		}
		return children;
	}

	@Nullable
	private static ContainerInfoManipulator resolveAppliedContainerInfoManipulator(
		boolean container,
		List containerInfoManipulators,
		List objectProperties
	) {
		if (!container) {
			return null;
		}

		ContainerInfoManipulator appliedContainerInfoManipulator = null;
		for (ContainerInfoManipulator containerInfoManipulator : containerInfoManipulators) {
			if (containerInfoManipulator.isMatch(objectProperties)) {
				appliedContainerInfoManipulator = containerInfoManipulator;
			}
		}
		return appliedContainerInfoManipulator;
	}

	private static PropertyGenerator getPropertyGenerator(
		Map, List> propertyConfigurers,
		FixtureMonkeyOptions fixtureMonkeyOptions
	) {
		PropertyGenerator resolvedPropertyGenerator = property -> {
			Class type = Types.getActualType(property.getType());
			List propertyConfigurer = propertyConfigurers.get(type);
			if (propertyConfigurer != null) {
				return propertyConfigurer;
			}

			PropertyGenerator propertyGenerator = fixtureMonkeyOptions.getOptionalPropertyGenerator(property);
			if (propertyGenerator != null) {
				return propertyGenerator.generateChildProperties(property);
			}

			ArbitraryGenerator defaultArbitraryGenerator = fixtureMonkeyOptions.getDefaultArbitraryGenerator();

			PropertyGenerator defaultArbitraryGeneratorPropertyGenerator =
				defaultArbitraryGenerator.getRequiredPropertyGenerator(property);

			if (defaultArbitraryGeneratorPropertyGenerator != null) {
				return defaultArbitraryGeneratorPropertyGenerator.generateChildProperties(property);
			}

			return fixtureMonkeyOptions.getDefaultPropertyGenerator().generateChildProperties(property);
		};

		return new LazyPropertyGenerator(resolvedPropertyGenerator);
	}

	private static List resolveCandidateProperties(
		Property property,
		FixtureMonkeyOptions fixtureMonkeyOptions
	) {
		CandidateConcretePropertyResolver candidateConcretePropertyResolver =
			fixtureMonkeyOptions.getCandidateConcretePropertyResolver(property);

		if (candidateConcretePropertyResolver == null) {
			return DefaultCandidateConcretePropertyResolver.INSTANCE.resolve(property);
		}

		return CANDIDATE_CONCRETE_PROPERTIES_BY_PROPERTY.computeIfAbsent(
			property,
			p -> {
				List resolvedCandidateProperties = new ArrayList<>();
				List candidateProperties = candidateConcretePropertyResolver.resolve(p);
				for (Property candidateProperty : candidateProperties) {
					// compares by type until a specific property implementation is created for the generic type.
					Type candidateType = candidateProperty.getType();

					if (p.getType().equals(candidateType)) {
						// prevents infinite recursion
						resolvedCandidateProperties.addAll(
							DefaultCandidateConcretePropertyResolver.INSTANCE.resolve(p)
						);
						continue;
					}
					resolvedCandidateProperties.addAll(
						resolveCandidateProperties(
							candidateProperty,
							fixtureMonkeyOptions
						)
					);
				}
				return resolvedCandidateProperties;
			}
		);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy