Template.java :  » Java-3D » ferox-gl » com » ferox » entity » Java Open Source

Java Open Source » Java 3D » ferox gl 
ferox gl » com » ferox » entity » Template.java
package com.ferox.entity;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * Template is a ComponentContainer, like {@link Entity}, except a Template's
 * main purpose is to consolidate the creation and set up of Entities. They
 * could also be used with a serialization service to easily configure game
 * entities.
 * </p>
 * <p>
 * In any case, Templates are built by composing Components just like an Entity
 * is. When an Entity is created from a Template (using {@link #createEntity()}
 * ), the Components in a Template are cloned and added to the new Entity. If
 * needed, a {@link ComponentProvider} can be used in place of a Component to
 * create components as needed for the new entities created by a template.
 * </p>
 * <p>
 * A Component and a ComponentProvider cannot be used for the same component
 * type at the same time. Adding a Component of type T will replace any
 * ComponentProvider for type T and vice versa.
 * </p>
 * 
 * @author Michael Ludwig
 */
public final class Template extends ComponentContainer {
    private final Map<TypedId<? extends Component>, ComponentProvider<?>> providers;
    
    /**
     * Create an empty Template without any Components or ComponentProviders.
     */
    public Template() {
        this((Component[]) null);
    }

    /**
     * Create a Template with the given Components. There will will be no
     * ComponentProviders attached, these must be added later with
     * {@link #addProvider(TypedId, ComponentProvider)}.
     * 
     * @param components Components to add
     * @throws NullPointerException if any of the components are null
     * @throws IllegalArgumentException if any of the component are already
     *             added to another container
     */
    public Template(Component... components) {
        providers = new HashMap<TypedId<? extends Component>, ComponentProvider<?>>();
        
        if (components != null && components.length > 0) {
            for (Component c: components)
                add(c);
        }
    }

    /**
     * Get the ComponentProvider for the given component type. If this returns a
     * non-null provider, then Components will be created by the returned
     * provider when {@link #createEntity()} is invoked. If null is returned,
     * then there is an actual Component attached to the template with the given
     * id, or the template does not use components of the given type at all.
     * 
     * @param <T> The Component type that the provider will create
     * @param id The TypedId of the given Component type
     * @return The ComponentProvider for the given type, or null if no provider
     *         is used
     */
    @SuppressWarnings("unchecked")
    public <T extends Component> ComponentProvider<T> getProvider(TypedId<T> id) {
        if (id == null)
            throw new NullPointerException("Id cannot be null");
        
        // Just lock, the Templates can afford to be a little lazier since
        // they're not used in as performance critical places as Entities
        synchronized(lock) {
            return (ComponentProvider<T>) providers.get(id);
        }
    }

    /**
     * <p>
     * Add a ComponentProvider to this Template for the given type of component.
     * When {@link #createEntity()} is called, the ComponentProvider will be
     * used to create new instances of the component type, instead of cloning an
     * added component.
     * </p>
     * <p>
     * For a single component type, only one provider or component may be in the
     * template at a given point in time. If there was a provider or component
     * that had a matching type, <tt>provider</tt> will overwrite them in the
     * template. This functions just like when adding a component overwrites an
     * old one of the same type; ComponentProviders are essentially place
     * holders within the template for the components they create.
     * </p>
     * 
     * @param <T> The Component type created by the provider
     * @param id The TypedId for the component type
     * @param provider The ComponentProvider to use
     * @throws NullPointerException if id or provider are null
     */
    public <T extends Component> void addProvider(TypedId<T> id, ComponentProvider<T> provider) {
        if (id == null)
            throw new NullPointerException("Id cannot be null");
        if (provider == null)
            throw new NullPointerException("ComponentProvider cannot be null");

        synchronized(lock) {
            // Remove any Component that happened to be configured for the given typed id.
            remove(id);
            
            // Store the provider in the map, possibly overwriting any old provider 
            // for the given typed id.
            providers.put(id, provider);
        }
    }

    /**
     * <p>
     * Remove the current ComponentProvider for the given type of Component from
     * this Template. If there was no configured ComponentProvider, null is
     * returned, otherwise the removed provider is returned. It is important to
     * note that this will not remove a Component of the given type if an actual
     * Component has been added to the Template, instead of a provider.
     * </p>
     * <p>
     * Additionally, {@link #remove(TypedId)} will not remove
     * ComponentProviders. That method will only remove Components. Use
     * {@link #removeAll(TypedId)} to completly remove any Component or
     * ComponentProvider from the Template for a given type.
     * </p>
     * 
     * @param <T> The Component type of the provider that is being removed
     * @param id The TypedId specifying the type
     * @return The ComponentProvider that was removed, or null if there was no
     *         provider for the given type
     * @throws NullPointerException if id is null
     */
    @SuppressWarnings("unchecked")
    public <T extends Component> ComponentProvider<T> removeProvider(TypedId<T> id) {
        if (id == null)
            throw new NullPointerException("Id cannot be null");
        
        synchronized(lock) {
            // Simply remove and return the old provider
            return (ComponentProvider<T>) providers.remove(id);
        }
    }

    /**
     * Remove either a ComponentProvider or Component from this Template for the
     * given type of component. This is equivalent to calling both
     * {@link #removeProvider(TypedId)} and {@link #remove(TypedId)}, except
     * that it is an atomic operation.
     * 
     * @param <T> The type of Component that is being removed
     * @param id The TypedId for the component being removed
     * @return True if a component or provider was removed, or false if neither
     *         a component nor provider were in the template when this was
     *         invoked
     * @throws NullPointerException if id is null
     */
    public <T extends Component> boolean removeAll(TypedId<T> id) {
        synchronized(lock) {
            // Must have an outer lock over both of these removes so
            // that they are in they same atomic unit
            ComponentProvider<T> oldProvider = removeProvider(id);
            T oldComponent = remove(id);
            
            return oldProvider != null || oldComponent != null;
        }
    }
    
    @Override
    public boolean add(Component c) {
        // Must have an outer lock over super.add() so that the removal from
        // providers happens within the same atomic unit
        synchronized(lock) {
            if (super.add(c)) {
                // Remove any provider that had previously been set for the component's type
                providers.remove(c.getTypedId());
                return true;
            } else
                return false; // Don't remove provider if add failed
        }
    }

    /**
     * <p>
     * Create an Entity based off of the current state of this Template. The
     * created Entity will have a Component for each Component or
     * ComponentProvider that is in the Template. If the template has a
     * Component, a new Component is created of the same type. If the template
     * has a ComponentProvider for a given type, the ComponentProvider's
     * {@link ComponentProvider#get() get} method is invoked to create a new
     * instance.
     * </p>
     * <p>
     * When creating a Component of type T, the Template looks for a constructor
     * defined in T that takes a single instance of T as an argument (i.e. a
     * copy constructor). It is not possible to use {@link Object#clone()} with
     * Components so this convention must be followed or
     * {@link TemplateException} will be thrown.
     * </p>
     * 
     * @return A new Entity, with new Components matching the current state of
     *         the template
     * @throws TemplateException if a ComponentProvider threw an exception, or
     *             if a Component did not expose a proper copy constructor to
     *             facilitate cloning
     */
    public Entity createEntity() {
        synchronized(lock) {
            // Operate within the lock so the Template doesn't change on us
            Entity fromTemplate = new Entity();
            
            for (Component c: this) {
                // First add clones of all components in the template
                Component clone = clone(c);
                if (clone != null)
                    fromTemplate.add(clone);
            }
            
            for (ComponentProvider<?> provider: providers.values()) {
                // Now add newly created components from the configured providers
                try {
                    Component c = provider.get();
                    if (c != null)
                        fromTemplate.add(c);
                } catch(Exception e) {
                    throw new TemplateException("ComponentProvider threw an exception", e);
                }
            }
            
            // Entity creation is completed
            return fromTemplate;
        }
    }
    
    private Component clone(Component c) {
        Class<? extends Component> type = c.getClass();
        Constructor<? extends Component> constructor;
        try {
            // We clone a Component by following the convention that each Component
            // class defines a copy constructor for its specific type.
            constructor = type.getConstructor(type);
            return constructor.newInstance(c);
        } catch(Exception e) {
            throw new TemplateException("Unable to clone Component of type " + c.getTypedId(), e);
        }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.