View Javadoc

1   /* This file is part of COPAL (COntext Provisioning for All).
2    *
3    * COPAL is a part of SM4All (Smart hoMes for All) project.
4    *
5    * COPAL is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU Lesser General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * COPAL is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public License
16   * along with COPAL. If not, see <http://www.gnu.org/licenses/>.
17   */
18  package at.ac.tuwien.infosys.sm4all.copal.api.event;
19  
20  import java.util.Date;
21  
22  /**
23   * Class which contains the meta-data for base events. This class is not thread
24   * safe.
25   * 
26   * @author fei
27   * @author sanjin
28   */
29  public class BaseEvent {
30  
31      private final Actions<ContextEventAction> actions = new Actions<ContextEventAction>();
32      private final String sourceID;
33      private final Date timeStamp;
34      private int currentActionIndex = -1;
35  
36      /**
37       * Create instance of {@link BaseEvent} with specified source ID and time
38       * stamp.
39       * 
40       * @param sourceID the source ID of {@link BaseEvent}.
41       * @param timeStamp the time stamp for {@link BaseEvent}.
42       * @throws NullPointerException if specified source ID or time stamp is
43       *         <code>null</code>.
44       * @throws IllegalArgumentException if specified source ID is an empty or
45       *         blank string.
46       */
47      protected BaseEvent(final String sourceID, final Date timeStamp) {
48          super();
49  
50          if (null == sourceID) {
51              throw new NullPointerException("Source ID cannot be null.");
52          }
53          if (sourceID.trim().isEmpty()) {
54              throw new IllegalArgumentException(
55                      "Source ID cannot be an empty or blank string.");
56          }
57          if (null == timeStamp) {
58              throw new NullPointerException("Time stamp cannot be null.");
59          }
60  
61          this.sourceID = sourceID;
62          this.timeStamp = timeStamp;
63      }
64  
65      /**
66       * Return the source ID of this {@link BaseEvent}.
67       * 
68       * @return the source ID of this {@link BaseEvent}.
69       */
70      public String getSourceID() {
71          return this.sourceID;
72      }
73  
74      /**
75       * Return the time stamp for this {@link BaseEvent}.
76       * 
77       * @return the time stamp for this {@link BaseEvent}.
78       */
79      public Date getTimeStamp() {
80          return this.timeStamp;
81      }
82  
83      /**
84       * Return the {@link ContextEventAction} at specified index.
85       * 
86       * @param index the index of {@link ContextEventAction}.
87       * @return the {@link ContextEventAction}.
88       */
89      public ContextEventAction getAction(final int index) {
90          return this.actions.get(index);
91      }
92  
93      /**
94       * Return all currently added {@link ContextEventAction}s.
95       * 
96       * @return all currently added {@link ContextEventAction}s.
97       */
98      public ContextEventAction[] getActions() {
99          return this.actions.getAll();
100     }
101 
102     /**
103      * Remove all previously added {@link ContextEventAction}s and add specified
104      * {@link ContextEventAction}s. This method will set the index of
105      * {@link CurrentAction} to the index of the {@link CurrentAction} in
106      * specified array of {@link ContextEventAction}s.
107      * 
108      * @param actions {@link ContextEventAction}s.
109      * @throws NullPointerException if specified array
110      *         {@link ContextEventAction}s or any {@link ContextEventAction} in
111      *         the array is <code>null</code>.
112      */
113     protected void set(final ContextEventAction... actions) {
114         if (null == actions) {
115             throw new NullPointerException("Actions cannot be null.");
116         }
117         for (final ContextEventAction action : actions) {
118             if (null == action) {
119                 throw new NullPointerException("Action cannot be null.");
120             }
121         }
122 
123         setCurrentActionIndex(actions);
124         this.actions.setAll(actions);
125     }
126 
127     /**
128      * Return the index of {@link CurrentAction} or <code>-1</code> if this
129      * {@link BaseEvent} was just created.
130      * 
131      * @return the index of {@link CurrentAction}.
132      */
133     public int getCurrentActionIndex() {
134         return this.currentActionIndex;
135     }
136 
137     /**
138      * Return the {@link CurrentAction}.
139      * 
140      * @return the {@link CurrentAction} or <code>null</code> if this
141      *         {@link BaseEvent}. was just created or there are no more actions
142      *         in the list.
143      */
144     public CurrentAction getCurrentAction() {
145         final CurrentAction result;
146 
147         if ((this.currentActionIndex >= 0)
148                 && (this.currentActionIndex < this.actions.size())) {
149             result = (CurrentAction) this.actions.get(this.currentActionIndex);
150         } else {
151             result = null;
152         }
153 
154         return result;
155     }
156 
157     /**
158      * Return if there is at least one {@link ContextEventAction} with specified
159      * name.
160      * 
161      * @param name the name of the {@link ContextEventAction}.
162      * @return if there is at least one {@link ContextEventAction} with
163      *         specified name.
164      */
165     public boolean hasAction(final String name) {
166         return this.actions.contains(name);
167     }
168 
169     /**
170      * Return the number of {@link ContextEventAction}s.
171      * 
172      * @return the number of {@link ContextEventAction}s.
173      */
174     public int getNumberOfActions() {
175         return this.actions.size();
176     }
177 
178     /**
179      * Move {@link CurrentAction} to next {@link UnprocessedAction}.
180      */
181     public void nextAction() {
182         if (this.currentActionIndex < this.actions.size()) {
183             if (this.currentActionIndex >= 0) {
184                 replaceCurrentAction(new ProcessedAction(getCurrentAction()));
185             }
186 
187             this.currentActionIndex++;
188             if (this.currentActionIndex < this.actions.size()) {
189                 replaceCurrentAction(new CurrentAction(
190                         (UnprocessedAction) this.actions.get(this.currentActionIndex)));
191             }
192         }
193     }
194 
195     /**
196      * Append specified {@link UnprocessedAction}s.
197      * 
198      * @param unprocessedActions the {@link UnprocessedAction}s.
199      * @throws IllegalArgumentException if all actions are processed (i.e.
200      *         {@link #getCurrentActionIndex()} points to the end of the
201      *         actions).
202      */
203     public void append(final UnprocessedAction... unprocessedActions) {
204         if (this.currentActionIndex >= this.actions.size()) {
205             throw new IllegalArgumentException("All actions are processed.");
206         }
207 
208         this.actions.append(unprocessedActions);
209     }
210 
211     /**
212      * Insert specified {@link UnprocessedAction} at specified index.
213      * 
214      * @param index the index at which the {@link UnprocessedAction} is
215      *        inserted.
216      * @param action the {@link UnprocessedAction}.
217      * @throws IllegalArgumentException if specified index is less than or equal
218      *         to {@link #getCurrentActionIndex()}.
219      */
220     public void insert(final int index, final UnprocessedAction action) {
221         if (this.currentActionIndex >= this.actions.size()) {
222             throw new IllegalArgumentException("All actions are processed.");
223         }
224         if (index < 0) {
225             throw new IllegalArgumentException("Index cannot be negative.");
226         }
227         if (index <= this.currentActionIndex) {
228             throw new IllegalArgumentException(
229                     "Index is before or at current action.");
230         }
231 
232         this.actions.insert(index, action);
233     }
234 
235     /**
236      * Replace the {@link UnprocessedAction} at specified index with specified
237      * {@link UnprocessedAction}.
238      * 
239      * @param index the index at which the {@link UnprocessedAction} is
240      *        replaced.
241      * @param action the {@link UnprocessedAction}.
242      * @throws IllegalArgumentException if specified index is less than or equal
243      *         to {@link #getCurrentActionIndex()} or is greater or equal than
244      *         {@link #getNumberOfActions()}.
245      */
246     public void replace(final int index, final UnprocessedAction action) {
247         if (this.currentActionIndex >= this.actions.size()) {
248             throw new IllegalArgumentException("All actions are processed.");
249         }
250         if (index < 0) {
251             throw new IllegalArgumentException("Index cannot be negative.");
252         }
253         if (index <= this.currentActionIndex) {
254             throw new IllegalArgumentException(
255                     "Index is before or at current action.");
256         }
257 
258         this.actions.set(index, action);
259     }
260 
261     /**
262      * Remove the {@link UnprocessedAction} at specified index.
263      * 
264      * @param index the index at which the {@link UnprocessedAction} is removed.
265      * @throws IllegalArgumentException if specified index is less than or equal
266      *         to {@link #getCurrentActionIndex()}.
267      */
268     public void removeAction(final int index) {
269         if (this.currentActionIndex >= this.actions.size()) {
270             throw new IllegalArgumentException("All actions are processed.");
271         }
272         if (index < 0) {
273             throw new IllegalArgumentException("Index cannot be negative.");
274         }
275         if (index <= this.currentActionIndex) {
276             throw new IllegalArgumentException(
277                     "Index is before or at current action.");
278         }
279 
280         this.actions.remove(index);
281     }
282 
283     /**
284      * Return hash code for this {@link BaseEvent}. The hash code for a
285      * {@link BaseEvent} object is computed as:
286      * 
287      * <pre>
288      * source ID * 31 + time stamp
289      * </pre>
290      * 
291      * using integer arithmetic.
292      * 
293      * @return a hash code value for this {@link BaseEvent}.
294      */
295     @Override
296     public int hashCode() {
297         final int prime = 31;
298         return (this.sourceID.hashCode() * prime) + this.timeStamp.hashCode();
299     }
300 
301     /**
302      * Compare this {@link BaseEvent} to the specified {@link Object}. The
303      * result is <code>true</code> if and only if the argument is not
304      * <code>null</code> and is a {@link BaseEvent} object that has same source
305      * ID and time stamp as this {@link BaseEvent}.
306      * 
307      * @param obj the {@link Object} to compare this {@link BaseEvent} against.
308      * @return <code>true</code> if {@link BaseEvent}s are equal;
309      *         <code>false</code> otherwise.
310      */
311     @Override
312     public boolean equals(final Object obj) {
313         boolean result = false;
314 
315         if (null != obj) {
316             if (this == obj) {
317                 result = true;
318             } else if (obj instanceof BaseEvent) {
319                 final BaseEvent other = (BaseEvent) obj;
320 
321                 result = this.sourceID.equals(other.sourceID)
322                         && this.timeStamp.equals(other.timeStamp);
323             }
324         }
325 
326         return result;
327     }
328 
329     private void setCurrentActionIndex(final ContextEventAction... actions) {
330         if (actions.length > 0) {
331             boolean foundCurrentAction = false;
332             ContextEventAction previousAction = null;
333             try {
334                 for (final ContextEventAction action : actions) {
335                     if (action instanceof ProcessedAction) {
336                         if (foundCurrentAction) {
337                             throw new IllegalArgumentException(
338                                     "Processed action cannot be after a current action.");
339                         }
340                         if (previousAction instanceof UnprocessedAction) {
341                             throw new IllegalArgumentException(
342                                     "Processed action cannot be after an unprocessed action.");
343                         }
344                         this.currentActionIndex++;
345                     } else if (action instanceof CurrentAction) {
346                         if (foundCurrentAction) {
347                             throw new IllegalArgumentException(
348                                     "More than one current action.");
349                         }
350                         if (previousAction instanceof UnprocessedAction) {
351                             throw new IllegalArgumentException(
352                                     "Current action cannot be after an unprocessed action.");
353                         }
354                         this.currentActionIndex++;
355                         foundCurrentAction = true;
356                     } else if (action instanceof UnprocessedAction) {
357                         if (previousAction instanceof ProcessedAction) {
358                             throw new IllegalArgumentException(
359                                     "No current action between processed and unprocessed actions.");
360                         }
361                     } else {
362                         throw new IllegalArgumentException(
363                                 "Only processed, current or unprocessed actions.");
364                     }
365                     previousAction = action;
366                 }
367             } catch (final IllegalArgumentException ex) {
368                 // rollback any change to current action index and rethrow
369                 // the exception
370                 this.currentActionIndex = -1;
371                 throw ex;
372             }
373             if ((!foundCurrentAction)
374                     && (actions[actions.length - 1] instanceof ProcessedAction)) {
375                 this.currentActionIndex++;
376             }
377         } else {
378             this.currentActionIndex = 0;
379         }
380     }
381 
382     private void replaceCurrentAction(final ContextEventAction action) {
383         this.actions.set(this.currentActionIndex, action);
384     }
385 }