com.homesnap.engine.controller.Controller.java Source code

Java tutorial

Introduction

Here is the source code for com.homesnap.engine.controller.Controller.java

Source

package com.homesnap.engine.controller;

/*
 * #%L
 * HomeSnapEngine
 * %%
 * Copyright (C) 2011 - 2014 A. de Giuli
 * %%
 * This file is part of HomeSnap done by Arnaud de Giuli (arnaud.degiuli(at)free.fr)
 *     helped by Olivier Driesbach (olivier.driesbach(at)gmail.com).
 * 
 *     HomeSnap 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.
 * 
 *     HomeSnap 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 HomeSnap. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.json.JSONException;
import org.json.JSONObject;

import com.homesnap.engine.JsonSerializable;
import com.homesnap.engine.configuration.Property;
import com.homesnap.engine.connector.Command;
import com.homesnap.engine.connector.Command.Type;
import com.homesnap.engine.connector.CommandListener;
import com.homesnap.engine.connector.CommandResult;
import com.homesnap.engine.connector.CommandResultStatus;
import com.homesnap.engine.connector.Commander;
import com.homesnap.engine.connector.DefaultCommandResult;
import com.homesnap.engine.controller.properties.StateSection;
import com.homesnap.engine.controller.properties.StatesReader;
import com.homesnap.engine.controller.what.State;
import com.homesnap.engine.controller.what.StateName;
import com.homesnap.engine.controller.what.StateValue;
import com.homesnap.engine.controller.what.StateValueType;
import com.homesnap.engine.controller.where.Where;
import com.homesnap.engine.controller.who.Who;

public abstract class Controller implements JsonSerializable, Serializable {

    /** serial uid */
    private static final long serialVersionUID = 1L;

    public static final String STATES_EXTENSION = ".states";

    private boolean waitingResult = false;
    protected Where where; // Represent the address of the controller
    private String title; // string representing the controller
    private String description; // string describing the controller
    protected transient Commander server;
    private List<ControllerChangeListener> controllerChangeListenerList = new ArrayList<ControllerChangeListener>();
    private LabelList labelList = new LabelList(this);

    /** List of all state names with their current values of the controller. This map repesents the complete status of the controller. */
    private Map<StateName, StateValue> stateList = new HashMap<StateName, StateValue>();
    /** The state section of the configuration file which contains all states names that the controller can implements. */
    private StateSection stateTypes;
    /** Cache of all controller classes with their state types */
    private static Map<Class<?>, StateSection> classTypes = new Hashtable<Class<?>, StateSection>();

    public static final String JSON_TITLE = "title";
    public static final String JSON_STATES = "states";
    public static final String JSON_WHERE = "where";
    public static final String JSON_WHO = "who";
    public static final String JSON_DESCRIPTION = "description";

    /**
     * Constructor.
     */
    protected Controller() {
        initStateTypes(getClass());
    }

    /**
     * Initializes the state names with their class types in order to prevent from a wrong assigment when the {@link #set(StateName, StateValue)} method is called.
     */
    private void initStateTypes(Class<?> clazz) {
        // Check if the controller class is known
        stateTypes = classTypes.get(clazz);
        if (stateTypes == null) {
            Class<?> superClass = clazz.getSuperclass();
            while (!Controller.class.equals(superClass)) {
                initStateTypes(superClass);
                break;
            }
            // Search the ".states" resource which defines the state types of the controller
            String pkgName = clazz.getPackage().getName().replace('.', '/');
            URL url = clazz.getClassLoader().getResource(pkgName + "/" + clazz.getSimpleName() + ".states");
            if (url == null) {
                throw new RuntimeException("Unable to find states definition file for " + clazz.getName());
            }
            // Load the definition file
            StatesReader reader = new StatesReader();
            try {
                reader.load(url.openStream());
            } catch (IOException e) {
                throw new RuntimeException("Unable to load states definition file for " + getClass().getName(), e);
            }
            stateTypes = (StateSection) reader.getControllerSection();
            classTypes.put(clazz, stateTypes);
        }
    }

    /**
     * Indicates if the controller is waiting information from the gateway.
     * 
     * @return <code>true</code> if controller is waiting information and <code>false</code> otherwise
     */
    public boolean isWaitingResult() {
        return waitingResult;
    }

    /**
     * Returns the address of the targeted device
     * 
     * @return the address of the targeted device
     */
    public Where getWhere() {
        return where;
    }

    /**
     * Defines the address of the targeted device to control
     * 
     * @param newValue the address of the targeted device to control
     */
    public void setWhere(Where newValue) {
        this.where = newValue;
        if (newValue == null) { // Manage null value because we create some
            // controller with no address (Gateway or
            // Heating central with MyHOME Bus)
            for (Property state : stateTypes.getProperties()) {
                stateList.put(new ControllerStateName(state.getName()), null);
            }
        } else {
            // Rcuprer les status
            for (Property state : stateTypes.getProperties()) {
                get(new ControllerStateName(state.getName()));
            }
        }
    }

    /**
     * Executes an action. The result is wait by a command listener.
     * 
     * @param what The state to update
     * @param commandListener The listener which will wait for action result
     */
    protected void executeAction(final State what, final CommandListener commandListener) {
        if (server == null || what == null || what.getName() == null) {
            commandListener.onCommand(new DefaultCommandResult("", CommandResultStatus.nok));
        } else {
            waitingResult = true;
            server.sendCommand(new Command(getWho(), what, where, Type.ACTION), new CommandListener() {
                @Override
                public void onCommand(CommandResult commandResult) {
                    // TODO tester que le type du status match avec ceux support!
                    waitingResult = false;
                    commandListener.onCommand(commandResult);
                }
            });
        }
    }

    /**
     * Executes a read operation on a state name of the controller.
     * The result is wait by a status listener.
     * 
     * @param stateName The state name to read
     * @param statusListener The listener which will wait for result
     */
    protected void executeStatus(final StateName stateName, final StatusListener statusListener) {
        if (server == null || stateName == null) {
            statusListener.onStatus(new State(stateName, null),
                    new DefaultCommandResult("", CommandResultStatus.nok));
        } else {
            waitingResult = true;
            server.sendCommand(new Command(getWho(), new State(stateName, null), where, Type.STATUS),
                    new CommandListener() {
                        @Override
                        public void onCommand(CommandResult result) {
                            waitingResult = false;
                            // TODO tester que le type du status match avec ceux support!
                            if (CommandResultStatus.ok.equals(result.getStatus())) {
                                // Return the status of the controller from the server
                                State status = result.getWhat(stateName);
                                statusListener.onStatus(status, result);
                            } else {
                                // ERROR: message not sent on Bus or error
                                // return... we keep the last value

                                statusListener.onStatus(new State(stateName, stateList.get(stateName)), result);
                            }
                        }
                    });
        }
    }

    public abstract Who getWho();

    /**
     * Define the gateway to connect on.
     * 
     * @param server
     */
    public void setServer(Commander server) {
        this.server = server;
    }

    /**
     * @param listener
     *            the new change listener.
     */
    public void addControllerChangeListener(ControllerChangeListener listener) {
        synchronized (controllerChangeListenerList) {
            controllerChangeListenerList.add(listener);
        }
    }

    /**
     * @param listener
     *            the change listener to remove.
     */
    public void removeControllerChangeListener(ControllerChangeListener listener) {
        synchronized (controllerChangeListenerList) {
            controllerChangeListenerList.remove(listener);
        }
    }

    private void notifyStateChange(State oldStatus, State newStatus) {
        synchronized (controllerChangeListenerList) {
            for (ControllerChangeListener listener : controllerChangeListenerList) {
                listener.onStateChange(this, oldStatus, newStatus);
            }
        }
    }

    private void notifyStateChangeError(State oldStatus, State newStatus, CommandResult result) {
        synchronized (controllerChangeListenerList) {
            for (ControllerChangeListener listener : controllerChangeListenerList) {
                listener.onStateChangeError(this, oldStatus, newStatus, result);
            }
        }
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String name) {
        this.title = name;
    }

    /**
     * Return the list of label associated to this component.
     * 
     * @return list of label.
     */
    public LabelList getLabels() {
        return labelList;
    }

    /**
     * Return the description of the controller
     * 
     * @return description of the controller
     */
    public String getDescription() {
        return description;
    }

    /**
     * Define the description of the controller.
     * 
     * @param description of the controller
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * Returns the current value of a state name.
     * 
     * @param stateName The state name
     * @return The current value of a state name
     */
    public String get(String stateName) {
        if (stateName == null) {
            throw new NullPointerException("Could not set null state name.");
        }
        StateValue value = get(new ControllerStateName(stateName));
        if (value != null) {
            return value.getValue();
        } else {
            return null;
        }
    }

    /**
     * Returns the current value of a state name.
     * 
     * @param stateName The state name
     * @return The current value of a state name
     */
    protected StateValue get(StateName stateName) {
        StateValue value = stateList.get(stateName);
        if (value == null) {
            executeStatus(stateName, new StatusListener() {
                @Override
                public void onStatus(State status, CommandResult result) {
                    // TODO : one pb with that: if we get the value of what
                    // before response we get null and nothing is done to be
                    // advertise of the change when value arrive...
                    stateList.put(status.getName(), status.getValue());
                }
            });
        }
        return value;
    }

    /**
     * 
     * @param stateName
     * @return
     */
    public StateValueType getType(String stateName) {
        return stateTypes.getProperty(stateName).getType();
    }

    /**
     * Create/update a value of the status.
     * @param stateName The state name to update
     * @param stateValue The new value of the state name
     */
    public void set(String stateName, String stateValue) {
        if (stateName == null) {
            throw new NullPointerException("Could not set null state name.");
        }
        StateName key = new ControllerStateName(stateName);
        StateValueType stateType = stateTypes.getProperty(stateName).getType();
        try {
            stateType.setValue(stateValue);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Unable to set " + stateName + "/" + stateValue + ": " + e.getMessage());
        }
        set(key, stateType);
    }

    /**
     * Create/update a value of the status.
     * @param stateName The state name to update
     * @param stateValue The new value of the state name
     */
    protected void set(StateName stateName, StateValue stateValue) {
        // The command is sent to the gateway. Gateway transmits it to the actuator.
        // If everything is fine, Gateway provides through the monitor session
        // the new status => not need to set it here since it will be set by the
        // monitor way.
        final State oldStatus = new State(stateName, get(stateName));
        final State newStatus = new State(stateName, stateValue);
        // what = newWhat; => it will be done with changeWhat by the monitor listener
        executeAction(newStatus, new CommandListener() {
            @Override
            public void onCommand(CommandResult result) {
                if (CommandResultStatus.ok.equals(result.getStatus())) {
                    // Status has been changed
                    // what = newWhat; => it will be done with changeWhat by the
                    // monitor listener
                    // notifyWhatChange(oldStatus, newWhat); call by monitor! =>
                    // it will be done with changeWhat by the monitor listener
                } else {
                    // Error
                    // what = oldStatus; => it will be done with changeWhat by
                    // the monitor listener
                    notifyStateChangeError(oldStatus, newStatus, result);
                }
            }
        });
    }

    /**
     * Define the new {@link Status} of the device without sent the command on
     * the bus.
     * 
     * @param newWhat
     *            {@link Status} of the device.
     */
    public void changeState(State newWhat) {
        State oldWhat = new State(newWhat.getName(), stateList.get(newWhat.getName()));
        stateList.put(newWhat.getName(), newWhat.getValue());
        notifyStateChange(oldWhat, newWhat);
    }

    @Override
    public String toString() {
        return toJson().toString();
    }

    @Override
    public JSONObject toJson() {
        JSONObject controllerJson = new JSONObject();
        controllerJson.put(JSON_WHO, getWho()).put(JSON_TITLE, getTitle()).put(JSON_DESCRIPTION, getDescription());
        if (getWhere() != null) {
            controllerJson.put(JSON_WHERE, getWhere().getTo());
        }
        JSONObject states = new JSONObject();
        if (!stateList.isEmpty()) {
            for (Entry<StateName, StateValue> entry : stateList.entrySet()) {
                StateValue sv = entry.getValue();
                states.put(entry.getKey().getName(), sv == null ? null : sv.getValue());
            }
        }
        controllerJson.put(JSON_STATES, states);
        return controllerJson;
    }

    @Override
    public void fromJson(JSONObject jsonObject) {
        setTitle(jsonObject.getString(JSON_TITLE));
        try {
            setDescription(jsonObject.getString(JSON_DESCRIPTION));
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        String to = jsonObject.getString(JSON_WHERE);
        if (!"null".equals(String.valueOf(to))) {
            setWhere(new Where(to, to));
        }

        //      JSONObject states = jsonObject.getJSONObject(JSON_STATES);
        //      for (final String name: states.keySet()) {
        //         String value = states.getString(name);
        //         StateName sname = new StateName() {
        //            
        //            @Override
        //            public String getName() {
        //               return name;
        //            }
        //         };
        //         StateValue svalue; Les valeurs sont en lecture seul???
        //         try {
        //            svalue = stateTypes.get(sname).newInstance();
        //            svalue.setValue(value);
        //            stateList.put(sname, svalue);
        //         } catch (InstantiationException e) {
        //            // TODO Auto-generated catch block
        //            e.printStackTrace();
        //         } catch (IllegalAccessException e) {
        //            // TODO Auto-generated catch block
        //            e.printStackTrace();
        //         }
        //         
        //      }
    }

    /**
     * 
     * @author DRIESBACH Olivier
     * @version 1.0
     * @since 1.0
     */
    private class ControllerStateName implements StateName {

        private String stateName;

        private ControllerStateName(String stateName) {
            this.stateName = stateName.toLowerCase();
        }

        @Override
        public String getName() {
            return stateName;
        }
    }
}