123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431 |
- /*
- * Copyright 2014 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.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- import java.io.UnsupportedEncodingException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.text.MessageFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.NoSuchElementException;
- import java.util.Set;
- import java.util.TreeSet;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
-
- import org.eclipse.jgit.util.RelativeDateFormatter;
-
- /**
- * The Gitblit Ticket model, its component classes, and enums.
- *
- * @author James Moger
- *
- */
- public class TicketModel implements Serializable, Comparable<TicketModel> {
-
- private static final long serialVersionUID = 1L;
-
- public String project;
-
- public String repository;
-
- public long number;
-
- public Date created;
-
- public String createdBy;
-
- public Date updated;
-
- public String updatedBy;
-
- public String title;
-
- public String body;
-
- public String topic;
-
- public Type type;
-
- public Status status;
-
- public String responsible;
-
- public String milestone;
-
- public String mergeSha;
-
- public String mergeTo;
-
- public List<Change> changes;
-
- public Integer insertions;
-
- public Integer deletions;
-
- public Priority priority;
-
- public Severity severity;
-
- /**
- * Builds an effective ticket from the collection of changes. A change may
- * Add or Subtract information from a ticket, but the collection of changes
- * is only additive.
- *
- * @param changes
- * @return the effective ticket
- */
- public static TicketModel buildTicket(Collection<Change> changes) {
- TicketModel ticket;
- List<Change> effectiveChanges = new ArrayList<Change>();
- Map<String, Change> comments = new HashMap<String, Change>();
- for (Change change : changes) {
- if (change.comment != null) {
- if (comments.containsKey(change.comment.id)) {
- Change original = comments.get(change.comment.id);
- Change clone = copy(original);
- clone.comment.text = change.comment.text;
- clone.comment.deleted = change.comment.deleted;
- int idx = effectiveChanges.indexOf(original);
- effectiveChanges.remove(original);
- effectiveChanges.add(idx, clone);
- comments.put(clone.comment.id, clone);
- } else {
- effectiveChanges.add(change);
- comments.put(change.comment.id, change);
- }
- } else {
- effectiveChanges.add(change);
- }
- }
-
- // effective ticket
- ticket = new TicketModel();
- for (Change change : effectiveChanges) {
- if (!change.hasComment()) {
- // ensure we do not include a deleted comment
- change.comment = null;
- }
- ticket.applyChange(change);
- }
- return ticket;
- }
-
- public TicketModel() {
- // the first applied change set the date appropriately
- created = new Date(0);
- changes = new ArrayList<Change>();
- status = Status.New;
- type = Type.defaultType;
- priority = Priority.defaultPriority;
- severity = Severity.defaultSeverity;
- }
-
- public boolean isOpen() {
- return !status.isClosed();
- }
-
- public boolean isClosed() {
- return status.isClosed();
- }
-
- public boolean isMerged() {
- return isClosed() && !isEmpty(mergeSha);
- }
-
- public boolean isProposal() {
- return Type.Proposal == type;
- }
-
- public boolean isBug() {
- return Type.Bug == type;
- }
-
- public Date getLastUpdated() {
- return updated == null ? created : updated;
- }
-
- public boolean hasPatchsets() {
- return getPatchsets().size() > 0;
- }
-
- /**
- * Returns true if multiple participants are involved in discussing a ticket.
- * The ticket creator is excluded from this determination because a
- * discussion requires more than one participant.
- *
- * @return true if this ticket has a discussion
- */
- public boolean hasDiscussion() {
- for (Change change : getComments()) {
- if (!change.author.equals(createdBy)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the list of changes with comments.
- *
- * @return
- */
- public List<Change> getComments() {
- List<Change> list = new ArrayList<Change>();
- for (Change change : changes) {
- if (change.hasComment()) {
- list.add(change);
- }
- }
- return list;
- }
-
- /**
- * Returns the list of participants for the ticket.
- *
- * @return the list of participants
- */
- public List<String> getParticipants() {
- Set<String> set = new LinkedHashSet<String>();
- for (Change change : changes) {
- if (change.isParticipantChange()) {
- set.add(change.author);
- }
- }
- if (responsible != null && responsible.length() > 0) {
- set.add(responsible);
- }
- return new ArrayList<String>(set);
- }
-
- public boolean hasLabel(String label) {
- return getLabels().contains(label);
- }
-
- public List<String> getLabels() {
- return getList(Field.labels);
- }
-
- public boolean isResponsible(String username) {
- return username.equals(responsible);
- }
-
- public boolean isAuthor(String username) {
- return username.equals(createdBy);
- }
-
- public boolean isReviewer(String username) {
- return getReviewers().contains(username);
- }
-
- public List<String> getReviewers() {
- return getList(Field.reviewers);
- }
-
- public boolean isWatching(String username) {
- return getWatchers().contains(username);
- }
-
- public List<String> getWatchers() {
- return getList(Field.watchers);
- }
-
- public boolean isVoter(String username) {
- return getVoters().contains(username);
- }
-
- public List<String> getVoters() {
- return getList(Field.voters);
- }
-
- public List<String> getMentions() {
- return getList(Field.mentions);
- }
-
- protected List<String> getList(Field field) {
- Set<String> set = new TreeSet<String>();
- for (Change change : changes) {
- if (change.hasField(field)) {
- String values = change.getString(field);
- for (String value : values.split(",")) {
- switch (value.charAt(0)) {
- case '+':
- set.add(value.substring(1));
- break;
- case '-':
- set.remove(value.substring(1));
- break;
- default:
- set.add(value);
- }
- }
- }
- }
- if (!set.isEmpty()) {
- return new ArrayList<String>(set);
- }
- return Collections.emptyList();
- }
-
- 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 boolean hasAttachments() {
- for (Change change : changes) {
- if (change.hasAttachments()) {
- return true;
- }
- }
- return false;
- }
-
- public List<Attachment> getAttachments() {
- List<Attachment> list = new ArrayList<Attachment>();
- for (Change change : changes) {
- if (change.hasAttachments()) {
- list.addAll(change.attachments);
- }
- }
- return list;
- }
-
- public List<Patchset> getPatchsets() {
- List<Patchset> list = new ArrayList<Patchset>();
- for (Change change : changes) {
- if (change.patchset != null) {
- list.add(change.patchset);
- }
- }
- return list;
- }
-
- public List<Patchset> getPatchsetRevisions(int number) {
- List<Patchset> list = new ArrayList<Patchset>();
- for (Change change : changes) {
- if (change.patchset != null) {
- if (number == change.patchset.number) {
- list.add(change.patchset);
- }
- }
- }
- return list;
- }
-
- public Patchset getPatchset(String sha) {
- for (Change change : changes) {
- if (change.patchset != null) {
- if (sha.equals(change.patchset.tip)) {
- return change.patchset;
- }
- }
- }
- return null;
- }
-
- public Patchset getPatchset(int number, int rev) {
- for (Change change : changes) {
- if (change.patchset != null) {
- if (number == change.patchset.number && rev == change.patchset.rev) {
- return change.patchset;
- }
- }
- }
- return null;
- }
-
- public Patchset getCurrentPatchset() {
- Patchset patchset = null;
- for (Change change : changes) {
- if (change.patchset != null) {
- if (patchset == null) {
- patchset = change.patchset;
- } else if (patchset.compareTo(change.patchset) == 1) {
- patchset = change.patchset;
- }
- }
- }
- return patchset;
- }
-
- public boolean isCurrent(Patchset patchset) {
- if (patchset == null) {
- return false;
- }
- Patchset curr = getCurrentPatchset();
- if (curr == null) {
- return false;
- }
- return curr.equals(patchset);
- }
-
- public List<Change> getReviews(Patchset patchset) {
- if (patchset == null) {
- return Collections.emptyList();
- }
- // collect the patchset reviews by author
- // the last review by the author is the
- // official review
- Map<String, Change> reviews = new LinkedHashMap<String, TicketModel.Change>();
- for (Change change : changes) {
- if (change.hasReview()) {
- if (change.review.isReviewOf(patchset)) {
- reviews.put(change.author, change);
- }
- }
- }
- return new ArrayList<Change>(reviews.values());
- }
-
-
- public boolean isApproved(Patchset patchset) {
- if (patchset == null) {
- return false;
- }
- boolean approved = false;
- boolean vetoed = false;
- for (Change change : getReviews(patchset)) {
- if (change.hasReview()) {
- if (change.review.isReviewOf(patchset)) {
- if (Score.approved == change.review.score) {
- approved = true;
- } else if (Score.vetoed == change.review.score) {
- vetoed = true;
- }
- }
- }
- }
- return approved && !vetoed;
- }
-
- public boolean isVetoed(Patchset patchset) {
- if (patchset == null) {
- return false;
- }
- for (Change change : getReviews(patchset)) {
- if (change.hasReview()) {
- if (change.review.isReviewOf(patchset)) {
- if (Score.vetoed == change.review.score) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public Review getReviewBy(String username) {
- for (Change change : getReviews(getCurrentPatchset())) {
- if (change.author.equals(username)) {
- return change.review;
- }
- }
- return null;
- }
-
- public boolean isPatchsetAuthor(String username) {
- for (Change change : changes) {
- if (change.hasPatchset()) {
- if (change.author.equals(username)) {
- return true;
- }
- }
- }
- return false;
- }
-
- public void applyChange(Change change) {
- if (changes.size() == 0) {
- // first change created the ticket
- created = change.date;
- createdBy = change.author;
- status = Status.New;
- } else if (created == null || change.date.after(created)) {
- // track last ticket update
- updated = change.date;
- updatedBy = change.author;
- }
-
- if (change.isMerge()) {
- // identify merge patchsets
- if (isEmpty(responsible)) {
- responsible = change.author;
- }
- status = Status.Merged;
- }
-
- if (change.hasFieldChanges()) {
- for (Map.Entry<Field, String> entry : change.fields.entrySet()) {
- Field field = entry.getKey();
- Object value = entry.getValue();
- switch (field) {
- case type:
- type = TicketModel.Type.fromObject(value, type);
- break;
- case status:
- status = TicketModel.Status.fromObject(value, status);
- break;
- case title:
- title = toString(value);
- break;
- case body:
- body = toString(value);
- break;
- case topic:
- topic = toString(value);
- break;
- case responsible:
- responsible = toString(value);
- break;
- case milestone:
- milestone = toString(value);
- break;
- case mergeTo:
- mergeTo = toString(value);
- break;
- case mergeSha:
- mergeSha = toString(value);
- break;
- case priority:
- priority = TicketModel.Priority.fromObject(value, priority);
- break;
- case severity:
- severity = TicketModel.Severity.fromObject(value, severity);
- break;
- default:
- // unknown
- break;
- }
- }
- }
-
- // add the change to the ticket
- changes.add(change);
- }
-
- protected String toString(Object value) {
- if (value == null) {
- return null;
- }
- return value.toString();
- }
-
- public String toIndexableString() {
- StringBuilder sb = new StringBuilder();
- if (!isEmpty(title)) {
- sb.append(title).append('\n');
- }
- if (!isEmpty(body)) {
- sb.append(body).append('\n');
- }
- for (Change change : changes) {
- if (change.hasComment()) {
- sb.append(change.comment.text);
- sb.append('\n');
- }
- }
- return sb.toString();
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("#");
- sb.append(number);
- sb.append(": " + title + "\n");
- for (Change change : changes) {
- sb.append(change);
- sb.append('\n');
- }
- return sb.toString();
- }
-
- @Override
- public int compareTo(TicketModel o) {
- return o.created.compareTo(created);
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof TicketModel) {
- return number == ((TicketModel) o).number;
- }
- return super.equals(o);
- }
-
- @Override
- public int hashCode() {
- return (repository + number).hashCode();
- }
-
- /**
- * Encapsulates a ticket change
- */
- public static class Change implements Serializable, Comparable<Change> {
-
- private static final long serialVersionUID = 1L;
-
- public final Date date;
-
- public final String author;
-
- public Comment comment;
-
- public Map<Field, String> fields;
-
- public Set<Attachment> attachments;
-
- public Patchset patchset;
-
- public Review review;
-
- private transient String id;
-
- public Change(String author) {
- this(author, new Date());
- }
-
- public Change(String author, Date date) {
- this.date = date;
- this.author = author;
- }
-
- public boolean isStatusChange() {
- return hasField(Field.status);
- }
-
- public Status getStatus() {
- Status state = Status.fromObject(getField(Field.status), null);
- return state;
- }
-
- public boolean isMerge() {
- return hasField(Field.status) && hasField(Field.mergeSha);
- }
-
- public boolean hasPatchset() {
- return patchset != null;
- }
-
- public boolean hasReview() {
- return review != null;
- }
-
- public boolean hasComment() {
- return comment != null && !comment.isDeleted() && comment.text != null;
- }
-
- public Comment comment(String text) {
- comment = new Comment(text);
- comment.id = TicketModel.getSHA1(date.toString() + author + text);
-
- try {
- Pattern mentions = Pattern.compile("\\s@([A-Za-z0-9-_]+)");
- Matcher m = mentions.matcher(text);
- while (m.find()) {
- String username = m.group(1);
- plusList(Field.mentions, username);
- }
- } catch (Exception e) {
- // ignore
- }
- return comment;
- }
-
- public Review review(Patchset patchset, Score score, boolean addReviewer) {
- if (addReviewer) {
- plusList(Field.reviewers, author);
- }
- review = new Review(patchset.number, patchset.rev);
- review.score = score;
- return review;
- }
-
- public boolean hasAttachments() {
- return !TicketModel.isEmpty(attachments);
- }
-
- public void addAttachment(Attachment attachment) {
- if (attachments == null) {
- attachments = new LinkedHashSet<Attachment>();
- }
- attachments.add(attachment);
- }
-
- public Attachment getAttachment(String name) {
- if (attachments != null) {
- for (Attachment attachment : attachments) {
- if (attachment.name.equalsIgnoreCase(name)) {
- return attachment;
- }
- }
- }
- return null;
- }
-
- public boolean isParticipantChange() {
- if (hasComment()
- || hasReview()
- || hasPatchset()
- || hasAttachments()) {
- return true;
- }
-
- if (TicketModel.isEmpty(fields)) {
- return false;
- }
-
- // identify real ticket field changes
- Map<Field, String> map = new HashMap<Field, String>(fields);
- map.remove(Field.watchers);
- map.remove(Field.voters);
- return !map.isEmpty();
- }
-
- public boolean hasField(Field field) {
- return !TicketModel.isEmpty(getString(field));
- }
-
- public boolean hasFieldChanges() {
- return !TicketModel.isEmpty(fields);
- }
-
- public String getField(Field field) {
- if (fields != null) {
- return fields.get(field);
- }
- return null;
- }
-
- public void setField(Field field, Object value) {
- if (fields == null) {
- fields = new LinkedHashMap<Field, String>();
- }
- if (value == null) {
- fields.put(field, null);
- } else if (Enum.class.isAssignableFrom(value.getClass())) {
- fields.put(field, ((Enum<?>) value).name());
- } else {
- fields.put(field, value.toString());
- }
- }
-
- public void remove(Field field) {
- if (fields != null) {
- fields.remove(field);
- }
- }
-
- public String getString(Field field) {
- String value = getField(field);
- if (value == null) {
- return null;
- }
- return value;
- }
-
- public void watch(String... username) {
- plusList(Field.watchers, username);
- }
-
- public void unwatch(String... username) {
- minusList(Field.watchers, username);
- }
-
- public void vote(String... username) {
- plusList(Field.voters, username);
- }
-
- public void unvote(String... username) {
- minusList(Field.voters, username);
- }
-
- public void label(String... label) {
- plusList(Field.labels, label);
- }
-
- public void unlabel(String... label) {
- minusList(Field.labels, label);
- }
-
- protected void plusList(Field field, String... items) {
- modList(field, "+", items);
- }
-
- protected void minusList(Field field, String... items) {
- modList(field, "-", items);
- }
-
- private void modList(Field field, String prefix, String... items) {
- List<String> list = new ArrayList<String>();
- for (String item : items) {
- list.add(prefix + item);
- }
- if (hasField(field)) {
- String flat = getString(field);
- if (isEmpty(flat)) {
- // field is empty, use this list
- setField(field, join(list, ","));
- } else {
- // merge this list into the existing field list
- Set<String> set = new TreeSet<String>(Arrays.asList(flat.split(",")));
- set.addAll(list);
- setField(field, join(set, ","));
- }
- } else {
- // does not have a list for this field
- setField(field, join(list, ","));
- }
- }
-
- public String getId() {
- if (id == null) {
- id = getSHA1(Long.toHexString(date.getTime()) + author);
- }
- return id;
- }
-
- @Override
- public int compareTo(Change c) {
- return date.compareTo(c.date);
- }
-
- @Override
- public int hashCode() {
- return getId().hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof Change) {
- return getId().equals(((Change) o).getId());
- }
- return false;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(RelativeDateFormatter.format(date));
- if (hasComment()) {
- sb.append(" commented on by ");
- } else if (hasPatchset()) {
- sb.append(MessageFormat.format(" {0} uploaded by ", patchset));
- } else {
- sb.append(" changed by ");
- }
- sb.append(author).append(" - ");
- if (hasComment()) {
- if (comment.isDeleted()) {
- sb.append("(deleted) ");
- }
- sb.append(comment.text).append(" ");
- }
-
- if (hasFieldChanges()) {
- for (Map.Entry<Field, String> entry : fields.entrySet()) {
- sb.append("\n ");
- sb.append(entry.getKey().name());
- sb.append(':');
- sb.append(entry.getValue());
- }
- }
- return sb.toString();
- }
- }
-
- /**
- * Returns true if the string is null or empty.
- *
- * @param value
- * @return true if string is null or empty
- */
- static boolean isEmpty(String value) {
- return value == null || value.trim().length() == 0;
- }
-
- /**
- * Returns true if the collection is null or empty
- *
- * @param collection
- * @return
- */
- static boolean isEmpty(Collection<?> collection) {
- return collection == null || collection.size() == 0;
- }
-
- /**
- * Returns true if the map is null or empty
- *
- * @param map
- * @return
- */
- static boolean isEmpty(Map<?, ?> map) {
- return map == null || map.size() == 0;
- }
-
- /**
- * Calculates the SHA1 of the string.
- *
- * @param text
- * @return sha1 of the string
- */
- static String getSHA1(String text) {
- try {
- byte[] bytes = text.getBytes("iso-8859-1");
- return getSHA1(bytes);
- } catch (UnsupportedEncodingException u) {
- throw new RuntimeException(u);
- }
- }
-
- /**
- * Calculates the SHA1 of the byte array.
- *
- * @param bytes
- * @return sha1 of the byte array
- */
- static String getSHA1(byte[] bytes) {
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- md.update(bytes, 0, bytes.length);
- byte[] digest = md.digest();
- return toHex(digest);
- } catch (NoSuchAlgorithmException t) {
- throw new RuntimeException(t);
- }
- }
-
- /**
- * Returns the hex representation of the byte array.
- *
- * @param bytes
- * @return byte array as hex string
- */
- static String toHex(byte[] bytes) {
- StringBuilder sb = new StringBuilder(bytes.length * 2);
- for (int i = 0; i < bytes.length; i++) {
- if ((bytes[i] & 0xff) < 0x10) {
- sb.append('0');
- }
- sb.append(Long.toString(bytes[i] & 0xff, 16));
- }
- return sb.toString();
- }
-
- /**
- * Join the list of strings into a single string with a space separator.
- *
- * @param values
- * @return joined list
- */
- static String join(Collection<String> values) {
- return join(values, " ");
- }
-
- /**
- * Join the list of strings into a single string with the specified
- * separator.
- *
- * @param values
- * @param separator
- * @return joined list
- */
- static String join(String[] values, String separator) {
- return join(Arrays.asList(values), separator);
- }
-
- /**
- * Join the list of strings into a single string with the specified
- * separator.
- *
- * @param values
- * @param separator
- * @return joined list
- */
- static String join(Collection<String> values, String separator) {
- StringBuilder sb = new StringBuilder();
- for (String value : values) {
- sb.append(value).append(separator);
- }
- if (sb.length() > 0) {
- // truncate trailing separator
- sb.setLength(sb.length() - separator.length());
- }
- return sb.toString().trim();
- }
-
-
- /**
- * Produce a deep copy of the given object. Serializes the entire object to
- * a byte array in memory. Recommended for relatively small objects.
- */
- @SuppressWarnings("unchecked")
- static <T> T copy(T original) {
- T o = null;
- try {
- ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(byteOut);
- oos.writeObject(original);
- ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
- ObjectInputStream ois = new ObjectInputStream(byteIn);
- try {
- o = (T) ois.readObject();
- } catch (ClassNotFoundException cex) {
- // actually can not happen in this instance
- }
- } catch (IOException iox) {
- // doesn't seem likely to happen as these streams are in memory
- throw new RuntimeException(iox);
- }
- return o;
- }
-
- public static class Patchset implements Serializable, Comparable<Patchset> {
-
- private static final long serialVersionUID = 1L;
-
- public int number;
- public int rev;
- public String tip;
- public String parent;
- public String base;
- public int insertions;
- public int deletions;
- public int commits;
- public int added;
- public PatchsetType type;
-
- public boolean isFF() {
- return PatchsetType.FastForward == type;
- }
-
- @Override
- public int hashCode() {
- return toString().hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof Patchset) {
- return hashCode() == o.hashCode();
- }
- return false;
- }
-
- @Override
- public int compareTo(Patchset p) {
- if (number > p.number) {
- return -1;
- } else if (p.number > number) {
- return 1;
- } else {
- // same patchset, different revision
- if (rev > p.rev) {
- return -1;
- } else if (p.rev > rev) {
- return 1;
- } else {
- // same patchset & revision
- return 0;
- }
- }
- }
-
- @Override
- public String toString() {
- return "patchset " + number + " revision " + rev;
- }
- }
-
- public static class Comment implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- public String text;
-
- public String id;
-
- public Boolean deleted;
-
- public CommentSource src;
-
- public String replyTo;
-
- Comment(String text) {
- this.text = text;
- }
-
- public boolean isDeleted() {
- return deleted != null && deleted;
- }
-
- @Override
- public String toString() {
- return text;
- }
- }
-
- public static class Attachment implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- public final String name;
- public long size;
- public byte[] content;
- public Boolean deleted;
-
- public Attachment(String name) {
- this.name = name;
- }
-
- public boolean isDeleted() {
- return deleted != null && deleted;
- }
-
- @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 class Review implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- public final int patchset;
-
- public final int rev;
-
- public Score score;
-
- public Review(int patchset, int revision) {
- this.patchset = patchset;
- this.rev = revision;
- }
-
- public boolean isReviewOf(Patchset p) {
- return patchset == p.number && rev == p.rev;
- }
-
- @Override
- public String toString() {
- return "review of patchset " + patchset + " rev " + rev + ":" + score;
- }
- }
-
- public static enum Score {
- approved(2), looks_good(1), not_reviewed(0), needs_improvement(-1), vetoed(
- -2);
-
- final int value;
-
- Score(int value) {
- this.value = value;
- }
-
- public int getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return name().toLowerCase().replace('_', ' ');
- }
-
- public static Score fromScore(int score) {
- for (Score s : values()) {
- if (s.getValue() == score) {
- return s;
- }
- }
- throw new NoSuchElementException(String.valueOf(score));
- }
- }
-
- public static enum Field {
- title, body, responsible, type, status, milestone, mergeSha, mergeTo,
- topic, labels, watchers, reviewers, voters, mentions, priority, severity;
- }
-
- public static enum Type {
- Enhancement, Task, Bug, Proposal, Question, Maintenance;
-
- public static Type defaultType = Task;
-
- public static Type [] choices() {
- return new Type [] { Enhancement, Task, Bug, Question, Maintenance };
- }
-
- @Override
- public String toString() {
- return name().toLowerCase().replace('_', ' ');
- }
-
- public static Type fromObject(Object o, Type defaultType) {
- 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.name().equalsIgnoreCase(str)
- || 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 defaultType;
- }
- }
-
- public static enum Status {
- New, Open, Closed, Resolved, Fixed, Merged, Wontfix, Declined, Duplicate, Invalid, Abandoned, On_Hold, No_Change_Required;
-
- public static Status [] requestWorkflow = { Open, Resolved, Declined, Duplicate, Invalid, Abandoned, On_Hold, No_Change_Required };
-
- public static Status [] bugWorkflow = { Open, Fixed, Wontfix, Duplicate, Invalid, Abandoned, On_Hold, No_Change_Required };
-
- public static Status [] proposalWorkflow = { Open, Resolved, Declined, Abandoned, On_Hold, No_Change_Required };
-
- public static Status [] milestoneWorkflow = { Open, Closed, Abandoned, On_Hold };
-
- @Override
- public String toString() {
- return name().toLowerCase().replace('_', ' ');
- }
-
- public static Status fromObject(Object o, Status defaultStatus) {
- if (o instanceof Status) {
- // cast and return
- return (Status) o;
- } else if (o instanceof String) {
- // find by name
- String name = o.toString();
- for (Status state : values()) {
- if (state.name().equalsIgnoreCase(name)
- || state.toString().equalsIgnoreCase(name)) {
- return state;
- }
- }
- } else if (o instanceof Number) {
- // by ordinal
- int id = ((Number) o).intValue();
- if (id >= 0 && id < values().length) {
- return values()[id];
- }
- }
-
- return defaultStatus;
- }
-
- public boolean isClosed() {
- return ordinal() > Open.ordinal();
- }
- }
-
- public static enum CommentSource {
- Comment, Email
- }
-
- public static enum PatchsetType {
- Proposal, FastForward, Rebase, Squash, Rebase_Squash, Amend;
-
- public boolean isRewrite() {
- return (this != FastForward) && (this != Proposal);
- }
-
- @Override
- public String toString() {
- return name().toLowerCase().replace('_', '+');
- }
-
- public static PatchsetType fromObject(Object o) {
- if (o instanceof PatchsetType) {
- // cast and return
- return (PatchsetType) o;
- } else if (o instanceof String) {
- // find by name
- String name = o.toString();
- for (PatchsetType type : values()) {
- if (type.name().equalsIgnoreCase(name)
- || type.toString().equalsIgnoreCase(name)) {
- 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(-1), Normal(0), High(1), Urgent(2);
-
- public static Priority defaultPriority = Normal;
-
- final int value;
-
- Priority(int value) {
- this.value = value;
- }
-
- public int getValue() {
- return value;
- }
-
- public static Priority [] choices() {
- return new Priority [] { Urgent, High, Normal, Low };
- }
-
- @Override
- public String toString() {
- return name().toLowerCase().replace('_', ' ');
- }
-
- public static Priority fromObject(Object o, Priority defaultPriority) {
- 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.name().equalsIgnoreCase(str)
- || priority.toString().equalsIgnoreCase(str)) {
- return priority;
- }
- }
- } else if (o instanceof Number) {
-
- switch (((Number) o).intValue()) {
- case -1: return Priority.Low;
- case 0: return Priority.Normal;
- case 1: return Priority.High;
- case 2: return Priority.Urgent;
- default: return Priority.Normal;
- }
- }
-
- return defaultPriority;
- }
- }
-
- public static enum Severity {
- Unrated(-1), Negligible(1), Minor(2), Serious(3), Critical(4), Catastrophic(5);
-
- public static Severity defaultSeverity = Unrated;
-
- final int value;
-
- Severity(int value) {
- this.value = value;
- }
-
- public int getValue() {
- return value;
- }
-
- public static Severity [] choices() {
- return new Severity [] { Unrated, Negligible, Minor, Serious, Critical, Catastrophic };
- }
-
- @Override
- public String toString() {
- return name().toLowerCase().replace('_', ' ');
- }
-
- public static Severity fromObject(Object o, Severity defaultSeverity) {
- if (o instanceof Severity) {
- // cast and return
- return (Severity) o;
- } else if (o instanceof String) {
- // find by name
- for (Severity severity : values()) {
- String str = o.toString();
- if (severity.name().equalsIgnoreCase(str)
- || severity.toString().equalsIgnoreCase(str)) {
- return severity;
- }
- }
- } else if (o instanceof Number) {
-
- switch (((Number) o).intValue()) {
- case -1: return Severity.Unrated;
- case 1: return Severity.Negligible;
- case 2: return Severity.Minor;
- case 3: return Severity.Serious;
- case 4: return Severity.Critical;
- case 5: return Severity.Catastrophic;
- default: return Severity.Unrated;
- }
- }
-
- return defaultSeverity;
- }
- }
- }
|