/* * Copyright 2017 ThoughtWorks, Inc. * * 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.thoughtworks.go.domain.materials; import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.thoughtworks.go.domain.MaterialInstance; import com.thoughtworks.go.domain.ModificationVisitor; import com.thoughtworks.go.domain.PersistentObject; import com.thoughtworks.go.domain.materials.mercurial.StringRevision; import com.thoughtworks.go.util.GoConstants; import com.thoughtworks.go.util.json.JsonHelper; import org.apache.commons.lang.StringUtils; /** * data structure for holding data about a single modification * to a source control tool. * <p/> * <modification type="" date="" user="" email=""> * <comment></comment> * <file > * </modification> * * @author <a href="mailto:alden@thoughtworks.com">alden almagro</a> */ public class Modification extends PersistentObject implements Comparable, Serializable { private static final long serialVersionUID = 6102576575583133520L; public static final Modification NEVER = new Modification(GoConstants.NEVER); public static final String ANONYMOUS = "anonymous"; private String userName = ""; private String comment = ""; private String emailAddress; private String revision; private String additionalData; private HashMap<String, String> additionalDataMap; private Date modifiedTime; private Set<ModifiedFile> files = new LinkedHashSet<>(); private MaterialInstance materialInstance; private String pipelineLabel; private Long pipelineId; public Modification() { } private Modification(Date datetime) { this.modifiedTime = datetime; } public Modification(Date date, String revision, String pipelineLabel, Long pipelineId) { this("Unknown", "Unknown", null, date, revision); this.pipelineLabel = pipelineLabel; this.pipelineId = pipelineId; } public Modification(String user, String comment, String email, Date dateTime, String revision) { this.userName = user; this.comment = comment; this.emailAddress = email; this.modifiedTime = dateTime; this.revision = revision; } public Modification(Modification modification) { this(modification, true); } public Modification(String user, String comment, String email, Date dateTime, String revision, String additionalData) { this(user, comment, email, dateTime, revision); setAdditionalData(additionalData); } public Modification(Modification modification, boolean shouldCopyModifiedFiles) { this(modification.userName, modification.comment, modification.emailAddress, modification.modifiedTime, modification.getRevision()); this.id = modification.id; if(shouldCopyModifiedFiles){ this.files = modification.files; } this.pipelineLabel = modification.pipelineLabel; this.pipelineId = modification.pipelineId; this.materialInstance = modification.materialInstance; this.additionalData = modification.additionalData; this.additionalDataMap = modification.additionalDataMap; } public final ModifiedFile createModifiedFile(String filename, String folder, ModifiedAction modifiedAction) { ModifiedFile file = new ModifiedFile(filename, folder, modifiedAction); files.add(file); return file; } public HashMap<String, String> getAdditionalDataMap() { return additionalDataMap == null ? new HashMap<>(): additionalDataMap; } public void setAdditionalData(String additionalData) { this.additionalData = additionalData; this.additionalDataMap = JsonHelper.safeFromJson(this.additionalData, HashMap.class); } /** * @deprecated used only by material parsers and in test */ public void setUserName(String name) { this.userName = name; } /** * @deprecated used only by material parsers and in tests */ public void setEmailAddress(String email) { this.emailAddress = email; } /** * @deprecated used only by material parsers and in tests */ public void setComment(String comment) { this.comment = comment; } /** * @deprecated used only by material parsers and in tests */ public void setRevision(String revision) { this.revision = revision; } /** * @deprecated used only by material parsers and in tests */ public void setModifiedTime(Date modifiedTime) { this.modifiedTime = modifiedTime; } /** * @deprecated used only in tests */ public void setModifiedFiles(List<ModifiedFile> files) { this.files = files == null ? new LinkedHashSet<>() : new LinkedHashSet<>(files); } /** * Returns the list of modified files for this modification set. * * @return list of {@link ModifiedFile} objects. If there are no files, this returns an empty list * (<code>null</code> is never returned). */ public List<ModifiedFile> getModifiedFiles() { return Collections.unmodifiableList(new ArrayList<>(files)); } public int compareTo(Object o) { Modification modification = (Modification) o; return modifiedTime.compareTo(modification.modifiedTime); } public Date getModifiedTime() { return modifiedTime; } public String getUserName() { return userName; } public String getUserDisplayName() { return StringUtils.isBlank(userName) ? ANONYMOUS : userName; } public String getRevision() { return revision; } public Long getPipelineId() { return pipelineId; } public String getComment() { return comment; } public String toString() { SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); StringBuffer sb = new StringBuffer(); if (materialInstance != null) { sb.append("Material: ").append(materialInstance).append('\n'); } String timeString = modifiedTime == null ? "" : formatter.format(modifiedTime); sb.append("Last Modified: ").append(timeString).append('\n'); sb.append("Revision: ").append(revision).append('\n'); sb.append("UserName: ").append(userName).append('\n'); sb.append("EmailAddress: ").append(emailAddress).append('\n'); sb.append("Comment: ").append(comment).append('\n'); sb.append("PipelineLabel: ").append(pipelineLabel).append('\n'); return sb.toString(); } public static Revision latestRevision(List<Modification> modifications) { if (modifications.isEmpty()) { throw new RuntimeException("Cannot find latest revision."); } else { return new StringRevision(modifications.get(0).getRevision()); } } public void accept(ModificationVisitor visitor) { visitor.visit(this); for (ModifiedFile file : files) { visitor.visit(file); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Modification)) { return false; } Modification that = (Modification) o; if (comment != null ? !comment.equals(that.comment) : that.comment != null) { return false; } if (emailAddress != null ? !emailAddress.equals(that.emailAddress) : that.emailAddress != null) { return false; } if (files != null ? !files.equals(that.files) : that.files != null) { return false; } if (modifiedTime != null ? !modifiedTime.equals(that.modifiedTime) : that.modifiedTime != null) { return false; } if (pipelineId != null ? !pipelineId.equals(that.pipelineId) : that.pipelineId != null) { return false; } if (pipelineLabel != null ? !pipelineLabel.equals(that.pipelineLabel) : that.pipelineLabel != null) { return false; } if (revision != null ? !revision.equals(that.revision) : that.revision != null) { return false; } if (userName != null ? !userName.equals(that.userName) : that.userName != null) { return false; } if (additionalData != null ? !additionalData.equals(that.additionalData) : that.additionalData != null) { return false; } return true; } @Override public int hashCode() { int result = userName != null ? userName.hashCode() : 0; result = 31 * result + (comment != null ? comment.hashCode() : 0); result = 31 * result + (emailAddress != null ? emailAddress.hashCode() : 0); result = 31 * result + (revision != null ? revision.hashCode() : 0); result = 31 * result + (modifiedTime != null ? modifiedTime.hashCode() : 0); result = 31 * result + (files != null ? files.hashCode() : 0); result = 31 * result + (pipelineLabel != null ? pipelineLabel.hashCode() : 0); result = 31 * result + (pipelineId != null ? pipelineId.hashCode() : 0); result = 31 * result + (additionalData != null ? additionalData.hashCode() : 0); return result; } public void setMaterialInstance(MaterialInstance materialInstance) { this.materialInstance = materialInstance; } public MaterialInstance getMaterialInstance() { return materialInstance; } public static ArrayList<Modification> modifications(Modification modification) { ArrayList<Modification> modifications = new ArrayList<>(); modifications.add(modification); return modifications; } /** * @deprecated Remove this when we do not need to serialize these to the db and agent */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeObject(userName); out.writeObject(comment); out.writeObject(emailAddress); out.writeObject(revision); out.writeObject(additionalData); out.writeObject(additionalDataMap); out.writeObject(modifiedTime); out.writeObject(pipelineLabel); out.writeObject(materialInstance); out.writeObject(new LinkedHashSet(files)); } /** * @deprecated Remove this when we do not need to serialize these to the db and agent */ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { userName = (String) in.readObject(); comment = (String) in.readObject(); emailAddress = (String) in.readObject(); revision = (String) in.readObject(); additionalData = (String) in.readObject(); additionalDataMap = (HashMap) in.readObject(); modifiedTime = (Date) in.readObject(); pipelineLabel = (String) in.readObject(); materialInstance = (MaterialInstance) in.readObject(); Set files = (Set) in.readObject(); if (files == null) { this.files = new LinkedHashSet<>(); } else { this.files = new LinkedHashSet<>(files); } } /** * @deprecated Remove this when we do not need to serialize these to the db and agent */ private void readObjectNoData() throws ObjectStreamException { } public boolean isSameRevision(Modification that) { return revision != null ? revision.equals(that.revision) : that.revision == null; } public String getPipelineLabel() { return pipelineLabel; } /** * @deprecated for tests only */ public void setPipelineLabel(String pipelineLabel) { this.pipelineLabel = pipelineLabel; } public Set<String> getCardNumbersFromComment() { Set<String> cardNumbers = new TreeSet<>(); Pattern pattern = Pattern.compile("#(\\d+)"); String comment = this.comment == null ? "" : this.comment; Matcher matcher = pattern.matcher(comment); while (hasMatch(matcher)) { cardNumbers.add(id(matcher)); matcher.end(); } return cardNumbers; } private boolean hasMatch(Matcher matcher) { return matcher.find() && id(matcher) != null; } public String id(Matcher matcher) { return matcher.groupCount() > 0 ? contentsOfFirstGroupThatMatched(matcher) : matcher.group(); } private String contentsOfFirstGroupThatMatched(Matcher matcher) { for (int i = 1; i <= matcher.groupCount(); i++) { String groupContent = matcher.group(i); if (groupContent != null) { return groupContent; } } return null; } public String getEmailAddress() { return emailAddress; } public static Revision oldestRevision(Modifications modifications) { if (modifications.isEmpty()) { throw new RuntimeException("Cannot find oldest revision."); } else { return new StringRevision(modifications.get(modifications.size()-1).getRevision()); } } public String getAdditionalData() { return additionalData; } }