提交 7b9becc8 编写于 作者: E Erik Molekamp

Merge branch 'master' of https://github.com/jenkinsci/jenkins

......@@ -54,6 +54,15 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class='major rfe'>
Build records are now lazy loaded, resulting in a reduced startup time
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.484>What's new in 1.484</a> <!--=DATE=--></h3>
<ul class=image>
<li class=bug>
Check view permissions before showing config page
......@@ -74,11 +83,8 @@ Upcoming changes</a>
Mac OS X installer now sends log to <tt>/var/log/jenkins/jenkins.log</tt>
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15178">issue 15178</a>)
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.483>What's new in 1.483</a> <!--=DATE=--></h3>
</div><!--=END=-->
<h3><a name=v1.483>What's new in 1.483</a> (2012/09/23)</h3>
<ul class=image>
<li class=bug>
Invalid warning message when the config-file-provider plugin is not installed
......@@ -110,7 +116,6 @@ Upcoming changes</a>
Track and verify plugins used in configuration XML
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15003">issue 15003</a>)
</ul>
</div><!--=END=-->
<h3><a name=v1.482>What's new in 1.482</a> (2012/09/16)</h3>
<ul class=image>
<li class=bug>
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
package hudson;
import jenkins.model.Jenkins;
import jenkins.model.Jenkins.MasterComputer;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
......@@ -9,6 +10,7 @@ import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -20,49 +22,55 @@ import java.util.logging.Logger;
public class DNSMultiCast implements Closeable {
private JmDNS jmdns;
public DNSMultiCast(Jenkins hudson) {
public DNSMultiCast(final Jenkins jenkins) {
if (disabled) return; // escape hatch
try {
this.jmdns = JmDNS.create();
// the registerService call can be slow. run these asynchronously
MasterComputer.threadPoolForRemoting.submit(new Callable<Object>() {
public Object call() {
try {
jmdns = JmDNS.create();
Map<String,String> props = new HashMap<String, String>();
String rootURL = hudson.getRootUrl();
if (rootURL==null) return;
Map<String,String> props = new HashMap<String, String>();
String rootURL = jenkins.getRootUrl();
if (rootURL==null) return null;
props.put("url", rootURL);
try {
props.put("version",String.valueOf(Jenkins.getVersion()));
} catch (IllegalArgumentException e) {
// failed to parse the version number
}
props.put("url", rootURL);
try {
props.put("version",String.valueOf(Jenkins.getVersion()));
} catch (IllegalArgumentException e) {
// failed to parse the version number
}
TcpSlaveAgentListener tal = hudson.getTcpSlaveAgentListener();
if (tal!=null)
props.put("slave-port",String.valueOf(tal.getPort()));
TcpSlaveAgentListener tal = jenkins.getTcpSlaveAgentListener();
if (tal!=null)
props.put("slave-port",String.valueOf(tal.getPort()));
props.put("server-id", Util.getDigestOf(hudson.getSecretKey()));
props.put("server-id", Util.getDigestOf(jenkins.getSecretKey()));
URL jenkins_url = new URL(rootURL);
int jenkins_port = jenkins_url.getPort();
if (jenkins_port == -1) {
jenkins_port = 80;
}
if (jenkins_url.getPath().length() > 0) {
props.put("path", jenkins_url.getPath());
}
URL jenkins_url = new URL(rootURL);
int jenkins_port = jenkins_url.getPort();
if (jenkins_port == -1) {
jenkins_port = 80;
}
if (jenkins_url.getPath().length() > 0) {
props.put("path", jenkins_url.getPath());
}
jmdns.registerService(ServiceInfo.create("_hudson._tcp.local.","hudson",
jenkins_port,0,0,props)); // for backward compatibility
jmdns.registerService(ServiceInfo.create("_jenkins._tcp.local.","jenkins",
jenkins_port,0,0,props));
// Make Jenkins appear in Safari's Bonjour bookmarks
jmdns.registerService(ServiceInfo.create("_http._tcp.local.","Jenkins",
jenkins_port,0,0,props));
} catch (IOException e) {
LOGGER.log(Level.WARNING,"Failed to advertise the service to DNS multi-cast",e);
}
jmdns.registerService(ServiceInfo.create("_hudson._tcp.local.","jenkins",
jenkins_port,0,0,props)); // for backward compatibility
jmdns.registerService(ServiceInfo.create("_jenkins._tcp.local.","jenkins",
jenkins_port,0,0,props));
// Make Jenkins appear in Safari's Bonjour bookmarks
jmdns.registerService(ServiceInfo.create("_http._tcp.local.","Jenkins",
jenkins_port,0,0,props));
} catch (IOException e) {
LOGGER.log(Level.WARNING,"Failed to advertise the service to DNS multi-cast",e);
}
return null;
}
});
}
public void close() {
......
......@@ -62,6 +62,8 @@ import hudson.util.Iterators;
import hudson.util.LogTaskListener;
import hudson.util.VariableResolver;
import jenkins.model.Jenkins;
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.model.lazy.BuildReference;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -154,6 +156,20 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
*/
protected transient List<Environment> buildEnvironments;
/**
* Pointers to form bi-directional link between adjacent {@link AbstractBuild}s.
*
* <p>
* Unlike {@link Run}, {@link AbstractBuild}s do lazy-loading, so we don't use
* {@link Run#previousBuild} and {@link Run#nextBuild}, and instead use these
* fields and point to {@link #selfReference} of adjacent builds.
*/
private volatile transient BuildReference<R> previousBuild, nextBuild;
/*package*/ final transient BuildReference<R> selfReference = new BuildReference<R>(getId(),_this());
protected AbstractBuild(P job) throws IOException {
super(job);
}
......@@ -170,6 +186,80 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
return getParent();
}
@Override
void dropLinks() {
super.dropLinks();
if(nextBuild!=null) {
AbstractBuild nb = nextBuild.get();
if (nb!=null) nb.previousBuild = previousBuild;
}
if(previousBuild!=null) {
AbstractBuild pb = previousBuild.get();
if (pb!=null) pb.nextBuild = nextBuild;
}
}
@Override
public R getPreviousBuild() {
while (true) {
BuildReference<R> r = previousBuild; // capture the value once
if (r==null) {
// having two neighbors pointing to each other is important to make RunMap.removeValue work
R pb = getParent().builds.search(number-1, Direction.DESC);
if (pb!=null) {
((AbstractBuild)pb).nextBuild = selfReference; // establish bi-di link
this.previousBuild = pb.selfReference;
return pb;
} else {
// this indicates that we know there's no previous build
// (as opposed to we don't know if/what our previous build is.
this.previousBuild = selfReference;
return null;
}
}
if (r==selfReference)
return null;
R referent = r.get();
if (referent!=null) return referent;
// the reference points to a GC-ed object, drop the reference and do it again
this.previousBuild = null;
}
}
@Override
public R getNextBuild() {
while (true) {
BuildReference<R> r = nextBuild; // capture the value once
if (r==null) {
// having two neighbors pointing to each other is important to make RunMap.removeValue work
R nb = getParent().builds.search(number+1, Direction.ASC);
if (nb!=null) {
((AbstractBuild)nb).previousBuild = selfReference; // establish bi-di link
this.nextBuild = nb.selfReference;
return nb;
} else {
// this indicates that we know there's no next build
// (as opposed to we don't know if/what our next build is.
this.nextBuild = selfReference;
return null;
}
}
if (r==selfReference)
return null;
R referent = r.get();
if (referent!=null) return referent;
// the reference points to a GC-ed object, drop the reference and do it again
this.nextBuild = null;
}
}
/**
* Returns a {@link Slave} on which this build was done.
*
......
......@@ -46,7 +46,6 @@ import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.listeners.SCMListener;
import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.SubTask;
import hudson.model.Queue.WaitingItem;
......@@ -80,9 +79,11 @@ import hudson.util.AlternativeUiTextProvider.Message;
import hudson.util.DescribableList;
import hudson.util.EditDistance;
import hudson.util.FormValidation;
import hudson.util.RunList;
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import jenkins.model.Jenkins;
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
......@@ -160,7 +161,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
* {@link Run#getPreviousBuild()}
*/
@Restricted(NoExternalUse.class)
protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();
protected transient RunMap<R> builds = new RunMap<R>();
/**
* The quiet period. Null to delegate to the system default.
......@@ -269,13 +270,19 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
super.onLoad(parent, name);
if (this.builds==null)
this.builds = new RunMap<R>();
this.builds.load(this,new Constructor<R>() {
RunMap<R> builds = new RunMap<R>(getBuildDir(), new Constructor<R>() {
public R create(File dir) throws IOException {
return loadBuild(dir);
}
});
if (this.builds!=null) {
// if we are reloading, keep all those that are still building intact
for (R r : this.builds.getLoadedBuilds().values()) {
if (r.isBuilding())
builds.put(r);
}
}
this.builds = builds;
if(triggers==null) {
// it didn't exist in < 1.28
......@@ -947,8 +954,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
@Override
public SortedMap<Integer, ? extends R> _getRuns() {
return builds.getView();
public RunMap<R> _getRuns() {
return builds;
}
@Override
......@@ -956,6 +963,51 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
this.builds.remove(run);
}
/**
* {@inheritDoc}
*
* More efficient implementation.
*/
@Override
public R getBuild(String id) {
return builds.getById(id);
}
/**
* {@inheritDoc}
*
* More efficient implementation.
*/
@Override
public R getBuildByNumber(int n) {
return builds.getByNumber(n);
}
/**
* {@inheritDoc}
*
* More efficient implementation.
*/
@Override
public R getFirstBuild() {
return builds.oldestBuild();
}
@Override
public R getLastBuild() {
return builds.newestBuild();
}
@Override
public R getNearestBuild(int n) {
return builds.search(n, Direction.ASC);
}
@Override
public R getNearestOldBuild(int n) {
return builds.search(n, Direction.DESC);
}
/**
* Determines Class&lt;R>.
*/
......@@ -1627,7 +1679,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
@Override
protected HistoryWidget createHistoryWidget() {
return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
return new BuildHistoryWidget<R>(this,builds,HISTORY_ADAPTER);
}
public boolean isParameterized() {
......
......@@ -518,12 +518,22 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*
* @return never null. The first entry is the latest build.
*/
@Exported
@Exported(name="allBuilds",visibility=-2)
@WithBridgeMethods(List.class)
public RunList<RunT> getBuilds() {
return RunList.fromRuns(_getRuns().values());
}
/**
* Gets the read-only view of the recent builds.
*
* @since 1.LAZYLOAD
*/
@Exported(name="builds")
public RunList<RunT> getNewBuilds() {
return getBuilds().newBuilds();
}
/**
* Obtains all the {@link Run}s whose build numbers matches the given {@link RangeSet}.
*/
......@@ -600,7 +610,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
* This is useful when you'd like to fetch a build but the exact build might
* be already gone (deleted, rotated, etc.)
*/
public final RunT getNearestBuild(int n) {
public RunT getNearestBuild(int n) {
SortedMap<Integer, ? extends RunT> m = _getRuns().headMap(n - 1); // the map should
// include n, so n-1
if (m.isEmpty())
......@@ -614,7 +624,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
* This is useful when you'd like to fetch a build but the exact build might
* be already gone (deleted, rotated, etc.)
*/
public final RunT getNearestOldBuild(int n) {
public RunT getNearestOldBuild(int n) {
SortedMap<Integer, ? extends RunT> m = _getRuns().tailMap(n);
if (m.isEmpty())
return null;
......@@ -626,7 +636,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
StaplerResponse rsp) {
try {
// try to interpret the token as build number
return _getRuns().get(Integer.valueOf(token));
return getBuildByNumber(Integer.valueOf(token));
} catch (NumberFormatException e) {
// try to map that to widgets
for (Widget w : getWidgets()) {
......@@ -660,7 +670,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
/**
* Gets all the runs.
*
* The resulting map must be immutable (by employing copy-on-write
* The resulting map must be treated immutable (by employing copy-on-write
* semantics.) The map is descending order, with newest builds at the top.
*/
protected abstract SortedMap<Integer, ? extends RunT> _getRuns();
......
/*
* The MIT License
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Copyright (c) 2004-2012, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Daniel Dyer, Red Hat, Inc., Tom Huybrechts, Romain Seguy, Yahoo! Inc.,
* Darek Ostolski
* Darek Ostolski, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -44,7 +44,6 @@ import hudson.model.Descriptor.FormException;
import hudson.model.listeners.RunListener;
import hudson.model.listeners.SaveableListener;
import hudson.security.PermissionScope;
import jenkins.model.Jenkins.MasterComputer;
import hudson.search.SearchIndexBuilder;
import hudson.security.ACL;
import hudson.security.AccessControlled;
......@@ -97,6 +96,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.Jenkins;
import jenkins.model.lazy.BuildReference;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONObject;
import org.apache.commons.io.input.NullInputStream;
......@@ -325,7 +325,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
/*package*/ static long parseTimestampFromBuildDir(File buildDir) throws IOException {
try {
return ID_FORMATTER.get().parse(buildDir.getName()).getTime();
// canonicalization to ensure we are looking at the ID in the directory name
// as opposed to build numbers which are used in symlinks
return ID_FORMATTER.get().parse(buildDir.getCanonicalFile().getName()).getTime();
} catch (ParseException e) {
throw new IOException2("Invalid directory name "+buildDir,e);
} catch (NumberFormatException e) {
......@@ -337,7 +339,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* Obtains 'this' in a more type safe signature.
*/
@SuppressWarnings({"unchecked"})
private RunT _this() {
protected RunT _this() {
return (RunT)this;
}
......@@ -640,10 +642,11 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
// a new build is in progress
BallColor baseColor;
if(previousBuild==null)
RunT pb = getPreviousBuild();
if(pb==null)
baseColor = BallColor.GREY;
else
baseColor = previousBuild.getIconColor();
baseColor = pb.getIconColor();
return baseColor.anime();
}
......@@ -688,6 +691,17 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
return number;
}
/**
* Called by {@link RunMap} to drop bi-directional links in preparation for
* deleting a build.
*/
/*package*/ void dropLinks() {
if(nextBuild!=null)
nextBuild.previousBuild = previousBuild;
if(previousBuild!=null)
previousBuild.nextBuild = nextBuild;
}
public RunT getPreviousBuild() {
return previousBuild;
}
......@@ -747,10 +761,10 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* Returns the last build that was actually built - i.e., skipping any with Result.NOT_BUILT
*/
public RunT getPreviousBuiltBuild() {
RunT r=previousBuild;
RunT r=getPreviousBuild();
// in certain situations (aborted m2 builds) r.getResult() can still be null, although it should theoretically never happen
while( r!=null && (r.getResult() == null || r.getResult()==Result.NOT_BUILT) )
r=r.previousBuild;
r=r.getPreviousBuild();
return r;
}
......@@ -758,9 +772,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* Returns the last build that didn't fail before this build.
*/
public RunT getPreviousNotFailedBuild() {
RunT r=previousBuild;
RunT r=getPreviousBuild();
while( r!=null && r.getResult()==Result.FAILURE )
r=r.previousBuild;
r=r.getPreviousBuild();
return r;
}
......@@ -768,9 +782,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* Returns the last failed build before this build.
*/
public RunT getPreviousFailedBuild() {
RunT r=previousBuild;
RunT r=getPreviousBuild();
while( r!=null && r.getResult()!=Result.FAILURE )
r=r.previousBuild;
r=r.getPreviousBuild();
return r;
}
......@@ -779,9 +793,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* @since 1.383
*/
public RunT getPreviousSuccessfulBuild() {
RunT r=previousBuild;
RunT r=getPreviousBuild();
while( r!=null && r.getResult()!=Result.SUCCESS )
r=r.previousBuild;
r=r.getPreviousBuild();
return r;
}
......
......@@ -23,22 +23,26 @@
*/
package hudson.model;
import com.google.common.collect.Maps;
import jenkins.model.lazy.AbstractLazyLoadRunMap;
import jenkins.model.lazy.BuildReference;
import org.apache.commons.collections.comparators.ReverseComparator;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.*;
/**
* {@link Map} from build number to {@link Run}.
*
......@@ -49,86 +53,72 @@ import java.util.logging.Logger;
*
* @author Kohsuke Kawaguchi
*/
public final class RunMap<R extends Run<?,R>> extends AbstractMap<Integer,R> implements SortedMap<Integer,R> {
// copy-on-write map
private transient volatile SortedMap<Integer,R> builds =
new TreeMap<Integer,R>(COMPARATOR);
// in practice R is always bound by AbstractBuild, but making that change causes all kinds of
// signature breakage.
public final class RunMap<R extends Run<?,R>> extends AbstractLazyLoadRunMap<R> implements Iterable<R> {
/**
* Read-only view of this map.
*/
private final SortedMap<Integer,R> view = Collections.unmodifiableSortedMap(this);
public Set<Entry<Integer,R>> entrySet() {
// since the map is copy-on-write, make sure no one modifies it
return Collections.unmodifiableSet(builds.entrySet());
}
public synchronized R put(R value) {
return put(value.getNumber(),value);
}
private Constructor<R> cons;
@Override
public synchronized R put(Integer key, R value) {
// copy-on-write update
TreeMap<Integer,R> m = new TreeMap<Integer,R>(builds);
// TODO: before first complete build
// patch up next/previous build link
R r = update(m, key, value);
this.builds = m;
return r;
/**
* @deprecated as of 1.LAZYLOAD
* Use {@link #RunMap(File, Constructor)}.
*/
public RunMap() {
super(null); // will be set later
}
@Override
public synchronized void putAll(Map<? extends Integer,? extends R> rhs) {
// copy-on-write update
TreeMap<Integer,R> m = new TreeMap<Integer,R>(builds);
for (Map.Entry<? extends Integer,? extends R> e : rhs.entrySet())
update(m, e.getKey(), e.getValue());
this.builds = m;
/**
* @param cons
* Used to create new instance of {@link Run}.
*/
public RunMap(File baseDir, Constructor cons) {
super(baseDir);
this.cons = cons;
}
private R update(TreeMap<Integer, R> m, Integer key, R value) {
// things are bit tricky because this map is order so that the newest one comes first,
// yet 'nextBuild' refers to the newer build.
R first = m.isEmpty() ? null : m.get(m.firstKey());
R r = m.put(key, value);
SortedMap<Integer,R> head = m.headMap(key);
if(!head.isEmpty()) {
R prev = m.get(head.lastKey());
value.previousBuild = prev.previousBuild;
value.nextBuild = prev;
if(value.previousBuild!=null)
value.previousBuild.nextBuild = value;
prev.previousBuild=value;
} else {
value.previousBuild = first;
value.nextBuild = null;
if(first!=null)
first.nextBuild = value;
}
return r;
public boolean remove(R run) {
return removeValue(run);
}
public synchronized boolean remove(R run) {
if(run.nextBuild!=null)
run.nextBuild.previousBuild = run.previousBuild;
if(run.previousBuild!=null)
run.previousBuild.nextBuild = run.nextBuild;
/**
* Walks through builds, newer ones first.
*/
public Iterator<R> iterator() {
return new Iterator<R>() {
R last = null;
R next = newestBuild();
public boolean hasNext() {
return next!=null;
}
// copy-on-write update
TreeMap<Integer,R> m = new TreeMap<Integer,R>(builds);
R r = m.remove(run.getNumber());
this.builds = m;
public R next() {
R last = next;
if (last!=null)
next = last.getPreviousBuild();
return last;
}
return r!=null;
public void remove() {
if (last==null)
throw new UnsupportedOperationException();
removeValue(last);
}
};
}
public synchronized void reset(TreeMap<Integer,R> builds) {
this.builds = new TreeMap<Integer,R>(COMPARATOR);
putAll(builds);
@Override
public boolean removeValue(R run) {
run.dropLinks();
return super.removeValue(run);
}
/**
......@@ -138,51 +128,24 @@ public final class RunMap<R extends Run<?,R>> extends AbstractMap<Integer,R> imp
return view;
}
//
// SortedMap delegation
//
public Comparator<? super Integer> comparator() {
return builds.comparator();
}
public SortedMap<Integer, R> subMap(Integer fromKey, Integer toKey) {
return builds.subMap(fromKey, toKey);
}
public SortedMap<Integer, R> headMap(Integer toKey) {
return builds.headMap(toKey);
}
public SortedMap<Integer, R> tailMap(Integer fromKey) {
return builds.tailMap(fromKey);
}
public Integer firstKey() {
return builds.firstKey();
}
public Integer lastKey() {
return builds.lastKey();
}
/**
* This is the newest build (with the biggest build number)
*/
public R newestValue() {
SortedMap<Integer, R> _builds = builds;
if (_builds.isEmpty()) return null;
return _builds.get(firstKey());
return search(Integer.MAX_VALUE, DESC);
}
/**
* This is the oldest build (with the smallest build number)
*/
public R oldestValue() {
SortedMap<Integer, R> _builds = builds;
if (_builds.isEmpty()) return null;
return _builds.get(lastKey());
return search(Integer.MIN_VALUE, ASC);
}
/**
* @deprecated as of 1.LAZYLOAD
* Use {@link ReverseComparator}
*/
public static final Comparator<Comparable> COMPARATOR = new Comparator<Comparable>() {
public int compare(Comparable o1, Comparable o2) {
return -o1.compareTo(o2);
......@@ -196,21 +159,33 @@ public final class RunMap<R extends Run<?,R>> extends AbstractMap<Integer,R> imp
R create(File dir) throws IOException;
}
@Override
protected final int getNumberOf(R r) {
return r.getNumber();
}
@Override
protected final String getIdOf(R r) {
return r.getId();
}
/**
* Fills in {@link RunMap} by loading build records from the file system.
*
* @param job
* Job that owns this map.
* @param cons
* Used to create new instance of {@link Run}.
* Reuses the same reference as much as we can.
* <p>
* If concurrency ends up creating a few extra, that's OK, because
* we are really just trying to reduce the # of references we create.
*/
public synchronized void load(Job job, Constructor<R> cons) {
@Override
protected BuildReference<R> createReference(R r) {
if (r instanceof AbstractBuild) return ((AbstractBuild)r).selfReference;
else return super.createReference(r);
}
@Override
protected FilenameFilter createDirectoryFilter() {
final SimpleDateFormat formatter = Run.ID_FORMATTER.get();
TreeMap<Integer,R> builds = new TreeMap<Integer,R>(RunMap.COMPARATOR);
File buildDir = job.getBuildDir();
buildDir.mkdirs();
String[] buildDirs = buildDir.list(new FilenameFilter() {
return new FilenameFilter() {
public boolean accept(File dir, String name) {
// JENKINS-1461 sometimes create bogus data directories with impossible dates, such as year 0, April 31st,
// or August 0th. Date object doesn't roundtrip those, so we eventually fail to load this data.
......@@ -231,53 +206,49 @@ public final class RunMap<R extends Run<?,R>> extends AbstractMap<Integer,R> imp
}
return false;
}
});
// keep all those that are building intact.
Map<Integer,R> building = Maps.newHashMap();
for (R b=newestValue(); b!=null && b.isBuilding(); b=b.getPreviousBuild()) {
building.put(b.getNumber(), b);
}
};
}
for( String build : buildDirs ) {
File d = new File(buildDir,build);
if(new File(d,"build.xml").exists()) {
// if the build result file isn't in the directory, ignore it.
try {
R b = cons.create(d);
R existing = builds.put(b.getNumber(), b);
if (existing != null) {
LOGGER.log(Level.WARNING, "multiple runs claiming to be #{0}; using run from {1}", new Object[] {b.getNumber(), d});
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, "could not load " + d, e);
} catch (InstantiationError e) {
LOGGER.log(Level.WARNING, "could not load " + d, e);
}
@Override
protected R retrieve(File d) throws IOException {
if(new File(d,"build.xml").exists()) {
// if the build result file isn't in the directory, ignore it.
try {
R b = cons.create(d);
b.onLoad();
if (LOGGER.isLoggable(FINE) || LOG_RETRIEVAL)
LOGGER.log(LOG_RETRIEVAL?INFO:FINE,"Loaded " + b.getFullDisplayName(),new ThisIsHowItsLoaded());
return b;
} catch (IOException e) {
LOGGER.log(Level.WARNING, "could not load " + d, e);
} catch (InstantiationError e) {
LOGGER.log(Level.WARNING, "could not load " + d, e);
}
}
return null;
}
// overlay what's currently building on top of what's loaded
builds.putAll(building);
/*
// we probably aren't saving every little changes during the build to disk,
// so it's risky to reload these from disk.
for (R b : building.values()) {
try {
b.reload();
} catch (IOException e) {
e.printStackTrace();
} catch (InstantiationError e) {
e.printStackTrace();
}
}
*/
reset(builds);
for (R r : builds.values())
r.onLoad();
/**
* Backward compatibility method that notifies {@link RunMap} of who the owner is.
*
* Traditionally, this method blocked and loaded all the build records on the disk,
* but now all the actual loading happens lazily.
*
* @param job
* Job that owns this map.
* @param cons
* Used to create new instance of {@link Run}.
* @deprecated as of 1.LAZYLOAD
* Use {@link #RunMap(File, Constructor)}
*/
public void load(Job job, Constructor<R> cons) {
this.cons = cons;
initBaseDir(job.getBuildDir());
}
private static final Logger LOGGER = Logger.getLogger(RunMap.class.getName());
public static boolean LOG_RETRIEVAL = false;
private static class ThisIsHowItsLoaded extends Exception {}
}
......@@ -70,6 +70,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
......@@ -295,6 +296,8 @@ public class Fingerprinter extends Recorder implements Serializable, DependecyDe
private final AbstractBuild build;
private static final Random rand = new Random();
/**
* From file name to the digest.
*/
......@@ -340,11 +343,15 @@ public class Fingerprinter extends Recorder implements Serializable, DependecyDe
}
public void onLoad() {
Run pb = build.getPreviousBuild();
if (pb!=null) {
FingerprintAction a = pb.getAction(FingerprintAction.class);
if (a!=null)
compact(a);
// share data structure with nearby builds, but to keep lazy loading efficient,
// don't go back the history forever.
if (rand.nextInt(2)!=0) {
Run pb = build.getPreviousBuild();
if (pb!=null) {
FingerprintAction a = pb.getAction(FingerprintAction.class);
if (a!=null)
compact(a);
}
}
}
......
......@@ -23,22 +23,22 @@
*/
package hudson.tasks;
import com.google.common.collect.Lists;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.Run;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
/**
* Deletes old builds.
*
......@@ -115,7 +115,7 @@ public class LogRotator implements Describable<LogRotator> {
if(numToKeep!=-1) {
List<? extends Run<?,?>> builds = job.getBuilds();
for (Run r : builds.subList(Math.min(builds.size(),numToKeep),builds.size())) {
for (Run r : copy(builds.subList(Math.min(builds.size(), numToKeep), builds.size()))) {
if (r.isKeepLog()) {
LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's marked as a keeper");
continue;
......@@ -136,7 +136,7 @@ public class LogRotator implements Describable<LogRotator> {
if(daysToKeep!=-1) {
Calendar cal = new GregorianCalendar();
cal.add(Calendar.DAY_OF_YEAR,-daysToKeep);
for( Run r : job.getBuilds() ) {
for( Run r : copy(job.getBuilds()) ) {
if (r.isKeepLog()) {
LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's marked as a keeper");
continue;
......@@ -160,7 +160,7 @@ public class LogRotator implements Describable<LogRotator> {
if(artifactNumToKeep!=null && artifactNumToKeep!=-1) {
List<? extends Run<?,?>> builds = job.getBuilds();
for (Run r : builds.subList(Math.min(builds.size(),artifactNumToKeep),builds.size())) {
for (Run r : copy(builds.subList(Math.min(builds.size(), artifactNumToKeep), builds.size()))) {
if (r.isKeepLog()) {
LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's marked as a keeper");
continue;
......@@ -180,7 +180,7 @@ public class LogRotator implements Describable<LogRotator> {
if(artifactDaysToKeep!=null && artifactDaysToKeep!=-1) {
Calendar cal = new GregorianCalendar();
cal.add(Calendar.DAY_OF_YEAR,-artifactDaysToKeep);
for( Run r : job.getBuilds() ) {
for( Run r : copy(job.getBuilds())) {
if (r.isKeepLog()) {
LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's marked as a keeper");
continue;
......@@ -200,7 +200,13 @@ public class LogRotator implements Describable<LogRotator> {
r.deleteArtifacts();
}
}
}
/**
* Creates a copy since we'll be deleting some entries from them.
*/
private <R> Collection<R> copy(Iterable<R> src) {
return Lists.newArrayList(src);
}
public int getDaysToKeep() {
......
......@@ -23,8 +23,11 @@
*/
package hudson.util;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
......@@ -326,4 +329,51 @@ public class Iterators {
public static <T> Iterator<T> sequence(Iterator<? extends T>... iterators) {
return com.google.common.collect.Iterators.concat(iterators);
}
/**
* Returns the elements in the base iterator until it hits any element that doesn't satisfy the filter.
* Then the rest of the elements in the base iterator gets ignored.
*
* @since 1.LAZYLOAD
*/
public static <T> Iterator<T> limit(final Iterator<? extends T> base, final CountingPredicate<? super T> filter) {
return new Iterator<T>() {
private T next;
private boolean end;
private int index=0;
public boolean hasNext() {
fetch();
return next!=null;
}
public T next() {
fetch();
T r = next;
next = null;
return r;
}
private void fetch() {
if (next==null && !end) {
if (base.hasNext()) {
next = base.next();
if (!filter.apply(index++,next)) {
next = null;
end = true;
}
} else {
end = true;
}
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public interface CountingPredicate<T> {
boolean apply(int index, T input);
}
}
......@@ -23,6 +23,9 @@
*/
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.Item;
import hudson.model.Job;
......@@ -30,6 +33,7 @@ import hudson.model.Node;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.View;
import hudson.util.Iterators.CountingPredicate;
import java.util.AbstractList;
import java.util.ArrayList;
......@@ -44,83 +48,193 @@ import java.util.List;
/**
* {@link List} of {@link Run}s, sorted in the descending date order.
*
* TODO: this should be immutable
*
* @author Kohsuke Kawaguchi
*/
public class RunList<R extends Run> extends ArrayList<R> {
public RunList() {
}
public class RunList<R extends Run> extends AbstractList<R> {
public RunList(Job j) {
addAll(j.getBuilds());
}
private Iterable<R> base;
public R getFirstBuild() {
return isEmpty() ? null : get(size()-1);
private R first;
private Integer size;
public RunList() {
base = Collections.emptyList();
}
public R getLastBuild() {
return isEmpty() ? null : get(0);
public RunList(Job j) {
base = j.getBuilds();
}
public RunList(View view) {// this is a type unsafe operation
List<Iterable<R>> jobs = new ArrayList<Iterable<R>>();
for (Item item : view.getItems())
for (Job<?,?> j : item.getAllJobs())
addAll((Collection<R>)j.getBuilds());
Collections.sort(this,Run.ORDER_BY_DATE);
jobs.add(((Job)j).getBuilds());
this.base = combine(jobs);
}
public RunList(Collection<? extends Job> jobs) {
List<Iterable<R>> src = new ArrayList<Iterable<R>>();
for (Job j : jobs)
addAll(j.getBuilds());
Collections.sort(this,Run.ORDER_BY_DATE);
src.add(j.getBuilds());
this.base = combine(src);
}
private Iterable<R> combine(Iterable<Iterable<R>> jobs) {
return Iterables.mergeSorted(jobs, 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.LAZYLOAD
* {@link RunList}, despite its name, should be really used as {@link Iterable}, not as {@link List}.
*/
@Override
public int size() {
if (size==null) {
int sz=0;
for (R r : this) {
first = r;
sz++;
}
size = sz;
}
return size;
}
/**
* @deprecated as of 1.LAZYLOAD
* {@link RunList}, despite its name, should be really used as {@link Iterable}, not as {@link List}.
*/
@Override
public R get(int index) {
return Iterators.get(iterator(),index);
}
@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();
}
private RunList(Collection<? extends R> c, boolean hack) {
super(c);
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>(runs,false);
return new RunList<R>((Iterable)runs);
}
/**
* Returns elements that satisfy the given predicate.
*/
// for compatibility reasons, this method doesn't create a new list but updates the current one
private 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;
}
/**
* Filter the list to non-successful builds only.
*/
public RunList<R> failureOnly() {
for (Iterator<R> itr = iterator(); itr.hasNext();) {
Run r = itr.next();
if(r.getResult()==Result.SUCCESS)
itr.remove();
}
return this;
return filter(new Predicate<R>() {
public boolean apply(R r) {
return r.getResult()!=Result.SUCCESS;
}
});
}
/**
* Filter the list to builds on a single node only
*/
public RunList<R> node(Node node) {
for (Iterator<R> itr = iterator(); itr.hasNext();) {
Run r = itr.next();
if (!(r instanceof AbstractBuild) || ((AbstractBuild)r).getBuiltOn()!=node) {
itr.remove();
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;
}
}
return this;
});
}
/**
* Filter the list to regression builds only.
*/
public RunList<R> regressionOnly() {
for (Iterator<R> itr = iterator(); itr.hasNext();) {
Run r = itr.next();
if(!r.getBuildStatusSummary().isWorse)
itr.remove();
}
return this;
return filter(new Predicate<R>() {
public boolean apply(R r) {
return r.getBuildStatusSummary().isWorse;
}
});
}
/**
......@@ -128,30 +242,17 @@ public class RunList<R extends Run> extends ArrayList<R> {
*
* {@code s&lt=;e}.
*/
public RunList<R> byTimestamp(long start, long end) {
AbstractList<Long> TIMESTAMP_ADAPTER = new AbstractList<Long>() {
public Long get(int index) {
return RunList.this.get(index).getTimeInMillis();
}
public int size() {
return RunList.this.size();
public RunList<R> byTimestamp(final long start, final long end) {
return
limit(new CountingPredicate<R>() {
public boolean apply(int index,R r) {
return r.getTimeInMillis()<end;
}
};
Comparator<Long> DESCENDING_ORDER = new Comparator<Long>() {
public int compare(Long o1, Long o2) {
if (o1 > o2) return -1;
if (o1 < o2) return +1;
return 0;
}).filter(new Predicate<R>() {
public boolean apply(R r) {
return start<=r.getTimeInMillis();
}
};
int s = Collections.binarySearch(TIMESTAMP_ADAPTER, start, DESCENDING_ORDER);
if (s<0) s=-(s+1); // min is inclusive
int e = Collections.binarySearch(TIMESTAMP_ADAPTER, end, DESCENDING_ORDER);
if (e<0) e=-(e+1); else e++; // max is exclusive, so the exact match should be excluded
return fromRuns(subList(e,s));
});
}
/**
......@@ -160,27 +261,21 @@ public class RunList<R extends Run> extends ArrayList<R> {
* if it changes.
*/
public RunList<R> newBuilds() {
GregorianCalendar threshold = new GregorianCalendar();
threshold.add(Calendar.DAY_OF_YEAR,-7);
int count=0;
GregorianCalendar cal = new GregorianCalendar();
cal.add(Calendar.DAY_OF_YEAR, -7);
final long t = cal.getTimeInMillis();
for (Iterator<R> itr = iterator(); itr.hasNext();) {
R r = itr.next();
if(r.isBuilding()) {
// can't publish on-going builds
itr.remove();
continue;
// can't publish on-going builds
return filter(new Predicate<R>() {
public boolean apply(R r) {
return !r.isBuilding();
}
// at least put 10 items
if(count<10) {
count++;
continue;
})
// 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;
}
// anything older than 7 days will be ignored
if(r.getTimestamp().before(threshold))
itr.remove();
}
return this;
});
}
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
/**
* ceil/floor/lower/higher implementations
* that takes the return value of a binary search as an input.
*
* <p>
* Consider a sorted array of int X={x<sub>i</sub>} and a binary search of p on it.
* this class provides likes of {@code ceil(X,p)} which is the smallest x<sub>i</sub>
* that still satisfies x<sub>i</sub> >= p.
*
* Simiarly, {@link #HIGHER} is the smallest x<sub>i</sub>
* that still satisfies x<sub>i</sub> > p.
*
* @author Kohsuke Kawaguchi
*/
enum Boundary {
LOWER(-1,-1),
HIGHER(1,0),
FLOOR(0,-1),
CEIL(0,0);
private final int offsetOfExactMatch, offsetOfInsertionPoint;
private Boundary(int offsetOfExactMatch, int offsetOfInsertionPoint) {
this.offsetOfExactMatch = offsetOfExactMatch;
this.offsetOfInsertionPoint = offsetOfInsertionPoint;
}
/**
* Computes the boundary value.
*/
public int apply(int binarySearchOutput) {
int r = binarySearchOutput;
if (r>=0) return r+offsetOfExactMatch; // if we had some x_i==p
int ip = -(r+1);
return ip+offsetOfInsertionPoint;
}
}
package jenkins.model.lazy;
import java.lang.ref.SoftReference;
/**
* {@link SoftReference} to a build object.
*
* <p>
* To be able to re-retrieve the referent in case it is lost, this class
* remembers its ID (the job name is provided by the context because a {@link BuildReference}
* belongs to one and only {@link AbstractLazyLoadRunMap}.)
*
* <p>
* We use this ID for equality/hashCode so that we can have a collection of {@link BuildReference}
* and find things in it.
*
* @author Kohsuke Kawaguchi
* @since 1.LAZYLOAD
*/
public final class BuildReference<R> extends SoftReference<R> {
final String id;
public BuildReference(String id, R referent) {
super(referent);
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BuildReference that = (BuildReference) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
package jenkins.model.lazy;
import groovy.util.MapEntry;
import hudson.util.AdaptedIterator;
import javax.annotation.Nullable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
/**
* Take {@code SortedMap<Integer,BuildReference<R>>} and make it look like {@code SortedMap<Integer,R>}.
*
* When {@link BuildReference} lost the build object, we'll use {@link AbstractLazyLoadRunMap#getById(String)}
* to obtain one.
*
* @author Kohsuke Kawaguchi
*/
class BuildReferenceMapAdapter<R> implements SortedMap<Integer,R> {
private final AbstractLazyLoadRunMap<R> loader;
private final SortedMap<Integer,BuildReference<R>> core;
BuildReferenceMapAdapter(AbstractLazyLoadRunMap<R> loader, SortedMap<Integer, BuildReference<R>> core) {
this.loader = loader;
this.core = core;
}
private R unwrap(@Nullable BuildReference<R> ref) {
if (ref==null) return null;
R v = ref.get();
if (v==null)
v = loader.getById(ref.id);
return v;
}
private BuildReference<R> wrap(@Nullable R value) {
if (value==null) return null;
return loader.createReference(value);
}
public Comparator<? super Integer> comparator() {
return core.comparator();
}
public SortedMap<Integer, R> subMap(Integer fromKey, Integer toKey) {
return new BuildReferenceMapAdapter<R>(loader,core.subMap(fromKey, toKey));
}
public SortedMap<Integer, R> headMap(Integer toKey) {
return new BuildReferenceMapAdapter<R>(loader,core.headMap(toKey));
}
public SortedMap<Integer, R> tailMap(Integer fromKey) {
return new BuildReferenceMapAdapter<R>(loader,core.tailMap(fromKey));
}
public Integer firstKey() {
return core.firstKey();
}
public Integer lastKey() {
return core.lastKey();
}
public Set<Integer> keySet() {
return core.keySet();
}
public Collection<R> values() {
return new CollectionAdapter(core.values());
}
public Set<Entry<Integer,R>> entrySet() {
return new SetAdapter(core.entrySet());
}
public int size() {
return core.size();
}
public boolean isEmpty() {
return core.isEmpty();
}
public boolean containsKey(Object key) {
return core.containsKey(key);
}
public boolean containsValue(Object value) {
return core.containsValue(value);
}
public R get(Object key) {
return unwrap(core.get(key));
}
public R put(Integer key, R value) {
return unwrap(core.put(key, wrap(value)));
}
public R remove(Object key) {
return unwrap(core.remove(key));
}
public void putAll(Map<? extends Integer, ? extends R> m) {
for (Entry<? extends Integer, ? extends R> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public void clear() {
core.clear();
}
@Override
public boolean equals(Object o) {
return core.equals(o);
}
@Override
public int hashCode() {
return core.hashCode();
}
private class CollectionAdapter implements Collection<R> {
private final Collection<BuildReference<R>> core;
private CollectionAdapter(Collection<BuildReference<R>> core) {
this.core = core;
}
public int size() {
return core.size();
}
public boolean isEmpty() {
return core.isEmpty();
}
public boolean contains(Object o) {
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
throw new UnsupportedOperationException();
}
public Iterator<R> iterator() {
return new AdaptedIterator<BuildReference<R>,R>(core.iterator()) {
protected R adapt(BuildReference<R> ref) {
return unwrap(ref);
}
};
}
public Object[] toArray() {
return _unwrap(core.toArray());
}
public <T> T[] toArray(T[] a) {
int size = size();
T[] r = a;
if (r.length>size)
r = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
Iterator<R> itr = iterator();
int i=0;
while (itr.hasNext()) {
r[i++] = (T)itr.next();
}
return r;
}
public boolean add(R value) {
return core.add(wrap(value));
}
public boolean remove(Object o) {
// return core.remove(o);
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
throw new UnsupportedOperationException();
}
public boolean containsAll(Collection<?> c) {
for (Object o : c) {
if (!contains(o))
return false;
}
return true;
}
public boolean addAll(Collection<? extends R> c) {
boolean b=false;
for (R r : c) {
b |= add(r);
}
return b;
}
public boolean removeAll(Collection<?> c) {
boolean b=false;
for (Object o : c) {
b|=remove(o);
}
return b;
}
public boolean retainAll(Collection<?> c) {
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
throw new UnsupportedOperationException();
}
public void clear() {
core.clear();
}
@Override
public boolean equals(Object o) {
return core.equals(o);
}
@Override
public int hashCode() {
return core.hashCode();
}
private Object[] _unwrap(Object[] r) {
for (int i=0; i<r.length; i++)
r[i] = unwrap((BuildReference<R>) r[i]);
return r;
}
}
private class SetAdapter implements Set<Entry<Integer, R>> {
private final Set<Entry<Integer, BuildReference<R>>> core;
private SetAdapter(Set<Entry<Integer, BuildReference<R>>> core) {
this.core = core;
}
public int size() {
return core.size();
}
public boolean isEmpty() {
return core.isEmpty();
}
public boolean contains(Object o) {
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
throw new UnsupportedOperationException();
}
public Iterator<Entry<Integer, R>> iterator() {
return new AdaptedIterator<Entry<Integer,BuildReference<R>>,Entry<Integer,R>>(core.iterator()) {
protected Entry<Integer, R> adapt(Entry<Integer, BuildReference<R>> e) {
return _unwrap(e);
}
};
}
public Object[] toArray() {
return _unwrap(core.toArray());
}
public <T> T[] toArray(T[] a) {
int size = size();
T[] r = a;
if (r.length>size)
r = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
Iterator<Entry<Integer, R>> itr = iterator();
int i=0;
while (itr.hasNext()) {
r[i++] = (T)itr.next();
}
return r;
}
public boolean add(Entry<Integer, R> value) {
return core.add(_wrap(value));
}
public boolean remove(Object o) {
// return core.remove(o);
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
throw new UnsupportedOperationException();
}
public boolean containsAll(Collection<?> c) {
for (Object o : c) {
if (!contains(o))
return false;
}
return true;
}
public boolean addAll(Collection<? extends Entry<Integer,R>> c) {
boolean b=false;
for (Entry<Integer,R> r : c) {
b |= add(r);
}
return b;
}
public boolean removeAll(Collection<?> c) {
boolean b=false;
for (Object o : c) {
b|=remove(o);
}
return b;
}
public boolean retainAll(Collection<?> c) {
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
throw new UnsupportedOperationException();
}
public void clear() {
core.clear();
}
@Override
public boolean equals(Object o) {
return core.equals(o);
}
@Override
public int hashCode() {
return core.hashCode();
}
private Entry<Integer,BuildReference<R>> _wrap(Entry<Integer,R> e) {
return new MapEntry(e.getKey(),wrap(e.getValue()));
}
private Entry<Integer, R> _unwrap(Entry<Integer, BuildReference<R>> e) {
return new MapEntry(e.getKey(),unwrap(e.getValue()));
}
private Object[] _unwrap(Object[] r) {
for (int i=0; i<r.length; i++)
r[i] = _unwrap((Entry) r[i]);
return r;
}
}
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
import java.util.AbstractList;
import java.util.Arrays;
/**
* {@code ArrayList&lt;Integer>} that uses {@code int} for storage.
*
* Plus a number of binary-search related methods that assume the array is sorted in the ascending order.
*
* @author Kohsuke Kawaguchi
*/
class SortedIntList extends AbstractList<Integer> {
private int[] data;
private int size;
public SortedIntList(int capacity) {
this.data = new int[capacity];
this.size = 0;
}
/**
* Internal copy constructor.
*/
public SortedIntList(SortedIntList rhs) {
this.data = Arrays.copyOf(rhs.data,rhs.data.length+8);
this.size = rhs.size;
}
/**
* Binary search to find the position of the given string.
*
* @return
* -(insertionPoint+1) if the exact string isn't found.
* That is, -1 means the probe would be inserted at the very beginning.
*/
public int find(int probe) {
return Arrays.binarySearch(data, 0, size, probe);
}
@Override
public boolean contains(Object o) {
return o instanceof Integer && contains(((Integer)o).intValue());
}
public boolean contains(int i) {
return find(i)>=0;
}
@Override
public Integer get(int index) {
if (size<=index) throw new IndexOutOfBoundsException();
return data[index];
}
@Override
public int size() {
return size;
}
@Override
public boolean add(Integer i) {
return add(i.intValue());
}
public boolean add(int i) {
ensureCapacity(size+1);
data[size++] = i;
return true;
}
private void ensureCapacity(int i) {
if (data.length<i) {
int[] r = new int[Math.max(data.length*2,i)];
System.arraycopy(data,0,r,0,size);
data = r;
}
}
/**
* Finds the index of the entry lower than v.
*/
public int lower(int v) {
return Boundary.LOWER.apply(find(v));
}
/**
* Finds the index of the entry greater than v.
*/
public int higher(int v) {
return Boundary.HIGHER.apply(find(v));
}
/**
* Finds the index of the entry lower or equal to v.
*/
public int floor(int v) {
return Boundary.FLOOR.apply(find(v));
}
/**
* Finds the index of the entry greater or equal to v.
*/
public int ceil(int v) {
return Boundary.CEIL.apply(find(v));
}
public boolean isInRange(int idx) {
return 0<=idx && idx<size;
}
public void sort() {
Arrays.sort(data,0,size);
}
public void copyInto(int[] dest) {
System.arraycopy(data,0,dest,0,size);
}
public void removeValue(int n) {
int idx = find(n);
if (idx<0) return;
System.arraycopy(data,idx+1,data,idx,size-(idx+1));
size--;
}
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
/**
* {@link List} decorator that provides a number of binary-search related methods
* by assuming that the array is sorted in the ascending order.
*
* @author Kohsuke Kawaguchi
*/
class SortedList<T extends Comparable<T>> extends AbstractList<T> {
private List<T> data;
public SortedList(List<T> data) {
this.data = data;
}
/**
* Binary search to find the position of the given string.
*
* @return
* -(insertionPoint+1) if the exact string isn't found.
* That is, -1 means the probe would be inserted at the very beginning.
*/
public int find(T probe) {
return Collections.binarySearch(data, probe);
}
@Override
public boolean contains(Object o) {
return find((T)o)>=0;
}
public T get(int idx) {
return data.get(idx);
}
@Override
public int size() {
return data.size();
}
@Override
public T remove(int index) {
return data.remove(index);
}
@Override
public boolean remove(Object o) {
return data.remove(o);
}
/**
* Finds the index of the entry lower than v.
*
* @return
* return value will be in the [-1,size) range
*/
public int lower(T v) {
return Boundary.LOWER.apply(find(v));
}
/**
* Finds the index of the entry greater than v.
*
* @return
* return value will be in the [0,size] range
*/
public int higher(T v) {
return Boundary.HIGHER.apply(find(v));
}
/**
* Finds the index of the entry lower or equal to v.
*
* @return
* return value will be in the [-1,size) range
*/
public int floor(T v) {
return Boundary.FLOOR.apply(find(v));
}
/**
* Finds the index of the entry greater or equal to v.
*
* @return
* return value will be in the [0,size] range
*/
public int ceil(T v) {
return Boundary.CEIL.apply(find(v));
}
public boolean isInRange(int idx) {
return 0<=idx && idx<data.size();
}
}
......@@ -23,16 +23,20 @@
*/
package hudson.util;
import hudson.util.Iterators.CountingPredicate;
import junit.framework.TestCase;
import java.util.List;
import static java.util.Arrays.*;
/**
* @author Kohsuke Kawaguchi
*/
public class IteratorsTest extends TestCase {
public void testReverseSequence() {
List<Integer> lst = Iterators.reverseSequence(1,4);
List<Integer> lst = Iterators.reverseSequence(1, 4);
assertEquals(3,(int)lst.get(0));
assertEquals(2,(int)lst.get(1));
assertEquals(1,(int)lst.get(2));
......@@ -44,6 +48,17 @@ public class IteratorsTest extends TestCase {
assertEquals(1,(int)lst.get(0));
assertEquals(2,(int)lst.get(1));
assertEquals(3,(int)lst.get(2));
assertEquals(3,lst.size());
assertEquals(3, lst.size());
}
public void testLimit() {
assertEquals("[0]",com.google.common.collect.Iterators.toString(Iterators.limit(asList(0,1,2,3,4).iterator(), EVEN)));
assertEquals("[]", com.google.common.collect.Iterators.toString(Iterators.limit(asList(1,2,4,6).iterator(), EVEN)));
}
public static final CountingPredicate<Integer> EVEN = new CountingPredicate<Integer>() {
public boolean apply(int index, Integer input) {
return input % 2 == 0;
}
};
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.SortedMap;
/**
* @author Kohsuke Kawaguchi
*/
public class AbstractLazyLoadRunMapTest extends Assert {
// A=1, B=3, C=5
@Rule
public FakeMapBuilder aBuilder = new FakeMapBuilder();
private FakeMap a;
// empty map
@Rule
public FakeMapBuilder bBuilder = new FakeMapBuilder();
private FakeMap b;
@Rule
public FakeMapBuilder localBuilder = new FakeMapBuilder();
@Before
public void setUp() throws Exception {
a = aBuilder.add(1, "A").add(3, "B").add(5, "C").make();
b = bBuilder.make();
}
@Test
public void lookup() {
a.get(1).asserts(1,"A");
assertNull(a.get(2));
a.get(3).asserts(3,"B");
assertNull(a.get(4));
a.get(5).asserts(5,"C");
assertNull(b.get(1));
assertNull(b.get(3));
assertNull(b.get(5));
}
@Test
public void idempotentLookup() {
for (int i=0; i<5; i++) {
a.get(1).asserts(1,"A");
a.get((Object)1).asserts(1, "A");
}
}
@Test
public void lookupWithBogusKeyType() {
assertNull(a.get(null));
assertNull(a.get("foo"));
assertNull(a.get(this));
}
@Test
public void firstKey() {
assertEquals(5, a.firstKey().intValue());
try {
b.firstKey();
fail();
} catch (NoSuchElementException e) {
// as expected
}
}
@Test
public void lastKey() {
assertEquals(1, a.lastKey().intValue());
try {
b.lastKey();
fail();
} catch (NoSuchElementException e) {
// as expected
}
}
@Test
public void search() {
// searching toward non-existent direction
assertNull(a.search( 99, Direction.ASC));
assertNull(a.search(-99, Direction.DESC));
}
/**
* If load fails, search needs to gracefully handle it
*/
@Test
public void unloadableData() throws IOException {
FakeMap m = localBuilder.add(1, "A").addUnloadable("B").add(5, "C").make();
assertNull(m.search(3,Direction.EXACT));
m.search(3,Direction.DESC).asserts(1,"A");
m.search(3, Direction.ASC ).asserts(5,"C");
}
@Test
public void eagerLoading() throws IOException {
Map.Entry[] b = a.entrySet().toArray(new Map.Entry[3]);
((Build)b[0].getValue()).asserts(5,"C");
((Build)b[1].getValue()).asserts(3,"B");
((Build)b[2].getValue()).asserts(1,"A");
}
@Test
public void fastLookup() throws IOException {
FakeMap a = localBuilder.addBoth(1, "A").addBoth(3, "B").addBoth(5, "C"). make();
a.get(1).asserts(1,"A");
assertNull(a.get(2));
a.get(3).asserts(3,"B");
assertNull(a.get(4));
a.get(5).asserts(5,"C");
}
@Test
public void fastSearch() throws IOException {
FakeMap a = localBuilder.addBoth(1, "A").addBoth(3, "B").addBoth(5, "C").addBoth(7,"D").make();
// we should be using the cache to find the entry efficiently
a.search(6, Direction.ASC).asserts(7,"D");
a.search(2, Direction.DESC).asserts(1, "A");
}
@Test
public void bogusCache() throws IOException {
FakeMap a = localBuilder.addUnloadableCache(1).make();
assertNull(a.get(1));
}
@Test
public void bogusCacheAndHiddenRealData() throws IOException {
FakeMap a = localBuilder.addUnloadableCache(1).add(1,"A").make();
a.get(1).asserts(1, "A");
}
@Test
public void bogusCache2() throws IOException {
FakeMap a = localBuilder.addBogusCache(1,3,"A").make();
assertNull(a.get(1));
a.get(3).asserts(3,"A");
}
@Test
public void incompleteCache() throws IOException {
FakeMapBuilder setup = localBuilder.addBoth(1, "A").add(3, "B").addBoth(5, "C");
// each test uses a fresh map since cache lookup causes additional loads
// to verify the results
// if we just rely on cache,
// it'll pick up 5:C as the first ascending value,
// but we should be then verifying this by loading B, so in the end we should
// find the correct value
setup.make().search(2, Direction.ASC).asserts(3,"B");
setup.make().search(4, Direction.DESC).asserts(3,"B");
// variation of the cache based search where we find the outer-most value via cache
setup.make().search(0, Direction.ASC).asserts(1,"A");
setup.make().search(6, Direction.DESC).asserts(5,"C");
// variation of the cache search where the cache tells us that we are searching
// in the direction that doesn't have any records
assertNull(setup.make().search(0, Direction.DESC));
assertNull(setup.make().search(6, Direction.ASC));
}
@Test
public void fastSubMap() throws Exception {
SortedMap<Integer,Build> m = a.subMap(99, 2);
assertEquals(2, m.size());
Build[] b = m.values().toArray(new Build[2]);
assertEquals(2,b.length);
b[0].asserts(5,"C");
b[1].asserts(3,"B");
}
@Test
public void identity() {
assertTrue(a.equals(a));
assertTrue(!a.equals(b));
a.hashCode();
b.hashCode();
}
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
/**
* @author Kohsuke Kawaguchi
*/
public class FakeMap extends AbstractLazyLoadRunMap<Build> {
public FakeMap(File dir) {
super(dir);
}
@Override
protected int getNumberOf(Build build) {
return build.n;
}
@Override
protected String getIdOf(Build build) {
return build.id;
}
@Override
protected FilenameFilter createDirectoryFilter() {
return new FilenameFilter() {
public boolean accept(File dir, String name) {
try {
Integer.parseInt(name);
return false;
} catch (NumberFormatException e) {
return true;
}
}
};
}
@Override
protected Build retrieve(File dir) throws IOException {
String n = FileUtils.readFileToString(new File(dir, "n")).trim();
String id = FileUtils.readFileToString(new File(dir, "id")).trim();
return new Build(Integer.parseInt(n),id);
}
}
class Build {
final int n;
final String id;
Build(int n, String id) {
this.n = n;
this.id = id;
}
public void asserts(int n, String id) {
assert this.n==n;
assert this.id.equals(id);
}
}
\ No newline at end of file
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
import org.apache.commons.io.FileUtils;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.IOException;
/**
* Builder for creating a {@link FakeMap}
*
* @author Kohsuke Kawaguchi
*/
public class FakeMapBuilder implements TestRule {
private File dir;
public FakeMapBuilder() {
}
public FakeMapBuilder add(int n, String id) throws IOException {
verifyId(id);
File build = new File(dir,id);
build.mkdir();
FileUtils.writeStringToFile(new File(build, "n"), Integer.toString(n));
FileUtils.writeStringToFile(new File(build,"id"),id);
return this;
}
/**
* Adds a symlink from n to build id.
*
* (in test we should ideally create a symlink, but we fake the test
* by actually making it a directory and staging the same data.)
*/
public FakeMapBuilder addCache(int n, String id) throws IOException {
return addBogusCache(n,n,id);
}
public FakeMapBuilder addBogusCache(int label, int actual, String id) throws IOException {
verifyId(id);
File build = new File(dir,Integer.toString(label));
build.mkdir();
FileUtils.writeStringToFile(new File(build, "n"), Integer.toString(actual));
FileUtils.writeStringToFile(new File(build,"id"),id);
return this;
}
public FakeMapBuilder addBoth(int n, String id) throws IOException {
return add(n,id).addCache(n,id);
}
private void verifyId(String id) {
try {
Integer.parseInt(id);
throw new IllegalMonitorStateException("ID cannot be a number");
} catch (NumberFormatException e) {
// OK
}
}
/**
* Adds a build record under the givn ID but make it unloadable,
* which will cause a failure when a load is attempted on this build ID.
*/
public FakeMapBuilder addUnloadable(String id) throws IOException {
File build = new File(dir,id);
build.mkdir();
return this;
}
public FakeMapBuilder addUnloadableCache(int n) throws IOException {
File build = new File(dir,String.valueOf(n));
build.mkdir();
return this;
}
public FakeMap make() {
return new FakeMap(dir);
}
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
dir = File.createTempFile("lazyload","test");
dir.delete();
dir.mkdirs();
try {
base.evaluate();
} finally {
FileUtils.deleteDirectory(dir);
}
}
};
}
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* 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 jenkins.model.lazy;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author Kohsuke Kawaguchi
*/
public class SortedListTest extends Assert {
SortedList<String> l = new SortedList<String>(new ArrayList<String>(Arrays.asList("B","D","F")));
@Test
public void testCeil() {
assertEquals(0,l.ceil("A"));
assertEquals(0,l.ceil("B"));
assertEquals(1,l.ceil("C"));
assertEquals(1,l.ceil("D"));
assertEquals(2,l.ceil("E"));
assertEquals(2,l.ceil("F"));
assertEquals(3,l.ceil("G"));
}
@Test
public void testFloor() {
assertEquals(-1,l.floor("A"));
assertEquals(0,l.floor("B"));
assertEquals(0,l.floor("C"));
assertEquals(1,l.floor("D"));
assertEquals(1,l.floor("E"));
assertEquals(2,l.floor("F"));
assertEquals(2,l.floor("G"));
}
@Test
public void testLower() {
assertEquals(-1,l.lower("A"));
assertEquals(-1,l.lower("B"));
assertEquals(0,l.lower("C"));
assertEquals(0,l.lower("D"));
assertEquals(1,l.lower("E"));
assertEquals(1,l.lower("F"));
assertEquals(2,l.lower("G"));
}
@Test
public void testHigher() {
assertEquals(0,l.higher("A"));
assertEquals(1,l.higher("B"));
assertEquals(1,l.higher("C"));
assertEquals(2,l.higher("D"));
assertEquals(2,l.higher("E"));
assertEquals(3,l.higher("F"));
assertEquals(3,l.higher("G"));
}
@Test
public void testRange() {
assertTrue(l.isInRange(0));
assertTrue(l.isInRange(1));
assertTrue(l.isInRange(2));
assertFalse(l.isInRange(-1));
assertFalse(l.isInRange(3));
}
@Test
public void remove() {
l.remove("nosuchthing");
assertEquals(3,l.size());
l.remove("B");
assertEquals(2, l.size());
assertEquals("D",l.get(0));
assertEquals("F",l.get(1));
}
}
jenkins (1.483) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
-- Kohsuke Kawaguchi <kk@kohsuke.org> Sun, 23 Sep 2012 20:28:41 -0700
jenkins (1.482) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
</parent>
<artifactId>maven-plugin</artifactId>
......
TODO:
- remove Java6 dependency (or do we really?)
NOTE:
- permalinks, such as "last unstable build" can end up loading all the records if there's no build that has the said status.
......@@ -10,7 +10,7 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<name>Jenkins plugin POM</name>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<packaging>pom</packaging>
<!--
......@@ -33,7 +33,7 @@
<dependency><!-- if a plugin wants to depend on the maven plugin, choose the right version automatically -->
<groupId>org.jenkins-ci.main</groupId>
<artifactId>maven-plugin</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
......@@ -43,25 +43,25 @@
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-war</artifactId>
<type>war</type>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>ui-samples-plugin</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
......@@ -316,6 +316,13 @@
</plugin>
</plugins>
</pluginManagement>
<extensions>
<extension>
<groupId>org.kohsuke</groupId>
<artifactId>wagon-gitsite</artifactId>
<version>0.3.5</version>
</extension>
</extensions>
</build>
<repositories>
......@@ -345,6 +352,10 @@
</pluginRepositories>
<distributionManagement>
<site>
<id>github-pages</id>
<url>gitsite:git@github.com/jenkinsci/maven-site.git:plugin-parent</url>
</site>
<snapshotRepository>
<id>maven.jenkins-ci.org</id>
<url>http://maven.jenkins-ci.org:8081/content/repositories/snapshots</url>
......
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Jenkins main module</name>
......@@ -63,8 +63,8 @@ THE SOFTWARE.
</scm>
<distributionManagement>
<site>
<id>hudson-www</id>
<url>scp://jenkins-ci.org/home/kohsuke/www/hudson-labs.org/maven-site/</url>
<id>github-pages</id>
<url>gitsite:git@github.com/jenkinsci/maven-site.git:core</url>
</site>
<snapshotRepository>
<id>maven.jenkins-ci.org</id>
......@@ -678,6 +678,13 @@ THE SOFTWARE.
</plugin>
</plugins>
<extensions>
<extension>
<groupId>org.kohsuke</groupId>
<artifactId>wagon-gitsite</artifactId>
<version>0.3.5</version>
</extension>
</extensions>
</build>
<profiles>
......
What is this?
This is a collection of auto-generated documentations of Jenkins core. You probably want to start from {{{https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins}Wiki}}.
此差异由.gitattributes 抑制。
<project name="Jenkins">
<bannerLeft>
<name>Jenkins</name>
<href>http://jenkins-ci.org/</href>
<src>jenkins_logo.png</src>
</bannerLeft>
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.3.0</version>
</skin>
<body>
<menu ref="modules"/>
<menu ref="reports" inherit="top"/>
</body>
</project>
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
</parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
......
......@@ -27,7 +27,6 @@ package hudson.tasks;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Cause.LegacyCodeCause;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
</parent>
<artifactId>ui-samples-plugin</artifactId>
......
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.484-SNAPSHOT</version>
<version>1.485-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册