package jalse.entities; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jalse.attributes.AttributeListener; import jalse.attributes.NamedAttributeType; import jalse.entities.EntityVisitor.EntityVisitResult; /** * A utility for {@link Entity} related functionality (specifically around entity types).<br> * <br> * An Entity type allows entities to be used in an Object-Oriented way (get/set). It does this by * providing a number of annotations that specify to the proxy its behaviour.<br> * <br> * Entity types are soft types - any entity can be 'cast' ({@link #asType(Entity, Class)}) to * another entity type even if it does not have the defining data. This does not set the entity as * this type but entities can be marked as a type manually for easy filtering/processing ( * {@link Entity#markAsType(Class)}). When creating an entity and supplying an entity type (like * {@link EntityContainer#newEntity(Class)}) this automatically marks the entity as the supplied * type - in fact all of the types in the entity type inheritance tree are added too (removable). An * entity can be marked as multiple entity types so filtering this way can become very useful for * processing similar entities. <br> * * @author Elliot Ford * * @see DefaultEntityProxyFactory * @see #walkEntities(EntityContainer) * @see #walkEntityTree(EntityContainer, EntityVisitor) */ public final class Entities { /** * An empty EntityContainer. */ public static final EntityContainer EMPTY_ENTITYCONTAINER = new UnmodifiableDelegateEntityContainer(null); /** * An empty EntityFactory. */ public static final EntityFactory EMPTY_ENTITYFACTORY = new UnmodifiableDelegateEntityFactory(null); private static AtomicReference<EntityProxyFactory> proxyFactory = new AtomicReference<>( new DefaultEntityProxyFactory()); @SuppressWarnings("unchecked") private static void addDirectTypeAncestors(final Set<Class<? extends Entity>> ancestry, final Class<?> type) { for (final Class<?> t : type.getInterfaces()) { if (!t.equals(Entity.class) && ancestry.add((Class<? extends Entity>) t)) { addDirectTypeAncestors(ancestry, t); } } } /** * Gets any entity within the container. * * @param container * Entity container. * * @return Gets an Optional of the resulting entity or an empty Optional if it was not found. */ public static Optional<Entity> anyEntity(final EntityContainer container) { return container.streamEntities().findAny(); } /** * Gets any entity within the container marked with the specified type. * * @param container * Entity container. * * @param type * Entity type. * * @return Gets an Optional of the resulting entity or an empty Optional if it was not found. * * @see Entity#markAsType(Class) */ public static <T extends Entity> Optional<T> anyEntityOfType(final EntityContainer container, final Class<T> type) { return container.streamEntitiesOfType(type).findAny(); } /** * Wraps an Entity as the supplied Entity type. This is a convenience method for * {@link EntityProxyFactory#proxyOfEntity(Entity, Class)}. * * @param entity * Entity to wrap. * @param type * Entity type to wrap to. * @return The wrapped Entity. * * @see #getProxyFactory() */ public static <T extends Entity> T asType(final Entity entity, final Class<T> type) { return getProxyFactory().proxyOfEntity(entity, type); } /** * Creates an immutable empty entity container. * * @return Empty entity container. */ public static EntityContainer emptyEntityContainer() { return EMPTY_ENTITYCONTAINER; } /** * Creates an immutable empty entity factory. * * @return Empty entity factory. */ public static EntityFactory emptyEntityFactory() { return EMPTY_ENTITYFACTORY; } /** * Walks through the entity tree looking for an entity. * * @param container * Entity container. * @param id * Entity ID to look for. * @return Whether the entity was found. * * @see #walkEntityTree(EntityContainer, EntityVisitor) */ public static boolean findEntityRecursively(final EntityContainer container, final UUID id) { final AtomicBoolean found = new AtomicBoolean(); walkEntityTree(container, e -> { if (id.equals(e.getID())) { found.set(true); return EntityVisitResult.EXIT; } else { return EntityVisitResult.CONTINUE; } }); return found.get(); } /** * Gets the total entity count (recursive). * * @param container * Entity container. * * @return Total entity count. * * @see #walkEntityTree(EntityContainer, EntityVisitor) */ public static int getEntityCountRecursively(final EntityContainer container) { final AtomicInteger result = new AtomicInteger(); walkEntityTree(container, e -> { result.incrementAndGet(); return EntityVisitResult.CONTINUE; }); return result.get(); } /** * Gets the IDs of all the entities (recursive). * * @param container * Entity container. * * @return Set of all entity identifiers. * * @see #walkEntityTree(EntityContainer, EntityVisitor) */ public static Set<UUID> getEntityIDsRecursively(final EntityContainer container) { final Set<UUID> result = new HashSet<>(); walkEntityTree(container, e -> result.add(e.getID()) ? EntityVisitResult.CONTINUE : EntityVisitResult.IGNORE_CHILDREN); return result; } public static EntityProxyFactory getProxyFactory() { return proxyFactory.get(); } /** * Gets the highest level parent of this container. * * @param container * Container to get parent for. * @return Highest level parent (or this container if it has no parent). */ public static EntityContainer getRootContainer(final EntityContainer container) { Objects.requireNonNull(container); if (container instanceof Entity) { final EntityContainer parent = ((Entity) container).getContainer(); if (parent != null) { return getRootContainer(parent); } } return container; } /** * Gets all ancestors for the specified descendant type (not including {@link Entity}). * * @param type * Descendant type. * @return All ancestors or an empty set if its only ancestor is {@link Entity}. * * @throws IllegalArgumentException * If the Entity type is invalid * * @see #validateType(Class) */ public static Set<Class<? extends Entity>> getTypeAncestry(final Class<? extends Entity> type) { validateType(type); final Set<Class<? extends Entity>> ancestry = new HashSet<>(); addDirectTypeAncestors(ancestry, type); return ancestry; } /** * Checks whether the type is an entity subtype. * * @param type * Type to check. * @return Whether the type is a descendant of entity. */ public static boolean isEntityOrSubtype(final Class<?> type) { return Entity.class.isAssignableFrom(type); } /** * Checks whether the type is an entity subtype. * * @param type * Type to check. * @return Whether the type is a descendant of entity. */ public static boolean isEntitySubtype(final Class<?> type) { return !Entity.class.equals(type) && Entity.class.isAssignableFrom(type); } /** * Checks to see if the entity has been tagged with the type. * * @param type * Entity type to check for. * @return Predicate of {@code true} if the entity is of the type or {@code false} if it is not. */ public static Predicate<Entity> isMarkedAsType(final Class<? extends Entity> type) { return i -> i.isMarkedAsType(type); } /** * Checks if the specified type is equal to or a descendant from the specified ancestor type. * * @param descendant * Descendant type. * @param ancestor * Ancestor type. * @return Whether the descendant is equal or descended from the ancestor type. */ public static boolean isOrSubtype(final Class<? extends Entity> descendant, final Class<? extends Entity> ancestor) { return ancestor.isAssignableFrom(descendant); } /** * Checks if the specified type is a descendant from the specified ancestor type. * * @param descendant * Descendant type. * @param ancestor * Ancestor type. * @return Whether the descendant is descended from the ancestor type. */ public static boolean isSubtype(final Class<? extends Entity> descendant, final Class<? extends Entity> ancestor) { return !ancestor.equals(descendant) && ancestor.isAssignableFrom(descendant); } /** * Creates an recursive entity listener for named attribute type and the supplied attribute * listener supplier with Integer.MAX_VALUE recursion limit. * * @param namedType * Named attribute type being listened for by supplier's listeners. * @param supplier * Supplier of the attribute listener to be added to created entities. * @return Recursive attribute listener for named type and supplier. */ public static <T> EntityListener newRecursiveAttributeListener(final NamedAttributeType<T> namedType, final Supplier<AttributeListener<T>> supplier) { return newRecursiveAttributeListener(namedType, supplier, Integer.MAX_VALUE); } /** * Creates an recursive entity listener for named attribute type and the supplied attribute * listener supplier with specified recursion limit. * * @param namedType * Named attribute type being listened for by supplier's listeners. * @param supplier * Supplier of the attribute listener to be added to created entities. * @param depth * The recursion limit of the listener. * @return Recursive attribute listener for named type and supplier. */ public static <T> EntityListener newRecursiveAttributeListener(final NamedAttributeType<T> namedType, final Supplier<AttributeListener<T>> supplier, final int depth) { if (depth <= 0) { throw new IllegalArgumentException(); } return new RecursiveAttributeListener<>(namedType, supplier, depth); } /** * Creates a recursive entity listener for the supplied entity listener supplier with * Integer.MAX_VALUE recursion limit. * * @param supplier * Supplier of the entity listener to be added to created entities. * @return Recursive entity listener with Integer.MAX_VALUE recursion limit. */ public static EntityListener newRecursiveEntityListener(final Supplier<EntityListener> supplier) { return newRecursiveEntityListener(supplier, Integer.MAX_VALUE); } /** * Creates a recursive entity listener for the supplied entity listener supplier and specified * recursion limit. * * @param supplier * Supplier of the entity listener to be added to created entities. * @param depth * The recursion limit of the listener. * @return Recursive entity listener with specified recursion limit. */ public static EntityListener newRecursiveEntityListener(final Supplier<EntityListener> supplier, final int depth) { if (depth <= 0) { throw new IllegalArgumentException(); } return new RecursiveEntityListener(supplier, depth); } /** * Checks to see if the entity has not been tagged with the type. * * @param type * Entity type to check for. * @return Predicate of {@code true} if the entity is not of the type or {@code false} if it is. */ public static Predicate<Entity> notMarkedAsType(final Class<? extends Entity> type) { return isMarkedAsType(type).negate(); } /** * Gets a random entity from the container (if there is one). * * @param container * Source container. * @return Random entity (if found). */ public static Optional<Entity> randomEntity(final EntityContainer container) { return randomEntity0(container.streamEntities(), container.getEntityCount()); } private static <T extends Entity> Optional<T> randomEntity0(final Stream<T> entities, final int size) { return size > 0 ? entities.skip(ThreadLocalRandom.current().nextInt(size)).findFirst() : Optional.empty(); } /** * Gets a random entity from the container of the given type (if there is one). * * @param container * Source container. * @param type * Entity type. * @return Random entity (if found). */ public static <T extends Entity> Optional<T> randomEntityOfType(final EntityContainer container, final Class<T> type) { final Set<T> entities = container.getEntitiesOfType(type); final int size = entities.size(); return randomEntity0(entities.stream(), size); } public static void setProxyFactory(final EntityProxyFactory newFactory) { proxyFactory.set(Objects.requireNonNull(newFactory)); } /** * Creates an immutable read-only delegate entity container for the supplied container. * * @param container * Container to delegate for. * @return Immutable entity container. */ public static EntityContainer unmodifiableEntityContainer(final EntityContainer container) { return new UnmodifiableDelegateEntityContainer(Objects.requireNonNull(container)); } /** * Creates an immutable read-only delegate entity factory for the supplied factory. * * @param factory * Factory to delegate for. * @return Immutable entity factory. */ public static EntityFactory unmodifiableEntityFactory(final EntityFactory factory) { return new UnmodifiableDelegateEntityFactory(Objects.requireNonNull(factory)); } /** * Validates the entity type. This is a convenience method for * {@link EntityProxyFactory#validateType(Class)}. * * @param type * Type to validate. * * @see #getProxyFactory() */ public static void validateType(final Class<? extends Entity> type) { getProxyFactory().validateType(type); } /** * A lazy-walked stream of entities (recursive and breadth-first). The entire stream will not be * loaded until it is iterated through. * * This is equivalent to {@code walkEntities(container, Integer.MAX_VALUE)} * * @param container * Entity container. * @return Lazy-walked recursive stream of entities. */ public static Stream<Entity> walkEntities(final EntityContainer container) { return walkEntities(container, Integer.MAX_VALUE); } /** * A lazy-walked stream of entities (recursive and breadth-first). The entire stream will not be * loaded until it is iterated through. * * @param container * Entity container. * @param maxDepth * Maximum depth of the walk. * @return Lazy-walked recursive stream of entities. */ public static Stream<Entity> walkEntities(final EntityContainer container, final int maxDepth) { final EntityTreeWalker walker = new EntityTreeWalker(container, maxDepth, e -> EntityVisitResult.CONTINUE); final Iterator<Entity> iterator = new Iterator<Entity>() { @Override public boolean hasNext() { return walker.isWalking(); } @Override public Entity next() { return walker.walk(); } }; final int characteristics = Spliterator.CONCURRENT | Spliterator.NONNULL | Spliterator.DISTINCT; return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false); } /** * Walks through all entities (recursive and breadth-first). Walking can be stopped or filtered * based on the visit result returned. * * This is equivalent to {@code walkEntityTree(container, Integer.MAX_VALUE, visitor)} * * @param container * Entity container. * @param visitor * Entity visitor. * * @see EntityVisitor */ public static void walkEntityTree(final EntityContainer container, final EntityVisitor visitor) { walkEntityTree(container, Integer.MAX_VALUE, visitor); } /** * Walks through all entities (recursive and breadth-first). Walking can be stopped or filtered * based on the visit result returned. * * @param container * Entity container. * @param maxDepth * Maximum depth of the walk. * @param visitor * Entity visitor. * * @see EntityVisitor */ public static void walkEntityTree(final EntityContainer container, final int maxDepth, final EntityVisitor visitor) { final EntityTreeWalker walker = new EntityTreeWalker(container, maxDepth, visitor); while (walker.isWalking()) { walker.walk(); } } /** * Checks to see if an container is in the same tree as the other container (checking highest * parent container). * * @param one * Container to check. * @param two * Container to check. * @return Whether the container is within the same tree as the other container. * * @see #getRootContainer(EntityContainer) */ public static boolean withinSameTree(final EntityContainer one, final EntityContainer two) { return Objects.equals(getRootContainer(one), getRootContainer(two)); } private Entities() { throw new UnsupportedOperationException(); } }