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

eu.cqse.check.framework.shallowparser.framework.ShallowEntityTraversalUtils Maven / Gradle / Ivy

There is a newer version: 2025.1.0
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * 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 eu.cqse.check.framework.shallowparser.framework;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import eu.cqse.check.framework.shallowparser.SubTypeNames;

/** Utility methods for working with shallow entities. */
public class ShallowEntityTraversalUtils {

	/** Lists all entities. */
	public static List listAllEntities(Collection entities) {
		return listEntitiesOfTypes(entities, EnumSet.allOf(EShallowEntityType.class));
	}

	/**
	 * Lists all entities of the given type, includes matching child entities
	 * recursively.
	 */
	public static List listEntitiesOfType(Collection entities, EShallowEntityType type) {
		return listEntitiesOfTypes(entities, EnumSet.of(type));
	}

	/**
	 * Lists all entities of the given types, includes matching child entities
	 * recursively.
	 */
	public static List listEntitiesOfTypes(Collection entities,
			Set types) {
		return new CollectingVisitorBase() {
			@Override
			protected boolean collect(ShallowEntity entity) {
				return types.contains(entity.getType());
			}
		}.apply(entities);
	}

	/** Lists all entities that are selected by the given predicate. */
	public static List selectEntities(Collection entities,
			Predicate predicate) {
		return new CollectingVisitorBase() {
			@Override
			protected boolean collect(ShallowEntity entity) {
				return predicate.test(entity);
			}
		}.apply(entities);
	}

	/**
	 * Returns the first incomplete entity found (or null). Unclosed entities
	 * correspond to parsing errors.
	 */
	private static ShallowEntity findIncompleteEntity(ShallowEntity entity) {
		if (!entity.isCompleted()) {
			return entity;
		}
		return findIncompleteEntity(entity.getChildren());
	}

	/**
	 * Returns the first incomplete entity found (or null). Unclosed entities
	 * correspond to parsing errors.
	 */
	public static ShallowEntity findIncompleteEntity(List entities) {
		for (ShallowEntity entity : entities) {
			ShallowEntity incomplete = findIncompleteEntity(entity);
			if (incomplete != null) {
				return incomplete;
			}
		}
		return null;
	}

	/**
	 * Traverses the given collection of entities and returns a flat list of
	 * entities.
	 */
	public static List getAllEntities(Collection entities) {
		return new CollectingVisitorBase() {
			@Override
			protected boolean collect(ShallowEntity entity) {
				return true;
			}
		}.apply(entities);
	}

	/**
	 * Lists all entities of the given type, but does not traverse into the
	 * entities, i.e. entities contained in the returned ones are not returned.
	 */
	public static List listEntitiesOfTypeNonRecursive(Collection entities,
			EShallowEntityType type) {
		List methods = new ArrayList<>();
		ShallowEntity.traverse(entities, new ShallowEntityVisitorBase() {
			/** {@inheritDoc} */
			@Override
			public boolean visit(ShallowEntity entity) {
				if (entity.getType() == type) {
					methods.add(entity);
					// do not recurse into methods
					return false;
				}
				return true;
			}
		});
		return methods;
	}

	/**
	 * Returns all methods found in the given entities, but does not traverse into
	 * the entities, i.e. methods contained in other methods are not returned.
	 */
	public static List listMethodsNonRecursive(List entities) {
		return listEntitiesOfTypeNonRecursive(entities, EShallowEntityType.METHOD);
	}

	/**
	 * Recursively traverses the given shallow entities and returns all entities
	 * that match the given predicate.
	 */
	public static List listMatchingEntitiesRecursive(List entities,
			Predicate matchPredicate) {
		return new CollectingVisitorBase() {
			@Override
			protected boolean collect(ShallowEntity entity) {
				return matchPredicate.test(entity);
			}
		}.apply(entities);
	}

	/** Empty default implementation of {@link IShallowEntityVisitor}. */
	public static abstract class ShallowEntityVisitorBase implements IShallowEntityVisitor {
		/** {@inheritDoc} */
		@Override
		public boolean visit(ShallowEntity entity) {
			return true;
		}

