hudson.model.AbstractProject.java Source code

Java tutorial

Introduction

Here is the source code for hudson.model.AbstractProject.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi,
 * Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot,
 * Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts,
 * id:cactusman, Yahoo! Inc., Anton Kozak, Nikita Levyankov
 *
 * 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 antlr.ANTLRException;
import hudson.AbortException;
import hudson.CopyOnWrite;
import hudson.FeedAdapter;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Cause.LegacyCodeCause;
import hudson.model.Cause.RemoteCause;
import hudson.model.Cause.UserCause;
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.SubTask;
import hudson.model.queue.SubTaskContributor;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.NullSCM;
import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
import hudson.scm.SCMS;
import hudson.search.SearchIndexBuilder;
import hudson.security.Permission;
import hudson.slaves.WorkspaceList;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildTrigger;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.tasks.Mailer;
import hudson.tasks.Publisher;
import hudson.triggers.SCMTrigger;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.AutoCompleteSeeder;
import hudson.util.CascadingUtil;
import hudson.util.DescribableList;
import hudson.util.DescribableListUtil;
import hudson.util.EditDistance;
import hudson.util.FormValidation;
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.hudsonci.api.model.IAbstractProject;
import org.hudsonci.model.project.property.IntegerProjectProperty;
import org.hudsonci.model.project.property.TriggerProjectProperty;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;

import static hudson.scm.PollingResult.*;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

/**
 * Base implementation of {@link Job}s that build software.
 *
 * For now this is primarily the common part of {@link Project} and MavenModule.
 *
 * @author Kohsuke Kawaguchi
 * @see AbstractBuild
 */
