/* * 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; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeSet; import static com.thoughtworks.go.util.ExceptionUtils.bomb; /** * @understands a pipeline which can be compared based on its material checkin (natural) order */ public class PipelineTimelineEntry implements Comparable { private final String pipelineName; private final long id; private final int counter; private final Map<String, List<Revision>> revisions; private PipelineTimelineEntry insertedBefore; private PipelineTimelineEntry insertedAfter; private double naturalOrder = 0.0; private boolean hasBeenUpdated; public PipelineTimelineEntry(String pipelineName, long id, int counter, Map<String, List<Revision>> revisions) { this.pipelineName = pipelineName; this.id = id; this.counter = counter; this.revisions = revisions; } public PipelineTimelineEntry(String pipelineName, long id, Integer counter, Map<String, List<Revision>> revisions, double naturalOrder) { this(pipelineName, id, counter, revisions); this.naturalOrder = naturalOrder; } public int compareTo(Object o) { if (o == null) { throw new NullPointerException("Cannot compare this object with null"); } if (o.getClass() != this.getClass()) { throw new RuntimeException("Cannot compare '" + o + "' with '" + this + "'"); } if (this.equals(o)) { return 0; } PipelineTimelineEntry that = (PipelineTimelineEntry) o; Map<Date, TreeSet<Integer>> earlierMods = new HashMap<>(); for (String materialFlyweight : revisions.keySet()) { List<Revision> thisRevs = this.revisions.get(materialFlyweight); List<Revision> thatRevs = that.revisions.get(materialFlyweight); if (thisRevs == null || thatRevs == null) { continue; } Revision thisRevision = thisRevs.get(0); Revision thatRevision = thatRevs.get(0); if (thisRevision == null || thatRevision == null) { continue; } Date thisDate = thisRevision.date; Date thatDate = thatRevision.date; if (thisDate.equals(thatDate)) { continue; } populateEarlierModification(earlierMods, thisDate, thatDate); } if (earlierMods.isEmpty()) { return counter < that.counter ? -1 : 1; } TreeSet<Date> sortedModDate = new TreeSet<>(earlierMods.keySet()); if (hasContentionOnEarliestMod(earlierMods, sortedModDate.first())) { return counter < that.counter ? -1 : 1; } return earlierMods.get(sortedModDate.first()).first(); } public int getCounter() { return counter; } private void populateEarlierModification(Map<Date, TreeSet<Integer>> earlierMods, Date thisDate, Date thatDate) { int value = thisDate.before(thatDate) ? -1 : 1; Date actual = thisDate.before(thatDate) ? thisDate : thatDate; if (!earlierMods.containsKey(actual)) { earlierMods.put(actual, new TreeSet<>()); } earlierMods.get(actual).add(value); } private boolean hasContentionOnEarliestMod(Map<Date, TreeSet<Integer>> earlierMods, Date earliestModDate) { return earlierMods.get(earliestModDate).size() > 1; } public PipelineTimelineEntry insertedBefore() { return insertedBefore; } public PipelineTimelineEntry insertedAfter() { return insertedAfter; } public void setInsertedBefore(PipelineTimelineEntry insertedBefore) { if (this.insertedBefore != null) { throw bomb("cannot change insertedBefore for: " + this + " with " + insertedBefore); } this.insertedBefore = insertedBefore; } public void setInsertedAfter(PipelineTimelineEntry insertedAfter) { if (this.insertedAfter != null) { throw bomb("cannot change insertedAfter for: " + this + " with " + insertedAfter); } this.insertedAfter = insertedAfter; } public String getPipelineName() { return pipelineName; } public Long getId() { return id; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PipelineTimelineEntry that = (PipelineTimelineEntry) o; return id == that.id; } @Override public int hashCode() { return (int) (id ^ (id >>> 32)); } @Override public String toString() { return "PipelineTimelineEntry{" + "pipelineName='" + pipelineName + '\'' + ", id=" + id + ", counter=" + counter + ", revisions=" + revisions + ", naturalOrder=" + naturalOrder + '}'; } public PipelineTimelineEntry previous() { return insertedAfter(); } public double naturalOrder() { return naturalOrder; } public void updateNaturalOrder() { double calculatedOrder = calculateNaturalOrder(); if (this.naturalOrder > 0.0 && this.naturalOrder != calculatedOrder) { bomb(String.format("Calculated natural ordering %s is not the same as the existing naturalOrder %s, for pipeline %s, with id %s", calculatedOrder, this.naturalOrder, this.pipelineName, this.id)); } if (this.naturalOrder == 0.0 && this.naturalOrder != calculatedOrder) { this.naturalOrder = calculatedOrder; this.hasBeenUpdated = true; } } public boolean hasBeenUpdated() { return this.hasBeenUpdated; } private double calculateNaturalOrder() { double previous = 0.0; if (insertedAfter != null) { previous = insertedAfter.naturalOrder; } if (insertedBefore != null) { return (previous + insertedBefore.naturalOrder) / 2.0; } else { return previous + 1.0; } } public PipelineIdentifier getPipelineLocator() { return new PipelineIdentifier(pipelineName, counter, null); } public Map<String, List<Revision>> revisions() { return revisions; } public static class Revision { public final Date date; public final String revision; public final String folder; public final long id; public Revision(Date date, String revision, String folder, long id) { this.date = date; this.revision = revision; this.folder = folder; this.id = id; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Revision revision1 = (Revision) o; if (date != null ? !date.equals(revision1.date) : revision1.date != null) { return false; } if (revision != null ? !revision.equals(revision1.revision) : revision1.revision != null) { return false; } return true; } @Override public int hashCode() { int result = date != null ? date.hashCode() : 0; result = 31 * result + (revision != null ? revision.hashCode() : 0); result = 31 * result + (folder != null ? folder.hashCode() : 0); return result; } @Override public String toString() { return "Revision{" + "date=" + date + ", revision='" + revision + '\'' + ", folder='" + folder + '\'' + '}'; } public boolean lessThan(Revision revision) { if (this == revision) { return true; } // if (!folder.equals(revision.folder)) { // return false; // } if (date.compareTo(revision.date) < 0) { return true; } return false; } } }