James Moger
2012-03-20 aebae04b6d44d90434f5829c2b2242b1aa1f9f7b
commit | author | age
0f43a5 1 /*
JM 2  * Copyright 2012 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.models;
17
18 import java.io.Serializable;
19 import java.util.ArrayList;
20 import java.util.Date;
69a559 21 import java.util.LinkedHashSet;
0f43a5 22 import java.util.List;
69a559 23 import java.util.Set;
0f43a5 24
JM 25 import com.gitblit.utils.ArrayUtils;
26 import com.gitblit.utils.StringUtils;
69a559 27 import com.gitblit.utils.TimeUtils;
0f43a5 28
JM 29 /**
30  * The Gitblit Issue model, its component classes, and enums.
31  * 
32  * @author James Moger
33  * 
34  */
35 public class IssueModel implements Serializable, Comparable<IssueModel> {
36
69a559 37     private static final long serialVersionUID = 1L;
0f43a5 38
JM 39     public String id;
40
41     public Type type;
42
43     public Status status;
44
45     public Priority priority;
46
47     public Date created;
48
49     public String summary;
50
51     public String description;
52
53     public String reporter;
54
55     public String owner;
56
57     public String milestone;
58
59     public List<Change> changes;
60
61     public IssueModel() {
35a71b 62         // the first applied change set the date appropriately
JM 63         created = new Date(0);
0f43a5 64
JM 65         type = Type.Defect;
66         status = Status.New;
67         priority = Priority.Medium;
68
69         changes = new ArrayList<Change>();
70     }
71
72     public String getStatus() {
73         String s = status.toString();
74         if (!StringUtils.isEmpty(owner))
75             s += " (" + owner + ")";
76         return s;
77     }
78
69a559 79     public boolean hasLabel(String label) {
JM 80         return getLabels().contains(label);
81     }
82
0f43a5 83     public List<String> getLabels() {
JM 84         List<String> list = new ArrayList<String>();
85         String labels = null;
86         for (Change change : changes) {
69a559 87             if (change.hasField(Field.Labels)) {
JM 88                 labels = change.getString(Field.Labels);
0f43a5 89             }
JM 90         }
91         if (!StringUtils.isEmpty(labels)) {
92             list.addAll(StringUtils.getStringsFromValue(labels, " "));
93         }
94         return list;
95     }
96
97     public Attachment getAttachment(String name) {
98         Attachment attachment = null;
99         for (Change change : changes) {
100             if (change.hasAttachments()) {
101                 Attachment a = change.getAttachment(name);
102                 if (a != null) {
103                     attachment = a;
104                 }
105             }
106         }
107         return attachment;
108     }
109
06ff61 110     public List<Attachment> getAttachments() {
JM 111         List<Attachment> list = new ArrayList<Attachment>();
112         for (Change change : changes) {
113             if (change.hasAttachments()) {
114                 list.addAll(change.attachments);
115             }
116         }
117         return list;
118     }
119
69a559 120     public void applyChange(Change change) {
35a71b 121         if (changes.size() == 0) {
JM 122             // first change created the issue
123             created = change.created;
124         }
0f43a5 125         changes.add(change);
69a559 126
JM 127         if (change.hasFieldChanges()) {
128             for (FieldChange fieldChange : change.fieldChanges) {
129                 switch (fieldChange.field) {
130                 case Id:
131                     id = fieldChange.value.toString();
132                     break;
133                 case Type:
134                     type = IssueModel.Type.fromObject(fieldChange.value);
135                     break;
136                 case Status:
137                     status = IssueModel.Status.fromObject(fieldChange.value);
138                     break;
139                 case Priority:
140                     priority = IssueModel.Priority.fromObject(fieldChange.value);
141                     break;
142                 case Summary:
143                     summary = fieldChange.value.toString();
144                     break;
145                 case Description:
146                     description = fieldChange.value.toString();
147                     break;
148                 case Reporter:
149                     reporter = fieldChange.value.toString();
150                     break;
151                 case Owner:
152                     owner = fieldChange.value.toString();
153                     break;
154                 case Milestone:
155                     milestone = fieldChange.value.toString();
156                     break;
157                 }
158             }
159         }
0f43a5 160     }
JM 161
162     @Override
163     public String toString() {
69a559 164         StringBuilder sb = new StringBuilder();
JM 165         sb.append("issue ");
166         sb.append(id.substring(0, 8));
167         sb.append(" (" + summary + ")\n");
168         for (Change change : changes) {
169             sb.append(change);
170             sb.append('\n');
171         }
172         return sb.toString();
0f43a5 173     }
JM 174
175     @Override
176     public int compareTo(IssueModel o) {
177         return o.created.compareTo(created);
178     }
179
180     @Override
181     public boolean equals(Object o) {
182         if (o instanceof IssueModel)
183             return id.equals(((IssueModel) o).id);
184         return super.equals(o);
185     }
186
187     @Override
188     public int hashCode() {
189         return id.hashCode();
190     }
191
69a559 192     public static class Change implements Serializable, Comparable<Change> {
0f43a5 193
JM 194         private static final long serialVersionUID = 1L;
195
69a559 196         public final Date created;
0f43a5 197
69a559 198         public final String author;
JM 199
200         public String id;
201
202         public char code;
0f43a5 203
JM 204         public Comment comment;
205
69a559 206         public Set<FieldChange> fieldChanges;
0f43a5 207
69a559 208         public Set<Attachment> attachments;
0f43a5 209
69a559 210         public Change(String author) {
JM 211             this.created = new Date((System.currentTimeMillis() / 1000) * 1000);
212             this.author = author;
213             this.id = StringUtils.getSHA1(created.toString() + author);
0f43a5 214         }
JM 215
216         public boolean hasComment() {
69a559 217             return comment != null && !comment.deleted;
JM 218         }
219
220         public void comment(String text) {
221             comment = new Comment(text);
222             comment.id = StringUtils.getSHA1(created.toString() + author + text);
0f43a5 223         }
JM 224
225         public boolean hasAttachments() {
226             return !ArrayUtils.isEmpty(attachments);
227         }
228
229         public void addAttachment(Attachment attachment) {
230             if (attachments == null) {
69a559 231                 attachments = new LinkedHashSet<Attachment>();
0f43a5 232             }
JM 233             attachments.add(attachment);
234         }
235
236         public Attachment getAttachment(String name) {
237             for (Attachment attachment : attachments) {
238                 if (attachment.name.equalsIgnoreCase(name)) {
239                     return attachment;
240                 }
241             }
242             return null;
243         }
244
69a559 245         public boolean hasField(Field field) {
JM 246             return !StringUtils.isEmpty(getString(field));
247         }
248
249         public boolean hasFieldChanges() {
250             return !ArrayUtils.isEmpty(fieldChanges);
251         }
252
253         public Object getField(Field field) {
254             if (fieldChanges != null) {
255                 for (FieldChange fieldChange : fieldChanges) {
256                     if (fieldChange.field == field) {
257                         return fieldChange.value;
258                     }
259                 }
260             }
261             return null;
262         }
263
264         public void setField(Field field, Object value) {
265             FieldChange fieldChange = new FieldChange(field, value);
266             if (fieldChanges == null) {
267                 fieldChanges = new LinkedHashSet<FieldChange>();
268             }
269             fieldChanges.add(fieldChange);
270         }
271
272         public String getString(Field field) {
273             Object value = getField(field);
274             if (value == null) {
275                 return null;
276             }
277             return value.toString();
278         }
279
280         @Override
281         public int compareTo(Change c) {
282             return created.compareTo(c.created);
283         }
284
285         @Override
286         public int hashCode() {
287             return id.hashCode();
288         }
289
290         @Override
291         public boolean equals(Object o) {
292             if (o instanceof Change) {
293                 return id.equals(((Change) o).id);
294             }
295             return false;
296         }
297
0f43a5 298         @Override
JM 299         public String toString() {
69a559 300             StringBuilder sb = new StringBuilder();
JM 301             sb.append(TimeUtils.timeAgo(created));
302             switch (code) {
303             case '+':
304                 sb.append(" created by ");
305                 break;
306             default:
307                 if (hasComment()) {
308                     sb.append(" commented on by ");
309                 } else {
310                     sb.append(" changed by ");
311                 }
312             }
313             sb.append(author).append(" - ");
314             if (hasComment()) {
315                 if (comment.deleted) {
316                     sb.append("(deleted) ");
317                 }
318                 sb.append(comment.text).append(" ");
319             }
320             if (hasFieldChanges()) {
321                 switch (code) {
322                 case '+':
323                     break;
324                 default:
325                     for (FieldChange fieldChange : fieldChanges) {
326                         sb.append("\n  ");
327                         sb.append(fieldChange);
328                     }
329                     break;
330                 }
331             }
332             return sb.toString();
0f43a5 333         }
JM 334     }
335
336     public static class Comment implements Serializable {
337
338         private static final long serialVersionUID = 1L;
339
340         public String text;
69a559 341
JM 342         public String id;
343
0f43a5 344         public boolean deleted;
JM 345
346         Comment(String text) {
347             this.text = text;
348         }
349
350         @Override
351         public String toString() {
352             return text;
353         }
354     }
355
356     public static class FieldChange implements Serializable {
357
358         private static final long serialVersionUID = 1L;
359
69a559 360         public final Field field;
0f43a5 361
69a559 362         public final Object value;
JM 363
364         FieldChange(Field field, Object value) {
365             this.field = field;
366             this.value = value;
367         }
368
369         @Override
370         public int hashCode() {
371             return field.hashCode();
372         }
373
374         @Override
375         public boolean equals(Object o) {
376             if (o instanceof FieldChange) {
377                 return field.equals(((FieldChange) o).field);
378             }
379             return false;
380         }
0f43a5 381
JM 382         @Override
383         public String toString() {
384             return field + ": " + value;
385         }
386     }
387
388     public static class Attachment implements Serializable {
389
390         private static final long serialVersionUID = 1L;
391
69a559 392         public final String name;
JM 393         public String id;
0f43a5 394         public long size;
JM 395         public byte[] content;
396         public boolean deleted;
397
398         public Attachment(String name) {
399             this.name = name;
400         }
401
402         @Override
69a559 403         public int hashCode() {
JM 404             return name.hashCode();
405         }
406
407         @Override
408         public boolean equals(Object o) {
409             if (o instanceof Attachment) {
410                 return name.equalsIgnoreCase(((Attachment) o).name);
411             }
412             return false;
413         }
414
415         @Override
0f43a5 416         public String toString() {
JM 417             return name;
418         }
419     }
420
421     public static enum Field {
69a559 422         Id, Summary, Description, Reporter, Owner, Type, Status, Priority, Milestone, Component, Labels;
0f43a5 423     }
JM 424
425     public static enum Type {
426         Defect, Enhancement, Task, Review, Other;
69a559 427
JM 428         public static Type fromObject(Object o) {
429             if (o instanceof Type) {
430                 // cast and return
431                 return (Type) o;
432             } else if (o instanceof String) {
433                 // find by name
434                 for (Type type : values()) {
435                     String str = o.toString();
436                     if (type.toString().equalsIgnoreCase(str)) {
437                         return type;
438                     }
439                 }
440             } else if (o instanceof Number) {
441                 // by ordinal
442                 int id = ((Number) o).intValue();
443                 if (id >= 0 && id < values().length) {
444                     return values()[id];
445                 }
446             }
447             return null;
448         }
0f43a5 449     }
JM 450
451     public static enum Priority {
452         Low, Medium, High, Critical;
69a559 453
JM 454         public static Priority fromObject(Object o) {
455             if (o instanceof Priority) {
456                 // cast and return
457                 return (Priority) o;
458             } else if (o instanceof String) {
459                 // find by name
460                 for (Priority priority : values()) {
461                     String str = o.toString();
462                     if (priority.toString().equalsIgnoreCase(str)) {
463                         return priority;
464                     }
465                 }
466             } else if (o instanceof Number) {
467                 // by ordinal
468                 int id = ((Number) o).intValue();
469                 if (id >= 0 && id < values().length) {
470                     return values()[id];
471                 }
472             }
473             return null;
474         }
0f43a5 475     }
JM 476
477     public static enum Status {
478         New, Accepted, Started, Review, Queued, Testing, Done, Fixed, WontFix, Duplicate, Invalid;
69a559 479
JM 480         public static Status fromObject(Object o) {
481             if (o instanceof Status) {
482                 // cast and return
483                 return (Status) o;
484             } else if (o instanceof String) {
485                 // find by name
486                 for (Status status : values()) {
487                     String str = o.toString();
488                     if (status.toString().equalsIgnoreCase(str)) {
489                         return status;
490                     }
491                 }
492             } else if (o instanceof Number) {
493                 // by ordinal
494                 int id = ((Number) o).intValue();
495                 if (id >= 0 && id < values().length) {
496                     return values()[id];
497                 }
498             }
499             return null;
500         }
0f43a5 501
JM 502         public boolean atLeast(Status status) {
503             return ordinal() >= status.ordinal();
504         }
505
506         public boolean exceeds(Status status) {
507             return ordinal() > status.ordinal();
508         }
509
69a559 510         public boolean isClosed() {
JM 511             return ordinal() >= Done.ordinal();
512         }
513
0f43a5 514         public Status next() {
JM 515             switch (this) {
516             case New:
517                 return Started;
518             case Accepted:
519                 return Started;
520             case Started:
521                 return Testing;
522             case Review:
523                 return Testing;
524             case Queued:
525                 return Testing;
526             case Testing:
527                 return Done;
528             }
529             return Accepted;
530         }
531     }
532 }