/* * 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 { 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 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(); } 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 getLabels() { List list = new ArrayList(); 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 getAttachments() { List list = new ArrayList(); 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 { 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 fieldChanges; public Set 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(); } 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(); } 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(new 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; } } }