IdPlace.java

package emissary.id;

import emissary.config.Configurator;
import emissary.core.Form;
import emissary.core.IBaseDataObject;
import emissary.place.ServiceProviderPlace;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Abstract class that is the parent of all places that operate in the ID phase of the workflow.
 */
public abstract class IdPlace extends ServiceProviderPlace {
    /** Set of final id forms that do not add unknown on top */
    protected Set<String> finalForms;

    /* Forms to rename */
    protected Map<String, String> renames;

    /* Forms to ignore */
    protected Set<String> ignores;

    /**
     * Create and register an id place with all default config information
     */
    protected IdPlace() throws IOException {
        super();
        configureIdPlace();
    }

    /**
     * Create an Id Place and register it at the location specified
     * 
     * @param placeLoc the location for registering this place
     */
    protected IdPlace(final String placeLoc) throws IOException {
        super(placeLoc);
        configureIdPlace();
    }

    /**
     * Create and register constructor, called from the place by super(x,y,z)
     * 
     * @param configFile the config location file or resource
     * @param theDir controlling directory
     * @param thePlaceLocation key to use in registration
     */
    protected IdPlace(final String configFile, final String theDir, final String thePlaceLocation) throws IOException {
        super(configFile, theDir, thePlaceLocation);
        configureIdPlace();
    }

    /**
     * Create and register with default directory
     * 
     * @param configFile the config location file or resource
     * @param placeLocation key to use in registration
     */
    protected IdPlace(final String configFile, final String placeLocation) throws IOException {
        super(configFile, placeLocation);
        configureIdPlace();
    }

    /**
     * Create an id place with config data from a stream
     * 
     * @param configStream stream of config data
     * @param theDir string name of our directory
     * @param thePlaceLocation string name of our location
     */
    protected IdPlace(final InputStream configStream, final String theDir, final String thePlaceLocation) throws IOException {
        super(configStream, theDir, thePlaceLocation);
    }

    /**
     * Construct with config data from a stream on the local directory
     * 
     * @param configStream stream of config data
     */
    protected IdPlace(final InputStream configStream) throws IOException {
        super(configStream);
    }

    /**
     * Save a list of all the forms that this place is a service proxy for and a list of final id determinations that this
     * place can make so that any new form being set can be differentiated as final or non-final and the stacks cleaned up
     * appropriately
     * <ul>
     * <li>FINAL_ID - current form values that do not get UNKNOWN pushed on top</li>
     * <li>ID_RENAME_ - current form values to rename</li>
     * <li>ID_IGNORE - current form values to ignore</li>
     * </ul>
     */
    public void configureIdPlace() {
        // FINAL_ID should be a subset of SERVICE_PROXY
        this.finalForms = configG.findEntriesAsSet("FINAL_ID");
        this.renames = configG.findStringMatchMap("ID_RENAME_", Configurator.PRESERVE_CASE);
        this.ignores = configG.findEntriesAsSet("ID_IGNORE");
    }

    /**
     * Before setting a non-final current form, pop everything this place is s proxy for, then push the new form onto the
     * currentForm() stack, then push UNKNOWN on right after it (unless the newForm itself is UNKNOWN).
     */
    public int setNonFinalCurrentForm(final IBaseDataObject d, @Nullable final String newForm) {

        if (this.ignores.contains(newForm)) {
            return d.currentFormSize();
        }

        final Set<String> serviceProxies = getProxies();
        while (serviceProxies.contains(d.currentForm()) || d.currentForm().equals(newForm)) {
            d.popCurrentForm();
        }

        int sz = 0;

        if (newForm != null) {
            sz = d.pushCurrentForm(renamedForm(newForm));
        }

        if (!Form.UNKNOWN.equals(newForm)) {
            sz = d.pushCurrentForm(Form.UNKNOWN);
        }

        return sz;
    }

    /**
     * Return the form renamed if it is listed as a RENAME_ID otherwise just return as-id
     * 
     * @param form the form to check
     * @return the renamed form
     */
    protected String renamedForm(final String form) {
        return this.renames.getOrDefault(form, form);
    }

    /**
     * Before setting a final current form, pop everything this place is a proxy for, then push the new form onto both the
     * currentForm() and destination() stack.
     */
    public int setFinalCurrentForm(final IBaseDataObject d, final String newForm) {

        if (this.ignores.contains(newForm)) {
            return d.currentFormSize();
        }

        final Set<String> serviceProxies = getProxies();
        final String nf = renamedForm(newForm);
        while (serviceProxies.contains(d.currentForm()) || d.currentForm().equals(nf)) {
            d.popCurrentForm();
        }

        return d.pushCurrentForm(nf);
    }


    /**
     * Set the current form after deciding if it's a FINAL_ID or not
     */
    public int setCurrentForm(final IBaseDataObject d, @Nullable final String newForm) {

        if (this.ignores.contains(newForm)) {
            return d.currentFormSize();
        }

        final int sz;
        if (newForm != null && (this.finalForms.contains(newForm) || this.finalForms.contains("*"))) {
            sz = setFinalCurrentForm(d, newForm);
        } else {
            sz = setNonFinalCurrentForm(d, newForm);
        }

        return sz;
    }

    /**
     * Set a whole bunch of new forms. The top one may or may not be a FINAL_ID, all others are FINAL_ID de facto so that
     * extraneous UNKNOWNs are not put on the stack
     */
    public int setCurrentForm(final IBaseDataObject d, @Nullable final Collection<String> newForms) {

        final int sz;
        if (newForms == null || newForms.isEmpty()) {
            sz = setNonFinalCurrentForm(d, null);
        } else {
            final String[] forms = newForms.toArray(new String[0]);
            // Set all the but top dog as final
            for (int i = (forms.length - 1); i > 0; i--) {
                setFinalCurrentForm(d, forms[i]);
            }

            // Only decide between final/non-final on the top dog
            sz = setCurrentForm(d, forms[0]);
        }

        return sz;
    }
}