James Moger
2012-02-19 bd01eebfa57b4012bc7a7abc1aaaa1b69278b9de
src/com/gitblit/models/IssueModel.java
New file
@@ -0,0 +1,532 @@
/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.models;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
/**
 * The Gitblit Issue model, its component classes, and enums.
 *
 * @author James Moger
 *
 */
public class IssueModel implements Serializable, Comparable<IssueModel> {
   private static final long serialVersionUID = 1L;
   public String id;
   public Type type;
   public Status status;
   public Priority priority;
   public Date created;
   public String summary;
   public String description;
   public String reporter;
   public String owner;
   public String milestone;
   public List<Change> changes;
   public IssueModel() {
      // the first applied change set the date appropriately
      created = new Date(0);
      type = Type.Defect;
      status = Status.New;
      priority = Priority.Medium;
      changes = new ArrayList<Change>();
   }
   public String getStatus() {
      String s = status.toString();
      if (!StringUtils.isEmpty(owner))
         s += " (" + owner + ")";
      return s;
   }
   public boolean hasLabel(String label) {
      return getLabels().contains(label);
   }
   public List<String> getLabels() {
      List<String> list = new ArrayList<String>();
      String labels = null;
      for (Change change : changes) {
         if (change.hasField(Field.Labels)) {
            labels = change.getString(Field.Labels);
         }
      }
      if (!StringUtils.isEmpty(labels)) {
         list.addAll(StringUtils.getStringsFromValue(labels, " "));
      }
      return list;
   }
   public Attachment getAttachment(String name) {
      Attachment attachment = null;
      for (Change change : changes) {
         if (change.hasAttachments()) {
            Attachment a = change.getAttachment(name);
            if (a != null) {
               attachment = a;
            }
         }
      }
      return attachment;
   }
   public List<Attachment> getAttachments() {
      List<Attachment> list = new ArrayList<Attachment>();
      for (Change change : changes) {
         if (change.hasAttachments()) {
            list.addAll(change.attachments);
         }
      }
      return list;
   }
   public void applyChange(Change change) {
      if (changes.size() == 0) {
         // first change created the issue
         created = change.created;
      }
      changes.add(change);
      if (change.hasFieldChanges()) {
         for (FieldChange fieldChange : change.fieldChanges) {
            switch (fieldChange.field) {
            case Id:
               id = fieldChange.value.toString();
               break;
            case Type:
               type = IssueModel.Type.fromObject(fieldChange.value);
               break;
            case Status:
               status = IssueModel.Status.fromObject(fieldChange.value);
               break;
            case Priority:
               priority = IssueModel.Priority.fromObject(fieldChange.value);
               break;
            case Summary:
               summary = fieldChange.value.toString();
               break;
            case Description:
               description = fieldChange.value.toString();
               break;
            case Reporter:
               reporter = fieldChange.value.toString();
               break;
            case Owner:
               owner = fieldChange.value.toString();
               break;
            case Milestone:
               milestone = fieldChange.value.toString();
               break;
            }
         }
      }
   }
   @Override
   public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append("issue ");
      sb.append(id.substring(0, 8));
      sb.append(" (" + summary + ")\n");
      for (Change change : changes) {
         sb.append(change);
         sb.append('\n');
      }
      return sb.toString();
   }
   @Override
   public int compareTo(IssueModel o) {
      return o.created.compareTo(created);
   }
   @Override
   public boolean equals(Object o) {
      if (o instanceof IssueModel)
         return id.equals(((IssueModel) o).id);
      return super.equals(o);
   }
   @Override
   public int hashCode() {
      return id.hashCode();
   }
   public static class Change implements Serializable, Comparable<Change> {
      private static final long serialVersionUID = 1L;
      public final Date created;
      public final String author;
      public String id;
      public char code;
      public Comment comment;
      public Set<FieldChange> fieldChanges;
      public Set<Attachment> attachments;
      public Change(String author) {
         this.created = new Date((System.currentTimeMillis() / 1000) * 1000);
         this.author = author;
         this.id = StringUtils.getSHA1(created.toString() + author);
      }
      public boolean hasComment() {
         return comment != null && !comment.deleted;
      }
      public void comment(String text) {
         comment = new Comment(text);
         comment.id = StringUtils.getSHA1(created.toString() + author + text);
      }
      public boolean hasAttachments() {
         return !ArrayUtils.isEmpty(attachments);
      }
      public void addAttachment(Attachment attachment) {
         if (attachments == null) {
            attachments = new LinkedHashSet<Attachment>();
         }
         attachments.add(attachment);
      }
      public Attachment getAttachment(String name) {
         for (Attachment attachment : attachments) {
            if (attachment.name.equalsIgnoreCase(name)) {
               return attachment;
            }
         }
         return null;
      }
      public boolean hasField(Field field) {
         return !StringUtils.isEmpty(getString(field));
      }
      public boolean hasFieldChanges() {
         return !ArrayUtils.isEmpty(fieldChanges);
      }
      public Object getField(Field field) {
         if (fieldChanges != null) {
            for (FieldChange fieldChange : fieldChanges) {
               if (fieldChange.field == field) {
                  return fieldChange.value;
               }
            }
         }
         return null;
      }
      public void setField(Field field, Object value) {
         FieldChange fieldChange = new FieldChange(field, value);
         if (fieldChanges == null) {
            fieldChanges = new LinkedHashSet<FieldChange>();
         }
         fieldChanges.add(fieldChange);
      }
      public String getString(Field field) {
         Object value = getField(field);
         if (value == null) {
            return null;
         }
         return value.toString();
      }
      @Override
      public int compareTo(Change c) {
         return created.compareTo(c.created);
      }
      @Override
      public int hashCode() {
         return id.hashCode();
      }
      @Override
      public boolean equals(Object o) {
         if (o instanceof Change) {
            return id.equals(((Change) o).id);
         }
         return false;
      }
      @Override
      public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append(TimeUtils.timeAgo(created));
         switch (code) {
         case '+':
            sb.append(" created by ");
            break;
         default:
            if (hasComment()) {
               sb.append(" commented on by ");
            } else {
               sb.append(" changed by ");
            }
         }
         sb.append(author).append(" - ");
         if (hasComment()) {
            if (comment.deleted) {
               sb.append("(deleted) ");
            }
            sb.append(comment.text).append(" ");
         }
         if (hasFieldChanges()) {
            switch (code) {
            case '+':
               break;
            default:
               for (FieldChange fieldChange : fieldChanges) {
                  sb.append("\n  ");
                  sb.append(fieldChange);
               }
               break;
            }
         }
         return sb.toString();
      }
   }
   public static class Comment implements Serializable {
      private static final long serialVersionUID = 1L;
      public String text;
      public String id;
      public boolean deleted;
      Comment(String text) {
         this.text = text;
      }
      @Override
      public String toString() {
         return text;
      }
   }
   public static class FieldChange implements Serializable {
      private static final long serialVersionUID = 1L;
      public final Field field;
      public final Object value;
      FieldChange(Field field, Object value) {
         this.field = field;
         this.value = value;
      }
      @Override
      public int hashCode() {
         return field.hashCode();
      }
      @Override
      public boolean equals(Object o) {
         if (o instanceof FieldChange) {
            return field.equals(((FieldChange) o).field);
         }
         return false;
      }
      @Override
      public String toString() {
         return field + ": " + value;
      }
   }
   public static class Attachment implements Serializable {
      private static final long serialVersionUID = 1L;
      public final String name;
      public String id;
      public long size;
      public byte[] content;
      public boolean deleted;
      public Attachment(String name) {
         this.name = name;
      }
      @Override
      public int hashCode() {
         return name.hashCode();
      }
      @Override
      public boolean equals(Object o) {
         if (o instanceof Attachment) {
            return name.equalsIgnoreCase(((Attachment) o).name);
         }
         return false;
      }
      @Override
      public String toString() {
         return name;
      }
   }
   public static enum Field {
      Id, Summary, Description, Reporter, Owner, Type, Status, Priority, Milestone, Component, Labels;
   }
   public static enum Type {
      Defect, Enhancement, Task, Review, Other;
      public static Type fromObject(Object o) {
         if (o instanceof Type) {
            // cast and return
            return (Type) o;
         } else if (o instanceof String) {
            // find by name
            for (Type type : values()) {
               String str = o.toString();
               if (type.toString().equalsIgnoreCase(str)) {
                  return type;
               }
            }
         } else if (o instanceof Number) {
            // by ordinal
            int id = ((Number) o).intValue();
            if (id >= 0 && id < values().length) {
               return values()[id];
            }
         }
         return null;
      }
   }
   public static enum Priority {
      Low, Medium, High, Critical;
      public static Priority fromObject(Object o) {
         if (o instanceof Priority) {
            // cast and return
            return (Priority) o;
         } else if (o instanceof String) {
            // find by name
            for (Priority priority : values()) {
               String str = o.toString();
               if (priority.toString().equalsIgnoreCase(str)) {
                  return priority;
               }
            }
         } else if (o instanceof Number) {
            // by ordinal
            int id = ((Number) o).intValue();
            if (id >= 0 && id < values().length) {
               return values()[id];
            }
         }
         return null;
      }
   }
   public static enum Status {
      New, Accepted, Started, Review, Queued, Testing, Done, Fixed, WontFix, Duplicate, Invalid;
      public static Status fromObject(Object o) {
         if (o instanceof Status) {
            // cast and return
            return (Status) o;
         } else if (o instanceof String) {
            // find by name
            for (Status status : values()) {
               String str = o.toString();
               if (status.toString().equalsIgnoreCase(str)) {
                  return status;
               }
            }
         } else if (o instanceof Number) {
            // by ordinal
            int id = ((Number) o).intValue();
            if (id >= 0 && id < values().length) {
               return values()[id];
            }
         }
         return null;
      }
      public boolean atLeast(Status status) {
         return ordinal() >= status.ordinal();
      }
      public boolean exceeds(Status status) {
         return ordinal() > status.ordinal();
      }
      public boolean isClosed() {
         return ordinal() >= Done.ordinal();
      }
      public Status next() {
         switch (this) {
         case New:
            return Started;
         case Accepted:
            return Started;
         case Started:
            return Testing;
         case Review:
            return Testing;
         case Queued:
            return Testing;
         case Testing:
            return Done;
         }
         return Accepted;
      }
   }
}