diff --git a/bundles/binding/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/messages/DataModel.java b/bundles/binding/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/messages/DataModel.java
index 5a35bcacf55..b127b2bd3ac 100644
--- a/bundles/binding/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/messages/DataModel.java
+++ b/bundles/binding/org.openhab.binding.nest/src/main/java/org/openhab/binding/nest/internal/messages/DataModel.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2010-2015, openHAB.org and others.
+ * Copyright (c) 2010-2016, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -10,6 +10,7 @@
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
@@ -18,24 +19,25 @@
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.Converter;
+import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
-import org.openhab.binding.nest.internal.messages.Structure.AwayState;
-import org.openhab.binding.nest.internal.messages.Thermostat.HvacMode;
-import org.openhab.binding.nest.internal.messages.Thermostat.HvacState;
import org.openhab.binding.nest.internal.messages.SmokeCOAlarm.AlarmState;
import org.openhab.binding.nest.internal.messages.SmokeCOAlarm.BatteryHealth;
import org.openhab.binding.nest.internal.messages.SmokeCOAlarm.ColorState;
+import org.openhab.binding.nest.internal.messages.Structure.AwayState;
+import org.openhab.binding.nest.internal.messages.Thermostat.HvacMode;
+import org.openhab.binding.nest.internal.messages.Thermostat.HvacState;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
/**
* The DataModel Java Bean represents the entire Nest API data model.
- *
+ *
* @see API Reference
* @author John Cocula
* @since 1.7.0
@@ -43,547 +45,560 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class DataModel extends AbstractMessagePart {
- private static BeanUtilsBean beanUtils;
- private static PropertyUtilsBean propertyUtils;
- static {
- /**
- * Configure BeanUtilsBean to use our converters and resolver.
- */
- ConvertUtilsBean convertUtils = new ConvertUtilsBean();
-
- // Register bean type converters
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof StringType) {
- return AwayState.forValue(value.toString());
- } else {
- return null;
- }
- }
- }, AwayState.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof StringType) {
- return HvacMode.forValue(value.toString());
- } else {
- return null;
- }
- }
- }, HvacMode.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof StringType) {
- return HvacState.forValue(value.toString());
- } else {
- return null;
- }
- }
- }, HvacState.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof StringType) {
- return BatteryHealth.forValue(value.toString());
- } else {
- return null;
- }
- }
- }, BatteryHealth.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof StringType) {
- return AlarmState.forValue(value.toString());
- } else {
- return null;
- }
- }
- }, AlarmState.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof StringType) {
- return ColorState.forValue(value.toString());
- } else {
- return null;
- }
- }
- }, ColorState.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof DecimalType) {
- return ((DecimalType) value).intValue();
- } else {
- return null;
- }
- }
- }, Integer.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- if (value instanceof OnOffType) {
- return ((OnOffType) value) == OnOffType.ON;
- } else {
- return null;
- }
- }
- }, Boolean.class);
- convertUtils.register(new Converter() {
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Class type, Object value) {
- return value.toString();
- }
- }, String.class);
-
- propertyUtils = new PropertyUtilsBean();
- propertyUtils.setResolver(new DataModelPropertyResolver());
- beanUtils = new BeanUtilsBean(convertUtils, propertyUtils);
- }
-
- @JsonProperty("devices")
- private Devices devices;
- @JsonProperty("structures")
- private Map structures_by_id;
- @JsonIgnore
- private Map structures_by_name;
- @JsonIgnore
- Date last_connection;
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Devices extends AbstractMessagePart implements DataModelElement {
- private Map thermostats_by_id;
- @JsonIgnore
- private Map thermostats_by_name;
- private Map smoke_co_alarms_by_id;
- @JsonIgnore
- private Map smoke_co_alarms_by_name;
- private Map cameras_by_id;
- @JsonIgnore
- private Map cameras_by_name;
-
- /**
- * @return the thermostats_by_id
- */
- @JsonProperty("thermostats")
- public Map getThermostats_by_id() {
- return this.thermostats_by_id;
- }
-
- /**
- * @param thermostats
- * the thermostats to set (mapped by ID)
- */
- @JsonProperty("thermostats")
- public void setThermostats_by_id(Map thermostats_by_id) {
- this.thermostats_by_id = thermostats_by_id;
- }
-
- /**
- * Return the thermostats map, mapped by name.
- *
- * @return the thermostats_by_name;
- */
- @JsonIgnore
- public Map getThermostats() {
- return this.thermostats_by_name;
- }
-
- /**
- * @return the smoke_co_alarms_by_id
- */
- @JsonProperty("smoke_co_alarms")
- public Map getSmoke_co_alarms_by_id() {
- return this.smoke_co_alarms_by_id;
- }
-
- /**
- * @param smoke_co_alarms
- * the smoke_co_alarms to set (mapped by ID)
- */
- @JsonProperty("smoke_co_alarms")
- public void setSmoke_co_alarms_by_id(Map smoke_co_alarms_by_id) {
- this.smoke_co_alarms_by_id = smoke_co_alarms_by_id;
- }
-
- /**
- * @return the smoke_co_alarms_by_name
- */
- @JsonIgnore
- public Map getSmoke_co_alarms() {
- return this.smoke_co_alarms_by_name;
- }
-
- /**
- * @return the cameras_by_id
- */
- @JsonProperty("cameras")
- public Map getCameras_by_id() {
- return this.cameras_by_id;
- }
-
- /**
- * @param cameras_by_id
- * the cameras to set (mapped by ID)
- */
- @JsonProperty("cameras")
- public void setCameras_by_id(Map cameras_by_id) {
- this.cameras_by_id = cameras_by_id;
- }
-
- /**
- * @return the cameras_by_name
- */
- @JsonIgnore
- public Map getCameras() {
- return this.cameras_by_name;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void sync(DataModel dataModel) {
- // Create our map of Thermostat names to objects
- this.thermostats_by_name = new HashMap();
- if (this.thermostats_by_id != null) {
- for (Thermostat thermostat : this.thermostats_by_id.values()) {
- thermostat.sync(dataModel);
- this.thermostats_by_name.put(thermostat.getName(), thermostat);
- }
- }
- // Create our map of SmokeCOAlarm names to objects
- this.smoke_co_alarms_by_name = new HashMap();
- if (this.smoke_co_alarms_by_id != null) {
- for (SmokeCOAlarm smoke_co_alarm : this.smoke_co_alarms_by_id.values()) {
- smoke_co_alarm.sync(dataModel);
- this.smoke_co_alarms_by_name.put(smoke_co_alarm.getName(), smoke_co_alarm);
- }
- }
- // Create our map of Camera names to objects
- this.cameras_by_name = new HashMap();
- if (this.cameras_by_id != null) {
- for (Camera camera : this.cameras_by_id.values()) {
- camera.sync(dataModel);
- this.cameras_by_name.put(camera.getName(), camera);
- }
- }
- }
-
- @Override
- public String toString() {
- final ToStringBuilder builder = createToStringBuilder();
- builder.appendSuper(super.toString());
- builder.append("thermostats", this.thermostats_by_id);
- builder.append("smoke_co_alarms", this.smoke_co_alarms_by_id);
- builder.append("cameras", this.cameras_by_id);
-
- return builder.toString();
- }
- }
-
- public DataModel() {
- }
-
- /**
- * Use a dialect of the BeanUtils property resolver that URL-decodes the keys that are used to retrieve mapped
- * objects.
- */
- public static class DataModelPropertyResolver extends org.apache.commons.beanutils.expression.DefaultResolver {
- /**
- * {@inheritDoc}
- */
- @SuppressWarnings("deprecation")
- public String getKey(String expression) {
- String key = super.getKey(expression);
- if (key != null) {
- try {
- return URLDecoder.decode(key, "UTF-8");
- } catch (UnsupportedEncodingException ex) {
- return URLDecoder.decode(key);
- }
- }
- return null;
- }
- }
-
- /**
- * Return a JavaBean property by name.
- *
- * @param name
- * the named property to return
- * @return the named property's value
- * @see PropertyUtils#getProperty()
- * @throws IllegalAccessException
- * if the caller does not have access to the property accessor method
- * @throws InvocationTargetException
- * if the property accessor method throws an exception
- * @throws NoSuchMethodException
- * if the accessor method for this property cannot be found
- */
- public Object getProperty(String name) throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException {
-
- return propertyUtils.getProperty(this, name);
- }
-
- /**
- * Set the specified property value, performing type conversions as required to conform to the type of the
- * destination property.
- *
- * @param name
- * property name (can be nested/indexed/mapped/combo)
- * @param value
- * value to be set
- * @throws IllegalAccessException
- * if the caller does not have access to the property accessor method
- * @throws InvocationTargetException
- * if the property accessor method throws an exception
- */
- public void setProperty(String name, Object value) throws IllegalAccessException, InvocationTargetException {
-
- beanUtils.setProperty(this, name, value);
- }
-
- /**
- * @return the devices
- */
- @JsonProperty("devices")
- public Devices getDevices() {
- return this.devices;
- }
-
- /**
- * @param devices
- * the devices to set
- */
- @JsonProperty("devices")
- public void setDevices(Devices devices) {
- this.devices = devices;
- }
-
- /**
- * Convenience method so property specs don't have to include "devices." in each one.
- *
- * @return name-based map of thermostats
- */
- @JsonIgnore
- public Map getThermostats() {
- return (devices == null) ? null : devices.getThermostats();
- }
-
- /**
- * Convenience method so property specs don't have to include "devices." in each one.
- *
- * @return name-based map of smoke_co_alarms
- */
- @JsonIgnore
- public Map getSmoke_co_alarms() {
- return (devices == null) ? null : devices.getSmoke_co_alarms();
- }
-
- /**
- * Convenience method so property specs don't have to include "devices." in each one.
- *
- * @return name-based map of cameras
- */
- @JsonIgnore
- public Map getCameras() {
- return (devices == null) ? null : devices.getCameras();
- }
-
- /**
- * @return the structures
- */
- @JsonProperty("structures")
- public Map getStructures_by_id() {
- return this.structures_by_id;
- }
-
- /**
- * @param structures_by_id
- * the ID-keyed structure map to set
- */
- @JsonProperty("structures")
- public void setStructures_by_id(Map structures_by_id) {
- this.structures_by_id = structures_by_id;
- }
-
- @JsonIgnore
- public Map getStructures() {
- return this.structures_by_name;
- }
-
- /**
- * @param structures_by_name
- * the name-keyed structure map to set
- */
- public void setStructures_by_name(final Map structures_by_name) {
- this.structures_by_name = structures_by_name;
- }
-
- /**
- * @param last_connection
- * the last time we obtained the data model from the Nest API
- */
- @JsonIgnore
- public void setLast_connection(final Date last_connection) {
- this.last_connection = last_connection;
- }
-
- /**
- * @return the last_connection
- */
- @JsonIgnore
- public Date getLast_connection() {
- return this.last_connection;
- }
-
- /**
- * Visit each data model element and call its sync method with the root data model element (this). Do this right
- * after it's been unmarshalled from JSON.
- */
- public void sync() {
- this.structures_by_name = new HashMap();
- if (this.structures_by_id != null) {
- for (Structure st : this.structures_by_id.values()) {
- this.structures_by_name.put(st.getName(), st);
- st.sync(this);
- }
- }
- if (this.devices != null) {
- this.devices.sync(this);
- }
- }
-
- /**
- * This method returns a new data model containing only the affected Structure, Thermostat, SmokeCOAlarm or Camera,
- * and only the property of the bean that was changed. This new DataModel object can be sent to the Nest API in
- * order to perform an update via HTTP PUT.
- *
- * @param property
- * the property to change
- * @param newState
- * the new state to set for the property
- * @return a new data model that only contains the affected bean and only the property set
- * @throws IllegalAccessException
- * @throws InvocationTargetException
- * @throws NoSuchMethodException
- */
- public DataModel updateDataModel(String property, Object newState) throws IllegalAccessException,
- InvocationTargetException, NoSuchMethodException {
-
- /**
- * Find the Structure, Thermostat, SmokeCOAlarm or Camera that the given property is trying to update.
- */
- Object oldObject = null;
- String beanProperty = property;
- do {
- Object obj = this.getProperty(beanProperty);
- if (obj instanceof Structure || obj instanceof Thermostat || obj instanceof SmokeCOAlarm
- || obj instanceof Camera) {
- oldObject = obj;
- break;
- }
- if (beanProperty.indexOf('.') != -1) {
- beanProperty = beanProperty.substring(0, beanProperty.lastIndexOf('.'));
- } else {
- break;
- }
- } while (beanProperty.length() > 0);
-
- /**
- * Now based on the type of the object, create a new DataModel that has an empty one, properly mapped by ID and
- * by name.
- */
- DataModel updateDataModel = null;
-
- if (oldObject != null) {
- if (oldObject instanceof Structure) {
- String structureId = ((Structure) oldObject).getStructure_id();
- String structureName = ((Structure) oldObject).getName();
-
- updateDataModel = new DataModel();
- Structure structure = new Structure(null);
- updateDataModel.structures_by_id = new HashMap();
- updateDataModel.structures_by_id.put(structureId, structure);
- updateDataModel.structures_by_name = new HashMap();
- updateDataModel.structures_by_name.put(structureName, structure);
- } else if (oldObject instanceof Thermostat) {
- String deviceId = ((Thermostat) oldObject).getDevice_id();
- String deviceName = ((Thermostat) oldObject).getName();
-
- updateDataModel = new DataModel();
- updateDataModel.devices = new Devices();
- Thermostat thermostat = new Thermostat(null);
- updateDataModel.devices.thermostats_by_id = new HashMap();
- updateDataModel.devices.thermostats_by_id.put(deviceId, thermostat);
- updateDataModel.devices.thermostats_by_name = new HashMap();
- updateDataModel.devices.thermostats_by_name.put(deviceName, thermostat);
- } else if (oldObject instanceof SmokeCOAlarm) {
- String deviceId = ((SmokeCOAlarm) oldObject).getDevice_id();
- String deviceName = ((SmokeCOAlarm) oldObject).getName();
-
- updateDataModel = new DataModel();
- updateDataModel.devices = new Devices();
- SmokeCOAlarm smokeCOAlarm = new SmokeCOAlarm(null);
- updateDataModel.devices.smoke_co_alarms_by_id = new HashMap();
- updateDataModel.devices.smoke_co_alarms_by_id.put(deviceId, smokeCOAlarm);
- updateDataModel.devices.smoke_co_alarms_by_name = new HashMap();
- updateDataModel.devices.smoke_co_alarms_by_name.put(deviceName, smokeCOAlarm);
- } else if (oldObject instanceof Camera) {
- String deviceId = ((Camera) oldObject).getDevice_id();
- String deviceName = ((Camera) oldObject).getName();
-
- updateDataModel = new DataModel();
- updateDataModel.devices = new Devices();
- Camera camera = new Camera(null);
- updateDataModel.devices.cameras_by_id = new HashMap();
- updateDataModel.devices.cameras_by_id.put(deviceId, camera);
- updateDataModel.devices.cameras_by_name = new HashMap();
- updateDataModel.devices.cameras_by_name.put(deviceName, camera);
- }
- }
-
- /**
- * Lastly, set the property into the update data model
- *
- * TODO: cannot update a binding string of the form "=[structures(Name).thermostats(Name).X]" or
- * "=[structures(Name).smoke_co_alarms(Name).X]" because the name-based map of structures is not present in the
- * updateDataModel
- */
- if (updateDataModel != null) {
- updateDataModel.setProperty(property, newState);
- updateDataModel.structures_by_name = null;
- if (updateDataModel.devices != null) {
- updateDataModel.devices.smoke_co_alarms_by_name = null;
- updateDataModel.devices.thermostats_by_name = null;
- updateDataModel.devices.cameras_by_name = null;
- }
- }
-
- return updateDataModel;
- }
-
- @Override
- public String toString() {
- final ToStringBuilder builder = createToStringBuilder();
- builder.appendSuper(super.toString());
- builder.append("devices", this.devices);
- builder.append("structures", this.structures_by_id);
-
- return builder.toString();
- }
+ private static BeanUtilsBean beanUtils;
+ private static PropertyUtilsBean propertyUtils;
+
+ static {
+ /**
+ * Configure BeanUtilsBean to use our converters and resolver.
+ */
+ ConvertUtilsBean convertUtils = new ConvertUtilsBean();
+
+ // Register bean type converters
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return AwayState.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, AwayState.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return HvacMode.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, HvacMode.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return HvacState.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, HvacState.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return BatteryHealth.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, BatteryHealth.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return AlarmState.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, AlarmState.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return ColorState.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, ColorState.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof DecimalType) {
+ return ((DecimalType) value).toBigDecimal();
+ } else {
+ return null;
+ }
+ }
+ }, BigDecimal.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof DecimalType) {
+ return ((DecimalType) value).intValue();
+ } else {
+ return null;
+ }
+ }
+ }, Integer.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof OnOffType) {
+ return ((OnOffType) value) == OnOffType.ON;
+ } else {
+ return null;
+ }
+ }
+ }, Boolean.class);
+ convertUtils.register(new Converter() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ return value.toString();
+ }
+ }, String.class);
+
+ propertyUtils = new PropertyUtilsBean();
+ propertyUtils.setResolver(new DataModelPropertyResolver());
+ beanUtils = new BeanUtilsBean(convertUtils, propertyUtils);
+ }
+
+ @JsonProperty("devices")
+ private Devices devices;
+ @JsonProperty("structures")
+ private Map structures_by_id;
+ @JsonIgnore
+ private Map structures_by_name;
+ @JsonIgnore
+ Date last_connection;
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Devices extends AbstractMessagePart implements DataModelElement {
+ private Map thermostats_by_id;
+ @JsonIgnore
+ private Map thermostats_by_name;
+ private Map smoke_co_alarms_by_id;
+ @JsonIgnore
+ private Map smoke_co_alarms_by_name;
+ private Map cameras_by_id;
+ @JsonIgnore
+ private Map cameras_by_name;
+
+ /**
+ * @return the thermostats_by_id
+ */
+ @JsonProperty("thermostats")
+ public Map getThermostats_by_id() {
+ return this.thermostats_by_id;
+ }
+
+ /**
+ * @param thermostats
+ * the thermostats to set (mapped by ID)
+ */
+ @JsonProperty("thermostats")
+ public void setThermostats_by_id(Map thermostats_by_id) {
+ this.thermostats_by_id = thermostats_by_id;
+ }
+
+ /**
+ * Return the thermostats map, mapped by name.
+ *
+ * @return the thermostats_by_name;
+ */
+ @JsonIgnore
+ public Map getThermostats() {
+ return this.thermostats_by_name;
+ }
+
+ /**
+ * @return the smoke_co_alarms_by_id
+ */
+ @JsonProperty("smoke_co_alarms")
+ public Map getSmoke_co_alarms_by_id() {
+ return this.smoke_co_alarms_by_id;
+ }
+
+ /**
+ * @param smoke_co_alarms
+ * the smoke_co_alarms to set (mapped by ID)
+ */
+ @JsonProperty("smoke_co_alarms")
+ public void setSmoke_co_alarms_by_id(Map smoke_co_alarms_by_id) {
+ this.smoke_co_alarms_by_id = smoke_co_alarms_by_id;
+ }
+
+ /**
+ * @return the smoke_co_alarms_by_name
+ */
+ @JsonIgnore
+ public Map getSmoke_co_alarms() {
+ return this.smoke_co_alarms_by_name;
+ }
+
+ /**
+ * @return the cameras_by_id
+ */
+ @JsonProperty("cameras")
+ public Map getCameras_by_id() {
+ return this.cameras_by_id;
+ }
+
+ /**
+ * @param cameras_by_id
+ * the cameras to set (mapped by ID)
+ */
+ @JsonProperty("cameras")
+ public void setCameras_by_id(Map cameras_by_id) {
+ this.cameras_by_id = cameras_by_id;
+ }
+
+ /**
+ * @return the cameras_by_name
+ */
+ @JsonIgnore
+ public Map getCameras() {
+ return this.cameras_by_name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void sync(DataModel dataModel) {
+ // Create our map of Thermostat names to objects
+ this.thermostats_by_name = new HashMap();
+ if (this.thermostats_by_id != null) {
+ for (Thermostat thermostat : this.thermostats_by_id.values()) {
+ thermostat.sync(dataModel);
+ this.thermostats_by_name.put(thermostat.getName(), thermostat);
+ }
+ }
+ // Create our map of SmokeCOAlarm names to objects
+ this.smoke_co_alarms_by_name = new HashMap();
+ if (this.smoke_co_alarms_by_id != null) {
+ for (SmokeCOAlarm smoke_co_alarm : this.smoke_co_alarms_by_id.values()) {
+ smoke_co_alarm.sync(dataModel);
+ this.smoke_co_alarms_by_name.put(smoke_co_alarm.getName(), smoke_co_alarm);
+ }
+ }
+ // Create our map of Camera names to objects
+ this.cameras_by_name = new HashMap();
+ if (this.cameras_by_id != null) {
+ for (Camera camera : this.cameras_by_id.values()) {
+ camera.sync(dataModel);
+ this.cameras_by_name.put(camera.getName(), camera);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("thermostats", this.thermostats_by_id);
+ builder.append("smoke_co_alarms", this.smoke_co_alarms_by_id);
+ builder.append("cameras", this.cameras_by_id);
+
+ return builder.toString();
+ }
+ }
+
+ public DataModel() {
+ }
+
+ /**
+ * Use a dialect of the BeanUtils property resolver that URL-decodes the keys that are used to retrieve mapped
+ * objects.
+ */
+ public static class DataModelPropertyResolver extends org.apache.commons.beanutils.expression.DefaultResolver {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public String getKey(String expression) {
+ String key = super.getKey(expression);
+ if (key != null) {
+ try {
+ return URLDecoder.decode(key, "UTF-8");
+ } catch (UnsupportedEncodingException ex) {
+ return URLDecoder.decode(key);
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Return a JavaBean property by name.
+ *
+ * @param name
+ * the named property to return
+ * @return the named property's value
+ * @see PropertyUtils#getProperty()
+ * @throws IllegalAccessException
+ * if the caller does not have access to the property accessor method
+ * @throws InvocationTargetException
+ * if the property accessor method throws an exception
+ * @throws NoSuchMethodException
+ * if the accessor method for this property cannot be found
+ */
+ public Object getProperty(String name)
+ throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+
+ return propertyUtils.getProperty(this, name);
+ }
+
+ /**
+ * Set the specified property value, performing type conversions as required to conform to the type of the
+ * destination property.
+ *
+ * @param name
+ * property name (can be nested/indexed/mapped/combo)
+ * @param value
+ * value to be set
+ * @throws IllegalAccessException
+ * if the caller does not have access to the property accessor method
+ * @throws InvocationTargetException
+ * if the property accessor method throws an exception
+ */
+ public void setProperty(String name, Object value) throws IllegalAccessException, InvocationTargetException {
+
+ beanUtils.setProperty(this, name, value);
+ }
+
+ /**
+ * @return the devices
+ */
+ @JsonProperty("devices")
+ public Devices getDevices() {
+ return this.devices;
+ }
+
+ /**
+ * @param devices
+ * the devices to set
+ */
+ @JsonProperty("devices")
+ public void setDevices(Devices devices) {
+ this.devices = devices;
+ }
+
+ /**
+ * Convenience method so property specs don't have to include "devices." in each one.
+ *
+ * @return name-based map of thermostats
+ */
+ @JsonIgnore
+ public Map getThermostats() {
+ return (devices == null) ? null : devices.getThermostats();
+ }
+
+ /**
+ * Convenience method so property specs don't have to include "devices." in each one.
+ *
+ * @return name-based map of smoke_co_alarms
+ */
+ @JsonIgnore
+ public Map getSmoke_co_alarms() {
+ return (devices == null) ? null : devices.getSmoke_co_alarms();
+ }
+
+ /**
+ * Convenience method so property specs don't have to include "devices." in each one.
+ *
+ * @return name-based map of cameras
+ */
+ @JsonIgnore
+ public Map getCameras() {
+ return (devices == null) ? null : devices.getCameras();
+ }
+
+ /**
+ * @return the structures
+ */
+ @JsonProperty("structures")
+ public Map getStructures_by_id() {
+ return this.structures_by_id;
+ }
+
+ /**
+ * @param structures_by_id
+ * the ID-keyed structure map to set
+ */
+ @JsonProperty("structures")
+ public void setStructures_by_id(Map structures_by_id) {
+ this.structures_by_id = structures_by_id;
+ }
+
+ @JsonIgnore
+ public Map getStructures() {
+ return this.structures_by_name;
+ }
+
+ /**
+ * @param structures_by_name
+ * the name-keyed structure map to set
+ */
+ public void setStructures_by_name(final Map structures_by_name) {
+ this.structures_by_name = structures_by_name;
+ }
+
+ /**
+ * @param last_connection
+ * the last time we obtained the data model from the Nest API
+ */
+ @JsonIgnore
+ public void setLast_connection(final Date last_connection) {
+ this.last_connection = last_connection;
+ }
+
+ /**
+ * @return the last_connection
+ */
+ @JsonIgnore
+ public Date getLast_connection() {
+ return this.last_connection;
+ }
+
+ /**
+ * Visit each data model element and call its sync method with the root data model element (this). Do this right
+ * after it's been unmarshalled from JSON.
+ */
+ public void sync() {
+ this.structures_by_name = new HashMap();
+ if (this.structures_by_id != null) {
+ for (Structure st : this.structures_by_id.values()) {
+ this.structures_by_name.put(st.getName(), st);
+ st.sync(this);
+ }
+ }
+ if (this.devices != null) {
+ this.devices.sync(this);
+ }
+ }
+
+ /**
+ * This method returns a new data model containing only the affected Structure, Thermostat, SmokeCOAlarm or Camera,
+ * and only the property of the bean that was changed. This new DataModel object can be sent to the Nest API in
+ * order to perform an update via HTTP PUT.
+ *
+ * @param property
+ * the property to change
+ * @param newState
+ * the new state to set for the property
+ * @return a new data model that only contains the affected bean and only the property set
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws NoSuchMethodException
+ */
+ public DataModel updateDataModel(String property, Object newState)
+ throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+
+ /**
+ * Find the Structure, Thermostat, SmokeCOAlarm or Camera that the given property is trying to update.
+ */
+ Object oldObject = null;
+ String beanProperty = property;
+ do {
+ Object obj = this.getProperty(beanProperty);
+ if (obj instanceof Structure || obj instanceof Thermostat || obj instanceof SmokeCOAlarm
+ || obj instanceof Camera) {
+ oldObject = obj;
+ break;
+ }
+ if (beanProperty.indexOf('.') != -1) {
+ beanProperty = beanProperty.substring(0, beanProperty.lastIndexOf('.'));
+ } else {
+ break;
+ }
+ } while (beanProperty.length() > 0);
+
+ /**
+ * Now based on the type of the object, create a new DataModel that has an empty one, properly mapped by ID and
+ * by name.
+ */
+ DataModel updateDataModel = null;
+
+ if (oldObject != null) {
+ if (oldObject instanceof Structure) {
+ String structureId = ((Structure) oldObject).getStructure_id();
+ String structureName = ((Structure) oldObject).getName();
+
+ updateDataModel = new DataModel();
+ Structure structure = new Structure(null);
+ updateDataModel.structures_by_id = new HashMap();
+ updateDataModel.structures_by_id.put(structureId, structure);
+ updateDataModel.structures_by_name = new HashMap();
+ updateDataModel.structures_by_name.put(structureName, structure);
+ } else if (oldObject instanceof Thermostat) {
+ String deviceId = ((Thermostat) oldObject).getDevice_id();
+ String deviceName = ((Thermostat) oldObject).getName();
+
+ updateDataModel = new DataModel();
+ updateDataModel.devices = new Devices();
+ Thermostat thermostat = new Thermostat(null);
+ updateDataModel.devices.thermostats_by_id = new HashMap();
+ updateDataModel.devices.thermostats_by_id.put(deviceId, thermostat);
+ updateDataModel.devices.thermostats_by_name = new HashMap();
+ updateDataModel.devices.thermostats_by_name.put(deviceName, thermostat);
+ } else if (oldObject instanceof SmokeCOAlarm) {
+ String deviceId = ((SmokeCOAlarm) oldObject).getDevice_id();
+ String deviceName = ((SmokeCOAlarm) oldObject).getName();
+
+ updateDataModel = new DataModel();
+ updateDataModel.devices = new Devices();
+ SmokeCOAlarm smokeCOAlarm = new SmokeCOAlarm(null);
+ updateDataModel.devices.smoke_co_alarms_by_id = new HashMap();
+ updateDataModel.devices.smoke_co_alarms_by_id.put(deviceId, smokeCOAlarm);
+ updateDataModel.devices.smoke_co_alarms_by_name = new HashMap();
+ updateDataModel.devices.smoke_co_alarms_by_name.put(deviceName, smokeCOAlarm);
+ } else if (oldObject instanceof Camera) {
+ String deviceId = ((Camera) oldObject).getDevice_id();
+ String deviceName = ((Camera) oldObject).getName();
+
+ updateDataModel = new DataModel();
+ updateDataModel.devices = new Devices();
+ Camera camera = new Camera(null);
+ updateDataModel.devices.cameras_by_id = new HashMap();
+ updateDataModel.devices.cameras_by_id.put(deviceId, camera);
+ updateDataModel.devices.cameras_by_name = new HashMap();
+ updateDataModel.devices.cameras_by_name.put(deviceName, camera);
+ }
+ }
+
+ /**
+ * Lastly, set the property into the update data model
+ *
+ * TODO: cannot update a binding string of the form "=[structures(Name).thermostats(Name).X]" or
+ * "=[structures(Name).smoke_co_alarms(Name).X]" because the name-based map of structures is not present in the
+ * updateDataModel
+ */
+ if (updateDataModel != null) {
+ updateDataModel.setProperty(property, newState);
+ updateDataModel.structures_by_name = null;
+ if (updateDataModel.devices != null) {
+ updateDataModel.devices.smoke_co_alarms_by_name = null;
+ updateDataModel.devices.thermostats_by_name = null;
+ updateDataModel.devices.cameras_by_name = null;
+ }
+ }
+
+ return updateDataModel;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("devices", this.devices);
+ builder.append("structures", this.structures_by_id);
+
+ return builder.toString();
+ }
}