		/** {@inheritDoc} */
		@Override
		public void endVisit(ShallowEntity entity) {
			// nothing
		}
	}

	/**
	 * Base class for visitors that collect shallow entities, includes matching
	 * child entities recursively.
	 */
	public static abstract class CollectingVisitorBase extends ShallowEntityVisitorBase {

		/** The collected entities. */
		private final List entities = new ArrayList<>();

		/** {@inheritDoc} */
		@Override
		public boolean visit(ShallowEntity entity) {
			if (collect(entity)) {
				entities.add(entity);
			}
			return true;
		}

		/** Template method that returns true if the entity should be collected. */
		protected abstract boolean collect(ShallowEntity entity);

		/**
		 * Applies this collecting visitor depth-first to the given entities and returns
		 * the collected result.
		 */
		public List apply(Collection entities) {
			ShallowEntity.traverse(entities, this);
			return this.entities;
		}
	}

	/** Utility method for traversing multiple entities. */
	public static void traverseWithKey(List entities, IShallowEntityKeyVisitor visitor) {
		traverseWithKey(entities, visitor, new int[0]);
	}

	/**
	 * Utility method for traversing multiple entities.
	 *
	 * @param parentKey
	 *            Specifies the unique key of the entity lists parent.
	 */
	private static void traverseWithKey(List entities, IShallowEntityKeyVisitor visitor,
			int[] parentKey) {
		int length = parentKey.length;
		for (int i = 0; i < entities.size(); i++) {
			ShallowEntity entity = entities.get(i);
			int[] key = Arrays.copyOf(parentKey, length + 1);
			key[length] = i;
			if (visitor.visit(key, entity)) {
				traverseWithKey(entity.getChildren(), visitor, key);
			}
		}
	}

	/** Empty default implementation of {@link IShallowEntityVisitor}. */
	public interface IShallowEntityKeyVisitor {

		/**
		 * Denotes that visiting the entity begins.
		 *
		 * @param key
		 *            Key, which uniquely identifies the entity for the traversed list
		 *            of {@link ShallowEntity}s.
		 * @return true if the children of this entity are to be visited as well.
		 */
		boolean visit(int[] key, ShallowEntity entity);
	}

	/**
	 * Returns a {@link ShallowEntity} from the given list of entities, which is
	 * identified by the given key.
	 *
	 * @see ShallowEntityTraversalUtils#traverseWithKey(List,
	 *      IShallowEntityKeyVisitor)
	 */
	public static ShallowEntity getEntityFromListWithKey(List entities, int[] key) {
		if (key.length == 0 || key[0] >= entities.size()) {
			return null;
		}
		ShallowEntity entity = entities.get(key[0]);
		if (key.length == 1) {
			return entity;
		}
		return getEntityFromListWithKey(entity.getChildren(), Arrays.copyOfRange(key, 1, key.length));
	}

	/**
	 * Returns the next {@link ShallowEntity} after the given entity. Returns
	 * null if there is no subsequent entity.
	 */
	public static ShallowEntity getSubsequentEntity(ShallowEntity entity) {
		boolean found = false;
		while (entity != null && entity.getParent() != null) {
			List neighboringEntities = entity.getParent().getChildren();
			for (ShallowEntity neighboringEntity : neighboringEntities) {
				if (found) {
					return neighboringEntity;
				} else if (neighboringEntity.equals(entity)) {
					found = true;
				}
			}
			entity = entity.getParent();
			found = false;
		}
		return null;
	}

	/**
	 * Returns the previous {@link ShallowEntity} before the given entity. Returns
	 * null if there is no previous entity.
	 */
	public static ShallowEntity getPreviousEntity(ShallowEntity entity) {
		ShallowEntity previousEntity = null;
		while (entity != null && entity.getParent() != null) {
			List neighboringEntities = entity.getParent().getChildren();
			for (ShallowEntity neighboringEntity : neighboringEntities) {
				if (neighboringEntity.equals(entity)) {
					return previousEntity;
				}
				previousEntity = neighboringEntity;
			}
			entity = entity.getParent();
		}
		return null;
	}

	/**
	 * Returns the outermost entity containing the given (one-based) line number.
	 */
	public static Optional findEntityForLineNonRec(int line, List entities) {
		return findEntityForLine(line, false, entities, false);
	}

