/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
*
* 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.model;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.logging.Level;
import static java.util.logging.Level.*;
import java.util.logging.Logger;
import jenkins.model.RunIdMigrator;
import jenkins.model.lazy.AbstractLazyLoadRunMap;
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.*;
import jenkins.model.lazy.BuildReference;
import org.apache.commons.collections.comparators.ReverseComparator;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* {@link Map} from build number to {@link Run}.
*
* <p>
* This class is multi-thread safe by using copy-on-write technique,
* and it also updates the bi-directional links within {@link Run}
* accordingly.
*
* @author Kohsuke Kawaguchi
*/
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);
private Constructor<R> cons;
/** Normally overwritten by {@link LazyBuildMixIn#onLoad} or {@link LazyBuildMixIn#onCreatedFromScratch}, in turn created during {@link Job#onLoad}. */
@Restricted(NoExternalUse.class)
public RunIdMigrator runIdMigrator = new RunIdMigrator();
// TODO: before first complete build
// patch up next/previous build link
/**
* @deprecated as of 1.485
* Use {@link #RunMap(File, Constructor)}.
*/
@Deprecated
public RunMap() {
super(null); // will be set later
}
/**
* @param cons
* Used to create new instance of {@link Run}.
*/
public RunMap(File baseDir, Constructor cons) {
super(baseDir);
this.cons = cons;
}
public boolean remove(R run) {
return removeValue(run);
}
/**
* 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;
}
public R next() {
last = next;
if (last!=null)
next = last.getPreviousBuild();
else
throw new NoSuchElementException();
return last;
}
public void remove() {
if (last==null)
throw new UnsupportedOperationException();
removeValue(last);
}
};
}
@Override
public boolean removeValue(R run) {
run.dropLinks();
runIdMigrator.delete(dir, run.getId());
return super.removeValue(run);
}
/**
* Gets the read-only view of this map.
*/
public SortedMap<Integer,R> getView() {
return view;
}
/**
* This is the newest build (with the biggest build number)
*/
public R newestValue() {
return search(Integer.MAX_VALUE, DESC);
}
/**
* This is the oldest build (with the smallest build number)
*/
public R oldestValue() {
return search(Integer.MIN_VALUE, ASC);
}
/**
* @deprecated as of 1.485
* Use {@link ReverseComparator}
*/
@Deprecated
public static final Comparator<Comparable> COMPARATOR = new Comparator<Comparable>() {
public int compare(Comparable o1, Comparable o2) {
return -o1.compareTo(o2);
}
};
/**
* {@link Run} factory.
*/
public interface Constructor<R extends Run<?,R>> {
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();
}
/**
* Add a <em>new</em> build to the map.
* Do not use when loading existing builds (use {@link #put(Integer, Object)}).
*/
@Override
public R put(R r) {
// Defense against JENKINS-23152 and its ilk.
File rootDir = r.getRootDir();
if (rootDir.isDirectory()) {
throw new IllegalStateException("JENKINS-23152: " + rootDir + " already existed; will not overwrite with " + r);
}
if (!r.getClass().getName().equals("hudson.matrix.MatrixRun")) { // JENKINS-26739: grandfathered in
proposeNewNumber(r.getNumber());
}
rootDir.mkdirs();
return super._put(r);
}
@Override public R getById(String id) {
int n;
try {
n = Integer.parseInt(id);
} catch (NumberFormatException x) {
n = runIdMigrator.findNumber(id);
}
return getByNumber(n);
}
/**
* 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.
*/
@Override
protected BuildReference<R> createReference(R r) {
return r.createReference();
}
@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(FINEST)) {
LOGGER.log(FINEST, "Loaded " + b.getFullDisplayName() + " in " + Thread.currentThread().getName(), 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);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "could not load " + d, e);
}
}
return null;
}
/**
* 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.485
* Use {@link #RunMap(File, Constructor)}
*/
@Deprecated
public void load(Job job, Constructor<R> cons) {
this.cons = cons;
initBaseDir(job.getBuildDir());
}
private static final Logger LOGGER = Logger.getLogger(RunMap.class.getName());
private static class ThisIsHowItsLoaded extends Exception {}
}