adams.data.instances.AbstractInstanceGenerator.java Source code

Java tutorial

Introduction

Here is the source code for adams.data.instances.AbstractInstanceGenerator.java

Source

/*
 *   This program 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.
 *
 *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * AbstractInstancesGenerator.java
 * Copyright (C) 2011-2013 University of Waikato, Hamilton, New Zealand
 */

package adams.data.instances;

import java.util.logging.Level;

import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.SelectedTag;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Add;
import adams.core.ClassLister;
import adams.core.CleanUpHandler;
import adams.core.ShallowCopySupporter;
import adams.core.option.AbstractOptionConsumer;
import adams.core.option.AbstractOptionHandler;
import adams.core.option.ArrayConsumer;
import adams.core.option.OptionUtils;
import adams.data.container.DataContainer;
import adams.data.report.Report;
import adams.data.report.ReportHandler;
import adams.data.weka.ArffUtils;
import adams.db.AbstractDatabaseConnection;
import adams.db.DatabaseConnectionHandler;

/**
 * Abstract base class for schemes that turn temperature profiles into
 * weka.core.Instance objects.
 *
 * @author  fracpete (fracpete at waikato dot ac dot nz)
 * @version $Revision$
 * <T> the type of data to process
 */
public abstract class AbstractInstanceGenerator<T extends DataContainer & ReportHandler>
        extends AbstractOptionHandler implements Comparable, CleanUpHandler, DatabaseConnectionHandler,
        ShallowCopySupporter<AbstractInstanceGenerator> {

    /** for serialization. */
    private static final long serialVersionUID = 5543015283566767256L;

    /** the "true" label for boolean data types. */
    public final static String LABEL_TRUE = "true";

    /** the "false" label for boolean data types. */
    public final static String LABEL_FALSE = "false";

    /** the generated header. */
    protected Instances m_OutputHeader;

    /** whether to add the database ID. */
    protected boolean m_AddDatabaseID;

    /** the database connection. */
    protected AbstractDatabaseConnection m_DatabaseConnection;

    /** whether to tolerate header changes. */
    protected boolean m_TolerateHeaderChanges;

    /** whether to operate in offline mode. */
    protected boolean m_Offline;

    /**
     * Adds options to the internal list of options.
     */
    @Override
    public void defineOptions() {
        super.defineOptions();

        m_OptionManager.add("add-db-id", "addDatabaseID", false);

        m_OptionManager.add("tolerate-header-changes", "tolerateHeaderChanges", false);

        m_OptionManager.add("offline", "offline", false);
    }

    /**
     * Initializes the members.
     */
    @Override
    protected void initialize() {
        super.initialize();

        m_DatabaseConnection = getDefaultDatabaseConnection();
    }

    /**
     * Resets the generator (but does not clear the input data!).
     * Derived classes must call this method in set-methods of parameters to
     * assure the invalidation of previously generated data.
     */
    @Override
    protected void reset() {
        super.reset();

        m_OutputHeader = null;
    }

    /**
     * Cleans up data structures, frees up memory.
     * Sets the input data to null.
     */
    public void cleanUp() {
        reset();
    }

    /**
     * Frees up memory in a "destructive" non-reversible way.
     * <br><br>
     * Calls cleanUp() and cleans up the options.
     */
    @Override
    public void destroy() {
        cleanUp();
        super.destroy();
    }

    /**
     * Sets whether the database ID is added to the data or not.
     *
     * @param value    true if database ID should be added
     */
    public void setAddDatabaseID(boolean value) {
        m_AddDatabaseID = value;
        reset();
    }

    /**
     * Returns whether the database ID is added.
     *
     * @return       true if database ID is added
     */
    public boolean getAddDatabaseID() {
        return m_AddDatabaseID;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String addDatabaseIDTipText() {
        return "If set to true, then the database ID will be added to the output.";
    }

    /**
     * Sets whether to operate in offline mode.
     *
     * @param value    true if to operate in offline mode
     */
    public void setOffline(boolean value) {
        m_Offline = value;
        reset();
    }

    /**
     * Returns whether the generator operates in offline mode.
     *
     * @return       true if operating in offline mode
     */
    public boolean getOffline() {
        return m_Offline;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String offlineTipText() {
        return "If set to true, the generator operates in offline mode, ie does not access database.";
    }

    /**
     * Sets whether to tolerate header changes and merely re-generating the 
     * header instead of throwing an exception.
     *
     * @param value    true if to tolerate header changes
     */
    public void setTolerateHeaderChanges(boolean value) {
        m_TolerateHeaderChanges = value;
        reset();
    }

    /**
     * Returns whether to tolerate header changes and merely re-generating the 
     * header instead of throwing an exception.
     *
     * @return       true if to tolerate header changes
     */
    public boolean getTolerateHeaderChanges() {
        return m_TolerateHeaderChanges;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String tolerateHeaderChangesTipText() {
        return "If set to true, then changes in the header get tolerated (and the "
                + "header recreated) instead of causing an error.";
    }

    /**
     * Returns the default database connection.
     *
     * @return      the default database connection
     */
    protected abstract AbstractDatabaseConnection getDefaultDatabaseConnection();

    /**
     * Returns the currently used database connection object, can be null.
     *
     * @return      the current object
     */
    public AbstractDatabaseConnection getDatabaseConnection() {
        return m_DatabaseConnection;
    }

    /**
     * Sets the database connection object to use.
     *
     * @param value   the object to use
     */
    public void setDatabaseConnection(AbstractDatabaseConnection value) {
        m_DatabaseConnection = value;
        reset();
    }

    /**
     * Returns the current header.
     *
     * @return      the header, can be null
     */
    public Instances getOutputHeader() {
        return m_OutputHeader;
    }

    /**
     * Checks whether the setup is consistent.
     * <br><br>
     * Default implementation does nothing.
     *
     * @return      null if everything OK, otherwise the error message
     */
    public String checkSetup() {
        return null;
    }

    /**
     * Returns the generated data, generates it if necessary.
     *
     * @param data   the profile to turn into an instance
     * @return      the generated data
     */
    public Instance generate(T data) {
        Instance result;

        // input/profile
        checkInput(data);

        // header/instances
        if (m_OutputHeader == null) {
            generateHeader(data);
            postProcessHeader(data);
        }

        // check header
        try {
            checkHeader(data);
        } catch (Exception e) {
            if (m_TolerateHeaderChanges) {
                generateHeader(data);
                postProcessHeader(data);
            } else {
                throw new IllegalStateException(e);
            }
        }

        // output/instance
        result = generateOutput(data);
        result = postProcessOutput(data, result);

        return result;
    }

    /**
     * Checks the input profile.
     * <br><br>
     * The default implementation only checks whether there is any data set.
     *
     * @param data   the data to process
     */
    protected void checkInput(T data) {
        if (data == null)
            throw new IllegalStateException("No input data provided!");
    }

    /**
     * Checks whether the number of waves are the same.
     *
     * @param data   the input data
     */
    protected abstract void checkHeader(T data);

    /**
     * Generates the header of the output data.
     *
     * @param data   the input data
     */
    protected abstract void generateHeader(T data);

    /**
     * Interpretes the position string based on the given dataset.
     * "first", "second", "third", "last-2", "last-1" and "last" and "last+1" are valid as well.
     *
     * @param data   the data to base the intepretation on
     * @param position   the position string
     * @return      the numeric position string
     */
    protected String interpretePosition(Instances data, String position) {
        if (position.equals("first"))
            return "1";
        else if (position.equals("second"))
            return "2";
        else if (position.equals("third"))
            return "3";
        else if (position.equals("last-2"))
            return "" + (data.numAttributes() - 2);
        else if (position.equals("last-1"))
            return "" + (data.numAttributes() - 1);
        else if (position.equals("last"))
            return "" + data.numAttributes();
        else if (position.equals("last+1"))
            return "" + (data.numAttributes() + 1);
        else
            return position;
    }

    /**
     * Adds IDs, notes, additional fields to header.
     *
     * @param data   the input data
     */
    protected void postProcessHeader(T data) {
        Add add;

        // add the database ID to the output?
        if (m_AddDatabaseID) {
            try {
                add = new Add();
                add.setAttributeIndex("1");
                add.setAttributeName(ArffUtils.getDBIDName());
                add.setAttributeType(new SelectedTag(Attribute.NUMERIC, Add.TAGS_TYPE));
                add.setInputFormat(m_OutputHeader);
                m_OutputHeader = Filter.useFilter(m_OutputHeader, add);
            } catch (Exception e) {
                getLogger().log(Level.SEVERE, "Error initializing the Add filter for database ID!", e);
            }
        }
    }

    /**
     * Generates the actual data.
     *
     * @param data   the input data to transform
     * @return      the generated data
     */
    protected abstract Instance generateOutput(T data);

    /**
     * For adding IDs, notes, additional fields to the data.
     *
     * @param data   the input data
     * @param inst   the generated instance
     * @return      the processed instance
     */
    protected Instance postProcessOutput(T data, Instance inst) {
        Instance result;
        double[] values;
        int index;
        Report report;

        values = inst.toDoubleArray();
        report = data.getReport();

        if (m_AddDatabaseID) {
            index = m_OutputHeader.attribute(ArffUtils.getDBIDName()).index();
            values[index] = report.getDatabaseID();
        }

        result = new DenseInstance(1.0, values);
        result.setDataset(m_OutputHeader);

        return result;
    }

    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     * <br><br>
     * Only compares the commandlines of the two objects.
     *
     * @param o    the object to be compared.
     * @return     a negative integer, zero, or a positive integer as this object
     *      is less than, equal to, or greater than the specified object.
     *
     * @throws ClassCastException    if the specified object's type prevents it
     *                     from being compared to this object.
     */
    public int compareTo(Object o) {
        if (o == null)
            return 1;

        return OptionUtils.getCommandLine(this).compareTo(OptionUtils.getCommandLine(o));
    }

    /**
     * Returns whether the two objects are the same.
     * <br><br>
     * Only compares the commandlines of the two objects.
     *
     * @param o   the object to be compared
     * @return   true if the object is the same as this one
     */
    @Override
    public boolean equals(Object o) {
        return (compareTo(o) == 0);
    }

    /**
     * Returns a shallow copy of itself, i.e., based on the commandline options.
     *
     * @return      the shallow copy
     */
    public AbstractInstanceGenerator shallowCopy() {
        return shallowCopy(false);
    }

    /**
     * Returns a shallow copy of itself, i.e., based on the commandline options.
     *
     * @param expand   whether to expand variables to their current values
     * @return      the shallow copy
     */
    public AbstractInstanceGenerator shallowCopy(boolean expand) {
        return (AbstractInstanceGenerator) OptionUtils.shallowCopy(this, expand);
    }

    /**
     * Returns a list with classnames of generators.
     *
     * @return      the generator classnames
     */
    public static String[] getGenerators() {
        return ClassLister.getSingleton().getClassnames(AbstractInstanceGenerator.class);
    }

    /**
     * Instantiates the generator with the given options.
     *
     * @param classname   the classname of the generator to instantiate
     * @param options   the options for the generator
     * @return      the instantiated generator or null if an error occurred
     */
    public static AbstractInstanceGenerator forName(String classname, String[] options) {
        AbstractInstanceGenerator result;

        try {
            result = (AbstractInstanceGenerator) OptionUtils.forName(AbstractInstanceGenerator.class, classname,
                    options);
        } catch (Exception e) {
            e.printStackTrace();
            result = null;
        }

        return result;
    }

    /**
     * Instantiates the generator from the given commandline
     * (i.e., classname and optional options).
     *
     * @param cmdline   the classname (and optional options) of the
     *          generator to instantiate
     * @return      the instantiated generator
     *          or null if an error occurred
     */
    public static AbstractInstanceGenerator forCommandLine(String cmdline) {
        return (AbstractInstanceGenerator) AbstractOptionConsumer.fromString(ArrayConsumer.class, cmdline);
    }
}