public abstract class AbstractProject<P extends AbstractProject<P, R>, R extends AbstractBuild<P, R>>
        extends Job<P, R> implements BuildableItem, IAbstractProject {
    public static final String CONCURRENT_BUILD_PROPERTY_NAME = "concurrentBuild";
    public static final String CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME = "cleanWorkspaceRequired";
    public static final String BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME = "blockBuildWhenDownstreamBuilding";
    public static final String BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME = "blockBuildWhenUpstreamBuilding";
    public static final String QUIET_PERIOD_PROPERTY_NAME = "quietPeriod";
    public static final String SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME = "scmCheckoutRetryCount";
    public static final String CUSTOM_WORKSPACE_PROPERTY_NAME = "customWorkspace";
    public static final String JDK_PROPERTY_NAME = "jdk";
    public static final String SCM_PROPERTY_NAME = "scm";
    public static final String HAS_QUIET_PERIOD_PROPERTY_NAME = "hasQuietPeriod";
    public static final String HAS_SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME = "hasScmCheckoutRetryCount";
    public static final String BUILD_TRIGGER_PROPERTY_NAME = "hudson-tasks-BuildTrigger";
    public static final String APPOINTED_NODE_PROPERTY_NAME = "appointedNode";
    public static final String BASIC_KEY = "basic";
    public static final String AFFINITY_CHO0SER_KEY = "affinityChooser";
    public static final String SLAVE_KEY = "slave";
    public static final String ASSIGNED_LABEL_KEY = "_.assignedLabelString";

    /**
     * {@link SCM} associated with the project.
     * To allow derived classes to link {@link SCM} config to elsewhere,
     * access to this variable should always go through {@link #getScm()}.
        
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    private volatile SCM scm = new NullSCM();

    /**
     * State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
     */
    private volatile transient SCMRevisionState pollingBaseline = null;

    /**
     * All the builds keyed by their build number.
     */
    protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();

    /**
     * The quiet period. Null to delegate to the system default.
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    private volatile Integer quietPeriod = null;

    /**
     * The retry count. Null to delegate to the system default.
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    private volatile Integer scmCheckoutRetryCount = null;

    /**
     * If this project is configured to be only built on a certain label,
     * this value will be set to that label.
     *
     * For historical reasons, this is called 'assignedNode'. Also for
     * a historical reason, null to indicate the affinity
     * with the master node.
     *
     * @see #canRoam
     *
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to
     *             {@link hudson.model.AbstractProject#getAppointedNode()#getName()}.
     */
    @Deprecated
    private String assignedNode;

    /**
     * Node list is dropdown or textfield
     *
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to
     *             {@link hudson.model.AbstractProject#getAppointedNode()#isAdvancedAffinityChooser()}.
     */
    @Deprecated
    private Boolean advancedAffinityChooser;

    /**
     * True if this project can be built on any node.
     *
     * <p>
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
     *
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to
     *             {@link hudson.model.AbstractProject#getAppointedNode()#getCanRoam}.
     */
    @Deprecated
    private volatile boolean canRoam;

    /**
     * True to suspend new builds.
     */
    protected volatile boolean disabled;

    /**
     * True to keep builds of this project in queue when downstream projects are building.
     *
     * @deprecated as of 2.1.2.
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    protected volatile boolean blockBuildWhenDownstreamBuilding;

    /**
     * True to keep builds of this project in queue when upstream projects are building.
     *
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    protected volatile boolean blockBuildWhenUpstreamBuilding;

    /**
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     * <p/>
     * <p/>
     * Can't store {@link JDK} directly because {@link Hudson} and {@link Project}
     * are saved independently.
     *
     * @see Hudson#getJDK(String)
     * @deprecated 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    private volatile String jdk;

    /**
     * @deprecated since 2007-01-29.
     */
    @Deprecated
    private transient boolean enableRemoteTrigger;

    private volatile BuildAuthorizationToken authToken = null;

    /**
     * List of all {@link Trigger}s for this project.
     *
     * @deprecated as of 2.2.0
     *
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();

    /**
     * {@link Action}s contributed from subsidiary objects associated with
     * {@link AbstractProject}, such as from triggers, builders, publishers, etc.
     *
     * We don't want to persist them separately, and these actions
     * come and go as configuration change, so it's kept separate.
     */
    @CopyOnWrite
    protected transient volatile List<Action> transientActions = new Vector<Action>();

    /**
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    private boolean concurrentBuild;

    /**
     * True to clean the workspace prior to each build.
     *
     * @deprecated as of 2.2.0
     *             don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
     *             Use getter/setter for accessing to this field.
     */
    @Deprecated
    private volatile boolean cleanWorkspaceRequired;

    protected AbstractProject(ItemGroup parent, String name) {
        super(parent, name);

        //TODO: Investigate when this case happens.
        //if (Hudson.getInstance() != null && !Hudson.getInstance().getNodes().isEmpty()) {
        // if a new job is configured with Hudson that already has slave nodes
        // make it roamable by default
        // canRoam = true;
        //}
    }

    @Override
    public void onCreatedFromScratch() {
        super.onCreatedFromScratch();
        // solicit initial contributions, especially from TransientProjectActionFactory
        updateTransientActions();
        setCreationTime(new GregorianCalendar().getTimeInMillis());
        User user = User.current();
        if (user != null) {
            setCreatedBy(user.getId());
            grantProjectMatrixPermissions(user);
        }
    }

    @Override
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
        super.onLoad(parent, name);

        this.builds = new RunMap<R>();
        this.builds.load(this, new Constructor<R>() {
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });

        // boolean! Can't tell if xml file contained false..
        if (enableRemoteTrigger)
            OldDataMonitor.report(this, "1.77");
        for (Trigger t : getTriggerDescribableList()) {
            t.start(this, false);
        }
        if (scm == null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.

        if (transientActions == null)
            transientActions = new Vector<Action>(); // happens when loaded from disk
        updateTransientActions();
        getTriggerDescribableList().setOwner(this);
    }

    @Override
    protected void buildProjectProperties() throws IOException {
        super.buildProjectProperties();
        convertBlockBuildWhenUpstreamBuildingProperty();
        convertBlockBuildWhenDownstreamBuildingProperty();
        convertConcurrentBuildProperty();
        convertCleanWorkspaceRequiredProperty();
        convertQuietPeriodProperty();
        convertScmCheckoutRetryCountProperty();
        convertJDKProperty();
        convertScmProperty();
        convertTriggerProperties();
        convertAppointedNode();
    }

    void convertBlockBuildWhenUpstreamBuildingProperty() throws IOException {
        if (null == getProperty(BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME)) {
            setBlockBuildWhenUpstreamBuilding(blockBuildWhenUpstreamBuilding);
            blockBuildWhenUpstreamBuilding = false;
        }
    }

    void convertBlockBuildWhenDownstreamBuildingProperty() throws IOException {
        if (null == getProperty(BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME)) {
            setBlockBuildWhenDownstreamBuilding(blockBuildWhenDownstreamBuilding);
            blockBuildWhenDownstreamBuilding = false;
        }
    }

    void convertConcurrentBuildProperty() throws IOException {
        if (null == getProperty(CONCURRENT_BUILD_PROPERTY_NAME)) {
            setConcurrentBuild(concurrentBuild);
            concurrentBuild = false;
        }
    }

    void convertCleanWorkspaceRequiredProperty() throws IOException {
        if (null == getProperty(CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME)) {
            setCleanWorkspaceRequired(cleanWorkspaceRequired);
            cleanWorkspaceRequired = false;
        }
    }

    void convertQuietPeriodProperty() throws IOException {
        if (null != quietPeriod && null == getProperty(QUIET_PERIOD_PROPERTY_NAME)) {
            setQuietPeriod(quietPeriod);
            quietPeriod = null;
        }
    }

    void convertScmCheckoutRetryCountProperty() throws IOException {
        if (null != scmCheckoutRetryCount && null == getProperty(SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME)) {
            setScmCheckoutRetryCount(scmCheckoutRetryCount);
            scmCheckoutRetryCount = null;
        }
    }

    void convertJDKProperty() throws IOException {
        if (null != jdk && null == getProperty(JDK_PROPERTY_NAME)) {
            setJDK(jdk);
            jdk = null;
        }
    }

    void convertScmProperty() throws IOException {
        if (null != scm && null == getProperty(SCM_PROPERTY_NAME)) {
            setScm(scm);
            scm = null;
        }
    }

    void convertTriggerProperties() {
        if (triggers != null) {
            setTriggers(triggers);
            triggers = null;
        }
    }

    void convertAppointedNode() {
        if (assignedNode != null && getProperty(APPOINTED_NODE_PROPERTY_NAME) == null) {
            setAppointedNode(new AppointedNode(assignedNode, advancedAffinityChooser));
            assignedNode = null;
            advancedAffinityChooser = null;
        }
    }

    @Override
    protected void performDelete() throws IOException, InterruptedException {
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
        FilePath ws = getWorkspace();
        if (ws != null) {
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if (on != null)
                on.getFileSystemProvisioner().discardWorkspace(this, ws);
        }
        super.performDelete();
    }

    /**
     * Does this project perform concurrent builds?
     * @since 1.319
     */
    @Exported
    public boolean isConcurrentBuild() {
        return Hudson.CONCURRENT_BUILD
                && CascadingUtil.getBooleanProjectProperty(this, CONCURRENT_BUILD_PROPERTY_NAME).getValue();
    }

    public void setConcurrentBuild(boolean b) throws IOException {
        CascadingUtil.getBooleanProjectProperty(this, CONCURRENT_BUILD_PROPERTY_NAME).setValue(b);
        save();
    }

    public boolean isCleanWorkspaceRequired() {
        return CascadingUtil.getBooleanProjectProperty(this, CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME).getValue();
    }

    public void setCleanWorkspaceRequired(boolean cleanWorkspaceRequired) {
        CascadingUtil.getBooleanProjectProperty(this, CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME)
                .setValue(cleanWorkspaceRequired);
    }

    /**
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
     */
    public Label getAssignedLabel() {
        return getAppointedNode() == null ? null : getAppointedNode().getAssignedLabel();
    }

    /**
     * Gets the textual representation of the assigned label as it was entered by the user.
     */
    public String getAssignedLabelString() {
        return getAppointedNode() == null ? null : getAppointedNode().getAssignedLabelString();
    }

    /**
     * Sets the assigned label.
     * @param label node label.
     *
     * @throws java.io.IOException exception.
     */
    public void setAssignedLabel(Label label) throws IOException {
        AppointedNode node = getAppointedNode();
        if (node == null) {
            node = new AppointedNode();
            setAppointedNode(node);
        }
        node.setAssignedLabel(label);
        save();
    }

    /**
     * Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
     *
     * @param node node.
     * @throws java.io.IOException exception
     */
    public void setAssignedNode(Node node) throws IOException {
        setAssignedLabel(node.getSelfLabel());
    }

    /**
     * Gets whether this project is using the advanced affinity chooser UI.
     *
     * @return true - advanced chooser, false - simple textfield.
     */
    public boolean isAdvancedAffinityChooser() {
        //For newly created project advanced chooser is not used.
        //Set value to false in order to avoid NullPointerException
        return getAppointedNode() != null && getAppointedNode().getAdvancedAffinityChooser();
    }

    /**
     * Sets whether this project is using the advanced affinity chooser UI.
     *
     * @param b true - advanced chooser, false - otherwise
     * @throws java.io.IOException exception.
     */
    public void setAdvancedAffinityChooser(boolean b) throws IOException {
        AppointedNode node = getAppointedNode();
        if (node == null) {
            node = new AppointedNode();
            setAppointedNode(node);
        }
        node.setAdvancedAffinityChooser(b);
        save();
    }

    /**
     * Sets {@link AppointedNode}.
     *
     * @param appointedNode {@link AppointedNode}.
     */
    @SuppressWarnings("unchecked")
    public void setAppointedNode(AppointedNode appointedNode) {
        CascadingUtil.getBaseProjectProperty(this, APPOINTED_NODE_PROPERTY_NAME).setValue(appointedNode);
    }

    /**
     * Returns {@link AppointedNode}. Returned value is not null.
     *
     * @return appointedNode {@link AppointedNode}.
     */
    public AppointedNode getAppointedNode() {
        return (AppointedNode) CascadingUtil.getBaseProjectProperty(this, APPOINTED_NODE_PROPERTY_NAME).getValue();
    }

    /**
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
     */
    @Override
    public String getPronoun() {
        return Messages.AbstractProject_Pronoun();
    }

    /**
     * Returns the root project value.
     *
     * @return the root project value.
     */
    public AbstractProject getRootProject() {
        if (this.getParent() instanceof Hudson) {
            return this;
        } else {
            return ((AbstractProject) this.getParent()).getRootProject();
        }
    }

    /**
     * Gets the directory where the module is checked out.
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
     * @deprecated as of 1.319
     *      To support concurrent builds of the same project, this method is moved to {@link AbstractBuild}.
     *      For backward compatibility, this method returns the right {@link AbstractBuild#getWorkspace()} if called
     *      from {@link Executor}, and otherwise the workspace of the last build.
     *
     *      <p>
     *      If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
     *      If you are calling this method to serve a file from the workspace, doing a form validation, etc., then
     *      use {@link #getSomeWorkspace()}
     */
    public final FilePath getWorkspace() {
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getWorkspace() : null;

    }

    /**
     * Various deprecated methods in this class all need the 'current' build.  This method returns
     * the build suitable for that purpose.
     *
     * @return An AbstractBuild for deprecated methods to use.
     */
    private AbstractBuild getBuildForDeprecatedMethods() {
        Executor e = Executor.currentExecutor();
        if (e != null) {
            Executable exe = e.getCurrentExecutable();
            if (exe instanceof AbstractBuild) {
                AbstractBuild b = (AbstractBuild) exe;
                if (b.getProject() == this)
                    return b;
            }
        }
        R lb = getLastBuild();
        if (lb != null)
            return lb;
        return null;
    }

    /**
     * Gets a workspace for some build of this project.
     *
     * <p>
     * This is useful for obtaining a workspace for the purpose of form field validation, where exactly
     * which build the workspace belonged is less important. The implementation makes a cursory effort
     * to find some workspace.
     *
     * @return
     *      null if there's no available workspace.
     * @since 1.319
     */
    public final FilePath getSomeWorkspace() {
        R b = getSomeBuildWithWorkspace();
        return b != null ? b.getWorkspace() : null;
    }

    /**
     * Gets some build that has a live workspace.
     *
     * @return null if no such build exists.
     */
    public final R getSomeBuildWithWorkspace() {
        int cnt = 0;
        for (R b = getLastBuild(); cnt < 5 && b != null; b = b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
            if (ws != null)
                return b;
        }
        return null;
    }

    /**
     * Returns the root directory of the checked-out module.
     * <p>
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
     *
     * @deprecated as of 1.319
     *      See {@link #getWorkspace()} for a migration strategy.
     */
    public FilePath getModuleRoot() {
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoot() : null;
    }

    /**
     * Returns the root directories of all checked-out modules.
     * <p>
     * Some SCMs support checking out multiple modules into the same workspace.
     * In these cases, the returned array will have a length greater than one.
     * @return The roots of all modules checked out from the SCM.
     *
     * @deprecated as of 1.319
     *      See {@link #getWorkspace()} for a migration strategy.
     */
    public FilePath[] getModuleRoots() {
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoots() : null;
    }

    public int getQuietPeriod() {
        IntegerProjectProperty property = CascadingUtil.getIntegerProjectProperty(this, QUIET_PERIOD_PROPERTY_NAME);
        Integer value = property.getValue();
        return property.getDefaultValue().equals(value) ? Hudson.getInstance().getQuietPeriod() : value;
    }

    /**
     * Sets the custom quiet period of this project, or revert to the global default if null is given.
     *
     * @param seconds quiet period
     * @throws IOException if any.
     */
    public void setQuietPeriod(Integer seconds) throws IOException {
        CascadingUtil.getIntegerProjectProperty(this, QUIET_PERIOD_PROPERTY_NAME).setValue(seconds);
        save();
    }

    public int getScmCheckoutRetryCount() {
        IntegerProjectProperty property = CascadingUtil.getIntegerProjectProperty(this,
                SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME);
        Integer value = property.getValue();
        return property.getDefaultValue().equals(value) ? Hudson.getInstance().getScmCheckoutRetryCount() : value;
    }

    public void setScmCheckoutRetryCount(Integer retryCount) {
        CascadingUtil.getIntegerProjectProperty(this, SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME).setValue(retryCount);
    }

    /**
     * Sets scmCheckoutRetryCount, Uses {@link NumberUtils#isNumber(String)} for checking retryCount param.
     * If it is not valid number, null will be set.
     *
     * @param scmCheckoutRetryCount retry count.
     * @throws IOException if any.
     */
    protected void setScmCheckoutRetryCount(String scmCheckoutRetryCount) throws IOException {
        Integer retryCount = null;
        if (NumberUtils.isNumber(scmCheckoutRetryCount)) {
            retryCount = NumberUtils.createInteger(scmCheckoutRetryCount);
        }
        setScmCheckoutRetryCount(retryCount);
    }

    /**
     * @return true if quiet period was configured.
     * @deprecated as of 2.1.2
     *             This method was used only on UI side. No longer required.
     */
    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
        return null != CascadingUtil.getIntegerProjectProperty(this, QUIET_PERIOD_PROPERTY_NAME).getValue();
    }

    /**
     * Sets quietPeriod, Uses {@link NumberUtils#isNumber(String)} for checking seconds param. If seconds is not valid
     * number, null will be set.
     *
     * @param seconds quiet period.
     * @throws IOException if any.
     */
    protected void setQuietPeriod(String seconds) throws IOException {
        Integer period = null;
        if (NumberUtils.isNumber(seconds)) {
            period = NumberUtils.createInteger(seconds);
        }
        setQuietPeriod(period);
    }

    /**
     * Checks whether scmRetryCount is configured
     *
     * @return true if yes, false - otherwise.
     * @deprecated as of 2.1.2
     */
    public boolean hasCustomScmCheckoutRetryCount() {
        return null != CascadingUtil.getIntegerProjectProperty(this, SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME)
                .getValue();
    }

    @Override
    public boolean isBuildable() {
        return !isDisabled() && !isHoldOffBuildUntilSave();
    }

    /**
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
     */
    public boolean isConfigurable() {
        return true;
    }

    public boolean blockBuildWhenDownstreamBuilding() {
        return CascadingUtil.getBooleanProjectProperty(this, BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME)
                .getValue();
    }

    public void setBlockBuildWhenDownstreamBuilding(boolean b) throws IOException {
        CascadingUtil.getBooleanProjectProperty(this, BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME)
                .setValue(b);
        save();
    }

    public boolean blockBuildWhenUpstreamBuilding() {
        return CascadingUtil.getBooleanProjectProperty(this, BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME)
                .getValue();
    }

    public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
        CascadingUtil.getBooleanProjectProperty(this, BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME).setValue(b);
        save();
    }

    public boolean isDisabled() {
        return disabled;
    }

    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value) throws IOException, ServletException {
        // retry count is optional so this is ok
        if (value == null || value.trim().equals(""))
            return FormValidation.ok();
        if (!value.matches("[0-9]*")) {
            return FormValidation.error("Invalid retry count");
        }
        return FormValidation.ok();
    }

    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
        if (disabled == b)
            return; // noop
        this.disabled = b;
        if (b)
            Hudson.getInstance().getQueue().cancel(this);
        save();
    }

    public void disable() throws IOException {
        makeDisabled(true);
    }

    public void enable() throws IOException {
        makeDisabled(false);
    }

    @Override
    public BallColor getIconColor() {
        if (isDisabled())
            return BallColor.DISABLED;
        else
            return super.getIconColor();
    }

    /**
     * effectively deprecated. Since using updateTransientActions correctly
     * under concurrent environment requires a lock that can too easily cause deadlocks.
     *
     * <p>
     * Override {@link #createTransientActions()} instead.
     */
    protected void updateTransientActions() {
        transientActions = createTransientActions();
    }

    protected List<Action> createTransientActions() {
        Vector<Action> ta = new Vector<Action>();

        for (JobProperty<? super P> p : getAllProperties())
            ta.addAll(p.getJobActions((P) this));

        for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
            ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
        return ta;
    }

    /**
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
     * <p>
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
     */
    public abstract DescribableList<Publisher, Descriptor<Publisher>> getPublishersList();

    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

    public List<ProminentProjectAction> getProminentActions() {
        List<Action> a = getActions();
        List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
        for (Action action : a) {
            if (action instanceof ProminentProjectAction)
                pa.add((ProminentProjectAction) action);
        }
        return pa;
    }

    @Override
    public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, FormException {
        super.doConfigSubmit(req, rsp);

        updateTransientActions();

        Set<AbstractProject> upstream = Collections.emptySet();
        if (req.getParameter("pseudoUpstreamTrigger") != null) {
            upstream = new HashSet<AbstractProject>(
                    Items.fromNameList(req.getParameter("upstreamProjects"), AbstractProject.class));
        }

        // dependency setting might have been changed by the user, so rebuild.
        Hudson.getInstance().rebuildDependencyGraph();

        // reflect the submission of the pseudo 'upstream build trriger'.
        // this needs to be done after we release the lock on 'this',
        // or otherwise we could dead-lock

        for (AbstractProject<?, ?> p : Hudson.getInstance().getAllItems(AbstractProject.class)) {
            // Don't consider child projects such as MatrixConfiguration:
            if (!p.isConfigurable())
                continue;
            boolean isUpstream = upstream.contains(p);
            synchronized (p) {
                // does 'p' include us in its BuildTrigger? 
                DescribableList<Publisher, Descriptor<Publisher>> pl = p.getPublishersList();
                BuildTrigger trigger = pl.get(BuildTrigger.class);
                List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>()
                        : trigger.getChildProjects();
                if (isUpstream) {
                    if (!newChildProjects.contains(this))
                        newChildProjects.add(this);
                } else {
                    newChildProjects.remove(this);
                }

                if (newChildProjects.isEmpty()) {
                    pl.remove(BuildTrigger.class);
                } else {
                    // here, we just need to replace the old one with the new one,
                    // but there was a regression (we don't know when it started) that put multiple BuildTriggers
                    // into the list.
                    // for us not to lose the data, we need to merge them all.
                    List<BuildTrigger> existingList = pl.getAll(BuildTrigger.class);
                    BuildTrigger existing;
                    switch (existingList.size()) {
                    case 0:
                        existing = null;
                        break;
                    case 1:
                        existing = existingList.get(0);
                        break;
                    default:
                        pl.removeAll(BuildTrigger.class);
                        Set<AbstractProject> combinedChildren = new HashSet<AbstractProject>();
                        for (BuildTrigger bt : existingList)
                            combinedChildren.addAll(bt.getChildProjects());
                        existing = new BuildTrigger(new ArrayList<AbstractProject>(combinedChildren),
                                existingList.get(0).getThreshold());
                        pl.add(existing);
                        break;
                    }

                    if (existing != null && existing.hasSame(newChildProjects))
                        continue; // no need to touch
                    pl.replace(new BuildTrigger(newChildProjects,
                            existing == null ? Result.SUCCESS : existing.getThreshold()));
                }
                BuildTrigger buildTrigger = pl.get(BuildTrigger.class);
                CascadingUtil.getExternalProjectProperty(p, BUILD_TRIGGER_PROPERTY_NAME).setValue(buildTrigger);
            }
        }

        // notify the queue as the project might be now tied to different node
        Hudson.getInstance().getQueue().scheduleMaintenance();

        // this is to reflect the upstream build adjustments done above
        Hudson.getInstance().rebuildDependencyGraph();
    }

    /**
     * @deprecated
     *    Use {@link #scheduleBuild(Cause)}.  Since 1.283
     */
    public boolean scheduleBuild() {
        return scheduleBuild(new LegacyCodeCause());
    }

    /**
     * @deprecated
     *    Use {@link #scheduleBuild(int, Cause)}.  Since 1.283
     */
    public boolean scheduleBuild(int quietPeriod) {
        return scheduleBuild(quietPeriod, new LegacyCodeCause());
    }

    /**
     * Schedules a build of this project.
     *
     * @return
     *      true if the project is actually added to the queue.
     *      false if the queue contained it and therefore the add()
     *      was noop
     */
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
    }

    public boolean scheduleBuild(int quietPeriod, Cause c) {
        return scheduleBuild(quietPeriod, c, new Action[0]);
    }

    /**
     * Schedules a build.
     *
     * Important: the actions should be persistable without outside references (e.g. don't store
     * references to this project). To provide parameters for a parameterized project, add a ParametersAction. If
     * no ParametersAction is provided for such a project, one will be created with the default parameter values.
     *
     * @param quietPeriod the quiet period to observer
     * @param c the cause for this build which should be recorded
     * @param actions a list of Actions that will be added to the build
     * @return whether the build was actually scheduled
     */
    public boolean scheduleBuild(int quietPeriod, Cause c, Action... actions) {
        return scheduleBuild2(quietPeriod, c, actions) != null;
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * @param actions
     *      For the convenience of the caller, this array can contain null, and those will be silently ignored.
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
        return scheduleBuild2(quietPeriod, c, Arrays.asList(actions));
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * @param actions
     *      For the convenience of the caller, this collection can contain null, and those will be silently ignored.
     * @since 1.383
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Collection<? extends Action> actions) {
        if (!isBuildable())
            return null;

        List<Action> queueActions = new ArrayList<Action>(actions);
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

        WaitingItem i = Hudson.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
        if (i != null)
            return (Future) i.getFuture();
        return null;
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();

        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
        if (paramDefProp == null)
            return defValues;

        /* Scan for all parameter with an associated default values */
        for (ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions()) {
            ParameterValue defaultValue = paramDefinition.getDefaultParameterValue();

            if (defaultValue != null)
                defValues.add(defaultValue);
        }

        return defValues;
    }

    /**
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
     * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
     * as deprecated.
     */
    public Future<R> scheduleBuild2(int quietPeriod) {
        return scheduleBuild2(quietPeriod, new LegacyCodeCause());
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c) {
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
        if (isDisabled())
            return false;
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
        if (scmt == null)
            return false;
        scmt.run();
        return true;
    }

    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
        return Hudson.getInstance().getQueue().contains(this);
    }

    @Override
    public Queue.Item getQueueItem() {
        return Hudson.getInstance().getQueue().getItem(this);
    }

    /**
     * @return name of jdk chosen for current project. Could taken from parent
     */
    public String getJDKName() {
        return CascadingUtil.getStringProjectProperty(this, JDK_PROPERTY_NAME).getValue();
    }

    /**
     * @return JDK that this project is configured with, or null.
     */
    public JDK getJDK() {
        return Hudson.getInstance().getJDK(getJDKName());
    }

    /**
     * Overwrites the JDK setting.
     */
    public void setJDK(JDK jdk) throws IOException {
        setJDK(jdk.getName());
        save();
    }

    public void setJDK(String jdk) {
        CascadingUtil.getStringProjectProperty(this, JDK_PROPERTY_NAME).setValue(jdk);
    }

    public BuildAuthorizationToken getAuthToken() {
        return authToken;
    }

    @Override
    public SortedMap<Integer, ? extends R> _getRuns() {
        return builds.getView();
    }

    @Override
    public void removeRun(R run) {
        this.builds.remove(run);
    }

    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

    // keep track of the previous time we started a build
    private transient long lastBuildStartTime;

    /**
     * Creates a new build of this project for immediate execution.
     */
    protected synchronized R newBuild() throws IOException {
        // make sure we don't start two builds in the same second
        // so the build directories will be different too
        long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
        if (timeSinceLast < 1000) {
            try {
                Thread.sleep(1000 - timeSinceLast);
            } catch (InterruptedException e) {
            }
        }
        lastBuildStartTime = System.currentTimeMillis();
        try {
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
            throw handleInvocationTargetException(e);
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }

    private IOException handleInvocationTargetException(InvocationTargetException e) {
        Throwable t = e.getTargetException();
        if (t instanceof Error)
            throw (Error) t;
        if (t instanceof RuntimeException)
            throw (RuntimeException) t;
        if (t instanceof IOException)
            return (IOException) t;
        throw new Error(t);
    }

    /**
     * Loads an existing build record from disk.
     */
    protected R loadBuild(File dir) throws IOException {
        try {
            return getBuildClass().getConstructor(getClass(), File.class).newInstance(this, dir);
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
            throw handleInvocationTargetException(e);
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }

    /**
     * {@inheritDoc}
     *
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
     * {@link BuildStep}s and others who want to add a project action
     * should do so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
     *
     * @see TransientProjectActionFactory
     */
    @Override
    public synchronized List<Action> getActions() {
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
        // return the read only list to cause a failure on plugins who try to add an action here
        return Collections.unmodifiableList(actions);
    }

    /**
     * Gets the {@link Node} where this project was last built on.
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
        if (b == null)
            return null;
        else
            return b.getBuiltOn();
    }

    public Object getSameNodeConstraint() {
        return this; // in this way, any member that wants to run with the main guy can nominate the project itself 
    }

    public final Task getOwnerTask() {
        return this;
    }

    /**
     * {@inheritDoc}
     *
     * <p>
     * A project must be blocked if its own previous build is in progress,
     * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
     * project is building, but derived classes can also check other conditions.
     */
    public boolean isBuildBlocked() {
        return getCauseOfBlockage() != null;
    }

    public String getWhyBlocked() {
        CauseOfBlockage cb = getCauseOfBlockage();
        return cb != null ? cb.getShortDescription() : null;
    }

    /**
     * Blocked because the previous build is already in progress.
     */
    public static class BecauseOfBuildInProgress extends CauseOfBlockage {
        private final AbstractBuild<?, ?> build;

        public BecauseOfBuildInProgress(AbstractBuild<?, ?> build) {
            this.build = build;
        }

        @Override
        public String getShortDescription() {
            Executor e = build.getExecutor();
            String eta = "";
            if (e != null)
                eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
            int lbn = build.getNumber();
            return Messages.AbstractProject_BuildInProgress(lbn, eta);
        }
    }

    /**
     * Because the downstream build is in progress, and we are configured to wait for that.
     */
    public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage {
        //TODO: review and check whether we can do it private
        public final AbstractProject<?, ?> up;

        public AbstractProject getUp() {
            return up;
        }

        public BecauseOfDownstreamBuildInProgress(AbstractProject<?, ?> up) {
            this.up = up;
        }

        @Override
        public String getShortDescription() {
            return Messages.AbstractProject_DownstreamBuildInProgress(up.getName());
        }
    }

    /**
     * Because the upstream build is in progress, and we are configured to wait for that.
     */
    public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage {
        //TODO: review and check whether we can do it private
        public final AbstractProject<?, ?> up;

        public BecauseOfUpstreamBuildInProgress(AbstractProject<?, ?> up) {
            this.up = up;
        }

        public AbstractProject getUp() {
            return up;
        }

        @Override
        public String getShortDescription() {
            return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
        }
    }

    public CauseOfBlockage getCauseOfBlockage() {
        if (isBuilding() && !isConcurrentBuild())
            return new BecauseOfBuildInProgress(getLastBuild());
        if (blockBuildWhenDownstreamBuilding()) {
            AbstractProject<?, ?> bup = getBuildingDownstream();
            if (bup != null)
                return new BecauseOfDownstreamBuildInProgress(bup);
        }
        if (blockBuildWhenUpstreamBuilding()) {
            AbstractProject<?, ?> bup = getBuildingUpstream();
            if (bup != null)
                return new BecauseOfUpstreamBuildInProgress(bup);
        }
        return null;
    }

    /**
     * Returns the project if any of the downstream project (or itself) is either
     * building or is in the queue.
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
    protected AbstractProject getBuildingDownstream() {
        DependencyGraph graph = Hudson.getInstance().getDependencyGraph();
        Set<AbstractProject> tups = graph.getTransitiveDownstream(this);
        tups.add(this);
        for (AbstractProject tup : tups) {
            if (tup != this && (tup.isBuilding() || tup.isInQueue()))
                return tup;
        }
        return null;
    }

    /**
     * Returns the project if any of the upstream project (or itself) is either
     * building or is in the queue.
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
    protected AbstractProject getBuildingUpstream() {
        DependencyGraph graph = Hudson.getInstance().getDependencyGraph();
        Set<AbstractProject> tups = graph.getTransitiveUpstream(this);
        tups.add(this);
        for (AbstractProject tup : tups) {
            if (tup != this && (tup.isBuilding() || tup.isInQueue()))
                return tup;
        }
        return null;
    }

    public List<SubTask> getSubTasks() {
        List<SubTask> r = new ArrayList<SubTask>();
        r.add(this);
        for (SubTaskContributor euc : SubTaskContributor.all())
            r.addAll(euc.forProject(this));
        for (JobProperty<? super P> p : getAllProperties())
            r.addAll(p.getSubTasks());
        return r;
    }

    public R createExecutable() throws IOException {
        if (isDisabled())
            return null;
        return newBuild();
    }

    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

    /**
     * Gets the {@link Resource} that represents the workspace of this project.
     * Useful for locking and mutual exclusion control.
     *
     * @deprecated as of 1.319
     *      Projects no longer have a fixed workspace, ands builds will find an available workspace via
     *      {@link WorkspaceList} for each build (furthermore, that happens after a build is started.)
     *      So a {@link Resource} representation for a workspace at the project level no longer makes sense.
     *
     *      <p>
     *      If you need to lock a workspace while you do some computation, see the source code of
     *      {@link #pollSCMChanges(TaskListener)} for how to obtain a lock of a workspace through {@link WorkspaceList}.
     */
    public Resource getWorkspaceResource() {
        return new Resource(getFullDisplayName() + " workspace");
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
     * Set of child resource activities of the build of this project (override in child projects).
     * @return The set of child resource activities of the build of this project.
     */
    protected Set<ResourceActivity> getResourceActivities() {
        return Collections.emptySet();
    }

    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile)
            throws IOException, InterruptedException {
        SCM scm = getScm();
        if (scm == null)
            return true; // no SCM

        FilePath workspace = build.getWorkspace();
        workspace.mkdirs();

        boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
        calcPollingBaseline(build, launcher, listener);
        return r;
    }

    /**
     * Pushes the baseline up to the newly checked out revision.
     */
    private void calcPollingBaseline(AbstractBuild build, Launcher launcher, TaskListener listener)
            throws IOException, InterruptedException {
        SCMRevisionState baseline = build.getAction(SCMRevisionState.class);
        if (baseline == null) {
            try {
                baseline = getScm()._calcRevisionsFromBuild(build, launcher, listener);
            } catch (AbstractMethodError e) {
                baseline = SCMRevisionState.NONE; // pre-1.345 SCM implementations, which doesn't use the baseline in polling
            }
            if (baseline != null)
                build.addAction(baseline);
        }
        pollingBaseline = baseline;
    }

    /**
     * For reasons I don't understand, if I inline this method, AbstractMethodError escapes try/catch block.
     */
    private SCMRevisionState safeCalcRevisionsFromBuild(AbstractBuild build, Launcher launcher,
            TaskListener listener) throws IOException, InterruptedException {
        return getScm()._calcRevisionsFromBuild(build, launcher, listener);
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
     *
     * @deprecated as of 1.346
     *      Use {@link #poll(TaskListener)} instead.
     */
    public boolean pollSCMChanges(TaskListener listener) {
        return poll(listener).hasChanges();
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
     *
     * <p>
     * The implementation is responsible for ensuring mutual exclusion between polling and builds
     * if necessary.
     *
     * @since 1.345
     */
    public PollingResult poll(TaskListener listener) {
        SCM scm = getScm();
        if (scm == null) {
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
            return NO_CHANGES;
        }
        if (isDisabled()) {
            listener.getLogger().println(Messages.AbstractProject_Disabled());
            return NO_CHANGES;
        }

        R lb = getLastBuild();
        if (lb == null) {
            listener.getLogger().println(Messages.AbstractProject_NoBuilds());
            return isInQueue() ? NO_CHANGES : BUILD_NOW;
        }

        if (pollingBaseline == null) {
            R success = getLastSuccessfulBuild(); // if we have a persisted baseline, we'll find it by this
            for (R r = lb; r != null; r = r.getPreviousBuild()) {
                SCMRevisionState s = r.getAction(SCMRevisionState.class);
                if (s != null) {
                    pollingBaseline = s;
                    break;
                }
                if (r == success)
                    break; // searched far enough
            }
            // NOTE-NO-BASELINE:
            // if we don't have baseline yet, it means the data is built by old Hudson that doesn't set the baseline
            // as action, so we need to compute it. This happens later.
        }

        try {
            if (scm.requiresWorkspaceForPolling()) {
                // lock the workspace of the last build
                FilePath ws = lb.getWorkspace();

                if (ws == null || !ws.exists()) {
                    // workspace offline. build now, or nothing will ever be built
                    Label label = getAssignedLabel();
                    if (label != null && label.isSelfLabel()) {
                        // if the build is fixed on a node, then attempting a build will do us
                        // no good. We should just wait for the slave to come back.
                        listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
                        return NO_CHANGES;
                    }
                    listener.getLogger().println(ws == null ? Messages.AbstractProject_WorkspaceOffline()
                            : Messages.AbstractProject_NoWorkspace());
                    if (isInQueue()) {
                        listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
                        return NO_CHANGES;
                    } else {
                        listener.getLogger().println(Messages.AbstractProject_NewBuildForWorkspace());
                        return BUILD_NOW;
                    }
                } else {
                    Node node = lb.getBuiltOn();
                    if (node == null || node.toComputer() == null) {
                        LOGGER.log(Level.FINE,
                                "Node on which this job previously was built is not available now, build is started on an available node");
                        return isInQueue() ? NO_CHANGES : BUILD_NOW;
                    }
                    WorkspaceList l = node.toComputer().getWorkspaceList();
                    // if doing non-concurrent build, acquire a workspace in a way that causes builds to block for this workspace.
                    // this prevents multiple workspaces of the same job --- the behavior of Hudson < 1.319.
                    //
                    // OTOH, if a concurrent build is chosen, the user is willing to create a multiple workspace,
                    // so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
                    // by having multiple workspaces
                    WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
                    Launcher launcher = ws.createLauncher(listener);
                    try {
                        LOGGER.fine("Polling SCM changes of " + getName());
                        if (pollingBaseline == null) // see NOTE-NO-BASELINE above
                            calcPollingBaseline(lb, launcher, listener);
                        PollingResult r = scm.poll(this, launcher, ws, listener, pollingBaseline);
                        pollingBaseline = r.remote;
                        return r;
                    } finally {
                        lease.release();
                    }
                }
            } else {
                // polling without workspace
                LOGGER.fine("Polling SCM changes of " + getName());

                if (pollingBaseline == null) // see NOTE-NO-BASELINE above
                    calcPollingBaseline(lb, null, listener);
                PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
                pollingBaseline = r.remote;
                return r;
            }
        } catch (AbortException e) {
            listener.getLogger().println(e.getMessage());
            listener.fatalError(Messages.AbstractProject_Aborted());
            LOGGER.log(Level.FINE, "Polling " + this + " aborted", e);
            return NO_CHANGES;
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
            return NO_CHANGES;
        } catch (InterruptedException e) {
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
            return NO_CHANGES;
        }
    }

    /**
     * Returns true if this user has made a commit to this project.
     *
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
        for (R build = getLastBuild(); build != null; build = build.getPreviousBuild())
            if (build.hasParticipant(user))
                return true;
        return false;
    }

    @Exported
    public SCM getScm() {
        return CascadingUtil.getScmProjectProperty(this, SCM_PROPERTY_NAME).getValue();
    }

    @SuppressWarnings("unchecked")
    public void setScm(SCM scm) throws IOException {
        CascadingUtil.getScmProjectProperty(this, SCM_PROPERTY_NAME).setValue(scm);
        save();
    }

    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
    @SuppressWarnings("unchecked")
    public void addTrigger(Trigger<?> trigger) throws IOException {
        CascadingUtil.getTriggerProjectProperty(this, trigger.getDescriptor().getJsonSafeClassName())
                .setValue(trigger);
        save();
        updateTransientActions();
    }

    @SuppressWarnings("unchecked")
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
        CascadingUtil.getTriggerProjectProperty(this, trigger.getJsonSafeClassName()).setValue(null);
        save();
        updateTransientActions();
    }

    protected final synchronized <T extends Describable<T>> void addToList(T item, List<T> collection)
            throws IOException {
        for (int i = 0; i < collection.size(); i++) {
            if (collection.get(i).getDescriptor() == item.getDescriptor()) {
                // replace
                collection.set(i, item);
                save();
                return;
            }
        }
        // add
        collection.add(item);
        save();
        updateTransientActions();
    }

    protected final synchronized <T extends Describable<T>> void removeFromList(Descriptor<T> item,
            List<T> collection) throws IOException {
        for (int i = 0; i < collection.size(); i++) {
            if (collection.get(i).getDescriptor() == item) {
                // found it
                collection.remove(i);
                save();
                updateTransientActions();
                return;
            }
        }
    }

    @SuppressWarnings("unchecked")
    public synchronized Map<TriggerDescriptor, Trigger> getTriggers() {
        return (Map) Descriptor.toMap(getTriggerDescribableList());
    }

    /**
     * @return list of {@link Trigger} elements.
     */
    public List<Trigger<?>> getTriggersList() {
        return getTriggerDescribableList().toList();
    }

    /**
     * @return describable list of trigger elements.
     */
    public DescribableList<Trigger<?>, TriggerDescriptor> getTriggerDescribableList() {
        return DescribableListUtil.convertToDescribableList(Trigger.for_(this), this, TriggerProjectProperty.class);
    }

    /**
     * Gets the specific trigger, or null if the propert is not configured for this job.
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        for (Trigger p : getTriggersList()) {
            if (clazz.isInstance(p))
                return clazz.cast(p);
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public void setTriggers(List<Trigger<?>> triggerList) {
        for (Trigger trigger : triggerList) {
            CascadingUtil.getTriggerProjectProperty(this, trigger.getDescriptor().getJsonSafeClassName())
                    .setValue(trigger);
        }
    }

    //
    //
    // fingerprint related
    //
    //
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
     */
    @Exported
    public final List<AbstractProject> getDownstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getDownstream(this);
    }

    @Exported
    public final List<AbstractProject> getUpstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getUpstream(this);
    }

    /**
     * Returns only those upstream projects that defines {@link BuildTrigger} to this project.
     * This is a subset of {@link #getUpstreamProjects()}
     *
     * @return A List of upstream projects that has a {@link BuildTrigger} to this project.
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
        for (AbstractProject<?, ?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
            if (buildTrigger != null)
                if (buildTrigger.getChildProjects().contains(this))
                    result.add(ap);
        }
        return result;
    }

    /**
     * Gets all the upstream projects including transitive upstream projects.
     *
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getTransitiveUpstream(this);
    }

    /**
     * Gets all the downstream projects including transitive downstream projects.
     *
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getTransitiveDownstream(this);
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
        TreeMap<Integer, RangeSet> r = new TreeMap<Integer, RangeSet>(REVERSE_INTEGER_COMPARATOR);

        checkAndRecord(that, r, this.getBuilds());
        // checkAndRecord(that, r, that.getBuilds());

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
     *
     * For each given build, find the build number range of the given project and put that into the map.
     */
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
            if (rs == null || rs.isEmpty())
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
            if (value == null)
                r.put(n, rs);
            else
                value.add(rs);
        }
    }

    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

    @Override
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
        if (isBuildable() && hasPermission(Hudson.ADMINISTER))
            sib.add("build", "build");
        return sib;
    }

    @Override
    protected HistoryWidget createHistoryWidget() {
        return new BuildHistoryWidget<R>(this, getBuilds(), HISTORY_ADAPTER);
    }

    public boolean isParameterized() {
        return getProperty(ParametersDefinitionProperty.class) != null;
    }

    //
    //
    // actions
    //
    //
    /**
     * Schedules a new build command.
     */
    public void doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);

        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
            pp._doBuild(req, rsp);
            return;
        }

        if (!isBuildable())
            throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,
                    new IOException(getFullName() + " is not buildable"));

        Hudson.getInstance().getQueue().schedule(this, getDelay(req), getBuildCause(req));
        rsp.forwardToPreviousPage(req);
    }

    /**
     * Computes the build cause, using RemoteCause or UserCause as appropriate.
     */
    /*package*/ CauseAction getBuildCause(StaplerRequest req) {
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
        } else {
            cause = new UserCause();
        }
        return new CauseAction(cause);
    }

    /**
     * Computes the delay by taking the default value and the override in the request parameter into the account.
     */
    public int getDelay(StaplerRequest req) throws ServletException {
        String delay = req.getParameter("delay");
        if (delay == null)
            return getQuietPeriod();

        try {
            // TODO: more unit handling
            if (delay.endsWith("sec"))
                delay = delay.substring(0, delay.length() - 3);
            if (delay.endsWith("secs"))
                delay = delay.substring(0, delay.length() - 4);
            return Integer.parseInt(delay);
        } catch (NumberFormatException e) {
            throw new ServletException("Invalid delay parameter value: " + delay);
        }
    }

    /**
     * Supports build trigger with parameters via an HTTP GET or POST.
     * Currently only String parameters are supported.
     */
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException {
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);

        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
            pp.buildWithParameters(req, rsp);
        } else {
            throw new IllegalStateException("This build is not parameterized!");
        }

    }

    /**
     * Schedules a new SCM polling command.
     */
    public void doPolling(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
        rsp.forwardToPreviousPage(req);
    }

    /**
     * Cancels a scheduled build.
     */
    public void doCancelQueue(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        checkPermission(BUILD);

        Hudson.getInstance().getQueue().cancel(this);
        rsp.forwardToPreviousPage(req);
    }

    @Override
    protected void submit(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, FormException {
        super.submit(req, rsp);

        makeDisabled(null != req.getParameter("disable"));
        setCascadingProjectName(StringUtils.trimToNull(req.getParameter("cascadingProjectName")));
        setJDK(req.getParameter("jdk"));
        setQuietPeriod(
                null != req.getParameter(HAS_QUIET_PERIOD_PROPERTY_NAME) ? req.getParameter("quiet_period") : null);
        setScmCheckoutRetryCount(null != req.getParameter(HAS_SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME)
                ? req.getParameter("scmCheckoutRetryCount")
                : null);
        setBlockBuildWhenDownstreamBuilding(null != req.getParameter("blockBuildWhenDownstreamBuilding"));
        setBlockBuildWhenUpstreamBuilding(null != req.getParameter("blockBuildWhenUpstreamBuilding"));

        if (req.getParameter(APPOINTED_NODE_PROPERTY_NAME) != null) {
            // New logic for handling whether this choice came from the dropdown or textfield.
            if (BASIC_KEY.equals(req.getParameter(AFFINITY_CHO0SER_KEY))) {
                setAppointedNode(new AppointedNode(Util.fixEmptyAndTrim(req.getParameter(SLAVE_KEY)), false));
            } else {
                setAppointedNode(
                        new AppointedNode(Util.fixEmptyAndTrim(req.getParameter(ASSIGNED_LABEL_KEY)), true));
            }

        } else {
            setAppointedNode(null);
        }

        setCleanWorkspaceRequired(null != req.getParameter("cleanWorkspaceRequired"));

        setConcurrentBuild(req.getSubmittedForm().has("concurrentBuild"));

        authToken = BuildAuthorizationToken.create(req);

        setScm(SCMS.parseSCM(req, this));

        buildTriggers(req, req.getSubmittedForm(), Trigger.for_(this));
    }

    /**
     * @deprecated
     *      As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
     */
    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req,
            List<? extends Descriptor<T>> descriptors, String prefix) throws FormException, ServletException {
        return buildDescribable(req, descriptors);
    }

    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req,
            List<? extends Descriptor<T>> descriptors) throws FormException, ServletException {

        JSONObject data = req.getSubmittedForm();
        List<T> r = new Vector<T>();
        for (Descriptor<T> d : descriptors) {
            String safeName = d.getJsonSafeClassName();
            if (req.getParameter(safeName) != null) {
                T instance = d.newInstance(req, data.getJSONObject(safeName));
                r.add(instance);
            }
        }
        return r;
    }

    protected void buildTriggers(StaplerRequest req, JSONObject json, List<TriggerDescriptor> descriptors)
            throws FormException {
        for (TriggerDescriptor d : descriptors) {
            String propertyName = d.getJsonSafeClassName();
            CascadingUtil.setChildrenTrigger(this, d, propertyName, req, json);
        }
    }

    /**
     * Serves the workspace files.
     */
    public DirectoryBrowserSupport doWs(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, InterruptedException {
        checkPermission(AbstractProject.WORKSPACE);
        FilePath ws = getSomeWorkspace();
        if ((ws == null) || (!ws.exists())) {
            // if there's no workspace, report a nice error message
            rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            req.getView(this, "noWorkspace.jelly").forward(req, rsp);
            return null;
        } else {
            return new DirectoryBrowserSupport(this, ws, getDisplayName() + " workspace", "folder.gif", true);
        }
    }

    /**
     * Wipes out the workspace.
     */
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
        checkPermission(Functions.isWipeOutPermissionEnabled() ? WIPEOUT : BUILD);
        if (cleanWorkspace()) {
            return new HttpRedirect(".");
        } else {
            return new ForwardToView(this, "wipeOutWorkspaceBlocked.jelly");
        }
    }

    public boolean cleanWorkspace() throws IOException, InterruptedException {
        checkPermission(BUILD);
        R b = getSomeBuildWithWorkspace();
        FilePath ws = b != null ? b.getWorkspace() : null;
        if (ws != null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
            ws.deleteRecursive();
            return true;
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return false;
        }
    }

    @CLIMethod(name = "disable-job")
    public HttpResponse doDisable() throws IOException, ServletException {
        requirePOST();
        checkPermission(CONFIGURE);
        makeDisabled(true);
        return new HttpRedirect(".");
    }

    @CLIMethod(name = "enable-job")
    public HttpResponse doEnable() throws IOException, ServletException {
        requirePOST();
        checkPermission(CONFIGURE);
        makeDisabled(false);
        return new HttpRedirect(".");
    }

    /**
     * RSS feed for changes in this project.
     */
    public void doRssChangelog(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

            public FeedItem(Entry e, int idx) {
                this.e = e;
                this.idx = idx;
            }

            AbstractBuild<?, ?> getBuild() {
                return e.getParent().build;
            }
        }

        List<FeedItem> entries = new ArrayList<FeedItem>();

        for (R r = getLastBuild(); r != null; r = r.getPreviousBuild()) {
            int idx = 0;
            for (ChangeLogSet.Entry e : r.getChangeSet())
                entries.add(new FeedItem(e, idx++));
        }

        RSS.forwardToRss(getDisplayName() + ' ' + getScm().getDescriptor().getDisplayName() + " changes",
                getUrl() + "changes", entries, new FeedAdapter<FeedItem>() {
                    public String getEntryTitle(FeedItem item) {
                        return "#" + item.getBuild().number + ' ' + item.e.getMsg() + " (" + item.e.getAuthor()
                                + ")";
                    }

                    public String getEntryUrl(FeedItem item) {
                        return item.getBuild().getUrl() + "changes#detail" + item.idx;
                    }

                    public String getEntryID(FeedItem item) {
                        return getEntryUrl(item);
                    }

                    public String getEntryDescription(FeedItem item) {
                        StringBuilder buf = new StringBuilder();
                        for (String path : item.e.getAffectedPaths())
                            buf.append(path).append('\n');
                        return buf.toString();
                    }

                    public Calendar getEntryTimestamp(FeedItem item) {
                        return item.getBuild().getTimestamp();
                    }

                    public String getEntryAuthor(FeedItem entry) {
                        return Mailer.descriptor().getAdminAddress();
                    }
                }, req, rsp);
    }

    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
         * from showing up on their configuration screen. This is often useful when you are building
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
         *
         * <p>
         * Some {@link Descriptor}s define their own schemes for controlling applicability
         * (such as {@link BuildStepDescriptor#isApplicable(Class)}),
         * This method works like AND in conjunction with them;
         * Both this method and that method need to return true in order for a given {@link Descriptor}
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
         *
         * @see BuildStepDescriptor#isApplicable(Class)
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject)
         * @see TriggerDescriptor#isApplicable(Item)
         */
        @Override
        public boolean isApplicable(Descriptor descriptor) {
            return true;
        }

        public FormValidation doCheckAssignedLabelString(@QueryParameter String value) {
            if (Util.fixEmpty(value) == null)
                return FormValidation.ok(); // nothing typed yet
            try {
                Label.parseExpression(value);
            } catch (ANTLRException e) {
                return FormValidation.error(e,
                        Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
            }
            // TODO: if there's an atom in the expression that is empty, report it
            if (Hudson.getInstance().getLabel(value).isEmpty())
                return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
            return FormValidation.ok();
        }

        public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParameter String value) {
            AutoCompletionCandidates c = new AutoCompletionCandidates();
            Set<Label> labels = Hudson.getInstance().getLabels();
            List<String> queries = new AutoCompleteSeeder(value).getSeeds();

            for (String term : queries) {
                for (Label l : labels) {
                    if (l.getName().startsWith(term)) {
                        c.add(l.getName());
                    }
                }
            }
            return c;
        }
    }

    /**
     * Finds a {@link AbstractProject} that has the name closest to the given name.
     */
    public static AbstractProject findNearest(String name) {
        List<AbstractProject> projects = Hudson.getInstance().getItems(AbstractProject.class);
        String[] names = new String[projects.size()];
        for (int i = 0; i < projects.size(); i++)
            names[i] = projects.get(i).getName();

        String nearest = EditDistance.findNearest(name, names);
        return (AbstractProject) Hudson.getInstance().getItem(nearest);
    }

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    };

    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());

    /**
     * Permission to abort a build. For now, let's make it the same as {@link #BUILD}
     */
    public static final Permission ABORT = BUILD;

    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static AbstractProject resolveForCLI(
            @Argument(required = true, metaVar = "NAME", usage = "Job name") String name) throws CmdLineException {
        AbstractProject item = Hudson.getInstance().getItemByFullName(name, AbstractProject.class);
        if (item == null)
            throw new CmdLineException(null,
                    Messages.AbstractItem_NoSuchJobExists(name, AbstractProject.findNearest(name).getFullName()));
        return item;
    }
}