/* * Copyright (c) 2017 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.core.component; import nova.core.component.ComponentProvider.ComponentAdded; import nova.core.component.ComponentProvider.ComponentRemoved; import nova.core.component.exception.ComponentException; import nova.internal.core.Game; import se.jbee.inject.Dependency; import java.util.Collections; import java.util.HashMap; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * A class that contains all components. * * @author Calclavia */ public class ComponentMap extends HashMap<Class<? extends Component>, Component> { private static final long serialVersionUID = 2017_02_12L; public final ComponentProvider<?> provider; public ComponentMap(ComponentProvider<?> provider) { this.provider = provider; } /** * Adds a new component based on its superclass or interface using dependency injection. * @param componentType The interface or abstract class associated with the new component. * @param <C> The node type. * @return A new node of N type. */ public final <C extends Component> C add(Class<C> componentType) { return add(Game.injector().resolve(Dependency.dependency(componentType))); } /** * Adds a component to the provider. * @param component The component to add. * @return the component. * @throws ComponentException when the component already exists on the block. */ @SuppressWarnings("unchecked") public final <C extends Component> C add(C component) { if (has(component.getClass())) { throw new ComponentException("Attempt to add two components of the type %s to %s", component, this); } //Place component into component map put(component.getClass(), component); //Set component's provider. component.setProvider(provider); //Publish component add event provider.events.publish(new ComponentAdded(component)); return component; } /** * Adds a component to the block if it is not present. * @param component The component to add. * @return the component. */ @SuppressWarnings("unchecked") public final <C extends Component> C getOrAdd(C component) { if (has(component.getClass())) { return get((Class<C>) component.getClass()); } return add(component); } /** * Checks if a component type exists in this provider. * @param componentType the component type to check. * @return true if the component exists on the provider. */ public final boolean has(Class<?> componentType) { return keySet().stream().anyMatch(componentType::isAssignableFrom); } /** * Removes a component from the block. * @param component The component to remove. * @return the component removed. * @throws ComponentException when the component does not exist. */ public final <C extends Component> C remove(C component) { //Remove component based on class remove(component.getClass()); //Set provider on component to null component.setProvider(null); //Publish component event provider.events.publish(new ComponentProvider.ComponentRemoved(component)); return component; } /** * Removes the component from the provider. * @param componentType the component type. * @return the component removed. * @throws ComponentException when the component does not exist. */ @SuppressWarnings("unchecked") public final <C extends Component> C remove(Class<C> componentType) { if (!has(componentType)) { throw new ComponentException("Attempt to remove component that does not exist: %s", componentType); } C component = (C) super.remove(componentType); //Set provider on component to null component.setProvider(null); //Publish component event provider.events.publish(new ComponentProvider.ComponentRemoved(component)); return component; } /** * Gets an optional of the component with the specified type. * @param componentType the type to get. * @return the optional of the component found or {@code Optional.empty()}. * if the component was not found. */ @SuppressWarnings("unchecked") public final <C> Optional<C> getOp(Class<C> componentType) { if (componentType.isAssignableFrom(Component.class)) { Component component = get(componentType.asSubclass(Component.class)); if (component != null) { return Optional.of((C) component); } } Set<C> collect = getSet(componentType); if (collect.size() > 1) { throw new ComponentException("Ambiguous component search. For component/interface %s there are multiple components found: %s", componentType, collect); } return collect.stream().findFirst(); } /** * Gets the component with the specified type. * @param componentType the type to get. * @return the component. * @throws ComponentException if the component doesn't exist. */ public final <C> C get(Class<C> componentType) { return getOp(componentType).orElseThrow(() -> new ComponentException("Attempt to get component that does not exist: %s", componentType)); } /** * Gets the set of the components with the specified type. * @param componentType the type to get. * @return the set of the components. */ public final <C> Set<C> getSet(Class<C> componentType) { return Collections.unmodifiableSet(values().stream() .filter(c -> componentType.isAssignableFrom(c.getClass())) .map(componentType::cast) .collect(Collectors.toSet())); } }