001 package dk.deepthought.sidious.greenhouse;
002 
003 import java.util.ArrayList;
004 import java.util.Collection;
005 import java.util.Collections;
006 import java.util.Comparator;
007 import java.util.List;
008 
009 import net.jcip.annotations.Immutable;
010 
011 import org.apache.commons.logging.Log;
012 import org.apache.commons.logging.LogFactory;
013 
014 import dk.deepthought.sidious.supportsystem.State;
015 import dk.deepthought.sidious.supportsystem.SystemSettings;
016 
017 /**
018  * This class represents a climatic state of the environment. It is a collection
019  * of environmental sensor inputs.
020  
021  @author Deepthought
022  
023  */
024 @Immutable
025 public class ClimaticState implements State {
026     /**
027      * Logger for this class.
028      */
029     private static final Log logger = LogFactory.getLog(ClimaticState.class);
030 
031     /**
032      * The list of sensors.
033      */
034     private final List<SensorInput> sensors;
035 
036     /**
037      * This comparator orders sensors by their <code>SuperLinkID</code>.
038      */
039     static final Comparator<SensorInput> SENSOR_ORDER = new Comparator<SensorInput>() {
040         public int compare(SensorInput o1, SensorInput o2) {
041             return o1.getID().compareTo(o2.getID());
042         }
043     };
044 
045     /**
046      * Creates a <code>ClimaticState</code> object containing the specified
047      * sensor inputs.
048      
049      @param sensors
050      *            a collection of sensor inputs related this climatic state
051      */
052     public ClimaticState(Collection<SensorInput> sensors) {
053         this.sensors = new ArrayList<SensorInput>(sensors);
054     }
055 
056     /*
057      * (non-Javadoc)
058      
059      * @see dk.deepthought.sidious.supportsystem.State#impact(java.util.Collection)
060      */
061     public State impact(Collection<State> states) {
062         if (logger.isDebugEnabled()) {
063             logger.debug("impact(Collection<State> states=" + states
064                     ") - start");
065         }
066     
067         // Current implementation calculates the average.
068         ClimaticState returnState = average(toClimaticStateList(states));
069         returnState = returnState.incrementTime();
070         if (logger.isDebugEnabled()) {
071             logger.debug("impact(Collection<State> states=" + states
072                     ") - end - return value=" + returnState);
073         }
074         return returnState;
075     }
076 
077     /**
078      * Calculates and returns the average <code>State</code>, based on the
079      * input <code>states</code>.
080      <p>
081      * The method returns <code>null</code>, when presented with
082      <code>null</code> or an empty collection.
083      
084      @param states
085      *            states to base calculation on
086      @return the average state
087      */
088     static ClimaticState average(Collection<ClimaticState> states) {
089         if (logger.isDebugEnabled()) {
090             logger.debug("average(Collection<ClimaticState> states=" + states
091                     ") - start");
092         }
093 
094         if ((states == null|| states.isEmpty()) {
095             if (logger.isDebugEnabled()) {
096                 logger.debug("average(Collection<ClimaticState> states="
097                         + states + ") - end - return value=" null);
098             }
099             throw new IllegalArgumentException("States list cannot be: " + states);
100         }
101         int size = states.size();
102         ClimaticState average = null;
103         ClimaticState cs = total(new ArrayList<ClimaticState>(states), average);
104         ArrayList<SensorInput> sensors = new ArrayList<SensorInput>(cs
105                 .getSensors());
106         for (int i = 0, s = sensors.size(); i < s; i++) {
107             SensorInput sensor = sensors.remove(i);
108             sensors.add(i, sensor.newInstanceWithNewValue(sensor.getValue()
109                     / size));
110         }
111         ClimaticState returnClimaticState = new ClimaticState(sensors);
112         if (logger.isDebugEnabled()) {
113             logger.debug("average(Collection<ClimaticState> states=" + states
114                     ") - end - return value=" + returnClimaticState);
115         }
116         return returnClimaticState;
117     }
118 
119     /**
120      * This method converts the input <code>State</code> objects to a list of
121      <code>ClimaticState</code> objects.
122      
123      @param states
124      *            input states to be concerted
125      @return the input states as climatic states
126      */
127     static ArrayList<ClimaticState> toClimaticStateList(Collection<State> states) {
128         if (logger.isDebugEnabled()) {
129             logger.debug("toClimaticStateList(Collection<State> states="
130                     + states + ") - start");
131         }
132 
133         if ((states == null|| states.isEmpty()) {
134             ArrayList<ClimaticState> returnArrayList = new ArrayList<ClimaticState>();
135             if (logger.isDebugEnabled()) {
136                 logger.debug("toClimaticStateList(Collection<State> states="
137                         + states + ") - end - return value=" + returnArrayList);
138             }
139             return returnArrayList;
140         }
141         ArrayList<ClimaticState> returnStates = new ArrayList<ClimaticState>();
142         for (State state : states) {
143             if (state instanceof ClimaticState) {
144                 returnStates.add((ClimaticStatestate);
145             }
146         }
147 
148         if (logger.isDebugEnabled()) {
149             logger.debug("toClimaticStateList(Collection<State> states="
150                     + states + ") - end - return value=" + returnStates);
151         }
152         return returnStates;
153     }
154 
155     /**
156      * This method calculates the total sum of all related sensor inputs in the
157      <code>states</code>. The sum is calculated recursively.
158      <p>
159      * The method returns <code>null</code>, when presented with
160      <code>null</code> or an empty collection.
161      
162      @param states
163      *            the list of climatic states to be summed
164      @param total
165      *            the parameter in which the sum will be calculated; is needed
166      *            for recursive purposes
167      @return <code>ClimaticState</code> representing the total sum of the
168      *         related sensor inputs
169      */
170     static ClimaticState total(ArrayList<ClimaticState> states,
171             ClimaticState total) {
172         if (logger.isDebugEnabled()) {
173             logger.debug("total(ArrayList<ClimaticState> states=" + states
174                     ", ClimaticState total=" + total + ") - start");
175         }
176 
177         if ((states == null|| states.isEmpty()) {
178             if (logger.isDebugEnabled()) {
179                 logger.debug("total(ArrayList<ClimaticState> states=" + states
180                         ", ClimaticState total=" + total
181                         ") - end - return value=" null);
182             }
183             return null;
184         }
185         ArrayList<ClimaticState> listOfCS = new ArrayList<ClimaticState>(states);
186         ClimaticState currentCS = listOfCS.remove(0);
187         if (listOfCS.isEmpty()) {
188             total = currentCS.sum(total);
189 
190             if (logger.isDebugEnabled()) {
191                 logger.debug("total(ArrayList<ClimaticState> states=" + states
192                         ", ClimaticState total=" + total
193                         ") - end - return value=" + total);
194             }
195             return total;
196         else {
197             ClimaticState returnClimaticState = total(listOfCS, currentCS).sum(
198                     total);
199             if (logger.isDebugEnabled()) {
200                 logger.debug("total(ArrayList<ClimaticState> states=" + states
201                         ", ClimaticState total=" + total
202                         ") - end - return value=" + returnClimaticState);
203             }
204             return returnClimaticState;
205         }
206     }
207 
208     /**
209      * This method calculates the sum of this <code>ClimaticState</code> and
210      <code>other</code>.
211      <p>
212      * The method returns a deep-copy of <code>this</code>, when presented
213      * with <code>null</code>.
214      
215      @param other
216      *            the other climatic state
217      */
218     ClimaticState sum(ClimaticState other) {
219         if (logger.isDebugEnabled()) {
220             logger.debug("sum(ClimaticState other=" + other + ") - start");
221         }
222     
223         if (other == null) {
224             ClimaticState returnClimaticState = new ClimaticState(getSensors());
225             if (logger.isDebugEnabled()) {
226                 logger.debug("sum(ClimaticState other=null)"
227                         " - end - return value=" + returnClimaticState);
228             }
229             return returnClimaticState;
230         }
231         Collection<SensorInput> sensorList = new ArrayList<SensorInput>();
232         for (SensorInput sensor : sensors) {
233             double total = sensor.getValue();
234             for (SensorInput input : other.getSensors()) {
235                 if (sensor.equalsOnSuperLinkID(input)) {
236                     total += input.getValue();
237                 }
238             }
239             sensorList.add(sensor.newInstanceWithNewValue(total));
240         }
241         ClimaticState returnClimaticState = new ClimaticState(sensorList);
242         if (logger.isDebugEnabled()) {
243             logger.debug("sum(ClimaticState other=" + other
244                     ") - end - return value=" + returnClimaticState);
245         }
246         return returnClimaticState;
247     }
248 
249     /*
250      * (non-Javadoc)
251      
252      * @see dk.deepthought.sidious.supportsystem.State#sameStateSpace(dk.deepthought.sidious.supportsystem.State)
253      */
254     public boolean sameStateSpace(State other) {
255         if (other instanceof ClimaticState) {
256             ArrayList<SensorInput> otherSensors = new ArrayList<SensorInput>(
257                     ((ClimaticStateother).getSensors());
258             if (otherSensors.size() != sensors.size()) {
259                 return false;
260             }
261             Collections.sort(sensors, SENSOR_ORDER);
262             Collections.sort(otherSensors, SENSOR_ORDER);
263             for (int i = 0; i < sensors.size(); i++) {
264                 if (!sensors.get(i).equalsOnSuperLinkID(otherSensors.get(i))) {
265                     return false;
266                 }
267             }
268             return true;
269         }
270         return false;
271     }
272 
273     /* (non-Javadoc)
274      * @see dk.deepthought.sidious.supportsystem.State#partiallyEquals(dk.deepthought.sidious.supportsystem.State)
275      */
276     public boolean partiallyEquals(State state) {
277         if (state instanceof ClimaticState) {
278             ClimaticState climaticState = (ClimaticStatestate;
279             for (SensorInput sensor : climaticState.getSensors()) {
280                 if (!sensors.contains(sensor)) {
281                     return false;
282                 }
283             }
284             return true;
285         }
286         return false;
287     }
288 
289     /**
290      * Gets a <code>Collection</code> of <code>SensorInput</code>
291      
292      @return the sensors related to this climatic state
293      */
294     public Collection<SensorInput> getSensors() {
295         if (logger.isDebugEnabled()) {
296             logger.debug("getSensors() - start");
297         }
298     
299         Collection<SensorInput> returnCollection = new ArrayList<SensorInput>(
300                 sensors);
301         if (logger.isDebugEnabled()) {
302             logger.debug("getSensors() - end - return value="
303                     + returnCollection);
304         }
305         return returnCollection;
306     }
307 
308     /**
309      * Increments the time of this.
310      @return the time incremented version of this.
311      */
312     private ClimaticState incrementTime() {
313         ArrayList<SensorInput> newSensors = new ArrayList<SensorInput>();
314         for (SensorInput sensor : sensors) {
315             if (SystemSettings.getTimeID().equals(sensor.getID())) {
316                 sensor = sensor.newInstanceWithNewValue(sensor.getValue()
317                         + SystemSettings.getTimestep());
318             }
319             newSensors.add(sensor);
320         }
321         return new ClimaticState(newSensors);
322     }
323 
324     /*
325      * (non-Javadoc)
326      
327      * @see java.lang.Object#equals(java.lang.Object)
328      */
329     @Override
330     public boolean equals(Object obj) {
331         if (this == obj) {
332             return true;
333         }
334         if (obj == null) {
335             return false;
336         }
337         if (getClass() != obj.getClass()) {
338             return false;
339         }
340         final ClimaticState other = (ClimaticStateobj;
341         if (sensors == null) {
342             if (other.sensors != null) {
343                 return false;
344             }
345         else if (!sensors.equals(other.sensors)) {
346             return false;
347         }
348         return true;
349     }
350 
351     /*
352      * (non-Javadoc)
353      
354      * @see java.lang.Object#hashCode()
355      */
356     @Override
357     public int hashCode() {
358         final int PRIME = 31;
359         int result = 1;
360         result = PRIME * result + ((sensors == null: sensors.hashCode());
361         return result;
362     }
363 
364     /*
365      * (non-Javadoc)
366      
367      * @see java.lang.Object#toString()
368      */
369     @Override
370     public String toString() {
371         return getClass().getSimpleName() "[sensors=" + sensors + "]";
372     }
373 
374 }