001    package org.hackystat.sensor.ant.issue;
002    
003    import java.lang.reflect.InvocationTargetException;
004    import java.lang.reflect.Method;
005    import java.util.Arrays;
006    import java.util.List;
007    import javax.xml.datatype.DatatypeConstants;
008    import javax.xml.datatype.XMLGregorianCalendar;
009    import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
010    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
011    import org.hackystat.utilities.tstamp.Tstamp;
012    
013    /**
014     * An IssueEntry represent an issue in issue tracking system, always reflect the most current state.
015     * @author Shaoxuan Zhang
016     *
017     */
018    public class IssueEntry {
019    
020      /** property key of ID. */
021      public static final String ID_PROPERTY_KEY = "IssueId";
022      /** property key of TYPE. */
023      public static final String TYPE_PROPERTY_KEY = "Type";
024      /** property key of STATUS. */
025      public static final String STATUS_PROPERTY_KEY = "Status";
026      /** property key of PRIORITY. */
027      public static final String PRIORITY_PROPERTY_KEY = "Priority";
028      /** property key of MILESTONE. */
029      public static final String MILESTONE_PROPERTY_KEY = "Milestone";
030      /** property key of OWNER. */
031      public static final String OWNER_PROPERTY_KEY = "Owner";
032    
033      /** timestamp separator in property value. */
034      public static final String TIMESTAMP_SEPARATOR = "--";
035      
036      private int issueId;
037      private String type = "";
038      private String status = "";
039      private String priority = "";
040      private String milestone = "";
041      private String owner = "";
042      /** open time of this issue, or the earliest time this issue being detected.*/
043      private XMLGregorianCalendar openedTime = null;
044      /** close time of this issue, null if this issue is still open. */
045      private XMLGregorianCalendar closedTime = null;
046      private XMLGregorianCalendar modifiedTime = null;
047      
048      private SensorData sensorData = null;
049      
050      /**
051       * Update this IssueEntry as well as the associated sensordata to the given issue table column.
052       * @param line the content of the issue table column.
053       * @param runTimestamp the run time.
054       * @param isVerbose if do the update verbosely.
055       * @return true if the sensordata is modified.
056       */
057      public boolean upToDate(String[] line, XMLGregorianCalendar runTimestamp, boolean isVerbose) {
058        boolean modified = false;
059        modified |= checkFieldUpdate(TYPE_PROPERTY_KEY, line[1], runTimestamp, isVerbose); 
060        modified |= checkFieldUpdate(STATUS_PROPERTY_KEY, line[2], runTimestamp, isVerbose); 
061        modified |= checkFieldUpdate(PRIORITY_PROPERTY_KEY, line[3], runTimestamp, isVerbose); 
062        modified |= checkFieldUpdate(MILESTONE_PROPERTY_KEY, line[4], runTimestamp, isVerbose);
063        modified |= checkFieldUpdate(OWNER_PROPERTY_KEY, line[5], runTimestamp, isVerbose);
064        if (modified) {
065          sensorData.setLastMod(runTimestamp);
066        }
067        return modified;
068      }
069    
070      /**
071       * Check field update with the new value.
072       * @param fieldName name of the field.
073       * @param value new value.
074       * @param runTimestamp timestamp of the new value.
075       * @param isVerbose if do the update verbosely.
076       * @return true if the new value is different from the current value.
077       */
078      private boolean checkFieldUpdate(String fieldName, String value, 
079          XMLGregorianCalendar runTimestamp, boolean isVerbose)  {
080        Method getMethod = null;
081        Method setMethod = null;
082        try {
083          getMethod = this.getClass().getMethod("get" + fieldName, new Class[]{});
084          setMethod = this.getClass().getMethod("set" + fieldName, new Class[]{String.class});
085          String oldValue = "";
086          String newValue = value;
087          oldValue = (String)getMethod.invoke(this, new Object[]{});
088          if (oldValue.equals(newValue)) {
089            return false;
090          }
091          if (isVerbose) {
092            System.out.println(fieldName + " has been changed from [" + oldValue + 
093              "] to [" + newValue + "].");
094          }
095          sensorData.addProperty(fieldName, newValue + TIMESTAMP_SEPARATOR + runTimestamp);
096          setMethod.invoke(this, new Object[]{newValue});
097          return true;
098        }
099        catch (IllegalArgumentException e) {
100          e.printStackTrace();
101        }
102        catch (IllegalAccessException e) {
103          e.printStackTrace();
104        }
105        catch (InvocationTargetException e) {
106          e.printStackTrace();
107        }
108        catch (SecurityException e) {
109          e.printStackTrace();
110        }
111        catch (NoSuchMethodException e) {
112          e.printStackTrace();
113        }
114        return false;
115      }
116      /**
117       * @param data The associated SensorData
118       * @throws Exception if error
119       */
120      public IssueEntry(final SensorData data) throws Exception {
121        this.sensorData = data;
122        this.issueId = getIssueId(data);
123        if (this.issueId < 0) {
124          throw new Exception("Issue Id not found, " +
125            "probably because it is not Issue SensorData or malformatted");
126        }
127        this.type = getLatestValueWithKey(this.sensorData, TYPE_PROPERTY_KEY);
128        this.status = getLatestValueWithKey(this.sensorData, STATUS_PROPERTY_KEY);
129        this.priority = getLatestValueWithKey(this.sensorData, PRIORITY_PROPERTY_KEY);
130        this.milestone = getLatestValueWithKey(this.sensorData, MILESTONE_PROPERTY_KEY);
131        this.owner = getLatestValueWithKey(this.sensorData, OWNER_PROPERTY_KEY);
132      }
133    
134      /**
135       * Return the issue id of the issue sensordata.
136       * @param data the issue sensordata.
137       * @return the issue id. -1 if there is no property with key = IssueId.
138       */
139      public static int getIssueId(final SensorData data) {
140        for (Property property : data.getProperties().getProperty()) {
141          if (ID_PROPERTY_KEY.equals(property.getKey())) {
142            return Integer.valueOf(property.getValue());
143          }
144        }
145        return -1;
146      }
147    
148      /**
149       * Get the last update time of this issue sensor data.
150       * If LastModification time is available, it will be return.
151       * Otherwise, it will process through properties to find the 
152       * lastest timestamp in properties' timestamp.
153       * @return the timestamp of last udpate. NULL if no relative information found.
154       */
155      public XMLGregorianCalendar getLastUpdateTime() {
156        if (sensorData.getLastMod() != null) {
157          return sensorData.getLastMod();
158        }
159        XMLGregorianCalendar timestamp = null;
160        List<String> keys = Arrays.asList(new String[]{TYPE_PROPERTY_KEY, STATUS_PROPERTY_KEY, 
161            PRIORITY_PROPERTY_KEY, MILESTONE_PROPERTY_KEY, OWNER_PROPERTY_KEY});
162        for (Property property : sensorData.getProperties().getProperty()) {
163          if (keys.contains(property.getKey())) {
164            try {
165              XMLGregorianCalendar valueTimestamp = extractTimestamp(property.getValue());
166              if (timestamp == null || Tstamp.greaterThan(valueTimestamp, timestamp)) {
167                timestamp = valueTimestamp;
168              }
169            }
170            catch (Exception e) {
171              System.out.println("Error when extracting timestamp from " + property.getValue() +
172                  " Exception message: " + e.getMessage());
173            }
174          }
175        }
176        return timestamp;
177      }
178      
179      /**
180       * Extract timestamp from formatted string.
181       * @param value the string.
182       * @return the timestamp.
183       * @throws Exception if the string is not formatted.
184       */
185      private static XMLGregorianCalendar extractTimestamp(String value) throws Exception {
186        int startIndex = value.indexOf(TIMESTAMP_SEPARATOR) + TIMESTAMP_SEPARATOR.length();
187        return Tstamp.makeTimestamp(value.substring(startIndex));
188      }
189    
190      /**
191       * Extract value from formatted string.
192       * @param string the string.
193       * @return the value.
194       */
195      private static String extractValue(String string) {
196        return string.substring(0, string.indexOf(TIMESTAMP_SEPARATOR));
197      }
198      
199      /**
200       * Return the latest value with the given key from the SensorData.
201       * @param sensorData the SensorData.
202       * @param key the property key
203       * @return the latest value, null if not found.
204       * @throws Exception if error when parsing property values.
205       */
206      public static final String getLatestValueWithKey(SensorData sensorData, String key) 
207          throws Exception {
208        XMLGregorianCalendar latestTime = null;
209        String value = "";
210        for (Property property : sensorData.getProperties().getProperty()) {
211          if (key.equals(property.getKey())) {
212            XMLGregorianCalendar newTimestamp = extractTimestamp(property.getValue());
213            if (latestTime == null || 
214                latestTime.compare(newTimestamp) == DatatypeConstants.LESSER) {
215              latestTime = newTimestamp;
216              value = extractValue(property.getValue());
217            }
218          }
219        }
220        return value;
221      }
222    
223      
224      /**
225       * @param id the issueId to set
226       */
227      protected void setIssueId(int id) {
228        this.issueId = id;
229      }
230      /**
231       * @return the issueId
232       */
233      public int getIssueId() {
234        return issueId;
235      }
236      /**
237       * @param type the type to set
238       */
239      public void setType(String type) {
240        this.type = type;
241      }
242      /**
243       * @return the type
244       */
245      public String getType() {
246        return type;
247      }
248      /**
249       * @param status the status to set
250       */
251      public void setStatus(String status) {
252        this.status = status;
253      }
254      /**
255       * @return the status
256       */
257      public String getStatus() {
258        return status;
259      }
260      /**
261       * @param priority the priority to set
262       */
263      public void setPriority(String priority) {
264        this.priority = priority;
265      }
266      /**
267       * @return the priority
268       */
269      public String getPriority() {
270        return priority;
271      }
272      /**
273       * @param milestone the milestone to set
274       */
275      public void setMilestone(String milestone) {
276        this.milestone = milestone;
277      }
278      /**
279       * @return the milestone
280       */
281      public String getMilestone() {
282        return milestone;
283      }
284      /**
285       * @param owner the owner to set
286       */
287      public void setOwner(String owner) {
288        this.owner = owner;
289      }
290      /**
291       * @return the owner
292       */
293      public String getOwner() {
294        return owner;
295      }
296      /**
297       * @param openedTime the openedTime to set
298       */
299      public void setOpenedTime(XMLGregorianCalendar openedTime) {
300        this.openedTime = openedTime;
301      }
302      /**
303       * @return the openedTime
304       */
305      public XMLGregorianCalendar getOpenedTime() {
306        return openedTime;
307      }
308      /**
309       * @param closedTime the closedTime to set
310       */
311      protected void setClosedTime(XMLGregorianCalendar closedTime) {
312        this.closedTime = closedTime;
313      }
314      /**
315       * @return the closedTime
316       */
317      public XMLGregorianCalendar getClosedTime() {
318        return closedTime;
319      }
320    
321      /**
322       * @param sensorData the sensorData to set
323       */
324      protected void setSensorData(SensorData sensorData) {
325        this.sensorData = sensorData;
326      }
327    
328      /**
329       * @return the sensorData
330       */
331      public SensorData getSensorData() {
332        return sensorData;
333      }
334    
335    
336      /**
337       * @param modifiedTime the modifiedTime to set
338       */
339      protected void setModifiedTime(XMLGregorianCalendar modifiedTime) {
340        this.modifiedTime = modifiedTime;
341      }
342    
343    
344      /**
345       * @return the modifiedTime
346       */
347      public XMLGregorianCalendar getModifiedTime() {
348        return modifiedTime;
349      }
350    
351    
352    }