/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Geoff Cummings
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.util;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import hudson.model.AbstractBuild;
import hudson.model.Job;
import hudson.model.Node;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TopLevelItem;
import hudson.model.View;
import hudson.util.Iterators.CountingPredicate;
import java.util.*;
/**
* {@link List} of {@link Run}s, sorted in the descending date order.
*
* @author Kohsuke Kawaguchi
*/
public class RunList<R extends Run> extends AbstractList<R> {
private Iterable<R> base;
private R first;
private Integer size;
public RunList() {
base = Collections.emptyList();
}
public RunList(Job j) {
base = j.getBuilds();
}
public RunList(View view) {// this is a type unsafe operation
Set<Job> jobs = new HashSet<Job>();
for (TopLevelItem item : view.getItems())
jobs.addAll(item.getAllJobs());
List<Iterable<R>> runLists = new ArrayList<Iterable<R>>();
for (Job job : jobs) {
runLists.add(job.getBuilds());
}
this.base = combine(runLists);
}
public RunList(Collection<? extends Job> jobs) {
List<Iterable<R>> runLists = new ArrayList<Iterable<R>>();
for (Job j : jobs)
runLists.add(j.getBuilds());
this.base = combine(runLists);
}
private Iterable<R> combine(Iterable<Iterable<R>> runLists) {
return Iterables.mergeSorted(runLists, new Comparator<R>() {
public int compare(R o1, R o2) {
long lhs = o1.getTimeInMillis();
long rhs = o2.getTimeInMillis();
if (lhs > rhs) return -1;
if (lhs < rhs) return 1;
return 0;
}
});
}
private RunList(Iterable<R> c) {
base = c;
}
@Override
public Iterator<R> iterator() {
return base.iterator();
}
/**
* @deprecated as of 1.485
* {@link RunList}, despite its name, should be really used as {@link Iterable}, not as {@link List}.
*/
@Override
@Deprecated
public int size() {
if (size==null) {
int sz=0;
for (R r : this) {
first = r;
sz++;
}
size = sz;
}
return size;
}
/**
* @deprecated as of 1.485
* {@link RunList}, despite its name, should be really used as {@link Iterable}, not as {@link List}.
*/
@Override
@Deprecated
public R get(int index) {
return Iterators.get(iterator(),index);
}
/**
* {@link AbstractList#subList(int, int)} isn't very efficient on our {@link Iterable} based implementation.
* In fact the range check alone would require us to iterate all the elements,
* so we'd be better off just copying into ArrayList.
*/
@Override
public List<R> subList(int fromIndex, int toIndex) {
List<R> r = new ArrayList<R>();
Iterator<R> itr = iterator();
Iterators.skip(itr,fromIndex);
for (int i=toIndex-fromIndex; i>0; i--) {
r.add(itr.next());
}
return r;
}
@Override
public int indexOf(Object o) {
int index=0;
for (R r : this) {
if (r.equals(o))
return index;
index++;
}
return -1;
}
@Override
public int lastIndexOf(Object o) {
int a = -1;
int index=0;
for (R r : this) {
if (r.equals(o))
a = index;
index++;
}
return a;
}
@Override
public boolean isEmpty() {
return !iterator().hasNext();
}
/** @deprecated see {@link #size()} for why this violates lazy-loading */
@Deprecated
public R getFirstBuild() {
size();
return first;
}
public R getLastBuild() {
Iterator<R> itr = iterator();
return itr.hasNext() ? itr.next() : null;
}
public static <R extends Run>
RunList<R> fromRuns(Collection<? extends R> runs) {
return new RunList<R>((Iterable)runs);
}
/**
* Returns elements that satisfy the given predicate.
* <em>Warning:</em> this method mutates the original list and then returns it.
* @since 1.544
*/
public RunList<R> filter(Predicate<R> predicate) {
size = null;
first = null;
base = Iterables.filter(base,predicate);
return this;
}
/**
* Returns the first streak of the elements that satisfy the given predicate.
*
* For example, {@code filter([1,2,3,4],odd)==[1,3]} but {@code limit([1,2,3,4],odd)==[1]}.
*/
private RunList<R> limit(final CountingPredicate<R> predicate) {
size = null;
first = null;
final Iterable<R> nested = base;
base = new Iterable<R>() {
public Iterator<R> iterator() {
return hudson.util.Iterators.limit(nested.iterator(),predicate);
}
@Override
public String toString() {
return Iterables.toString(this);
}
};
return this;
}
/**
* Return only the most recent builds.
* <em>Warning:</em> this method mutates the original list and then returns it.
* @param n a count
* @return the n most recent builds
* @since 1.507
*/
public RunList<R> limit(final int n) {
return limit(new CountingPredicate<R>() {
public boolean apply(int index, R input) {
return index<n;
}
});
}
/**
* Filter the list to non-successful builds only.
* <em>Warning:</em> this method mutates the original list and then returns it.
*/
public RunList<R> failureOnly() {
return filter(new Predicate<R>() {
public boolean apply(R r) {
return r.getResult()!=Result.SUCCESS;
}
});
}
/**
* Filter the list to builds above threshold.
* <em>Warning:</em> this method mutates the original list and then returns it.
* @since 1.517
*/
public RunList<R> overThresholdOnly(final Result threshold) {
return filter(new Predicate<R>() {
public boolean apply(R r) {
return (r.getResult() != null && r.getResult().isBetterOrEqualTo(threshold));
}
});
}
/**
* Filter the list to completed builds.
* <em>Warning:</em> this method mutates the original list and then returns it.
* @since 1.561
*/
public RunList<R> completedOnly() {
return filter(new Predicate<R>() {
public boolean apply(R r) {
return !r.isBuilding();
}
});
}
/**
* Filter the list to builds on a single node only
* <em>Warning:</em> this method mutates the original list and then returns it.
*/
public RunList<R> node(final Node node) {
return filter(new Predicate<R>() {
public boolean apply(R r) {
return (r instanceof AbstractBuild) && ((AbstractBuild)r).getBuiltOn()==node;
}
});
}
/**
* Filter the list to regression builds only.
* <em>Warning:</em> this method mutates the original list and then returns it.
*/
public RunList<R> regressionOnly() {
return filter(new Predicate<R>() {
public boolean apply(R r) {
return r.getBuildStatusSummary().isWorse;
}
});
}
/**
* Filter the list by timestamp.
*
* {@code s<=;e}.
* <em>Warning:</em> this method mutates the original list and then returns it.
*/
public RunList<R> byTimestamp(final long start, final long end) {
return
limit(new CountingPredicate<R>() {
public boolean apply(int index, R r) {
return start<=r.getTimeInMillis();
}
}).filter(new Predicate<R>() {
public boolean apply(R r) {
return r.getTimeInMillis()<end;
}
});
}
/**
* Reduce the size of the list by only leaving relatively new ones.
* This also removes on-going builds, as RSS cannot be used to publish information
* if it changes.
* <em>Warning:</em> this method mutates the original list and then returns it.
*/
public RunList<R> newBuilds() {
GregorianCalendar cal = new GregorianCalendar();
cal.add(Calendar.DAY_OF_YEAR, -7);
final long t = cal.getTimeInMillis();
// can't publish on-going builds
return filter(new Predicate<R>() {
public boolean apply(R r) {
return !r.isBuilding();
}
})
// put at least 10 builds, but otherwise ignore old builds
.limit(new CountingPredicate<R>() {
public boolean apply(int index, R r) {
return index < 10 || r.getTimeInMillis() >= t;
}
});
}
}