package jenkins.model.lazy;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import jenkins.util.SystemProperties;
import hudson.model.Run;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.lazy.LazyBuildMixIn.RunMixIn;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Reference (by default a {@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.485 (but as of 1.548 not a {@link SoftReference})
*/
public final class BuildReference<R> {
private static final Logger LOGGER = Logger.getLogger(BuildReference.class.getName());
final String id;
private volatile Holder<R> holder;
public BuildReference(String id, R referent) {
this.id = id;
this.holder = findHolder(referent);
}
/**
* Gets the build if still in memory.
* @return the actual build, or null if it has been collected
* @see Holder#get
*/
public @CheckForNull R get() {
Holder<R> h = holder; // capture
return h!=null ? h.get() : null;
}
/**
* Clear the reference to make a particular R object effectively unreachable.
*
* @see RunMixIn#dropLinks()
*/
/*package*/ void clear() {
holder = null;
}
@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();
}
@Override public String toString() {
R r = get();
return r != null ? r.toString() : id;
}
/**
* An abstraction of {@link Reference}.
* @since 1.548
*/
public interface Holder<R> {
/**
* Gets a build.
* @return the build reference, or null if collected
*/
@CheckForNull R get();
}
/**
* Extensible factory for creating build references.
* @since 1.548
*/
public interface HolderFactory extends ExtensionPoint {
/**
* Constructs a single build reference.
* @param <R> the type of thing (generally {@link Run})
* @param referent the thing to load
* @return a reference, or null to consult the next factory
*/
@CheckForNull <R> Holder<R> make(@Nonnull R referent);
}
private static <R> Holder<R> findHolder(R referent) {
if (referent == null) {
// AbstractBuild.NONE
return new DefaultHolderFactory.NoHolder<R>();
}
for (HolderFactory f : ExtensionList.lookup(HolderFactory.class)) {
Holder<R> h = f.make(referent);
if (h != null) {
LOGGER.log(Level.FINE, "created build reference for {0} using {1}", new Object[] {referent, f});
return h;
}
}
return new DefaultHolderFactory().make(referent);
}
/**
* Default factory if none other are installed.
* Its behavior can be controlled via the system property {@link DefaultHolderFactory#MODE_PROPERTY}:
* <dl>
* <dt><code>soft</code> (default)
* <dd>Use {@link SoftReference}s. Builds will be kept around so long as memory pressure is not too high.
* <dt><code>weak</code>
* <dd>Use {@link WeakReference}s. Builds will be kept only until the next full garbage collection cycle.
* <dt><code>strong</code>
* <dd>Use strong references. Builds will still be loaded lazily, but once loaded, will not be released.
* <dt><code>none</code>
* <dd>Do not hold onto builds at all. Mainly offered as an option for the purpose of reproducing lazy-loading bugs.
* </dl>
*/
@Restricted(NoExternalUse.class)
@Extension(ordinal=Double.NEGATIVE_INFINITY) public static final class DefaultHolderFactory implements HolderFactory {
public static final String MODE_PROPERTY = "jenkins.model.lazy.BuildReference.MODE";
private static final String mode = SystemProperties.getString(MODE_PROPERTY);
@Override public <R> Holder<R> make(R referent) {
if (mode == null || mode.equals("soft")) {
return new SoftHolder<R>(referent);
} else if (mode.equals("weak")) {
return new WeakHolder<R>(referent);
} else if (mode.equals("strong")) {
return new StrongHolder<R>(referent);
} else if (mode.equals("none")) {
return new NoHolder<R>();
} else {
throw new IllegalStateException("unrecognized value of " + MODE_PROPERTY + ": " + mode);
}
}
private static final class SoftHolder<R> extends SoftReference<R> implements Holder<R> {
SoftHolder(R referent) {
super(referent);
}
}
private static final class WeakHolder<R> extends WeakReference<R> implements Holder<R> {
WeakHolder(R referent) {
super(referent);
}
}
private static final class StrongHolder<R> implements Holder<R> {
private final R referent;
StrongHolder(R referent) {
this.referent = referent;
}
@Override public R get() {return referent;}
}
private static final class NoHolder<R> implements Holder<R> {
@Override public R get() {return null;}
}
}
}