	/**
	 * Returns the innermost entity containing the given (one-based) line number.
	 */
	public static Optional findEntityForLine(int line, List entities) {
		return findEntityForLine(line, true, entities, false);
	}

	/**
	 * Returns the innermost entity containing the given (one-based) line number.
	 *
	 * @param returnSuccessor
	 *            Returns the next entity after the given line if the exact line has
	 *            no matching entity
	 */
	public static Optional findEntityForLine(int line, List entities,
			boolean returnSuccessor) {
		return findEntityForLine(line, true, entities, returnSuccessor);
	}

	/**
	 * Returns the entity containing the given (one-based) line number.
	 *
	 * @param returnSuccessor
	 *            Returns the next entity after the given line if the exact line has
	 *            no matching entity
	 */
	private static Optional findEntityForLine(int line, boolean recursive, List entities,
			boolean returnSuccessor) {
		// we know that entities are sorted according to line number, so we can
		// binary search them
		@SuppressWarnings("serial")
		ShallowEntity comparisonEntity = new ShallowEntity(null, null, null, null, 0) {
			@Override
			public int getStartLine() {
				return line;
			}
		};
		int index = Collections.binarySearch(entities, comparisonEntity,
				Comparator.comparingInt(ShallowEntity::getStartLine));
		if (index < 0) {
			// we want insertion point +1 in this case
			index = -index - 2;
		}
		if (index >= 0 && index < entities.size() && entities.get(index).getStartLine() <= line
				&& line <= entities.get(index).getEndLine()) {
			ShallowEntity match = entities.get(index);
			if (recursive && match.hasChildren()) {
				Optional betterMatch = findEntityForLine(line, match.getChildren(), returnSuccessor);
				if (betterMatch.isPresent()) {
					return betterMatch;
				}
			}
			return Optional.of(match);
		}
		if (returnSuccessor && index + 1 < entities.size()) {
			ShallowEntity match = entities.get(index + 1);
			return Optional.of(match);
		}

		return Optional.empty();
	}

	/**
	 * Traverses the parent hierarchy of the given startEntity upwards. Returns the
	 * first parent that matches the given predicate or Optional.empty if no entity
	 * matches the predicate.
	 */
	public static Optional findParentEntity(ShallowEntity startEntity,
			Predicate shallowEntityPredicate) {
		ShallowEntity current = startEntity;
		while (current.getParent() != null) {
			current = current.getParent();
			if (shallowEntityPredicate.test(current)) {
				return Optional.of(current);
			}
		}
		return Optional.empty();
	}

	/**
	 * Traverses the parent hierarchy of the given startEntity upwards. Returns the
	 * entity itself or the first parent that matches the given predicate or
	 * Optional.empty if no entity matches the predicate.
	 */
	public static Optional findEntityOrParentEntity(ShallowEntity startEntity,
			Predicate shallowEntityPredicate) {
		ShallowEntity current = startEntity;
		while (current != null && !shallowEntityPredicate.test(current)) {
			current = current.getParent();
		}
		return Optional.ofNullable(current);
	}

	/**
	 * Traverses the parent hierarchy of the given startEntity upwards. Returns all
	 * the shallow entities that match the given predicate, or an empty list if no
	 * entity matches the predicate.
	 */
	public static List findMatchingParentEntities(ShallowEntity startEntity,
			Predicate shallowEntityPredicate) {
		List parentEntities = new ArrayList<>();
		ShallowEntity current = startEntity;
		while (current.getParent() != null) {
			current = current.getParent();
			if (shallowEntityPredicate.test(current)) {
				parentEntities.add(current);
			}
		}
		return parentEntities;
	}

	/**
	 * Traverses the parent hierarchy of the given startEntity upwards. Returns the
	 * first parent that matches the given subtype (see {@link SubTypeNames}) or
	 * Optional.empty if no entity matches the subtype.
	 */
	public static Optional findParentEntityWithSubType(ShallowEntity startEntity, String subtype) {
		return findParentEntity(startEntity, entity -> entity.getSubtype().equals(subtype));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy