hudson.plugins.project_inheritance.projects.InheritanceProject.java Source code

Java tutorial

Introduction

Here is the source code for hudson.plugins.project_inheritance.projects.InheritanceProject.java

Source

/**
 * Copyright (c) 2011-2013, Intel Mobile Communications GmbH
 * 
 * 
 * This file is part of the Inheritance plug-in for Jenkins.
 * 
 * The Inheritance plug-in is free software: you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation in version 3
 * of the License
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

package hudson.plugins.project_inheritance.projects;

import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import hudson.BulkChange;
import hudson.Extension;
import hudson.Functions;
import hudson.Util;
import hudson.model.Action;
import hudson.model.DependencyGraph;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.model.ParameterValue;
import hudson.model.TopLevelItem;
import hudson.model.TransientProjectActionFactory;
import hudson.model.AbstractProject;
import hudson.model.Cause;
import hudson.model.Cause.RemoteCause;
import hudson.model.Cause.UserIdCause;
import hudson.model.CauseAction;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.Hudson;
import hudson.model.Job;
import hudson.model.Label;
import hudson.model.ParameterDefinition;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Project;
import hudson.model.Queue;
import hudson.model.Queue.WaitingItem;
import hudson.model.StringParameterValue;
import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.SubTask;
import hudson.model.queue.SubTaskContributor;
import hudson.plugins.project_inheritance.projects.InheritanceProject.Relationship.Type;
import hudson.plugins.project_inheritance.projects.actions.VersioningAction;
import hudson.plugins.project_inheritance.projects.creation.ProjectCreationEngine;
import hudson.plugins.project_inheritance.projects.creation.ProjectCreationEngine.TriggerInheritance;
import hudson.plugins.project_inheritance.projects.creation.ProjectCreationEngine.CreationClass;
import hudson.plugins.project_inheritance.projects.inheritance.InheritanceGovernor;
import hudson.plugins.project_inheritance.projects.parameters.InheritableStringParameterDefinition;
import hudson.plugins.project_inheritance.projects.parameters.InheritableStringParameterDefinition.IModes;
import hudson.plugins.project_inheritance.projects.parameters.InheritableStringParameterReferenceDefinition;
import hudson.plugins.project_inheritance.projects.parameters.InheritanceParametersDefinitionProperty;
import hudson.plugins.project_inheritance.projects.parameters.InheritanceParametersDefinitionProperty.ScopeEntry;
import hudson.plugins.project_inheritance.projects.rebuild.InheritanceRebuildAction;
import hudson.plugins.project_inheritance.projects.references.SimpleProjectReference;
import hudson.plugins.project_inheritance.projects.references.AbstractProjectReference;
import hudson.plugins.project_inheritance.projects.references.ParameterizedProjectReference;
import hudson.plugins.project_inheritance.projects.references.ProjectReference;
import hudson.plugins.project_inheritance.projects.references.ProjectReference.PrioComparator;
import hudson.plugins.project_inheritance.projects.references.ProjectReference.PrioComparator.SELECTOR;
import hudson.plugins.project_inheritance.projects.view.InheritanceViewAction;
import hudson.plugins.project_inheritance.util.Helpers;
import hudson.plugins.project_inheritance.util.Reflection;
import hudson.plugins.project_inheritance.util.ThreadAssocStore;
import hudson.plugins.project_inheritance.util.TimedBuffer;
import hudson.plugins.project_inheritance.util.VersionedObjectStore;
import hudson.plugins.project_inheritance.util.VersionedObjectStore.Version;
import hudson.plugins.project_inheritance.util.VersionsNotification;
import hudson.plugins.project_inheritance.util.svg.Graph;
import hudson.plugins.project_inheritance.util.svg.SVGNode;
import hudson.plugins.project_inheritance.util.svg.renderers.SVGTreeRenderer;
import hudson.scm.NullSCM;
import hudson.scm.SCM;
import hudson.security.ACL;
import hudson.security.Permission;
import hudson.security.PermissionScope;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tasks.LogRotator;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.DescribableList;
import hudson.util.FormApply;
import hudson.util.ListBoxModel;
import hudson.widgets.Widget;
import hudson.widgets.HistoryWidget;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import jenkins.model.BuildDiscarder;
import jenkins.model.Jenkins;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.util.TimeDuration;
import junit.framework.TestCase;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringEscapeUtils;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.w3c.dom.Document;

import com.sun.mail.util.BASE64EncoderStream;
import com.thoughtworks.xstream.XStreamException;

import difflib.DiffUtils;
import difflib.Patch;

/**
 * A simple base class for all inheritable jobs/projects.
 * 
 * TODO: Create suitable JavaDoc description for this class
 * 
 * @author Martin Schroeder
 *
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class InheritanceProject extends Project<InheritanceProject, InheritanceBuild>
        implements TopLevelItem, Comparable<Project>, SVGNode {

    private static final Logger log = Logger.getLogger(InheritanceProject.class.toString());

    private static ReentrantLock globalProjectLock = new ReentrantLock();

    // === NESTED CLASS AND ENUM DEFINITIONS ===

    /**
     * A very simple enum for the possible relationship states between
     * to projects.
     */
    public static class Relationship {
        public enum Type {
            PARENT, MATE, CHILD;

            public String toString() {
                switch (this) {
                case PARENT:
                    return Messages.InheritanceProject_Relationship_Type_Parent();
                case MATE:
                    return Messages.InheritanceProject_Relationship_Type_Mate();
                case CHILD:
                    return Messages.InheritanceProject_Relationship_Type_Child();
                default:
                    return "N/A";
                }
            }

            public String getDescription() {
                switch (this) {
                case PARENT:
                    return Messages.InheritanceProject_Relationship_Type_ParentDesc();
                case MATE:
                    return Messages.InheritanceProject_Relationship_Type_MateDesc();
                case CHILD:
                    return Messages.InheritanceProject_Relationship_Type_ChildDesc();
                default:
                    return "N/A";
                }
            }
        }

        public final Type type;
        public final int distance;
        public final boolean isLeaf;

        public Relationship(Type type, int distance, boolean isLeaf) {
            this.type = type;
            this.distance = distance;
            this.isLeaf = isLeaf;
        }
    }

    public class ParameterDerivationDetails implements Comparable<ParameterDerivationDetails> {
        private final String parameterName;
        private final String projectName;
        private final String detail;
        private final Object defaultValue;
        private int order = 0;

        public ParameterDerivationDetails(String paramName, String projectName, String detail,
                Object defaultValue) {
            this.parameterName = paramName;
            this.projectName = projectName;
            this.detail = detail;
            this.defaultValue = defaultValue;

            if (this.parameterName == null || this.projectName == null) {
                throw new NullPointerException();
            }
        }

        public String getParameterName() {
            return parameterName;
        }

        public String getProjectName() {
            return this.projectName;
        }

        public String getDetail() {
            return this.detail;
        }

        public String getProjectAndDetail() {
            if (this.detail != null && this.detail.length() > 0) {
                return this.projectName + "(" + detail + ")";
            } else {
                return this.projectName;
            }
        }

        public String getDefault() {
            if (this.defaultValue == null) {
                return "NULL";
            } else {
                return this.defaultValue.toString();
            }
        }

        public int getOrder() {
            return order;
        }

        public void setOrder(int order) {
            this.order = order;
        }

        public boolean equals(Object other) {
            if (!(other instanceof ParameterDerivationDetails)) {
                return false;
            }
            ParameterDerivationDetails o = (ParameterDerivationDetails) other;

            return (Helpers.bothNullOrEqual(parameterName, o.parameterName)
                    && Helpers.bothNullOrEqual(projectName, o.projectName)
                    && Helpers.bothNullOrEqual(detail, o.detail)
                    && Helpers.bothNullOrEqual(defaultValue, o.defaultValue));
        }

        public int compareTo(ParameterDerivationDetails o) {
            if (!Helpers.bothNullOrEqual(parameterName, o.parameterName)) {
                return parameterName.compareTo(parameterName);
            }
            if (!Helpers.bothNullOrEqual(projectName, o.projectName)) {
                return projectName.compareTo(projectName);
            }
            if (!Helpers.bothNullOrEqual(detail, o.detail)) {
                return detail.compareTo(detail);
            }
            if (!Helpers.bothNullOrEqual(defaultValue, o.defaultValue)) {
                return defaultValue.toString().compareTo(defaultValue.toString());
            }
            return 0;
        }

    }

    public static enum IMode {
        LOCAL_ONLY, INHERIT_FORCED, AUTO;
    }

    // === PRIVATE/PROTECTED STATIC FIELDS ===

    /**
     * This buffer is used for objects that don't need to be repeatedly
     * generated, as long as the configuration of this project or its
     * parents has not changed.
     * 
     * This class ensures that this buffer is cleared whenever the project or
     * its parents are changed.
     * 
     * @see #clearBuffers(InheritanceProject)
     */
    protected static TimedBuffer<InheritanceProject, String> onInheritChangeBuffer = null;

    /**
     * Same as {@link #onSelfChangeBuffer}, but this buffer is cleared only
     * when the project itself is changed.
     * 
     * @see #clearBuffers(InheritanceProject)
     */
    protected static TimedBuffer<InheritanceProject, String> onSelfChangeBuffer = null;

    /**
     * Same as {@link #onSelfChangeBuffer}, but this buffer is cleared when
     * <i>any</i> project is changed or loaded anew.
     * 
     * @see #clearBuffers(InheritanceProject)
     */
    protected static TimedBuffer<InheritanceProject, String> onChangeBuffer = null;

    public static Permission VERSION_CONFIG = new Permission(PERMISSIONS, "ConfigureVersions",
            Messages._InheritanceProject_VersionsConfigPermissionDescription(), Jenkins.ADMINISTER,
            PermissionScope.ITEM);

    // === PRIVATE/PROTECTED MEMBER FIELDS ===

    /**
     * This field is only valid for transient jobs.
     * It carries the additional, optional "variance" part as assigned by the
     * {@link ProjectCreationEngine} during its creation.
     */
    protected transient String variance = null;

    /**
     * This {@link VersionedObjectStore} is used to version all configurable
     * properties of this class.
     * <p>
     * Do note that transient projects (see {@link #isTransient}) do not do
     * versioning and always have an empty store. This is because they don't
     * actually have a configuration of their own.
     */
    protected transient VersionedObjectStore versionStore = null;

    // === FIELDS SET BY JELLY FORM TAGS ===

    /**
     * Flag to denote a transient project that is not serialized to disk.
     */
    protected final boolean isTransient;

    /**
     * Flag to denote a project that can't be built directly; but contrary to
     * to the {@link #isBuildable()} value, additionally means that certain
     * checks for inheritance consistence are relaxed.
     * 
     * Not hidden, because the getting/setting this value is not checked anyway.
     */
    public boolean isAbstract = false;

    /**
     * This stores the name of the creation class this project falls in.
     * @see ProjectCreationEngine
     */
    protected String creationClass = null;

    /**
     * This list stores references to the projects this project was marked as
     * being compatible with. For each project referenced in this list, the
     * {@link ProjectCreationEngine} will try to create a new, transient
     * project derived from both this project and the referenced one.
     * It also checks if:
     * <ol>
     *   <li>
     *    The referenced project is compatible with this project
     *    (see {@link #creationClass}),
     *   </li>
     *   <li>all parameters are correctly set,</i>
     *   <li>no circular, diamond or multiple inheritance is created,</li>
     *   <li>the resulting project is buildable and</li>
     *   <li>the newly created job does not already exist.</li>
     * </ol>
     */
    protected LinkedList<AbstractProjectReference> compatibleProjects = new LinkedList<AbstractProjectReference>();

    /**
     * This list stores the adjacency relationship of this project to its
     * parents. The order of objects is in <i>most</i> cases unimportant, as
     * the {@link ProjectReference} class itself stores priorization details.
     * <p>
     * Do note that any {@link AbstractProjectReference} not derived from
     * {@link ProjectReference} does not carry priority information and thus
     * treated as having a priority of 0 everywhere.
     */
    protected LinkedList<AbstractProjectReference> parentReferences = new LinkedList<AbstractProjectReference>();

    protected String parameterizedWorkspace;

    // === CONSTRUCTORS AND CONSTRUCTION HELPERS ===

    public InheritanceProject(ItemGroup parent, String name, boolean isTransient) {
        super(parent, name);
        this.isTransient = isTransient;

        //Creating the static buffers, if necessary
        createBuffers();

        this.versionStore = this.loadVersionedObjectStore();

        //Generating a new IP causes a refresh of the project map and buffers
        clearBuffers(null);

        //And we notify the PCE about the new project, if we're not transient
        if (!isTransient) {
            ProjectCreationEngine.instance.notifyProjectNew(this);
        }
        clearBuffers(null);
    }

    public int compareTo(Project o) {
        return this.name.compareTo(o.getName());
    }

    @Override
    public String toString() {
        return this.getName();
    }

    @Override
    protected Class<InheritanceBuild> getBuildClass() {
        return InheritanceBuild.class;
    }

    /**
     * This method returns a mapping of project names to the
     * {@link InheritanceProject} objects that carry that name.
     * 
     * Do note that this method is using aggressive buffering, to make sure
     * that repeated access is running in O(1), instead of having to scan
     * all defined projects again and again.
     * 
     * The downside of this, is that you have to call
     * {@link #forceProjectsMapRefresh()} whenever a change to this mapping
     * might have occurred.
     * 
     * @return a map of names to projects with guaranteed O(1) performance on
     * repeated read access. The first invocation might run in O(n), where n
     * is the number of Projects defined in Jenkins.
     * 
     * @deprecated Do not use this function anymore, as its caching is
     * somewhat unreliable in certain situations and it might cause deadlocks
     * as it iterates over all items registered in Jenkins.
     */
    public static Map<String, InheritanceProject> getProjectsMap() {
        Object obj = onChangeBuffer.get(null, "getProjectsMap");
        if (obj != null && obj instanceof Map) {
            return (Map) obj;
        }

        HashMap<String, InheritanceProject> pMap = new HashMap<String, InheritanceProject>();
        for (AbstractProject p : Hudson.getInstance().getAllItems(AbstractProject.class)) {
            // We ensure that we may only inherit from actually inheritable,
            // non-transient projects
            if (p instanceof InheritanceProject) {
                pMap.put(p.getName(), (InheritanceProject) p);
            }
        }

        onChangeBuffer.set(null, "getProjectsMap", pMap);
        return pMap;
    }

    public static InheritanceProject getProjectByName(String name) {
        TopLevelItem item = Jenkins.getInstance().getItem(name);
        if (item instanceof InheritanceProject) {
            return (InheritanceProject) item;
        }
        return null;
    }

    public static void createBuffers() {
        if (onChangeBuffer == null) {
            onChangeBuffer = new TimedBuffer<InheritanceProject, String>();
        }
        if (onSelfChangeBuffer == null) {
            onSelfChangeBuffer = new TimedBuffer<InheritanceProject, String>();
        }
        if (onInheritChangeBuffer == null) {
            onInheritChangeBuffer = new TimedBuffer<InheritanceProject, String>();
        }
    }

    public static void clearBuffers(InheritanceProject root) {
        //Ensuring that the buffers are present
        createBuffers();

        if (root == null) {
            //Nuke all
            onChangeBuffer.clearAll();
            onSelfChangeBuffer.clearAll();
            onInheritChangeBuffer.clearAll();
            return;
        }

        //First clearing the cross-project change buffer
        onChangeBuffer.clearAll();
        //Then clearing the self-change buffer
        onSelfChangeBuffer.clear(root);

        //Then we need to clear the inheritable changes for the root and its children
        //Do note that the root MUST be cleared first, as otherwise we may
        //fetch an "unclean" relationship set
        onInheritChangeBuffer.clear(root);
        Map<InheritanceProject, Relationship> relMap = root.getRelationships();
        for (Map.Entry<InheritanceProject, Relationship> e : relMap.entrySet()) {
            //We ignore siblings
            if (e.getValue().type == Relationship.Type.MATE) {
                continue;
            }
            //Otherwise, we clear that project's inheritance buffer
            onInheritChangeBuffer.clear(e.getKey());
        }
    }

    // === PROJECT CONFIGURATION METHODS ===

    public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, FormException {
        //Check if we're transient; in which case a submit does nothing
        if (this.isTransient) {
            return;
        }
        //FIXME: Possible deadlock due to the interaction between:
        // - this.doConfigSubmit() --> unsynchronized
        // - this.buildDependencyGraph() --> unsynchronized
        // - this.getPublishersList() --> SYNCHRONIZED

        //Calling the super implementation; will ultimately call submit()
        super.doConfigSubmit(req, rsp);
    }

    /**
     * This method evaluates the form request created by the Descriptor and
     * adjusts the properties of this project accordingly.
     */
    @Override
    protected void submit(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, FormException {
        //Check if we're transient; in which case a submit does nothing
        if (this.isTransient) {
            return;
        }

        /* A submit might cause property changes across projects, and since
         * the relationships between projects may change during a reconfigure,
         * we need to nuke the buffers at three stages:
         * 
         * 1.) Before any change -- FULL NUKE
         * 2.) Before saving versions -- LOCAL NUKE
         * 3.) After saving versions -- LOCAL NUKE (to get version IDs right)
         * 4.) After all changes applied -- FULL NUKE
         */
        clearBuffers(this);

        /* Apply the configuration inherited from the superclass.
         * Do note that the behaviour of that function might change erratically
         * with each new Jenkins version.
         * 
         * One such change is that -- starting with v1.492 -- the BuildWrappers,
         * Builders and Publisher fields are changed in-place instead of
         * reassigned. This broke versioning as that causes new fields to be
         * returned on each call; so that no in-place change can ever work.
         */
        super.submit(req, rsp);

        JSONObject json = req.getSubmittedForm();

        if (json.has("isAbstract")) {
            this.isAbstract = json.getBoolean("isAbstract");
        } else {
            this.isAbstract = false;
        }

        if (json.has("projects")) {
            Object obj = json.get("projects");
            List<AbstractProjectReference> refs = AbstractProjectReference.ProjectReferenceDescriptor
                    .newInstancesFromHeteroList(req, obj, AbstractProjectReference.all());
            if (this.parentReferences != null) {
                this.parentReferences.clear();
            } else {
                this.parentReferences = new LinkedList<AbstractProjectReference>();
            }
            this.parentReferences.addAll(refs);
        } else {
            if (this.parentReferences != null) {
                this.parentReferences.clear();
            }
        }

        if (req.hasParameter("parameterizedWorkspace")) {
            this.parameterizedWorkspace = Util
                    .fixEmptyAndTrim(req.getParameter("parameterizedWorkspace.directory"));
        } else {
            this.parameterizedWorkspace = null;
        }

        //Read the class of this project for listing and derivation purposes
        if (json.has("creationClass")) {
            this.creationClass = json.getString("creationClass");
        } else {
            this.creationClass = null;
        }

        //LOCAL NUKE before versioning is saved 
        clearBuffers(this);

        //After everything was altered, we generate a new version
        if (json.has("versionMessageString")) {
            this.dumpConfigToNewVersion(json.getString("versionMessageString"));
        } else {
            this.dumpConfigToNewVersion();
        }

        //LOCAL NUKE after versioning is saved 
        clearBuffers(this);

        //And at the very end, we notify the PCE about our changes
        ProjectCreationEngine.instance.notifyProjectChange(this);
    }

    @Override
    public void updateByXml(Source source) throws IOException {
        //Check if the job is a transient job; in which case this must fail
        if (this.getIsTransient()) {
            String msg = String.format("Updating %s by XML upload is not allowed: Transient project",
                    this.getName());
            log.warning(msg);
            throw new IOException(msg);
        }

        //Instruct the parent to update us
        super.updateByXml(source);
        //Then, save a new version

        clearBuffers(this);
        this.dumpConfigToNewVersion("New version uploaded as XML via API/CLI");
        clearBuffers(this);

        //Notify the PCE about our changes
        ProjectCreationEngine.instance.notifyProjectChange(this);
    }

    @RequirePOST
    public synchronized void doSubmitChildJobCreation(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, FormException {
        //Check if we're transient; in which case a submit does nothing
        if (this.isTransient) {
            return;
        }

        JSONObject json = req.getSubmittedForm();

        // FULL NUKE before configuration change
        clearBuffers(null);

        //Decode the new properties
        if (json.has("properties")) {

            //Saving the old properties; except the parameter props and
            //removing them all from the current list
            List<JobProperty<? super InheritanceProject>> oldProps = new LinkedList<JobProperty<? super InheritanceProject>>();
            for (JobProperty jobProperty : this.properties) {
                if (!(jobProperty instanceof ParametersDefinitionProperty)) {
                    oldProps.add(jobProperty);
                }
            }

            //Then, we read the new list from the JSON submission
            DescribableList<JobProperty<?>, JobPropertyDescriptor> newProps = new DescribableList<JobProperty<?>, JobPropertyDescriptor>(
                    NOOP);
            newProps.rebuild(req, json.optJSONObject("properties"),
                    JobPropertyDescriptor.getPropertyDescriptors(this.getClass()));

            //Then, we nuke the list, and add the rebuilt ones
            properties.clear();
            for (JobProperty p : newProps) {
                //Must use this.addProperty() to set correct owner
                this.addProperty(p);
            }

            //Finally, add the old properties; we don't need to call
            //this.addProperty(), because the old properties should already be
            //owned by this project
            this.properties.addAll(oldProps);
        }

        //Read the compatible projects
        if (json.has("compatibleProjects")) {
            Object obj = json.get("compatibleProjects");
            List<AbstractProjectReference> refs = AbstractProjectReference.ProjectReferenceDescriptor
                    .newInstancesFromHeteroList(req, obj, AbstractProjectReference.all());
            if (this.compatibleProjects != null) {
                this.compatibleProjects.clear();
            } else {
                this.compatibleProjects = new LinkedList<AbstractProjectReference>();
            }
            this.compatibleProjects.addAll(refs);
        } else {
            if (this.compatibleProjects != null) {
                this.compatibleProjects.clear();
            }
        }

        // FULL NUKE after configuration change
        clearBuffers(null);

        //Save data and send the redirect
        this.save();

        rsp.sendRedirect(this.getAbsoluteUrl());

        //After everything was altered, we generate a new version
        if (json.has("versionMessageString")) {
            this.dumpConfigToNewVersion(json.getString("versionMessageString"));
        } else {
            this.dumpConfigToNewVersion();
        }

        //LOCAL NUKE after versioning is saved 
        clearBuffers(this);

        //And at the very end, we notify the PCE about our changes
        ProjectCreationEngine.instance.notifyProjectChange(this);
    }

    @Override
    public void renameTo(String newName) throws IOException {
        if (this.name.equals(newName)) {
            return;
        }

        //Check if the user has the permission to rename transient projects
        //Do note that currently, that is impossible via the GUI for everyone anyway
        if (this.getIsTransient() && !ProjectCreationEngine.instance.currentUserMayRename()) {
            throw new IOException("Current user is not allowed to rename transient projects");
        }

        //Recording our old project name
        String oldName = this.name;

        //Executing the rename
        super.renameTo(newName);

        //This means, that we need to force a refresh various buffers
        clearBuffers(this);

        //And then fixing all named references
        for (InheritanceProject p : getProjectsMap().values()) {
            for (AbstractProjectReference ref : p.getParentReferences()) {
                if (ref.getName().equals(oldName)) {
                    ref.switchProject(this);
                }
            }
            for (AbstractProjectReference ref : p.compatibleProjects) {
                if (ref.getName().equals(oldName)) {
                    ref.switchProject(this);
                }
            }
        }
    }

    /**
     * Adds the given {@link ProjectReference} as a parent to this node.
     * <p>
     * TODO: The fact that this function is public is really nasty.
     * Basically, references should only be used through the validated
     * frontend, or set by the equally validated {@link ProjectCreationEngine}.
     * <p>
     * Of course, since the user can just scribble around in the XML -- if
     * the job isn't transient -- we can't prevent broken references
     * anyway.
     * <p>
     * Do note that this change will not trigger any versioning or saving to
     * disk. If you use this, you need to know exactly what you're doing; for
     * example calling this in proper UnitTests.
     * 
     * @param ref the reference to add
     * @param noDuplicateCheck set to false, if no duplication check shall be done.
     *       This is only useful in Unit-tests and nowhere else.
     */
    public void addParentReference(AbstractProjectReference ref, boolean duplicateCheck) {
        //Checking if we already have such a reference
        if (duplicateCheck) {
            for (AbstractProjectReference ourRef : this.getParentReferences()) {
                if (ourRef.getName().equals(ref.getName())) {
                    //No point in duplicated references
                    return;
                }
            }
        }
        //Otherwise, we can add it. Of course, it might still lead to circular
        //references, or simply and plainly not exist
        this.parentReferences.push(ref);

        //And invalidating all caches
        clearBuffers(this);
    }

    /**
     * Wrapper around
     * {@link #addParentReference(AbstractProjectReference, boolean)} with
     * duplication check enabled.
     * 
     * @param ref the references to add as a parent.
     */
    public void addParentReference(AbstractProjectReference ref) {
        this.addParentReference(ref, true);
    }

    /**
     * Removes a parent reference.
     * <p>
     * Same caveats apply as for {@link #addParentReference(AbstractProjectReference)}.
     * 
     * @param name the name of the project for which to remove one parent reference.
     * @return true, if a parent reference was removed.
     */
    public boolean removeParentReference(String name) {
        Iterator<AbstractProjectReference> iter = this.parentReferences.iterator();
        while (iter.hasNext()) {
            AbstractProjectReference apr = iter.next();
            if (apr.getName().equals(name)) {
                iter.remove();
                clearBuffers(this);
                return true;
            }
        }
        return false;
    }

    public void setVarianceLabel(String variance) {
        if (this.isTransient) {
            this.variance = (variance == null || variance.isEmpty()) ? null : variance;
        }
    }

    public void setCreationClass(String creationClass) {
        //Checking if such a class exists at all
        if (creationClass == null) {
            return;
        }
        for (CreationClass cc : ProjectCreationEngine.instance.getCreationClasses()) {
            if (cc.name.equals(creationClass)) {
                this.creationClass = creationClass;
                break;
            }
        }
    }

    /**
     * This method is called after a save to restructure the dependency graph.
     * The triggering method is
     * {@link #doConfigSubmit(StaplerRequest, StaplerResponse)}.
     * <p>
     * Unfortunately, it is wholly unsynchronized and can thus lead to a bad
     * case of deadlock if two rebuilds happen at the same time, explore the
     * inheritance tree and call a synchronized function.
     * <p>
     * The most likely culprit will be {@link #getPublishersList()}.
     */
    protected void buildDependencyGraph(DependencyGraph graph) {
        //Fetch the global lock of all projects
        //TODO: Use an interruptible lock here?
        globalProjectLock.lock();
        try {
            super.buildDependencyGraph(graph);
        } finally {
            globalProjectLock.unlock();
        }
    }

    // === SAVING/LOADING METHODS ===

    /**
     * This method serializes this object to offline storage. The default
     * implementation of Jenkins is XML-File based, but that can be
     * overridden herein. Of course, if you override the saving method,
     * you will also have to override the loading method from
     * {@link InheritanceProject.DescriptorImpl}.
     */
    @Override
    public synchronized void save() throws IOException {
        //Checking if we're marked as transient; which causes no saving to occur
        if (this.isTransient) {
            return;
        }

        //Invoking the super constructor to save use
        super.save();
        //TODO: Save the version store to disk here
    }

    /**
     * This method restores transient fields that could not be deserialized.
     * Do note that there is no guaranteed order of deserialization, so
     * don't expect other objects to be present, when this method is called.
     */
    @Override
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
        //Creating & clearing buffers, if necessary
        createBuffers();
        clearBuffers(null);

        /* We need to create a dummy version store first, as we can't get the
         * project root directory before super() is executed (as no name is
         * set yet); but that one needs a version store available to load
         * certain values reliably without a null pointer access.
         */
        this.versionStore = new VersionedObjectStore();

        //Then loading the elements defined in the parent
        //TODO: What to do if a transient job is attempted to be loaded?
        super.onLoad(parent, name);

        //Loading the correct version store
        this.versionStore = this.loadVersionedObjectStore();

        //And clearing the buffers again, as a new job with new props is available
        clearBuffers(null);
    }

    /**
     * This method tells this class and all its superclass's which directory
     * to use for storing stuff.
     * 
     * For regular jobs this is the default Jenkins path for jobs ([root]/jobs).
     * For transient jobs; this is redirected to ([root]/transient_jobs) to
     * make them more invisible to Jenkins.
     */
    public File getRootDir() {
        if (!this.isTransient) {
            return super.getRootDir();
        }
        File standardRoot = this.getParent().getRootDir();
        //Otherwise, we alter the last path segment
        String pathSafeJobName = this.getName().replaceAll("[/\\\\]", "_");
        File newRoot = new File(standardRoot.getAbsolutePath() + File.separator + "transient_jobs" + File.separator
                + pathSafeJobName);
        return newRoot;
    }

    protected File getVersionFile() {
        //Transient jobs do not have a concept of versions
        if (this.isTransient) {
            return null;
        }
        //TODO: This somewhat assumes, that the file will be compressed
        return new File(this.getRootDir(), "versions.xml.gz");
    }

    /**
     * {@inheritDoc}
     */
    public File getBuildDir() {
        return super.getBuildDir();
    }

    // === URL-BOUND ACTION METHODS ===

    /**
     * This method displays the configuration as a complete XML dump.
     * 
     * @return raw XML string
     */
    public String doGetConfigAsXML(StaplerRequest req, StaplerResponse rsp) {
        //Check if the user only wants the local data
        String depth = req.getParameter("depth");
        int iDepth = 0;
        if (depth != null && !depth.isEmpty()) {
            try {
                iDepth = Integer.valueOf(depth);
            } catch (NumberFormatException ex) {
            }
        }
        if (iDepth <= 0) {
            Object obj = onSelfChangeBuffer.get(this, "doGetConfigAsXML");
            if (obj != null && obj instanceof String) {
                return (String) obj;
            }
            String str = Jenkins.XSTREAM2.toXML(this);
            onSelfChangeBuffer.set(this, "doGetConfigAsXML", str);
            return str;
        } else {
            Map<String, InheritanceProject> projs = new LinkedHashMap();
            for (AbstractProjectReference apr : this.getAllParentReferences(SELECTOR.BUILDER)) {
                InheritanceProject ip = apr.getProject();
                if (ip == null) {
                    continue;
                }
                projs.put(ip.getFullName(), ip);
            }
            //Adding ourselves last
            projs.put(this.getFullName(), this);
            return Jenkins.XSTREAM2.toXML(projs);
        }
    }

    /**
     * This method dumps the full expansion of all parameters (even derived
     * ones) based on their default values into an XML file.
     * <p>
     * If you only want the default values of the last definition of each
     * parameter, use {@link #doGetParamDefaultsAsXML()}
     * 
     * @return raw XML string
     */
    public String doGetParamExpansionsAsXML() {
        /*
        Object obj = onInheritChangeBuffer.get(this, "doGetParamExpansionsAsXML");
        if (obj != null && obj instanceof String) {
           return (String) obj;
        }
        */

        //Fetching a list of unique parameters
        List<ParameterDefinition> defLst = this.getParameters(IMode.INHERIT_FORCED);

        LinkedList<ParameterValue> valLst = new LinkedList<ParameterValue>();

        //Then, we fetch the expansion of these based on their defaults
        for (ParameterDefinition pd : defLst) {
            ParameterValue pv = null;
            if (pd instanceof InheritableStringParameterDefinition) {
                InheritableStringParameterDefinition ispd = (InheritableStringParameterDefinition) pd;
                pv = ispd.createValue(ispd.getDefaultValue());
            } else {
                pv = pd.getDefaultParameterValue();
            }
            if (pv != null) {
                valLst.add(pv);
            }
        }

        String str = Jenkins.XSTREAM2.toXML(valLst);
        //onInheritChangeBuffer.set(this, "doGetParamExpansionsAsXML", str);
        return str;
    }

    /**
     * This method dumps the default values of all parameters (even derived
     * ones) into an XML file.
     * <p>
     * Do note that this does not do any expansion,
     * it merely outputs the last default value defined for the given
     * parameter. If you want the full expansion, call
     * {@link #doGetParamExpansionsAsXML()}
     * 
     * @return raw XML string
     */
    public String doGetParamDefaultsAsXML() {
        Object obj = onInheritChangeBuffer.get(this, "doGetParamDefaultsAsXML");
        if (obj != null && obj instanceof String) {
            return (String) obj;
        }

        //Fetching a list of unique parameters
        List<ParameterDefinition> defLst = this.getParameters(IMode.INHERIT_FORCED);

        LinkedList<ParameterValue> valLst = new LinkedList<ParameterValue>();

        //Then, we fetch the expansion of these based on their defaults
        for (ParameterDefinition pd : defLst) {
            ParameterValue pv = pd.getDefaultParameterValue();
            if (pv != null) {
                valLst.add(pv);
            }
        }

        String str = Jenkins.XSTREAM2.toXML(valLst);
        onInheritChangeBuffer.set(this, "doGetParamDefaultsAsXML", str);
        return str;
    }

    /**
     * This method dumps the version store as serialized XML.
     * @return
     */
    public String doGetVersionsAsXML() {
        if (this.versionStore == null) {
            return "";
        }
        return this.versionStore.toXML();
    }

    /**
     * This method dumps the version store as serialized,
     * GZIP compressed, Base64 encoded XML.
     * @return
     */
    public String doGetVersionsAsCompressedXML() {
        if (this.versionStore == null) {
            return "";
        }
        String xml = this.versionStore.toXML();
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
            BASE64EncoderStream b64s = new BASE64EncoderStream(baos);
            GZIPOutputStream gos = new GZIPOutputStream(b64s);
            gos.write(xml.getBytes());
            gos.finish();
            gos.close();
            return baos.toString();
        } catch (IOException ex) {
            return "";
        }

    }

    @Override
    public void doDoDelete(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, InterruptedException {
        //Checking if this project is still referenced by another project
        for (Relationship rel : this.getRelationships().values()) {
            if (rel.type == Type.CHILD || rel.type == Type.MATE) {
                //Abort and redirect to error page
                rsp.sendRedirect(this.getAbsoluteUrl() + "/showReferencedBy");
                return;
            }
        }
        //If we reach this spot, we can safely delete the project
        super.doDoDelete(req, rsp);

        //Then, we refresh the project map and buffers
        clearBuffers(this);
    }

    public String doComputeVersionDiff(StaplerRequest req, StaplerResponse rsp) {
        //Checking if the two necessary parameters are set
        if (!req.hasParameter("l") || !req.hasParameter("r")) {
            return "<span style=\"color:red\"><b>No left/right version selected!</b></span>";
        }

        Long l = null;
        Long r = null;
        String mode = "unified";
        try {
            l = Long.parseLong(req.getParameter("l"), 10);
            r = Long.parseLong(req.getParameter("r"), 10);
            if (req.hasParameter("mode")) {
                mode = req.getParameter("mode");
            }
        } catch (NumberFormatException ex) {
            return "<span style=\"color:red\"><b>Left/right version is not a number!</b></span>";
        }

        //Fetch the value maps of both versions
        Map<String, Object> lMap = this.versionStore.getValueMapFor(l);
        if (lMap == null) {
            return "<span style=\"color:red\"><b>Left version does not exist!</b></span>";
        }

        Map<String, Object> rMap = this.versionStore.getValueMapFor(r);
        if (rMap == null) {
            return "<span style=\"color:red\"><b>Right version does not exist!</b></span>";
        }

        //Turning them into escaped XML
        String lXml = Jenkins.XSTREAM2.toXML(lMap);
        String rXml = Jenkins.XSTREAM2.toXML(rMap);

        StringBuilder b = new StringBuilder();
        if (mode.equals("unified")) {
            return computeUnifiedDiff(5, new AbstractMap.SimpleEntry(l, lXml),
                    new AbstractMap.SimpleEntry(r, rXml));
        } else if (mode.equals("side")) {
            b.append("<span style=\"color:red\"><b>");
            b.append("Side-by-Side diff not yet implemented.");
            b.append("</b></span>");
        } else if (mode.equals("raw")) {
            return computeRawTable(new AbstractMap.SimpleEntry(l, lXml), new AbstractMap.SimpleEntry(r, rXml));
        } else {
            b.append("<span style=\"color:red\"><b>");
            b.append("Select a valid diff mode: 'unified', 'side' (for side-by-side), or 'raw'.");
            b.append("</b></span>");
        }
        return b.toString();
    }

    public String warnUserOnUnstableVersions() {
        String warnMessage = null;
        if (this.isAbstract) {
            Deque<Version> stableVersions = getStableVersions();
            Long latestVersion = getLatestVersion();
            if (stableVersions.size() > 0) {
                if (!this.versionStore.getVersion(latestVersion).getStability()) {
                    warnMessage = Messages.InheritanceProject_OlderVersionMarkedAsStable();
                }
            } else {
                warnMessage = Messages.InheritanceProject_NoVersionMarkedAsStable();
            }
        }
        return warnMessage;
    }

    /**
     * Returns the versioning notification based on the current user desired version.
     * @see VersionedObjectStore#getUserNotificationFor(Long)
     */
    public VersionsNotification getCurrentVersionNotification() {
        return versionStore.getUserNotificationFor(getUserDesiredVersion());
    }

    // === DIFF COMPUTATION METHODS ===

    private static String escapeHTMLFull(String str) {
        return StringEscapeUtils.escapeHtml(str);
    }

    private String computeRawTable(Map.Entry<Long, String>... versions) {
        String headFmt = "<tr><th $c style=\"width:3em\">#</th><th $c>Version %d</th><th $c style=\"width:3em\">#</th><th $c>Version %d</th></tr>"
                .replace("$c", "class=\"mono\"");
        String rowFmt = "<tr><td $c>%d</td><td $c>%s</td><td $c>%d</td><td $c>%s</td></tr>".replace("$c",
                "class=\"mono\"");

        StringBuilder b = new StringBuilder();

        //We print both files in a table next to each other
        b.append("<table frame=\"void\" rules=\"cols\" width=\"100%\"");
        b.append("class=\"mono\">");
        b.append(String.format(headFmt, versions[0].getKey(), versions[1].getKey()));

        final String[] lArr = versions[0].getValue().split("\n");
        final String[] rArr = versions[1].getValue().split("\n");
        String[] lines = new String[2];
        int max = Math.max(lArr.length, rArr.length);
        for (int i = 0; i < max; i++) {
            lines[0] = (i < lArr.length) ? escapeHTMLFull(lArr[i]) : "";
            lines[1] = (i < rArr.length) ? escapeHTMLFull(rArr[i]) : "";
            b.append(String.format(rowFmt, i, lines[0], i, lines[1]));
        }
        b.append("</table>");

        return b.toString();
    }

    private String computeUnifiedDiff(int context, Map.Entry<Long, String>... versions) {
        if (versions.length != 2) {
            throw new IllegalArgumentException("You must pass exactly two versions");
        }
        if (context < 0) {
            context = 0;
        }

        StringBuilder b = new StringBuilder();

        //Splitting texts along newlines
        List<String> lLst = Arrays.asList(versions[0].getValue().split("\n"));
        List<String> rLst = Arrays.asList(versions[1].getValue().split("\n"));

        //We use Google's diff utils to create the diff patch
        Patch p = DiffUtils.diff(lLst, rLst);

        //Then, we display a unified diff
        List<String> outLst = DiffUtils.generateUnifiedDiff("Version " + versions[0].getKey(),
                "Version " + versions[1].getKey(), lLst, p, context);

        for (String line : outLst) {
            boolean hasColour = false;
            if (line.startsWith("++")) {
                b.append("<span style=\"color:orange\">");
                hasColour = true;
            } else if (line.startsWith("+")) {
                b.append("<span style=\"color:green\">");
                hasColour = true;
            } else if (line.startsWith("--")) {
                b.append("<span style=\"color:blue\">");
                hasColour = true;
            } else if (line.startsWith("-")) {
                b.append("<span style=\"color:red\">");
                hasColour = true;
            }
            String mod = escapeHTMLFull(line);
            b.append(mod);
            if (hasColour) {
                b.append("</span>");
            }
            b.append("<br/>");
        }

        return b.toString();
    }

    // === BUILD STARTING METHODS ===

    /**
     * Executes a build started from the GUI.
     * <p>
     * Queries for parameters on an HTTP/GET, tries to decode submitted
     * parameters on a HTTP/POST.
     * <p>
     * Before we can call the actual build, we must make sure that
     * parameters are properly inherited; as the super implementation will
     * NOT query {@link #isParameterized()}, but instead rely on querying
     * whether the Project has a {@link ParametersDefinitionProperty} property.
     * <p>
     * As we need to treat Parameters created by ourselves different from
     * those assigned by parents, we must override {@link #getProperty(Class)}
     * to produce a suitable {@link ParametersDefinitionProperty} reference
     * on the spot.
     * <p>
     * Do note that the {@link ParametersAction} objects that store the actual
     * values will be created by
     * {@link ParametersDefinitionProperty#_doBuild(StaplerRequest, StaplerResponse)}
     * later on. Also do note that we can't extend
     * {@link ParametersDefinitionProperty} or {@link ParametersAction}, because
     * {@link AbstractProject} only checks for exact class matches.
     */
    @Override
    public void doBuild(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay)
            throws IOException, ServletException {
        //Purge whatever's stored in the thread from a previous run
        //ThreadAssocStore.getInstance().clear(Thread.currentThread());

        //The delay parameter might be null in case somebody used a custom URL
        if (delay == null) {
            delay = new TimeDuration(0);
        }

        //Checking if we can fetch a fully defined versioning parameter
        if (req.getMethod().equals("POST")) {
            //Checking if parameters are set -- non-parameterized builds
            //will throw a nasty exception on req.getSubmittedForm; since no
            //form will have been submitted.
            Map pMap = req.getParameterMap();
            String cType = req.getContentType();
            boolean hasFormContent = ((pMap != null && pMap.containsKey("json"))
                    || (cType != null && cType.startsWith("multipart/")));
            if (hasFormContent) {
                //Retrieving the submitted form
                JSONObject jForm = req.getSubmittedForm();

                //Trying to retrieve a versioning field
                try {
                    String jVerMap = jForm.getString("versionMap");
                    if (jVerMap != null && !jVerMap.isEmpty()) {
                        Map<String, Long> vMap = InheritanceParametersDefinitionProperty
                                .decodeVersioningMap(jVerMap);
                        if (vMap != null && !vMap.isEmpty()) {
                            InheritanceProject.setVersioningMap(vMap);
                        }
                    }
                } catch (JSONException ex) {
                    //No version map set as a parameter; will default to stable versions
                }
            }
        }

        /* We can't call the super-function, as it does not allow us to
         * add a custom action to rescue the versioning over to the start of
         * the actual build.
        */
        //super.doBuild(req, rsp, delay);

        /* === START COPY OF SUPER FUNCTION ===  */

        //TODO: Check if this ACL check actually does the same as the commented
        //instruction below
        ACL acl = Jenkins.getInstance().getACL();
        acl.checkPermission(BUILD);
        //BuildAuthorizationToken.checkPermission(this, getAuthToken(), 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"));
        }

        Jenkins.getInstance().getQueue().schedule(this, delay.getTime(), this.getBuildCauseOverride(req),
                new VersioningAction(this.getAllVersionsFromCurrentState()));

        //Send the user back, except if "rebuildNoRedirect" is set
        if (req.getAttribute("rebuildNoRedirect") == null) {
            rsp.forwardToPreviousPage(req);
        }
        /* === END COPY OF SUPER FUNCTION ===  */
    }

    public void doBuildSpecificVersion(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException {
        //Purge whatever's stored in the thread from a previous run
        //ThreadAssocStore.getInstance().clear(Thread.currentThread());

        //If we did not submit a form; just display the initial data
        if (!req.getMethod().equals("POST")) {
            req.getView(this, "buildSpecificVersion.jelly").forward(req, rsp);
            return;
        }

        Map<String, Long> verMap = InheritanceRebuildAction.decodeVersionMap(req);

        String verMapStr = InheritanceParametersDefinitionProperty.encodeVersioningMap(verMap);

        if (verMapStr == null || verMapStr.isEmpty()) {
            //TODO: Redirect to error page
            rsp.sendRedirect(".");
        }

        //TODO: Think about passing the verMapStr compressed
        String verUrlParm = "versions=\"" + verMapStr + "\"";

        //Checking if the user wants to build or refresh the page
        if (req.hasParameter("doRefresh")) {
            //Refreshing the page with the newly selected versions
            rsp.sendRedirect("buildSpecificVersion?" + verUrlParm);
        } else {
            //Triggering a nice build with the given version map
            rsp.sendRedirect("build?" + verUrlParm);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException {
        //Purge whatever's stored in the thread from a previous run
        //ThreadAssocStore.getInstance().clear(Thread.currentThread());

        //TODO: The below function did not have the TimeDuration param previously
        TimeDuration td = new TimeDuration(0);
        super.doBuildWithParameters(req, rsp, td);
    }

    /**
     * {@inheritDoc}
     * <p>
     * <b>Do note:</b> This method is <i>not</i> calling the super-implementation,
     * because it is not aware that the default values for parameters must be
     * derived via inheritance, if the method is called directly (instead of
     * via the CLI).
     * 
     * @see InheritableStringParameterDefinition#getDefaultParameterValue()
     */
    public QueueTaskFuture<InheritanceBuild> scheduleBuild2(int quietPeriod, Cause c,
            Collection<? extends Action> actions) {
        //Purge whatever's stored in the thread from a previous run
        //ThreadAssocStore.getInstance().clear(Thread.currentThread());

        //Checking if a version-setting action is present,
        boolean hasVersioningAction = false;
        for (Action a : actions) {
            if (a instanceof VersioningAction) {
                //Storing that version-map in the thread
                setVersioningMap(((VersioningAction) a).versionMap);
                hasVersioningAction = true;
                break;
            }
        }

        //Checking if we have a parameter set which overrides everything 
        Map<String, Long> vMap = new HashMap<String, Long>();
        for (Action a : actions) {
            if (!(a instanceof ParametersAction)) {
                continue;
            }

            ParametersAction pa = (ParametersAction) a;
            ParameterValue pv = pa.getParameter(InheritanceParametersDefinitionProperty.VERSION_PARAM_NAME);
            if (pv == null || !(pv instanceof StringParameterValue)) {
                continue;
            }
            StringParameterValue spv = (StringParameterValue) pv;
            vMap = InheritanceParametersDefinitionProperty.decodeVersioningMap(spv.value);
            if (vMap == null) {
                continue;
            }

            //The decoded map is registered in the thread
            setVersioningMap(vMap);
        }

        //If we need to create a new versioning action; we do this now
        //Do note that this will retrieve versions previously stored in the thread!
        //There is a special use case where the versions can be passed through the
        //InheritanceParametersDefinitionProperty.VERSION_PARAM_NAME so 
        //we need to create a Versioning Action based on that in this case
        if (!hasVersioningAction) {
            LinkedList<Action> newActions = new LinkedList<Action>(actions);
            if (!vMap.isEmpty()) {
                newActions.add(new VersioningAction(vMap));
            } else {
                newActions.add(new VersioningAction(this.getAllVersionsFromCurrentState()));
            }
            actions = newActions;
        }

        //The buildable check must be done after versioning assignment
        if (!isBuildable()) {
            return null;
        }

        List<Action> queueActions = new ArrayList<Action>(actions);
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            List<ParameterValue> pvLst = new ArrayList<ParameterValue>();
            for (ParameterDefinition def : this.getParameters()) {
                if (def instanceof InheritableStringParameterDefinition) {
                    InheritableStringParameterDefinition ispd = (InheritableStringParameterDefinition) def;
                    pvLst.add(ispd.createValue(ispd.getDefaultValue()));
                } else {
                    pvLst.add(def.getDefaultParameterValue());
                }
            }
            queueActions.add(new ParametersAction(pvLst));
        }

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

        WaitingItem i = Jenkins.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
        return (i == null) ? null : (QueueTaskFuture) i.getFuture();
    }

    /**
     * This is a copy (not a wrapper) of getBuildCause() in
     * {@link AbstractProject}. This is necessary, because we can't access
     * that field as our parent is loaded by a different class loader.
     * <p>
     * The function is used, because we need to splice-in one additional
     * {@link Action} for creation of Builds: {@link VersioningAction}.
     * <p>
     * FIXME: The ideal solution to this is to simply add an Extension Point
     * into Jenkins, that allows one to contribute additional actions.
     * 
     * @param req
     * @return
     */
    @SuppressWarnings("deprecation")
    public CauseAction getBuildCauseOverride(StaplerRequest req) {
        Cause cause;
        if (getAuthToken() != null && getAuthToken().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 UserIdCause();
        }
        return new CauseAction(cause);
    }

    // === VERSIONING HANDLING AND DERIVATION METHODS ===

    @RequirePOST
    public void doConfigVersionsSubmit(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, FormException {
        checkPermission(VERSION_CONFIG);

        class Entry {
            Long id;
            String desc;
            boolean stable;

            public Entry(Long id, String desc, boolean stable) {
                this.id = id;
                this.desc = desc;
                this.stable = stable;
            }
        }

        LinkedList<Entry> fields = new LinkedList<Entry>();

        try {
            //Decoding the form data to structured JSON
            JSONObject json = req.getSubmittedForm();

            //Checking if the JSON has all necessary fields
            String[] keys = { "versionID", "description", "stable" };
            for (String key : keys) {
                if (!json.has(key)) {
                    log.warning("Got submission of broken version config form.");
                    return;
                }
            }
            try {
                JSONArray vArr = json.getJSONArray("versionID");
                JSONArray dArr = json.getJSONArray("description");
                JSONArray sArr = json.getJSONArray("stable");
                //Sanity check
                if (vArr.size() != dArr.size() || vArr.size() != sArr.size()) {
                    log.warning("Field in version config form differ in length.");
                    return;
                }

                //Then, we decode each tuple and alter the version referenced
                for (int i = 0; i < vArr.size(); i++) {
                    try {
                        fields.add(new Entry(Long.valueOf(vArr.getString(i), 10), dArr.getString(i),
                                sArr.getBoolean(i)));
                    } catch (JSONException ex) {
                        log.warning("Invalid value in version config at index " + i);
                    } catch (NumberFormatException ex) {
                        log.warning("Invalid id in version config at index " + i);
                    }
                }
            } catch (JSONException ex) {
                try {
                    // One field in version config form was not an array; trying strings
                    Long jv = Long.valueOf(json.getString("versionID"), 10);
                    String jd = json.getString("description");
                    Boolean js = json.getBoolean("stable");
                    fields.add(new Entry(jv, jd, js));
                } catch (JSONException ex2) {
                    log.warning("Invalid value in version config!");
                    return;
                } catch (NumberFormatException ex2) {
                    log.warning("Invalid id in version config!");
                    return;
                }
            }

            //After having decoded the fields, we alter the versions appropriately
            for (Entry e : fields) {
                //Fetching version
                Version v = this.versionStore.getVersion(e.id);
                if (v == null) {
                    log.warning("No such version " + e.id + " for " + this.getName());
                    continue;
                }
                v.setStability(e.stable);
                v.setDescription(e.desc);
            }

            //Saving the altered versions to disk
            this.versionStore.save(this.getVersionFile());

            //The selection of the stable version might have changed. So we need
            //to clear the local buffers
            clearBuffers(this);

            //Finally, triggering generation of new projects
            ProjectCreationEngine.instance.notifyProjectChange(this);

            //At the end, we mark the forms as successfully submitted
            FormApply.success(".").generateResponse(req, rsp, null);
        } catch (JSONException e) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            pw.println("Failed to parse form data. Please report this problem as a bug!");
            pw.println("JSON=" + req.getSubmittedForm());
            pw.println();
            e.printStackTrace(pw);

            rsp.setStatus(SC_BAD_REQUEST);
            sendError(sw.toString(), req, rsp, true);
        }
    }

    protected VersionedObjectStore loadVersionedObjectStore() {
        //TODO: This should read stuff from disk / DB
        File vFile = this.getVersionFile();
        if (vFile == null || !vFile.isFile()) {
            //Creating an empty VOS, in case none is stored anywhere
            return new VersionedObjectStore();
        }
        //Otherwise, we attempt to load it from disk
        VersionedObjectStore vos = null;
        try {
            vos = VersionedObjectStore.load(vFile);
        } catch (IOException ex) {
            log.warning("No versions loaded for " + this.getName() + ". " + ex.getLocalizedMessage());
            return new VersionedObjectStore();
        } catch (IllegalArgumentException ex) {
            log.warning("No versions loaded for " + this.getName() + ". " + ex.getLocalizedMessage());
            return new VersionedObjectStore();
        } catch (XStreamException ex) {
            log.warning("Could not load Version for: " + this.getName() + ". " + ex.getLocalizedMessage());
            return new VersionedObjectStore();
        }

        //Since we'll add/remove lots of properties, we put Jenkins in
        //bulk-change mode
        BulkChange bc = new BulkChange(this);
        try {
            //Then, we need to patch up certain fields in the store
            for (HashMap<String, Object> m : vos.getAllValueMaps()) {
                Object obj = m.get("properties");
                if (obj != null && obj instanceof List) {
                    List<JobProperty<Job<?, ?>>> lst = (List<JobProperty<Job<?, ?>>>) obj;
                    for (JobProperty<Job<?, ?>> prop : lst) {
                        //Adding the property to us
                        this.addProperty(prop);
                        //And immediately removing the property
                        this.removeProperty(prop);
                    }
                }
            }
            bc.commit();
        } catch (IOException e) {
            //Do nothing but fall down to the finally block
        } finally {
            bc.abort();
        }

        return vos;
    }

    /**
     * Wrapper around {@link #dumpConfigToNewVersion(String)} with an empty message
     */
    protected synchronized void dumpConfigToNewVersion() {
        this.dumpConfigToNewVersion(null);
    }

    /**
     * This method takes the current configuration and dumps all relevant
     * fields into the versioning-store.
     * <p>
     * Do note that versioning is stored separately from inheritance, but
     * evaluated together. This means that, over time, parentage may change as
     * well as compatibility markings. These <b>all</b> need to be saved 
     * indefinitely.
     */
    protected synchronized void dumpConfigToNewVersion(String message) {
        //Sanity checks
        if (this.isTransient) {
            return;
        }
        if (this.versionStore == null) {
            this.versionStore = this.loadVersionedObjectStore();
        }

        /* ATTENTION! Do NOT save the lists themselves, but rather copy them,
         * unless you know that you already have a copy. Due to the nature
         * of how Jenkins saves data, you don't need to copy the stored objects
         * themselves.
         * 
         * Also never, ever save inherited or versioned data. Only save
         * whatever the "super" class believes to be true for the project or
         * whatever is directly saved as a field in this class.
         */
        //Creating the next, clean version
        Version v = this.versionStore.createNextVersionAsEmpty();

        //Fetching the currently logged-on user and assigning that to the version
        String username = Jenkins.getAuthentication().getName();
        if (username != null && !username.isEmpty()) {
            v.setUsername(username);
        }

        //Attach the message (if any)
        if (message != null) {
            v.setDescription(message);
        }

        //Storing the list of parents
        this.versionStore.setObjectFor(v, "parentReferences",
                new LinkedList<AbstractProjectReference>(this.getRawParentReferences()));

        //Storing the list of compatibility matings -- also contains
        //the parameters defined on them.
        this.versionStore.setObjectFor(v, "compatibleProjects",
                new LinkedList<AbstractProjectReference>(this.compatibleProjects));

        //Storing the properties of this job; this contains the project parameters
        this.versionStore.setObjectFor(v, "properties",
                new LinkedList<JobProperty<? super InheritanceProject>>(super.getAllProperties()));

        //Storing build wrappers
        this.versionStore.setObjectFor(v, "buildWrappersList",
                new DescribableList<BuildWrapper, Descriptor<BuildWrapper>>(NOOP,
                        super.getBuildWrappersList().toList()));

        //Storing builders
        this.versionStore.setObjectFor(v, "buildersList",
                new DescribableList<Builder, Descriptor<Builder>>(NOOP, super.getBuildersList().toList()));

        //Storing publishers
        this.versionStore.setObjectFor(v, "publishersList",
                new DescribableList<Publisher, Descriptor<Publisher>>(NOOP, super.getPublishersList().toList()));

        //Storing actions
        this.versionStore.setObjectFor(v, "actions", new LinkedList<Action>(super.getActions()));

        //Storing the other, more simple properties
        this.versionStore.setObjectFor(v, "scm", super.getScm());
        this.versionStore.setObjectFor(v, "quietPeriod", this.getRawQuietPeriod());
        this.versionStore.setObjectFor(v, "scmCheckoutRetryCount", this.getRawScmCheckoutRetryCount());
        this.versionStore.setObjectFor(v, "scmCheckoutStrategy", super.getScmCheckoutStrategy());
        this.versionStore.setObjectFor(v, "blockBuildWhenDownstreamBuilding",
                super.blockBuildWhenDownstreamBuilding());
        this.versionStore.setObjectFor(v, "blockBuildWhenUpstreamBuilding", super.blockBuildWhenUpstreamBuilding());
        this.versionStore.setObjectFor(v, "logRotator", super.getBuildDiscarder());
        this.versionStore.setObjectFor(v, "customWorkspace", super.getCustomWorkspace());
        this.versionStore.setObjectFor(v, "parameterizedWorkspace", this.getRawParameterizedWorkspace());

        //Now, we check if this version is the same as the last one
        Version prev = this.versionStore.getVersion(v.id - 1);
        if (prev != null && this.versionStore.areIdentical(prev, v)) {
            //Drop the version, if possible
            this.versionStore.undoVersion(v);
        }
        //Save the file, to persist our changes
        try {
            this.versionStore.save(this.getVersionFile());
        } catch (IOException ex) {
            log.severe(String.format("Failed to save version to: %s; Reason = %s", this.getVersionFile(),
                    ex.getMessage()));
        }
    }

    private InheritanceProject getProjectFromRequest(StaplerRequest req) {
        //First, we check if an ancestor is defined
        InheritanceProject ip = req.findAncestorObject(InheritanceProject.class);
        if (ip != null) {
            return ip;
        }

        //Otherwise, we decode the URI and try to find the project that way
        String jobName = null;
        String uri = req.getRequestURI();
        if (uri != null && !uri.isEmpty()) {
            Matcher m = DescriptorImpl.urlJobPattern.matcher(uri);
            if (m.find()) {
                if (m.group(1) != null && !m.group(1).isEmpty()) {
                    jobName = m.group(1);
                }
            }
        }
        ip = null;
        if (jobName != null) {
            ip = InheritanceProject.getProjectByName(jobName);
        }
        return ip;
    }

    /**
     * This method tries to fetch the version number from the current
     * Stapler request.
     * <p>
     * Do note that this only works in threads started by a Web-/GUI-request.
     * It will not work if a call is triggered by the CLI, during the
     * actual execution of a build or when Jenkins is querying values
     * internally.
     * 
     * @return the version desired by the user; or null if not a request
     */
    private Long getUserDesiredVersionFromRequest() {
        //Checking if we were invoked through an HTTP URL request
        StaplerRequest req = Stapler.getCurrentRequest();
        if (req == null) {
            return null;
        }

        //Checking if there's a specific "versions" attribute associated with
        //the current request, and is a Map of Strings to Long values
        Object verObj = req.getAttribute("versions");
        if (verObj != null && verObj instanceof Map) {
            Map verMap = (Map) verObj;
            try {
                Object ver = verMap.get(this.getName());
                if (ver != null && ver instanceof Number) {
                    return ((Number) ver).longValue();
                }
            } catch (ClassCastException ex) {
                log.warning("ClassCaseException when attempting to decode 'versions' attribute of HTTP-Request");
            } catch (NullPointerException ex) {
                log.warning("NullPointerException when attempting to decode 'versions' attribute of HTTP-Request");
            }
        }

        //If that did not exist, we try for the broader "timestamp" attribute
        Object tsObj = req.getAttribute("vTimestamp");
        if (tsObj != null && tsObj instanceof Number) {
            Number ts = (Number) tsObj;
            Version v = this.versionStore.getNearestTo(ts.longValue());
            if (v != null) {
                return v.id;
            }
        }

        //Now that we've exhausted the attributes, we need to check the raw
        //URL parameters, which are of course MUCH more brittle

        String verParm = req.getParameter("versions");
        if (verParm != null && !verParm.isEmpty()) {
            Map<String, Long> verMap = InheritanceParametersDefinitionProperty.decodeVersioningMap(verParm);
            if (!verMap.isEmpty()) {
                InheritanceProject.setVersioningMap(verMap);
            }
            //And checking if it contained a matching for the current project
            Long version = verMap.get(this.getName());
            if (version != null) {
                return version;
            }
        }

        //If that failed, we try to get the "timestamp" attribute
        String tsParm = req.getParameter("timestamp");
        if (tsParm != null && !tsParm.isEmpty()) {
            //Trying to parse as a number
            try {
                Long ts = Long.valueOf(tsParm, 10);
                if (ts != null && ts >= 0) {
                    //Saving it, to not re-decode it again
                    req.setAttribute("vTimestamp", ts);
                    Version v = this.versionStore.getNearestTo(ts.longValue());
                    if (v != null) {
                        return v.id;
                    }
                }
            } catch (NumberFormatException ex) {
                log.warning(
                        "NumberFormatException when attempting to decode 'timestamp' attribute of HTTP-Request");
            }
        }

        /* If that also failed, we try to decode the simple "version" parameter
         * Since this is always just defined for ONE particular project, we
         * need to grab the stable version closest to the timestamp of
         * the specified version when dealing with other projects.
         */

        verParm = req.getParameter("version");
        if (verParm != null) {
            //Fetching the project associated with that request; if any
            InheritanceProject ip = this.getProjectFromRequest(req);
            if (ip != null) {
                try {
                    Long vNum = Long.valueOf(verParm, 10);
                    if (this == ip) {
                        return vNum;
                    }
                    //Fetching the timestamp of that version
                    Version v = ip.versionStore.getVersion(vNum);
                    if (v != null) {
                        long ts = v.timestamp;
                        //Fetching the best matching version to that ts
                        Version near = this.versionStore.getNearestTo(ts);
                        if (near != null) {
                            return near.id;
                        }
                    }
                } catch (NumberFormatException ex) {
                    log.warning(
                            "NumberFormatException when attempting to decode 'version' attribute of HTTP-Request");
                }
            }
        }
        //If we reach this spot; no version was defined anywhere
        return null;
        //return this.getStableVersion();
    }

    /**
     * This method tries to use the {@link ThreadAssocStore} to fetch the
     * special "versions" field.
     * <p>
     * The assumptions are that:
     * <ol>
     * <li>The {@link InheritanceBuild#run()} method has set this field up.</li>
     * <li>No two builds share the same thread.</li>
     * <li>If a thread does get re-used, that the previous builds do not make
     * calls to Jenkins anymore that need versioning information.
     * </li>
     * </ol>
     * @return
     */
    private Long getUserDesiredVersionFromThread() {
        Object o = ThreadAssocStore.getInstance().getValue("versions");
        if (o != null) {
            if (o instanceof Number) {
                return ((Number) o).longValue();
            }
            if (o instanceof Map) {
                Map<String, Long> map = (Map<String, Long>) o;
                Long ret = map.get(this.getName());
                if (ret != null) {
                    return ret;
                }
            }
        }
        return null;
    }

    /**
     * This method tries to determine the actual version requested by the
     * current call sequence. This may be due to a configuration page request or
     * through a build started from the GUI / CLI.
     * <p>
     * Do note that the returned version is the one defined for <b>this</b>
     * project. If you desire the version of a particular parent project, call
     * the parent's implementation of this method. If you wish to get all
     * the parent's versions, it is faster to call
     * {@link #getUserDesiredParentVersions()}.
     * 
     * @see #getUserDesiredParentVersions()
     * @see #getVersionIDs()
     * 
     * @return the desired version number; or null if no such version can be
     * found and the latest version is to be used.
     */
    public Long getUserDesiredVersion() {
        return this.getUserDesiredVersion(false);
    }

    public Long getUserDesiredVersion(boolean noInsertStableVersion) {
        //TODO: Buffer this result in some way

        //First, we check if a version was passed via an URL parameter
        Long version = this.getUserDesiredVersionFromRequest();

        //If that failed, we try to fetch it from the Thread
        if (version == null) {
            version = this.getUserDesiredVersionFromThread();
        }

        //If that failed, too, we just use the latest stable version
        if (version == null) {
            if (noInsertStableVersion) {
                return null;
            }
            return this.getStableVersion();
        }

        //We check whether the user-passed version is valid at all
        if (this.getVersionIDs().contains(version)) {
            return version;
        }
        //Otherwise, the version does not exist and we return the latest one
        return this.getLatestVersion();
    }

    /**
     * This method returns the versions selected for this project and its
     * parents.
     * 
     * @return
     */
    public Map<String, Long> getAllVersionsFromCurrentState() {
        LinkedList<InheritanceProject> open = new LinkedList<InheritanceProject>();
        Set<String> closed = new HashSet<String>();
        Map<String, Long> out = new HashMap<String, Long>();

        //Adding ourselves as the first node
        open.add(this);

        while (!open.isEmpty()) {
            InheritanceProject ip = open.pop();
            //Fetching the user-requested version for the open node
            Long v = ip.getUserDesiredVersion();
            out.put(ip.getName(), v);
            //Then, adding this node to the closed set
            closed.add(ip.getName());
            //And adding the parent nodes to the open list
            for (AbstractProjectReference apr : ip.getParentReferences()) {
                if (closed.contains(apr.getName())) {
                    continue;
                }
                InheritanceProject next = apr.getProject();
                if (next == null) {
                    continue;
                }
                open.addLast(next);
            }
        }
        return out;
    }

    public Deque<Long> getVersionIDs() {
        Object obj = onSelfChangeBuffer.get(this, "getVersionIDs()");
        if (obj != null && obj instanceof Deque) {
            return (Deque) obj;
        }

        LinkedList<Long> lst = new LinkedList<Long>();
        for (Version v : this.getVersions()) {
            lst.add(v.id);
        }

        onSelfChangeBuffer.set(this, "getVersionIDs()", lst);
        return lst;
    }

    public Deque<Version> getVersions() {
        Object obj = onSelfChangeBuffer.get(this, "getVersions()");
        if (obj != null && obj instanceof Deque) {
            return (Deque) obj;
        }

        LinkedList<Version> lst = new LinkedList<Version>();
        if (this.versionStore == null) {
            return lst;
        }
        lst.addAll(this.versionStore.getAllVersions());

        onSelfChangeBuffer.set(this, "getVersions()", lst);
        return lst;
    }

    public Deque<Version> getStableVersions() {
        Object obj = onSelfChangeBuffer.get(this, "getStableVersions()");
        if (obj != null && obj instanceof Deque) {
            return (Deque) obj;
        }

        LinkedList<Version> lst = new LinkedList<Version>();
        if (this.versionStore == null) {
            return lst;
        }
        for (Version version : this.versionStore.getAllVersions()) {
            if (version.getStability()) {
                lst.add(version);
            }
        }
        onSelfChangeBuffer.set(this, "getStableVersions()", lst);
        return lst;
    }

    public VersionedObjectStore getVersionedObjectStore() {
        return this.versionStore;
    }

    public Long getStableVersion() {
        if (this.versionStore == null) {
            return null;
        }
        Version v = this.versionStore.getLatestStable();
        return (v == null) ? null : v.id;
    }

    public Long getLatestVersion() {
        if (this.versionStore == null) {
            return null;
        }
        Version v = this.versionStore.getLatestVersion();
        return (v == null) ? null : v.id;
    }

    public class InheritedVersionInfo {
        public final InheritanceProject project;
        public final Long version;
        public final List<Long> versions;
        public final String description;

        public InheritedVersionInfo(InheritanceProject project, Long version, List<Long> versions,
                String description) {
            this.project = project;
            this.version = version;
            this.versions = versions;
            this.description = description;
        }

        public List<Long> getVersions() {
            return versions;
        }
    }

    public List<InheritedVersionInfo> getAllInheritedVersionsList() {
        return this.getAllInheritedVersionsList(null);
    }

    public List<InheritedVersionInfo> getAllInheritedVersionsList(InheritanceBuild build) {
        List<InheritedVersionInfo> out = new LinkedList<InheritedVersionInfo>();

        //Adding ourselves as the first entry
        LinkedList<Long> verLst = new LinkedList<Long>();
        for (Version v : this.versionStore.getAllVersions()) {
            verLst.add(v.id);
        }
        if (!verLst.isEmpty()) {
            Long verID = this.getUserDesiredVersion();
            Version verObj = this.versionStore.getVersion(verID);

            out.add(new InheritedVersionInfo(this, verID, verLst,
                    (verObj != null) ? verObj.getDescription() : null));
        }

        Map<String, Long> buildVersions = null;
        if (build != null) {
            buildVersions = build.getProjectVersions();
        }

        //Fetching all parent references in order and adding them
        List<AbstractProjectReference> aprLst = this.getAllParentReferences(SELECTOR.MISC);
        for (AbstractProjectReference apr : aprLst) {
            InheritanceProject ip = apr.getProject();
            if (ip == null) {
                continue;
            }

            verLst = new LinkedList<Long>();
            for (Version v : ip.versionStore.getAllVersions()) {
                verLst.add(v.id);
            }
            if (verLst.isEmpty()) {
                // No versions available for that project; skipping it
                continue;
            }

            //Fetch the version; either from the passed build or URL params
            Long verID = ip.getUserDesiredVersion(true);
            if (verID == null) {
                if (buildVersions != null) {
                    verID = buildVersions.get(ip.getName());
                } else {
                    verID = ip.getUserDesiredVersion();
                }
            }

            if (verID != null) {
                //Fetch the version object associated with the given ID 
                Version verObj = ip.versionStore.getVersion(verID);
                if (verObj == null) {
                    continue;
                }

                out.add(new InheritedVersionInfo(ip, verID, verLst, verObj.getDescription()));
            }
        }
        return out;
    }

    // === INHERITANCE-HELPER METHODS ===

    public List<InheritanceProject> getChildrenProjects() {
        Object obj = onChangeBuffer.get(this, "getChildrenProjects");
        if (obj != null && obj instanceof LinkedList) {
            return (LinkedList) obj;
        }

        LinkedList<InheritanceProject> lst = new LinkedList<InheritanceProject>();

        Map<String, InheritanceProject> map = getProjectsMap();
        for (InheritanceProject p : map.values()) {
            //Checking if that project inherits from us
            for (AbstractProjectReference ref : p.getParentReferences()) {
                if (this.name.equals(ref.getName())) {
                    lst.add(p);
                }
            }
        }

        onChangeBuffer.set(this, "getChildrenProjects", lst);
        return lst;
    }

    public List<InheritanceProject> getParentProjects() {
        LinkedList<InheritanceProject> lst = new LinkedList<InheritanceProject>();

        for (AbstractProjectReference ref : this.getParentReferences()) {
            if (ref == null) {
                continue;
            }
            InheritanceProject ip = ref.getProject();
            if (ip == null) {
                continue;
            }
            lst.add(ip);
        }

        return lst;
    }

    public List<String> getProjectReferenceIssues() {
        LinkedList<String> lst = new LinkedList<String>();

        //Checking direct parents
        for (AbstractProjectReference apr : this.getParentReferences()) {
            InheritanceProject ip = apr.getProject();
            if (ip == null) {
                lst.add("Invalid parent reference to: " + apr.getName());
            }
        }

        //Checking matings
        for (AbstractProjectReference apr : this.compatibleProjects) {
            InheritanceProject ip = apr.getProject();
            if (ip == null) {
                lst.add("Invalid compatibility reference to: " + apr.getName());
            }
        }

        return lst;
    }

    /**
     * This method re-parents a given trigger, to ensure that it belongs to the
     * current project.
     * <p>
     * It does so by looping it through XStream to get a copy and then calling
     * {@link Trigger#start(Item, boolean)} on it; just like if the project was
     * just read from disk.
     * <p>
     * As such, this method should be highly robust, but is of course very much
     * slower than if it had a reliable direct copying method available.
     * 
     * @param trigger
     * @return
     */
    private <T extends Trigger> T getReparentedTrigger(T trigger) {
        //Copy the trigger by looping it through XSTREAM and then calling start()
        //based on the CURRENT project
        //TODO: Find out somehow if "trigger" already belongs to the current project
        try {
            String xml = Jenkins.XSTREAM2.toXML(trigger);
            if (xml == null) {
                return trigger;
            }
            Object copy = Jenkins.XSTREAM2.fromXML(xml);
            if (copy == null || !(copy instanceof Trigger)) {
                return trigger;
            }
            //The copying loop was successful! Calling start() on the trigger
            trigger = (T) copy;
            trigger.start(this, false);
            return trigger;
        } catch (XStreamException ex) {
            //The loop-copy failed; returning the originally retrieved field
            return trigger;
        }
    }

    // === NON-INHERITANCE CONTROLLED PROPERTY SETTING METHODS ===

    /**
     * This method is called by the configuration submission to set a new
     * SCM. This does not need to care about inheritance or versioning, as
     * this function should only be invoked from
     * {@link #doConfigSubmit(StaplerRequest, StaplerResponse)}.
     */
    @Override
    public void setScm(SCM scm) throws IOException {
        super.setScm(scm);
    }

    // === INHERITANCE-AWARE PROPERTY READING METHODS ===

    private InheritanceGovernor<List<AbstractProjectReference>> getParentReferencesGovernor(
            ProjectReference.PrioComparator.SELECTOR sortKey) {
        return new InheritanceGovernor<List<AbstractProjectReference>>("parentReferences", sortKey, this) {

            @Override
            protected List<AbstractProjectReference> castToDestinationType(Object o) {
                return castToList(o);
            }

            @Override
            public List<AbstractProjectReference> getRawField(InheritanceProject ip) {
                return ip.getRawParentReferences();
            }

            @Override
            protected List<AbstractProjectReference> reduceFromFullInheritance(
                    Deque<List<AbstractProjectReference>> list) {
                return InheritanceGovernor.reduceByMergeWithDuplicates(list, AbstractProjectReference.class,
                        this.caller);
            }
        };
    }

    public List<AbstractProjectReference> getParentReferences() {
        return this.getParentReferences(SELECTOR.MISC);
    }

    public List<AbstractProjectReference> getParentReferences(ProjectReference.PrioComparator.SELECTOR sortKey) {
        InheritanceGovernor<List<AbstractProjectReference>> gov = getParentReferencesGovernor(sortKey);
        //We will ALWAYS just return the LOCAL parent references.
        //If you ever do anything else; this WILL cause an infinite loop!
        return gov.retrieveFullyDerivedField(this, IMode.LOCAL_ONLY);
    }

    public List<AbstractProjectReference> getRawParentReferences() {
        return this.parentReferences;
    }

    /**
     * This method returns a list of all parent references.
     * <p>
     * <b><i>DO NOT</i></b> use this method inside any function from
     * {@link InheritanceGovernor} or any method called by it, because that
     * will almost always lead to an infinite recursion.
     * 
     * @param sortKey the key specifying the order in which projects are returned.
     * @return a list of all parent references
     */
    public List<AbstractProjectReference> getAllParentReferences(ProjectReference.PrioComparator.SELECTOR sortKey) {
        InheritanceGovernor<List<AbstractProjectReference>> gov = this.getParentReferencesGovernor(sortKey);
        return gov.retrieveFullyDerivedField(this, IMode.INHERIT_FORCED);
    }

    /**
     * Wrapper for {@link #getAllParentReferences(SELECTOR)}, but will add
     * a reference to this project too, if needed.
     * 
     * @param sortKey the key specifying the order in which projects are returned.
     * @param addSelf if true, add a self-reference in the correct spot
     * @return a list of all parent references, including a self-reference if
     * addSelf is true.
     */
    public List<AbstractProjectReference> getAllParentReferences(ProjectReference.PrioComparator.SELECTOR sortKey,
            boolean addSelf) {
        List<AbstractProjectReference> lst = this.getAllParentReferences(sortKey);

        if (addSelf) {
            boolean hasAddedSelf = false;
            ListIterator<AbstractProjectReference> iter = lst.listIterator();
            while (iter.hasNext()) {
                AbstractProjectReference ref = iter.next();
                int prio;
                if (ref instanceof ProjectReference) {
                    prio = PrioComparator.getPriorityFor(ref, sortKey);
                } else {
                    //An anonymous ref is always at priority 0
                    prio = 0;
                }
                if (!hasAddedSelf && prio > 0) {
                    hasAddedSelf = true;
                    iter.add(new SimpleProjectReference(this.getFullName()));
                }
            }
            //Check if we were able to add a self-reference at all
            if (!hasAddedSelf) {
                lst.add(new SimpleProjectReference(this.getFullName()));
            }
        }

        return lst;
    }

    public List<AbstractProjectReference> getCompatibleProjects() {
        return this.getCompatibleProjects(SELECTOR.MISC);
    }

    public List<AbstractProjectReference> getCompatibleProjects(ProjectReference.PrioComparator.SELECTOR sortKey) {
        InheritanceGovernor<List<AbstractProjectReference>> gov = new InheritanceGovernor<List<AbstractProjectReference>>(
                "compatibleProjects", sortKey, this) {
            @Override
            protected List<AbstractProjectReference> castToDestinationType(Object o) {
                return castToList(o);
            }

            @Override
            public List<AbstractProjectReference> getRawField(InheritanceProject ip) {
                return ip.getRawCompatibleProjects();
            }
        };
        //No sense in returning anything but local compatibles
        List<AbstractProjectReference> refs = gov.retrieveFullyDerivedField(this, IMode.LOCAL_ONLY);
        if (refs == null) {
            return new LinkedList<AbstractProjectReference>();
        }
        return refs;
    }

    public List<AbstractProjectReference> getRawCompatibleProjects() {
        return this.compatibleProjects;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized List<Action> getActions() {
        return this.getActions(IMode.AUTO);
    }

    public synchronized List<Action> getActions(IMode mode) {
        InheritanceGovernor<List<Action>> gov = new InheritanceGovernor<List<Action>>("actions", SELECTOR.MISC,
                this) {
            @Override
            protected List<Action> castToDestinationType(Object o) {
                return castToList(o);
            }

            @Override
            public List<Action> getRawField(InheritanceProject ip) {
                return ip.getRawActions();
            }

            @Override
            protected List<Action> reduceFromFullInheritance(Deque<List<Action>> list) {
                return InheritanceGovernor.reduceByMerge(list, Action.class, this.caller);
            }
        };
        //No sense in returning anything but local compatibles
        List<Action> nonTransients = gov.retrieveFullyDerivedField(this, IMode.LOCAL_ONLY);

        //TODO: Buffer the creation of transient actions somehow

        /* The above call will only return the non-transient actions. The actual
         * transient actions have to the spliced in now
         * 
         * This can lead to a stack overflow (see the annotation in the comments
         * for createTransientActions()), so we use the thread-store to register
         * that the current thread is trying to create transient actions.
         */
        List<Action> transients;
        ThreadAssocStore tas = ThreadAssocStore.getInstance();
        String key = String.format("project-%s-creates-transients", this.getFullName());
        Object o = tas.getValue(key);
        if (o == null) {
            try {
                //We're not fetching transients; so we fetch them
                tas.setValue(key, this);
                transients = this.createVersionAwareTransientActions();
            } finally {
                tas.clear(key);
            }
        } else {
            //We are already fetching transients and have entered a recursion
            transients = Collections.emptyList();
        }

        List<Action> merge = new LinkedList<Action>();
        merge.addAll(nonTransients);
        merge.addAll(transients);

        // return the read only list to cause a failure on plugins who try to add an action here
        return Collections.unmodifiableList(merge);
    }

    public synchronized List<Action> getRawActions() {
        /* Do notice that the function below will not actually return all
         * actions; as the override of createTransientActions() causes the
         * super's transientActions field to be always empty to ensure that
         * they are not accidentally saved in the version store.
         */
        return super.getActions();
    }

    /**
     * Overrides the super-function to always return an empty list. This is
     * vitally important so that the super class' transientActions member is
     * always kept empty.
     * <p>
     * Otherwise, you get the nasty problems that temporary actions contaminate
     * the versioning archive and generally cause troubles during build.
     * <p>
     * The downside with generating this on-the-fly is, that some plugins
     * themselves call {@link #getActions()} (maybe indirectly), which recurses
     * back into calling {@link #createVersionAwareTransientActions()};
     * <p>
     * This will cause a stack overflow. The only way to fix this is to
     * return an empty list if a recursion is detected.
     * 
     * @see #getActions()
     * @see #getActions(IMode)
     */
    @Override
    protected List<Action> createTransientActions() {
        return new LinkedList<Action>();
    }

    /**
     * Creates a list of temporary {@link Action}s as they are contributed
     * by the various Builders, Publishers, etc. from the correct version and
     * with the the correct inheritance.
     */
    protected List<Action> createVersionAwareTransientActions() {
        Vector<Action> ta = new Vector<Action>();

        // START Implementation from AbstractProject
        for (JobProperty<? super InheritanceProject> p : this.getAllProperties()) {
            ta.addAll(p.getJobActions(this));
        }

        for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all()) {
            ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
        }
        // END Implementation from AbstractProject

        // START Implementation from Project
        for (BuildStep step : this.getBuildersList())
            ta.addAll(step.getProjectActions(this));
        for (BuildStep step : this.getPublishersList())
            ta.addAll(step.getProjectActions(this));
        for (BuildWrapper step : this.getBuildWrappersList())
            ta.addAll(step.getProjectActions(this));

        //FIXME: Triggers are not yet versioned! Correct this!
        for (Trigger trigger : this.getTriggers().values())
            ta.addAll(trigger.getProjectActions());
        // END Implementation from Project

        return ta;
    }

    public DescribableList<Builder, Descriptor<Builder>> getBuildersListForVersion(Long versionId) {
        return (DescribableList<Builder, Descriptor<Builder>>) this.versionStore.getObject(versionId,
                "buildersList");
    }

    @Override
    public DescribableList<Builder, Descriptor<Builder>> getBuildersList() {
        return this.getBuildersList(IMode.AUTO);
    }

    public DescribableList<Builder, Descriptor<Builder>> getBuildersList(IMode mode) {
        InheritanceGovernor<DescribableList<Builder, Descriptor<Builder>>> gov = new InheritanceGovernor<DescribableList<Builder, Descriptor<Builder>>>(
                "buildersList", SELECTOR.BUILDER, this) {
            @Override
            protected DescribableList<Builder, Descriptor<Builder>> castToDestinationType(Object o) {
                return castToDescribableList(o);
            }

            @Override
            public DescribableList<Builder, Descriptor<Builder>> getRawField(InheritanceProject ip) {
                return ip.getRawBuildersList();
            }

            @Override
            protected DescribableList<Builder, Descriptor<Builder>> reduceFromFullInheritance(
                    Deque<DescribableList<Builder, Descriptor<Builder>>> list) {
                return InheritanceGovernor.reduceDescribableByMerge(list);
            }
        };

        return gov.retrieveFullyDerivedField(this, mode);
    }

    public DescribableList<Builder, Descriptor<Builder>> getRawBuildersList() {
        return super.getBuildersList();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DescribableList<BuildWrapper, Descriptor<BuildWrapper>> getBuildWrappersList() {
        return this.getBuildWrappersList(IMode.AUTO);
    }

    public DescribableList<BuildWrapper, Descriptor<BuildWrapper>> getBuildWrappersList(IMode mode) {
        InheritanceGovernor<DescribableList<BuildWrapper, Descriptor<BuildWrapper>>> gov = new InheritanceGovernor<DescribableList<BuildWrapper, Descriptor<BuildWrapper>>>(
                "buildWrappersList", SELECTOR.BUILD_WRAPPER, this) {
            @Override
            protected DescribableList<BuildWrapper, Descriptor<BuildWrapper>> castToDestinationType(Object o) {
                return castToDescribableList(o);
            }

            @Override
            public DescribableList<BuildWrapper, Descriptor<BuildWrapper>> getRawField(InheritanceProject ip) {
                return ip.getRawBuildWrappersList();
            }

            @Override
            protected DescribableList<BuildWrapper, Descriptor<BuildWrapper>> reduceFromFullInheritance(
                    Deque<DescribableList<BuildWrapper, Descriptor<BuildWrapper>>> list) {
                return InheritanceGovernor.reduceDescribableByMerge(list);
            }
        };

        return gov.retrieveFullyDerivedField(this, mode);
    }

    public DescribableList<BuildWrapper, Descriptor<BuildWrapper>> getRawBuildWrappersList() {
        return super.getBuildWrappersList();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized DescribableList<Publisher, Descriptor<Publisher>> getPublishersList() {
        //TODO: The marking of these functions as synchronized can easily lead
        // to a deadlock: doConfigSubmit() -> buildDependencyGraph() -> getPublishersList()
        return this.getPublishersList(IMode.AUTO);
    }

    public synchronized DescribableList<Publisher, Descriptor<Publisher>> getPublishersList(IMode mode) {
        InheritanceGovernor<DescribableList<Publisher, Descriptor<Publisher>>> gov = new InheritanceGovernor<DescribableList<Publisher, Descriptor<Publisher>>>(
                "publishersList", SELECTOR.PUBLISHER, this) {
            @Override
            protected DescribableList<Publisher, Descriptor<Publisher>> castToDestinationType(Object o) {
                return castToDescribableList(o);
            }

            @Override
            public DescribableList<Publisher, Descriptor<Publisher>> getRawField(InheritanceProject ip) {
                return ip.getRawPublishersList();
            }

            @Override
            protected DescribableList<Publisher, Descriptor<Publisher>> reduceFromFullInheritance(
                    Deque<DescribableList<Publisher, Descriptor<Publisher>>> list) {
                return InheritanceGovernor.reduceDescribableByMerge(list);
            }
        };

        return gov.retrieveFullyDerivedField(this, mode);
    }

    public synchronized DescribableList<Publisher, Descriptor<Publisher>> getRawPublishersList() {
        return super.getPublishersList();
    }

    /**
     * Returns all triggers defined on this project; or if detected to be
     * necessary, also all parents.
     * <p>
     * @return a map of triggers, might be empty, but never null
     */
    public synchronized Map<TriggerDescriptor, Trigger> getTriggers() {
        return this.getTriggers(IMode.AUTO);
    }

    public synchronized Map<TriggerDescriptor, Trigger> getTriggers(IMode mode) {
        if (ProjectCreationEngine.instance.getTriggersAreInherited() != TriggerInheritance.INHERIT) {
            return this.getRawTriggers();
        }

        InheritanceGovernor<Collection<Trigger>> gov = new InheritanceGovernor<Collection<Trigger>>("triggers",
                SELECTOR.MISC, this) {
            @Override
            protected Collection<Trigger> castToDestinationType(Object o) {
                try {
                    return (Collection<Trigger>) o;
                } catch (ClassCastException e) {
                    return null;
                }
            }

            @Override
            public Collection<Trigger> getRawField(InheritanceProject ip) {
                Map<TriggerDescriptor, Trigger> raw = ip.getRawTriggers();
                return raw.values();
            }

            @Override
            protected Collection<Trigger> reduceFromFullInheritance(Deque<Collection<Trigger>> list) {
                Collection<Trigger> out = new LinkedList<Trigger>();
                for (Collection<Trigger> sub : list) {
                    out.addAll(sub);
                }
                return out;
            }
        };

        Collection<Trigger> triggers = gov.retrieveFullyDerivedField(this, mode);
        Map<TriggerDescriptor, Trigger> out = new HashMap<TriggerDescriptor, Trigger>();
        for (Trigger t : triggers) {
            Trigger copied = this.getReparentedTrigger(t);
            out.put(copied.getDescriptor(), copied);
        }
        return out;
    }

    public synchronized Map<TriggerDescriptor, Trigger> getRawTriggers() {
        return super.getTriggers();
    }

    /**
     * Gets the specific trigger, or null if the property is not configured for this job.
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        return this.getTrigger(clazz, IMode.AUTO);
    }

    public <T extends Trigger> T getTrigger(Class<T> clazz, IMode mode) {
        if (ProjectCreationEngine.instance.getTriggersAreInherited() != TriggerInheritance.INHERIT) {
            return this.getRawTrigger(clazz);
        }

        final Class<T> fClazz = clazz;
        InheritanceGovernor<T> gov = new InheritanceGovernor<T>("triggers", SELECTOR.MISC, this) {
            @Override
            protected T castToDestinationType(Object o) {
                try {
                    return (T) o;
                } catch (ClassCastException e) {
                    return null;
                }
            }

            @Override
            public T getRawField(InheritanceProject ip) {
                return ip.getRawTrigger(fClazz);
            }

            /*
            @Override
            protected T reduceFromFullInheritance(Deque<T> list) {
               //Just select the last trigger; it will be of the correct class
               return list.getLast();
            }
            */
        };

        //Return a trigger that is guaranteed to be owned by the current project
        T trigger = gov.retrieveFullyDerivedField(this, mode);
        return getReparentedTrigger(trigger);
    }

    public <T extends Trigger> T getRawTrigger(Class<T> clazz) {
        return super.getTrigger(clazz);
    }

    public Map<JobPropertyDescriptor, JobProperty<? super InheritanceProject>> getProperties() {
        return this.getProperties(IMode.AUTO);
    }

    public Map<JobPropertyDescriptor, JobProperty<? super InheritanceProject>> getProperties(IMode mode) {
        List<JobProperty<? super InheritanceProject>> lst = this.getAllProperties(mode);
        if (lst == null || lst.isEmpty()) {
            return Collections.emptyMap();
        }

        HashMap<JobPropertyDescriptor, JobProperty<? super InheritanceProject>> map = new HashMap<JobPropertyDescriptor, JobProperty<? super InheritanceProject>>();

        for (JobProperty<? super InheritanceProject> prop : lst) {
            map.put(prop.getDescriptor(), prop);
        }

        return map;
    }

    /**
     * {@inheritDoc}
     */
    @Exported(name = "property", inline = true)
    public List<JobProperty<? super InheritanceProject>> getAllProperties() {
        return this.getAllProperties(IMode.AUTO);
    }

    public List<JobProperty<? super InheritanceProject>> getAllProperties(IMode mode) {
        //Fetching the variance of the current project; it is necessary
        //to access the correct compatibility setting in the correct parent
        final InheritanceProject rootProject = this;

        InheritanceGovernor<List<JobProperty<? super InheritanceProject>>> gov = new InheritanceGovernor<List<JobProperty<? super InheritanceProject>>>(
                "properties", SELECTOR.PARAMETER, this) {
            @Override
            protected List<JobProperty<? super InheritanceProject>> castToDestinationType(Object o) {
                return castToList(o);
            }

            @Override
            public List<JobProperty<? super InheritanceProject>> getRawField(InheritanceProject ip) {
                return ip.getRawAllProperties();
            }

            @Override
            protected List<JobProperty<? super InheritanceProject>> reduceFromFullInheritance(
                    Deque<List<JobProperty<? super InheritanceProject>>> list) {
                //First, we add the variances for the root project
                InheritanceParametersDefinitionProperty variance = rootProject.getVarianceParameters();
                if (variance != null) {
                    List<JobProperty<? super InheritanceProject>> varLst = new LinkedList<JobProperty<? super InheritanceProject>>();
                    varLst.add(variance);
                    list.addLast(varLst);
                }
                return InheritanceGovernor.reduceByMerge(list, JobProperty.class, this.caller);
            }
        };

        return gov.retrieveFullyDerivedField(this, mode);
    }

    /**
     * This method will fetch all properties defined for the current project
     * and only those defined on it.
     * <p>
     * There are two complications though:
     * <ol>
     * <li>
     *       {@link ParametersDefinitionProperty} instances need to be replaced
     *       with {@link InheritanceParametersDefinitionProperty} instances,
     *       to make sure that versioning details are correctly stored.
     *       Also, we wish to make sure that the more advanced Jelly-files from
     *       the latter class are used for build-purposes.
     * </li>
     * <li>
     *       Variances define additional properties; so that we can make sure
     *       to splice-in those additional properties if a request for
     *       parameters comes from a direct child with the correct variance.
     * </li>
     * </ol>
     * 
     * @param rootProject the project from which the call for inherited
     * properties originally came from.
     * @param rootParents the parents of that project. Passed to prevent having
     * to repeatedly access (versioning overhead!).
     * @return
     */
    public List<JobProperty<? super InheritanceProject>> getRawAllProperties() {
        LinkedList<JobProperty<? super InheritanceProject>> out = new LinkedList<JobProperty<? super InheritanceProject>>();

        //First, we add the global properties defined for this project
        List<JobProperty<? super InheritanceProject>> origProps = super.getAllProperties();

        //Filling the output list with the adjusted original properties
        for (JobProperty<? super InheritanceProject> prop : origProps) {
            if (!(prop instanceof ParametersDefinitionProperty)) {
                out.add(prop);
                continue;
            }
            //Converting a PDP to an IPDP
            ParametersDefinitionProperty pdp = (ParametersDefinitionProperty) prop;
            InheritanceParametersDefinitionProperty ipdp = new InheritanceParametersDefinitionProperty(
                    pdp.getOwner(), pdp);
            out.add(ipdp);
        }
        return out;
    }

    public InheritanceParametersDefinitionProperty getVarianceParameters() {
        if (this.isTransient == false) {
            //No variance is or can possibly be defined
            return null;
        }

        //Fetch parents of this project; if any
        List<InheritanceProject> parLst = this.getParentProjects();
        if (parLst == null || parLst.size() < 2) {
            return null;
        }

        //Now, determine which parent carries our definition
        for (InheritanceProject ip : parLst) {
            if (ip == null) {
                continue;
            }

            //A project carrying a variance MUST be the prefix of our name
            if (this.name.startsWith(ip.name) == false) {
                continue;
            }

            List<AbstractProjectReference> compatLst = ip.getCompatibleProjects();
            if (compatLst == null) {
                continue;
            }

            for (AbstractProjectReference apr : compatLst) {
                if (!(apr instanceof ParameterizedProjectReference)) {
                    continue;
                }
                ParameterizedProjectReference ppr = (ParameterizedProjectReference) apr;
                String projVar = ppr.getVariance();

                //Checking if the variance do not match up
                if (this.variance == null) {
                    if (projVar != null) {
                        continue;
                    }
                } else {
                    if (projVar == null || !this.variance.equals(ppr.getVariance())) {
                        continue;
                    }
                }

                //Now, generating the full name and comparing
                String compatName = ProjectCreationEngine.generateNameFor(ppr.getVariance(), ip.name,
                        ppr.getName());
                if (!this.name.equals(compatName)) {
                    continue;
                }

                //The correct variance description was found; adding its parameters
                InheritanceParametersDefinitionProperty ipdp = new InheritanceParametersDefinitionProperty(this,
                        ppr.getParameters());
                return ipdp;
            }
        }

        //No variance found
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public <T extends JobProperty> T getProperty(Class<T> clazz) {
        return this.getProperty(clazz, IMode.AUTO);
    }

    public <T extends JobProperty> T getProperty(Class<T> clazz, IMode mode) {
        /* Note: getAllProperties returns a list of properties in order of
         * inheritance. Therefore, properties might be defined twice. In these
         * cases, we need to return the last property.
         */
        List<JobProperty<? super InheritanceProject>> props = this.getAllProperties(mode);

        //Checking if we can reverse-iterate the list for more efficiency
        if (props instanceof Deque) {
            Iterator<JobProperty<? super InheritanceProject>> rIter = ((Deque) props).descendingIterator();
            while (rIter.hasNext()) {
                JobProperty p = rIter.next();
                if (clazz.isInstance(p))
                    return clazz.cast(p);
            }
        } else {
            for (JobProperty p : props) {
                if (clazz.isInstance(p))
                    return clazz.cast(p);
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public JobProperty getProperty(String className) {
        return this.getProperty(className, IMode.AUTO);
    }

    public JobProperty getProperty(String className, IMode mode) {
        for (JobProperty p : this.getAllProperties(mode)) {
            if (p.getClass().getName().equals(className)) {
                return p;
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<?> getOverrides() {
        return this.getOverrides(IMode.AUTO);
    }

    public Collection<?> getOverrides(IMode mode) {
        List<Object> r = new ArrayList<Object>();
        for (JobProperty<? super InheritanceProject> p : this.getAllProperties(mode)) {
            r.addAll(p.getJobOverrides());
        }
        return r;
    }

    /**
     * This needs to be overridden, because {@link AbstractProject} reads the
     * properties field directly; which circumvents inheritance.
     */
    @Override
    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<?> p : this.getAllProperties()) {
            r.addAll(p.getSubTasks());
        }
        return r;
    }

    public synchronized List<ParameterDefinition> getParameters() {
        return this.getParameters(IMode.AUTO);
    }

    public synchronized List<ParameterDefinition> getParameters(IMode mode) {
        ParametersDefinitionProperty pdp = this.getProperty(ParametersDefinitionProperty.class, mode);
        if (pdp == null) {
            return new LinkedList<ParameterDefinition>();
        }
        return pdp.getParameterDefinitions();
    }

    @Override
    public SCM getScm() {
        return getScm(IMode.AUTO);
    }

    public SCM getScm(IMode mode) {
        InheritanceGovernor<SCM> gov = new InheritanceGovernor<SCM>("scm", SELECTOR.MISC, this) {
            @Override
            protected SCM castToDestinationType(Object o) {
                return (o instanceof SCM) ? (SCM) o : null;
            }

            @Override
            public SCM getRawField(InheritanceProject ip) {
                return ip.getRawScm();
            }

            @Override
            protected SCM reduceFromFullInheritance(Deque<SCM> list) {
                if (list == null || list.isEmpty()) {
                    return new NullSCM();
                }
                //Return the SCM that was defined last and is not a NullSCM
                Iterator<SCM> iter = list.descendingIterator();
                while (iter.hasNext()) {
                    SCM scm = iter.next();
                    if (scm != null && !(scm instanceof NullSCM)) {
                        return scm;
                    }
                }
                //All SCMs are NullSCMs; so it does not matter which one to return
                return list.peekLast();
            }
        };

        SCM scm = gov.retrieveFullyDerivedField(this, mode);

        //We may not return null directly
        return (scm == null) ? new NullSCM() : scm;
    }

    public SCM getRawScm() {
        return super.getScm();
    }

    @Override
    public int getQuietPeriod() {
        Integer i = this.getQuietPeriodObject();
        return (i != null) ? i : super.getQuietPeriod();
    }

    public Integer getQuietPeriodObject() {
        InheritanceGovernor<Integer> gov = new InheritanceGovernor<Integer>("quietPeriod", SELECTOR.MISC, this) {
            @Override
            protected Integer castToDestinationType(Object o) {
                return (o instanceof Integer) ? (Integer) o : null;
            }

            @Override
            public Integer getRawField(InheritanceProject ip) {
                return ip.getRawQuietPeriod();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public Integer getRawQuietPeriod() {
        if (super.getHasCustomQuietPeriod()) {
            return super.getQuietPeriod();
        } else {
            return null;
        }
    }

    @Override
    public boolean getHasCustomQuietPeriod() {
        Integer i = this.getQuietPeriodObject();
        return i != null;
    }

    @Override
    public int getScmCheckoutRetryCount() {
        Integer i = this.getScmCheckoutRetryCountObject();
        return (i != null) ? i : super.getScmCheckoutRetryCount();
    }

    public Integer getScmCheckoutRetryCountObject() {
        InheritanceGovernor<Integer> gov = new InheritanceGovernor<Integer>("scmCheckoutRetryCount", SELECTOR.MISC,
                this) {
            @Override
            protected Integer castToDestinationType(Object o) {
                return (o instanceof Integer) ? (Integer) o : null;
            }

            @Override
            public Integer getRawField(InheritanceProject ip) {
                return ip.getRawScmCheckoutRetryCount();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public Integer getRawScmCheckoutRetryCount() {
        if (super.hasCustomScmCheckoutRetryCount()) {
            return super.getScmCheckoutRetryCount();
        } else {
            return null;
        }
    }

    @Override
    public boolean hasCustomScmCheckoutRetryCount() {
        return this.getScmCheckoutRetryCountObject() != null;
    }

    public SCMCheckoutStrategy getScmCheckoutStrategy() {
        InheritanceGovernor<SCMCheckoutStrategy> gov = new InheritanceGovernor<SCMCheckoutStrategy>(
                "scmCheckoutStrategy", SELECTOR.MISC, this) {
            @Override
            protected SCMCheckoutStrategy castToDestinationType(Object o) {
                return (o instanceof SCMCheckoutStrategy) ? (SCMCheckoutStrategy) o : null;
            }

            @Override
            public SCMCheckoutStrategy getRawField(InheritanceProject ip) {
                return ip.getRawScmCheckoutStrategy();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public SCMCheckoutStrategy getRawScmCheckoutStrategy() {
        return super.getScmCheckoutStrategy();
    }

    @Override
    public boolean blockBuildWhenDownstreamBuilding() {
        InheritanceGovernor<Boolean> gov = new InheritanceGovernor<Boolean>("blockBuildWhenDownstreamBuilding",
                SELECTOR.MISC, this) {
            @Override
            protected Boolean castToDestinationType(Object o) {
                return (o instanceof Boolean) ? (Boolean) o : null;
            }

            @Override
            public Boolean getRawField(InheritanceProject ip) {
                return ip.getRawBlockBuildWhenDownstreamBuilding();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public boolean getRawBlockBuildWhenDownstreamBuilding() {
        return super.blockBuildWhenDownstreamBuilding();
    }

    @Override
    public boolean blockBuildWhenUpstreamBuilding() {
        InheritanceGovernor<Boolean> gov = new InheritanceGovernor<Boolean>("blockBuildWhenUpstreamBuilding",
                SELECTOR.MISC, this) {
            @Override
            protected Boolean castToDestinationType(Object o) {
                return (o instanceof Boolean) ? (Boolean) o : null;
            }

            @Override
            public Boolean getRawField(InheritanceProject ip) {
                return ip.getRawBlockBuildWhenUpstreamBuilding();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public boolean getRawBlockBuildWhenUpstreamBuilding() {
        return super.blockBuildWhenUpstreamBuilding();
    }

    @Override
    public String getCustomWorkspace() {
        InheritanceGovernor<String> gov = new InheritanceGovernor<String>("customWorkspace", SELECTOR.MISC, this) {
            @Override
            protected String castToDestinationType(Object o) {
                return (o instanceof String) ? (String) o : null;
            }

            @Override
            public String getRawField(InheritanceProject ip) {
                return ip.getRawCustomWorkspace();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public String getRawCustomWorkspace() {
        return super.getCustomWorkspace();
    }

    public String getParameterizedWorkspace() {
        InheritanceGovernor<String> gov = new InheritanceGovernor<String>("parameterizedWorkspace", SELECTOR.MISC,
                this) {
            @Override
            protected String castToDestinationType(Object o) {
                return (o instanceof String) ? (String) o : null;
            }

            @Override
            public String getRawField(InheritanceProject ip) {
                return ip.getRawParameterizedWorkspace();
            }
        };

        return gov.retrieveFullyDerivedField(this, IMode.AUTO);
    }

    public String getRawParameterizedWorkspace() {
        return this.parameterizedWorkspace;
    }

    /**
     * Sets the parameterized workspace variable. Will only work, if called
     * <b>directly</b> by a "TestCase" class. Will throw an exception otherwise.
     * 
     * @deprecated Must only be used from within {@link TestCase} classes.
     *
     * @param workspace the new value for the parameterized workspace
     */
    @Deprecated
    public void setRawParameterizedWorkspace(String workspace) {
        if (Reflection.calledFromClass(2, TestCase.class)) {
            this.parameterizedWorkspace = workspace;
        } else {
            throw new IllegalAccessError("Should not be called outside by something other than a TestCase class");
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @deprecated as of 1.503
     *     Use {@link #getBuildDiscarder()}.
     */
    @Override
    public LogRotator getLogRotator() {
        BuildDiscarder d = this.getBuildDiscarder();
        if (d instanceof LogRotator) {
            return (LogRotator) d;
        }
        return null;
    }

    /**
     * @deprecated as of 1.503
     *     Use {@link #getBuildDiscarder()}.
     *  
     * @see #getLogRotator()
     */
    public LogRotator getLogRotator(IMode mode) {
        BuildDiscarder d = this.getBuildDiscarder(mode);
        if (d instanceof LogRotator) {
            return (LogRotator) d;
        }
        return null;
    }

    @Override
    public BuildDiscarder getBuildDiscarder() {
        return this.getBuildDiscarder(IMode.AUTO);
    }

    public BuildDiscarder getBuildDiscarder(IMode mode) {
        InheritanceGovernor<BuildDiscarder> gov = new InheritanceGovernor<BuildDiscarder>("logRotator",
                SELECTOR.MISC, this) {
            @Override
            protected BuildDiscarder castToDestinationType(Object o) {
                if (o instanceof BuildDiscarder) {
                    return (BuildDiscarder) o;
                } else if (o instanceof LogRotator) {
                    // Old-style log-rotator object; LR is a subtype of BD
                    return (LogRotator) o;
                }
                return null;
            }

            @Override
            public BuildDiscarder getRawField(InheritanceProject ip) {
                return ip.getRawBuildDiscarder();
            }
        };

        return gov.retrieveFullyDerivedField(this, mode);
    }

    public BuildDiscarder getRawBuildDiscarder() {
        return super.getBuildDiscarder();
    }

    /**
     * {@inheritDoc}
     * <p>
     * Contrary to all the other properties methods, this will ALWAYS return
     * the fully inherited version and will cache the result.
     * <br/>
     * This is done, because the only time when no inheritance is needed, is
     * when the project is configured, and this will call
     * {@link #getAssignedLabel(IMode)} instead with the {@link IMode#LOCAL_ONLY}
     * set.
     * <p>
     * The reason for the caching is, that this method is called quite often
     * by {@link Queue#maintain()}, a function that potentially blocks the
     * entire server from progressing with builds.
     * <br/>
     * Thus, this method must take the minimum possible amount of time, which
     * means that reflection is too expensive.
     * <p>
     * This has the downside of this method ignoring versioning completely,
     * which might affect the result of this call through changing the
     * inheritance.
     * This is an accepted break, compared to the potential slowdown of
     * {@link Queue#maintain()} under high queue load situations.
     */
    public Label getAssignedLabel() {
        //Check if there's a cached value
        Object cached = onChangeBuffer.get(this, "maintenanceAssignedLabel");
        if (cached != null && cached instanceof Label) {
            Label lbl = (Label) cached;
            /* Use the Jenkins cache to get an up-to-date version of that label
             * Jenkins will automatically flush cached labels when they change
             * 
             * See getAssignedLabel(IMode) to see why the "" quoting
             * is needed.
             */
            return Jenkins.getInstance().getLabel(String.format("\"%s\"", lbl.getName()));
        }

        //Generate a new label, forcing inheritance
        Label lbl = this.getAssignedLabel(IMode.INHERIT_FORCED);
        if (lbl == null) {
            lbl = super.getAssignedLabel();
        }

        //Caching the result
        if (lbl != null) {
            onChangeBuffer.set(this, "maintenanceAssignedLabel", lbl);
        }
        //The returned label is guaranteed to be fresh
        return lbl;
    }

    public Label getAssignedLabel(IMode mode) {
        InheritanceGovernor<Label> gov = new InheritanceGovernor<Label>("assignedLabel", SELECTOR.MISC, this) {
            @Override
            protected Label castToDestinationType(Object o) {
                if (o instanceof Label) {
                    return (Label) o;
                }
                return null;
            }

            @Override
            public Label getRawField(InheritanceProject ip) {
                return ip.getRawAssignedLabel();
            }

            @Override
            protected Label reduceFromFullInheritance(Deque<Label> list) {
                //We simply join the labels via the AND operator
                Label out = null;
                if (list == null || list.isEmpty()) {
                    return out;
                }
                for (Label l : list) {
                    if (l == null) {
                        continue;
                    }
                    out = (out == null) ? l : out.and(l);
                }
                return out;
            }
        };

        //Generate the label on this node and the optional "magic" label restriction
        Label lbl = gov.retrieveFullyDerivedField(this, mode);
        Label magic = ProjectCreationEngine.instance.getMagicNodeLabelForTestingValue();

        //Check if the magic label needs to be applied (only when building)
        if (magic != null && !magic.isEmpty() && InheritanceGovernor.inheritanceLookupRequired(this)) {
            if (lbl != null) {
                String labelExpr = lbl.getName();
                String magicExpr = magic.getName();
                if (!labelExpr.contains(magicExpr)) {
                    //We need to add the magic to the label
                    lbl = lbl.and(magic.not());
                }
            } else {
                //No label present, just use magic value as-is
                lbl = magic.not();
            }
        }

        if (lbl == null) {
            return null;
        }

        /* The labels stored in versioning are essentially cached; which means
         * that their "applicable nodes" list is out-of-date.
         * 
         * As such, we will use Jenkins' caching mechanism to update the labels,
         * as it will "know" when to refresh labels and when not.
         * Unfortunately, Jenkins is braindead and "unquotes" the strings
         * aggressively, by just stripping out the outermost and innermost
         * quote sign; EVEN if the quotes do not belong to each other.
         * 
         * E.g.:
         *       "os:linux"&&"role:foobar"
         * will be turned into:
         *       os:linux"&&"role:foobar
         * 
         * We "solve" this by adding a pointless quote around the label's
         * string representation
         */
        return Jenkins.getInstance().getLabel(String.format("\"%s\"", lbl.getName()));
    }

    public Label getRawAssignedLabel() {
        if (this.isTransient) {
            //Transient projects do not have a label, they merely inherit
            return null;
        }
        return super.getAssignedLabel();
    }

    @Override
    public String getAssignedLabelString() {
        if (InheritanceGovernor.inheritanceLookupRequired(this) == false) {
            return super.getAssignedLabelString();
        }
        Label lbl = this.getAssignedLabel();
        if (lbl == null) {
            return super.getAssignedLabelString();
        }
        return lbl.getExpression();
    }

    /**
     * {@inheritDoc}
     */
    @Exported
    @Override
    public boolean isConcurrentBuild() {
        //Check if we're called from a configure page; if so, do not inherit
        //In all other cases, do full inheritance
        StaplerRequest req = Stapler.getCurrentRequest();
        if (req != null && req.getRequestURI().endsWith("/configure")) {
            return this.isConcurrentBuildFast(false);
        }
        return this.isConcurrentBuildFast(true);
    }

    /**
     * This method behaves similar to {@link #isConcurrentBuild(IMode)}, but
     * will not even bother with versioning and skip reflection at all, if no
     * inheritance is needed.
     * 
     * @return
     */
    public boolean isConcurrentBuildFast(boolean inherit) {
        if (!inherit) {
            return super.isConcurrentBuild();
        }
        boolean isConc = super.isConcurrentBuild();
        if (isConc) {
            return true;
        }
        //Otherwise, check the parents' current config
        for (AbstractProjectReference apr : this.getParentReferences()) {
            if (apr == null || apr.getProject() == null) {
                continue;
            }
            if (apr.getProject().isConcurrentBuildFast(inherit)) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method learns the actual value of concurrency, but is too slow
     * to be executed thousands of times per second, as the Jenkins default
     * scheduler often does.
     * <p>
     * For faster, non-reflected access, use {@link #isConcurrentBuildFast(boolean)},
     * if you can live without versioning.
     * 
     * @param mode
     * @return
     */
    public boolean isConcurrentBuild(IMode mode) {
        InheritanceGovernor<Boolean> gov = new InheritanceGovernor<Boolean>("concurrentBuild", SELECTOR.MISC,
                this) {
            @Override
            protected Boolean castToDestinationType(Object o) {
                if (o instanceof Boolean) {
                    return (Boolean) o;
                }
                return null;
            }

            @Override
            public Boolean getRawField(InheritanceProject ip) {
                return ip.isRawConcurrentBuild();
            }
        };

        Boolean b = gov.retrieveFullyDerivedField(this, mode);
        return (b != null) ? b : false;
    }

    public boolean isRawConcurrentBuild() {
        return super.isConcurrentBuild();
    }

    /**
     * In Vanilla-Jenkins, this method is really just a glorious wrapper around
     * the following call:
     * <p>
     * <code>return getProperty(ParametersDefinitionProperty.class) != null;</code>
     * <p>
     * That means, its entire inheritance-based implementation would already
     * be covered by {@link #getProperty(Class)}. But unfortunately, this is
     * not something we can rely on, as this function &mdash; even though it's
     * just a wrapper &mdash; fulfills a very specific role:
     * <p>
     * When Jenkins creates the sidepanel of a job, it queries this function
     * to determine, whether the "Build Now" button should trigger a POST (if
     * not parameterized) or a GET (if parameters need to be queried). Thus,
     * this function <b>must always</b> return true, if <i>any</i> parent
     * project is parameterized.
     * <p>
     * As such, contrary to the other functions, this function must
     * <i>always</i> explore full inheritance. 
     */
    @Override
    public boolean isParameterized() {
        ParametersDefinitionProperty pdp = this.getProperty(ParametersDefinitionProperty.class,
                IMode.INHERIT_FORCED);
        return pdp != null;
    }

    public boolean isRawParameterized() {
        return super.isParameterized();
    }

    @Override
    public boolean isBuildable() {
        if (!super.isBuildable()) {
            log.fine(String.format("%s not buildable; super.isBuildable() is false", this.getFullName()));
            return false;
        }
        //Then, we check if it's an abstract job
        if (this.isAbstract) {
            log.fine(String.format("%s not buildable; project is abstract", this.getFullName()));
            return false;
        }

        //Then, we check if there's a parameter inheritance issue with the
        //user selected version
        AbstractMap.SimpleEntry<Boolean, String> paramCheck = this.getParameterSanity();
        if (paramCheck.getKey() == false) {
            log.fine(String.format("%s not buildable; Parameter inconsistency: %s", this.getFullName(),
                    paramCheck.getValue()));
            return false;
        }

        // Otherwise, we allow things to proceed
        return true;
    }

    // === INHERITANCE AND VERSIONING HELPER CLASSES AND MEMBERS ===

    public static void setVersioningMap(Map<String, Long> map) {
        StaplerRequest req = Stapler.getCurrentRequest();
        //Saving the versioning map into the current request
        if (req != null) {
            req.setAttribute("versions", map);
            //Removing the versioning buffer
            req.removeAttribute("versionedObjectBuffer");
        }
    }

    public static void setVersioningMapInThread(Map<String, Long> map) {
        //Setting the versioning for the current thread, in case the request is unavailable
        ThreadAssocStore.getInstance().setValue("versions", map);
    }

    public static void unsetVersioningMap() {
        StaplerRequest req = Stapler.getCurrentRequest();
        if (req != null) {
            //Saving the versioning to the request
            req.removeAttribute("versions");
            //Removing the versioning buffer
            req.removeAttribute("versionedObjectBuffer");
        }
        //Unsetting the versioning in the thread (in case it was set)
        ThreadAssocStore.getInstance().clear("versions");
    }

    // === GUI ACCESS METHODS ===

    public boolean getIsTransient() {
        return this.isTransient;
    }

    public String getCreationClass() {
        return this.creationClass;
    }

    // === RELATIONSHIP ACCESS METHODS ===

    private static class ProjectGraphNode {
        public HashSet<String> parents = new HashSet<String>();
        public HashSet<String> mates = new HashSet<String>();
        public HashSet<String> children = new HashSet<String>();
    }

    public static Map<String, ProjectGraphNode> getConnectionGraph() {
        Object obj = onChangeBuffer.get(null, "getConnectionGraph");
        if (obj != null && obj instanceof Map) {
            return (Map) obj;
        }

        Map<String, ProjectGraphNode> map = new HashMap<String, ProjectGraphNode>();

        for (InheritanceProject ip : getProjectsMap().values()) {
            String currName = ip.getName();
            ProjectGraphNode currNode = (map.containsKey(currName)) ? map.get(currName) : new ProjectGraphNode();

            for (AbstractProjectReference apr : ip.getParentReferences()) {
                String parName = apr.getName();
                currNode.parents.add(parName);
                ProjectGraphNode parNode = (map.containsKey(parName)) ? map.get(parName) : new ProjectGraphNode();
                parNode.children.add(currName);
                map.put(parName, parNode);
            }
            for (AbstractProjectReference apr : ip.getCompatibleProjects()) {
                currNode.mates.add(apr.getName());
            }
            map.put(currName, currNode);
        }

        onChangeBuffer.set(null, "getConnectionGraph", map);
        return map;
    }

    public Collection<InheritanceProject> getRelationshipsOfType(Relationship.Type type) {
        Collection<InheritanceProject> relationshipsOfType = new LinkedList<InheritanceProject>();
        Map<InheritanceProject, Relationship> relationships = getRelationships();

        /*
         * we are interested in getting the children ordered by last build time
         * if last build time exists
         */
        if (type == Relationship.Type.CHILD) {
            relationshipsOfType = getChildrenByBuildDate(relationships);
        } else if (type == Relationship.Type.PARENT) {
            LinkedList<InheritanceProject> parents = new LinkedList<InheritanceProject>();
            for (java.util.Map.Entry<InheritanceProject, Relationship> project : relationships.entrySet()) {
                if (Relationship.Type.PARENT == project.getValue().type) {
                    parents.add(project.getKey());
                }
            }
            relationshipsOfType = parents;
        }

        return relationshipsOfType;
    }

    private class RunTimeComparator implements Comparator<InheritanceProject> {
        public int compare(InheritanceProject a, InheritanceProject b) {
            InheritanceBuild aBuild = a.getLastBuild();
            InheritanceBuild bBuild = b.getLastBuild();
            if (aBuild == null) {
                int retVal = (bBuild == null) ? a.getFullName().compareTo(b.getFullName()) : 1;
                return retVal;
            } else if (bBuild == null) {
                int retVal = (aBuild == null) ? a.getFullName().compareTo(b.getFullName()) : -1;
                return retVal;
            }
            return bBuild.getTime().compareTo(aBuild.getTime());
        }
    }

    /**
     * we are interested in getting the children ordered by last build time
     * if last build time exists
     */
    public Collection<InheritanceProject> getChildrenByBuildDate(
            Map<InheritanceProject, Relationship> relationships) {
        //Using a TreeSet to do the sorting for last-build-time for us
        TreeSet<InheritanceProject> tree = new TreeSet<InheritanceProject>(new RunTimeComparator());
        Map<InheritanceProject, Relationship> relations = this.getRelationships();
        if (relations.isEmpty()) {
            return tree;
        }
        //Filtering for buildable children
        for (Map.Entry<InheritanceProject, Relationship> pair : relations.entrySet()) {
            InheritanceProject child = pair.getKey();
            Relationship.Type type = pair.getValue().type;
            //Excluding non-childs
            if (type != Relationship.Type.CHILD) {
                continue;
            }
            //The child is buildable, so add it to the tree
            tree.add(child);
        }
        return tree;
    }

    public Map<InheritanceProject, Relationship> getRelationships() {
        Object obj = onInheritChangeBuffer.get(this, "getRelationships");
        if (obj != null && obj instanceof Map) {
            return (Map) obj;
        }

        //Creating the returned map and pre-filling it with empty lists
        Map<InheritanceProject, Relationship> map = new HashMap<InheritanceProject, Relationship>();

        //Preparing the set of projects that were already explored
        HashSet<String> seenProjects = new HashSet<String>();

        //Fetching the map of all projects and their connections
        Map<String, ProjectGraphNode> connGraph = getConnectionGraph();

        //Fetching the node for the current (this) project
        ProjectGraphNode node = connGraph.get(this.getName());
        if (node == null) {
            return map;
        }

        //Mates can be filled quite easily
        for (String mate : node.mates) {
            InheritanceProject p = InheritanceProject.getProjectByName(mate);
            ProjectGraphNode mateNode = connGraph.get(mate);
            boolean isLeaf = (mateNode == null) ? true : mateNode.children.isEmpty();
            if (p == null) {
                continue;
            }
            //Checking if we've seen this mate already
            if (!seenProjects.contains(p.getName())) {
                map.put(p, new Relationship(Relationship.Type.MATE, 0, isLeaf));
                seenProjects.add(p.getName());
            }
        }

        //Exploring parents
        int distance = 1;
        seenProjects.clear();
        LinkedList<InheritanceProject> cOpen = new LinkedList<InheritanceProject>();
        LinkedList<InheritanceProject> nOpen = new LinkedList<InheritanceProject>();
        cOpen.add(this);
        while (!cOpen.isEmpty()) {
            InheritanceProject ip = cOpen.pop();
            if (ip == null || seenProjects.contains(ip.getName())) {
                continue;
            }
            seenProjects.add(ip.getName());

            node = connGraph.get(ip.getName());
            if (ip == null || node == null) {
                continue;
            }
            //Adding all parents
            for (String parent : node.parents) {
                InheritanceProject par = InheritanceProject.getProjectByName(parent);
                if (par == null || seenProjects.contains(parent)) {
                    continue;
                }
                map.put(par, new Relationship(Relationship.Type.PARENT, distance, false));
                nOpen.push(par);
            }
            if (cOpen.isEmpty() && !nOpen.isEmpty()) {
                cOpen = nOpen;
                nOpen = new LinkedList<InheritanceProject>();
                distance++;
            }
        }

        //Exploring children
        distance = 1;
        seenProjects.clear();
        cOpen.clear();
        nOpen.clear();
        cOpen.add(this);
        while (!cOpen.isEmpty()) {
            InheritanceProject ip = cOpen.pop();
            if (ip == null || seenProjects.contains(ip.getName())) {
                continue;
            }
            seenProjects.add(ip.getName());

            node = connGraph.get(ip.getName());
            if (ip == null || node == null) {
                continue;
            }
            //Adding all parents
            for (String child : node.children) {
                InheritanceProject cProj = InheritanceProject.getProjectByName(child);
                if (cProj == null || seenProjects.contains(child)) {
                    continue;
                }
                ProjectGraphNode childNode = connGraph.get(child);
                boolean isLeaf = (childNode == null) ? true : childNode.children.isEmpty();
                map.put(cProj, new Relationship(Relationship.Type.CHILD, distance, isLeaf));
                nOpen.push(cProj);
            }
            if (cOpen.isEmpty() && !nOpen.isEmpty()) {
                cOpen = nOpen;
                nOpen = new LinkedList<InheritanceProject>();
                distance++;
            }
        }

        onInheritChangeBuffer.set(this, "getRelationships", map);
        return map;
    }

    public List<Vector<String>> getRelatedProjects() {
        Object obj = onInheritChangeBuffer.get(this, "getRelatedProjects");
        if (obj != null && obj instanceof LinkedList) {
            return (LinkedList) obj;
        }

        LinkedList<Vector<String>> lst = new LinkedList<Vector<String>>();

        //Fetching the relationships of this project to others
        Map<InheritanceProject, Relationship> rels = this.getRelationships();
        for (Map.Entry<InheritanceProject, Relationship> entry : rels.entrySet()) {
            Relationship rel = entry.getValue();
            Vector<String> vec = new Vector<String>();
            vec.add(entry.getKey().getName());
            switch (rel.type) {
            case PARENT:
                vec.add(Messages.InheritanceProject_Relationship_Type_ParentDesc());
                break;
            case CHILD:
                vec.add(Messages.InheritanceProject_Relationship_Type_ChildDesc());
                break;
            case MATE:
                vec.add(Messages.InheritanceProject_Relationship_Type_MateDesc());
                break;
            }
            vec.add(Integer.toString(rel.distance));
            lst.add(vec);
        }

        onInheritChangeBuffer.set(this, "getRelatedProjects", lst);
        return lst;
    }

    private List<ScopeEntry> getFullParameterScope() {
        //Fetching the correct definition property
        ParametersDefinitionProperty pdp = this.getProperty(ParametersDefinitionProperty.class,
                IMode.INHERIT_FORCED);
        if (pdp == null) {
            //No parameters set, so we return an empty list
            return Collections.emptyList();
        }

        //Checking if it is a fully scoped inheritance-aware one; if yes, we
        //fetch the full scope of parameters.
        List<ScopeEntry> fullScope = null;
        if (pdp instanceof InheritanceParametersDefinitionProperty) {
            InheritanceParametersDefinitionProperty ipdp = (InheritanceParametersDefinitionProperty) pdp;
            fullScope = ipdp.getAllScopedParameterDefinitions();
        } else {
            String ownerName = (pdp.getOwner() != null) ? pdp.getOwner().getName() : "";
            fullScope = new LinkedList<ScopeEntry>();
            for (ParameterDefinition pd : pdp.getParameterDefinitions()) {
                fullScope.add(new ScopeEntry(ownerName, pd));
            }
        }

        if (fullScope != null) {
            return fullScope;
        } else {
            return Collections.emptyList();
        }
    }

    public List<ParameterDerivationDetails> getParameterDerivationList() {
        List<ParameterDerivationDetails> list = new LinkedList<ParameterDerivationDetails>();
        //Grab the full scope of all parameters
        List<ScopeEntry> fullScope = this.getFullParameterScope();

        int cnt = 0;
        for (ScopeEntry scope : fullScope) {
            String paramName = scope.param.getName();
            String projName = scope.owner;
            String detail = "";
            Object def = scope.param.getDefaultParameterValue().getShortDescription();

            if (scope.param instanceof InheritableStringParameterDefinition) {
                InheritableStringParameterDefinition ispd = (InheritableStringParameterDefinition) scope.param;
                StringBuilder b = new StringBuilder();
                b.append(ispd.getMustHaveDefaultValue());
                b.append("; ");
                b.append(ispd.getMustBeAssigned());
                detail = b.toString();
            }

            ParameterDerivationDetails pdd = new ParameterDerivationDetails(paramName, projName, detail, def);
            pdd.setOrder(cnt++);
            list.add(pdd);
        }

        return list;
    }

    // === SVGNode METHODS ===

    public String getSVGLabel() {
        return this.getName();
    }

    public String getSVGDetail() {
        List<ParameterDefinition> pLst = this.getParameters(IMode.LOCAL_ONLY);
        if (pLst == null) {
            return "";
        }
        StringBuilder b = new StringBuilder();
        for (ParameterDefinition pd : pLst) {
            if (pd == null) {
                continue;
            }
            b.append(pd.getName());
            ParameterValue pv = pd.getDefaultParameterValue();
            if (pv != null && pv instanceof StringParameterValue) {
                b.append(": ");
                b.append(((StringParameterValue) pv).value);
            }
            b.append('\n');
        }

        if (b.length() > 0) {
            b.append("\r\n");
        }

        List<Builder> builders = this.getBuilders();
        String str = (builders == null || builders.size() != 1) ? "steps" : "step";
        int num = (builders == null) ? 0 : builders.size();
        b.append(String.format("%d build %s\n", num, str));

        DescribableList<Publisher, Descriptor<Publisher>> pubs = this.getPublishersList();
        str = (pubs == null || pubs.size() != 1) ? "publishers" : "publisher";
        num = (pubs == null) ? 0 : pubs.size();
        b.append(String.format("%d %s", num, str));

        return b.toString();
    }

    public URL getSVGLabelLink() {
        try {
            return new URL(this.getAbsoluteUrl());
        } catch (MalformedURLException ex) {
            return null;
        }
    }

    public Graph<SVGNode> getSVGRelationGraph() {
        Graph<SVGNode> out = new Graph<SVGNode>();

        LinkedList<InheritanceProject> open = new LinkedList<InheritanceProject>();
        HashSet<InheritanceProject> visited = new HashSet<InheritanceProject>();

        open.add(this);
        while (!open.isEmpty()) {
            InheritanceProject ip = open.pop();
            if (visited.contains(ip)) {
                continue;
            } else {
                visited.add(ip);
            }
            out.addNode(ip);

            for (InheritanceProject parent : ip.getParentProjects()) {
                open.add(parent);
                out.addNode(ip, parent);
            }
        }

        return out;
    }

    public String doRenderSVGRelationGraph() {
        return this.renderSVGRelationGraph(0, 0);
    }

    public String renderSVGRelationGraph(int width, int height) {
        SVGTreeRenderer tree = new SVGTreeRenderer(this.getSVGRelationGraph(), width, height);
        Document doc = tree.render();
        try {
            DOMSource source = new DOMSource(doc);
            StringWriter stringWriter = new StringWriter();
            StreamResult result = new StreamResult(stringWriter);
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.transform(source, result);
            return stringWriter.getBuffer().toString();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        return "";
    }

    // === MISC. HELPER METHODS ===

    /**
     * Wrapper for {@link #hasCyclicDependency(String...)} with no new project
     * references added on top of the existing ones.
     */
    public final boolean hasCyclicDependency() {
        //TODO: Make this method version-aware

        //Checking if a result is buffered
        Object obj = onInheritChangeBuffer.get(this, "hasCyclicDependency");
        if (obj != null && obj instanceof Boolean) {
            return (Boolean) obj;
        }

        //Re-computing the result
        String[] arr = {};
        Boolean bufRes = this.hasCyclicDependency(arr);

        onInheritChangeBuffer.set(this, "hasCyclicDependency", bufRes);
        return bufRes;
    }

    /**
     * Tests if this project's configuration leads to a cyclic, diamond or
     * multiple dependency.<br/>
     * <br/>
     * See <a href="http://en.wikipedia.org/wiki/Cycle_detection">cycle detection</a> and
     * <a href="http://en.wikipedia.org/wiki/Diamond_problem">diamond problem</a>.
     * 
     * @return true, if there is a cyclic, diamond or repeated dependency among
     * this project's parents.
     */
    public final boolean hasCyclicDependency(String... whenTheseProjectsAdded) {
        /* TODO: While this method runs reasonably fast, it is run very often
         * As such, find a way to buffer the result across all projects and
         * only rebuild if necessary.
         */

        /* TODO: Further more, this method is not space-optimal
         * See: http://en.wikipedia.org/wiki/Cycle_detection
         * But do note that any replacement algorithm also, by contract, needs
         * to detect multiple inheritance and its special case of diamond
         * inheritance.
         */

        //Preparing the set of project names that were seen at least once
        HashSet<String> closed = new HashSet<String>();

        //Creating the list of parent projects to still explore
        LinkedList<InheritanceProject> open = new LinkedList<InheritanceProject>();
        //And scheduling ourselves as the first to evaluate
        open.push(this);

        //And finally, creating a list of additional references the caller
        //wishes to eventually add to THIS project
        LinkedList<String> additionalRefs = new LinkedList<String>();
        for (String pName : whenTheseProjectsAdded) {
            //We need to ignore those, that we already refer to as parents
            //Do note that this makes direct multiple inheritance impossible
            //to detect in advance, but such errors should be obvious anyway
            boolean isAlreadyReferenced = false;
            for (AbstractProjectReference par : this.getParentReferences()) {
                if (par.getName().equals(pName)) {
                    isAlreadyReferenced = true;
                    break;
                }
            }
            if (!isAlreadyReferenced) {
                additionalRefs.add(pName);
            }
        }

        //Processing the open stack, checking if we're already met that parent
        //and if not, adding its parent to our open stack
        while (open.isEmpty() == false) {
            //Popping the first element
            InheritanceProject p = open.pop();
            //Checking if we've seen that parent already
            if (closed.contains(p.name)) {
                //Detected a cyclic dependency
                return true;
            }
            // Otherwise, we add all its parents to our open set
            for (AbstractProjectReference ref : p.getParentReferences()) {
                InheritanceProject refP = ref.getProject();
                if (refP != null) {
                    open.push(refP);
                }
            }
            //And if the current object is active, we also need to check the
            //new future refs
            if (p == this && !additionalRefs.isEmpty()) {
                for (String ref : additionalRefs) {
                    InheritanceProject ip = InheritanceProject.getProjectByName(ref);
                    if (ip != null) {
                        open.push(ip);
                    }
                }
            }
            closed.add(p.name);
        }
        // If we reach this spot, there is no such dependency
        return false;
    }

    public final AbstractMap.SimpleEntry<Boolean, String> getParameterSanity() {
        //Creating a small local class to store sanity information
        final class SanityRestrictions {
            public Class<?> hasToBeOfThisClass;

            public boolean hasToHaveDefaultSet;
            public boolean hasToBeAssigned;

            public boolean hadDefaultSet;
            public IModes previousMode;
        }

        //Preparing a map of parameter name to restrictions
        HashMap<String, SanityRestrictions> resMap = new HashMap<String, SanityRestrictions>();

        //Fetch all parameters in the scope
        List<ScopeEntry> fullScope = this.getFullParameterScope();

        //Iterating through the parameters, and verifying their restrictions on-the-fly
        for (ScopeEntry scope : fullScope) {
            ParameterDefinition pd = scope.param;
            if (pd == null) {
                continue;
            }
            SanityRestrictions s = resMap.get(pd.getName());
            if (s == null) {
                //We've seen this PD for the first time
                s = new SanityRestrictions();
                s.hasToBeOfThisClass = pd.getClass();
                if (pd instanceof InheritableStringParameterDefinition) {
                    InheritableStringParameterDefinition ispd = (InheritableStringParameterDefinition) pd;
                    s.hasToHaveDefaultSet = ispd.getMustHaveDefaultValue();
                    s.hasToBeAssigned = ispd.getMustBeAssigned();

                    String defVal = ispd.getDefaultValue();
                    s.hadDefaultSet = !(defVal == null || defVal.isEmpty());

                    s.previousMode = ispd.getInheritanceModeAsVar();
                } else {
                    s.hasToHaveDefaultSet = false;
                    s.previousMode = IModes.OVERWRITABLE;
                }

                //No sense in checking this param instance further, as a
                //param can't make itself insane
                resMap.put(pd.getName(), s);
                continue;
            }

            /* Check if the scoped forms can be cast in at least one direction,
             * which means that they share parenthood.
             * This avoids an IntegerParamer becoming a StringParameter, but
             * allows a PasswordParameter to merge with a StringParameter.
             */
            boolean isScopeCastToCurrent = pd.getClass().isAssignableFrom(s.hasToBeOfThisClass);
            boolean isCurrentCastToScope = s.hasToBeOfThisClass.isAssignableFrom(pd.getClass());
            if (!(isScopeCastToCurrent || isCurrentCastToScope)) {
                return new AbstractMap.SimpleEntry<Boolean, String>(false,
                        "Parameter '" + pd.getName() + "' redefined with different class name.");
            }

            if (s.previousMode == IModes.FIXED) {
                return new AbstractMap.SimpleEntry<Boolean, String>(false,
                        "Fixed parameter '" + pd.getName() + "' may not be redefined at all.");
            }

            //Check additional restrictions on ISPDs
            if (pd instanceof InheritableStringParameterDefinition) {
                InheritableStringParameterDefinition ispd = (InheritableStringParameterDefinition) pd;
                //Check if overwriting causes a previous default to be lost
                String defVal = ispd.getDefaultValue();
                boolean defValNewlySet = !(defVal == null || defVal.isEmpty());

                switch (s.previousMode) {
                case OVERWRITABLE:
                    //An overwrite always causes the default to be discarded
                    s.hadDefaultSet = defValNewlySet;
                    break;
                case EXTENSIBLE:
                    //An extension does not overwrite an already set default
                    if (!s.hadDefaultSet) {
                        s.hadDefaultSet = defValNewlySet;
                    }
                    break;
                case FIXED:
                    //FIXED parameters are ignored
                    break;
                default:
                    log.warning("Detected invalid inheritance mode: " + s.previousMode.toString() + " on "
                            + this.getName() + "->" + pd.getName());
                    break;
                }

                //Ignore references, as they can never invalidate or change flags
                if (pd instanceof InheritableStringParameterReferenceDefinition) {
                    continue;
                }

                //Check if the "force-default-value" flag was unset
                if (s.hasToHaveDefaultSet && !ispd.getMustHaveDefaultValue()) {
                    return new AbstractMap.SimpleEntry<Boolean, String>(false, "Parameter '" + pd.getName()
                            + "' may not unset the flag that ensures that a" + " default value is set.");
                }
                //Check if the "must-be-assigned" flag was unset
                if (s.hasToBeAssigned && !ispd.getMustBeAssigned()) {
                    return new AbstractMap.SimpleEntry<Boolean, String>(false,
                            "Parameter '" + pd.getName() + "' may not unset the flag that ensures that a"
                                    + " final value is set before execution.");
                }
                //Overwrite the flags, now that their sanity is ensured
                s.previousMode = ispd.getInheritanceModeAsVar();
                s.hasToHaveDefaultSet = ispd.getMustHaveDefaultValue();
                s.hasToBeAssigned = ispd.getMustBeAssigned();
            }
        }

        //Then, if the build is not abstract, we must check whether all values
        //that carry defaults actually had defaults defined at some point
        if (this.isAbstract == false) {
            for (Map.Entry<String, SanityRestrictions> e : resMap.entrySet()) {
                String name = e.getKey();
                SanityRestrictions s = e.getValue();

                if (s.hasToHaveDefaultSet && !s.hadDefaultSet) {
                    return new AbstractMap.SimpleEntry<Boolean, String>(false,
                            "Parameter '" + name + "' did not" + " have a default set. This is only allowed"
                                    + " if the project is marked as abstract.");
                }
            }
        }

        //If we reach this spot, everything checked out fine.
        return new AbstractMap.SimpleEntry<Boolean, String>(true, "");
    }

    public String getPronoun() {
        if (this.getIsTransient()) {
            return Messages.InheritanceProject_TransientPronounLabel();
        } else if (this.getCreationClass() != null) {
            return this.getCreationClass();
        } else {
            return super.getPronoun();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * The above is overridden in a way, that the Build-History widget is
     * removed if the build is abstract and can't be run anyway.
     * 
     * This is ignored, in case there is a last build, though, to not
     * hide any information.
     */
    @Override
    public List<Widget> getWidgets() {
        List<Widget> widgets = super.getWidgets();
        if (!this.isBuildable() && this.getLastBuild() == null) {
            //Remove the history widgets
            List<Widget> strippedOffWidgets = new ArrayList<Widget>();
            for (Widget widget : widgets) {
                if (!(widget instanceof HistoryWidget<?, ?>)) {
                    strippedOffWidgets.add(widget);
                }
            }
            return strippedOffWidgets;
        } else {
            return widgets;
        }
    }

    public static List<JobPropertyDescriptor> getJobPropertyDescriptors(Class<? extends Job> clazz,
            boolean filterIsExcluding, String... filters) {
        List<JobPropertyDescriptor> out = new ArrayList<JobPropertyDescriptor>();

        //JobPropertyDescriptor.getPropertyDescriptors(clazz);
        List<JobPropertyDescriptor> allDesc = Functions.getJobPropertyDescriptors(clazz);

        for (JobPropertyDescriptor desc : allDesc) {
            String dName = desc.getClass().getName();
            if (filters.length > 0) {
                boolean matched = false;
                if (filters != null) {
                    for (String filter : filters) {
                        if (dName.contains(filter)) {
                            matched = true;
                            break;
                        }
                    }
                }
                if (filterIsExcluding && matched) {
                    continue;
                } else if (!filterIsExcluding && !matched) {
                    continue;
                }
            }
            //The class has survived the filter
            out.add(desc);
        }

        //At last, we make sure to sort the fields by full name; to ensure
        //that properties from the same package/plugin are next to each other
        Collections.sort(out, new Comparator<JobPropertyDescriptor>() {
            @Override
            public int compare(JobPropertyDescriptor o1, JobPropertyDescriptor o2) {
                String c1 = o1.getClass().getName();
                String c2 = o2.getClass().getName();
                return c1.compareTo(c2);
            }
        });

        return out;
    }

    // === HELPER METHODS FOR READONLY VIEW ===

    public Map<AbstractProjectReference, List<Builder>> getBuildersFor(Map<String, Long> verMap, Class<?> clazz) {
        //Set the current thread's versioning map
        if (verMap != null && !verMap.isEmpty()) {
            setVersioningMap(verMap);
        }
        //Loop over all parents and create a joined map of all builders
        Map<AbstractProjectReference, List<Builder>> out = new LinkedHashMap<AbstractProjectReference, List<Builder>>();

        List<AbstractProjectReference> refs = new LinkedList<AbstractProjectReference>(
                this.getAllParentReferences(SELECTOR.BUILDER));

        //Add a reference to ourselves
        refs.add(new SimpleProjectReference(this.getFullName()));

        for (AbstractProjectReference apr : refs) {
            InheritanceProject ip = apr.getProject();
            if (ip == null) {
                continue;
            }
            List<Builder> bLst = ip.getBuildersList(IMode.LOCAL_ONLY).toList();
            if (clazz != null) {
                List<Builder> bSubLst = new LinkedList<Builder>();
                for (Builder b : bLst) {
                    if (b != null && clazz.isAssignableFrom(b.getClass())) {
                        bSubLst.add(b);
                    }
                }
                out.put(apr, bSubLst);
            } else {
                out.put(apr, bLst);
            }
        }

        return out;
    }

    // === PROJECT DESCRIPTOR IMPLEMENTATION ===

    /**
     * Returns the {@link Descriptor} for the parent object.<br/>
     * <br/>
     * The returned object should be a class-singleton that
     * can be used to create an instance of its parent class and thereafter
     * display a configuration dialog.<br/>
     * As such, this class has the responsibility of creating a suitable
     * instance, serving up the HTML/Jelly configuration fields, reading their
     * values and modifying the created instance accordingly.<br/>
     * <br/>
     * Do note that the configuration-dialog for the object is displayed
     * <i>after</i> the instance was created.<br/>
     */
    public DescriptorImpl getDescriptor() {
        return DESCRIPTOR;
    }

    @Extension(ordinal = 1000)
    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

    public static final class DescriptorImpl extends AbstractProjectDescriptor {
        private final HashSet<String> projectsToBeCreatedTransient = new HashSet<String>();

        public final static Pattern urlJobPattern = Pattern.compile("/job/([^/]+)");

        public DescriptorImpl() {
            //Creating the static buffers of the IP class, if necessary
            InheritanceProject.createBuffers();
        }

        public String getDisplayName() {
            return Messages.InheritanceProject_DisplayName();
        }

        public String getDescription() {
            return "";
        }

        public InheritanceProject newInstance(ItemGroup parent, String name) {
            //Checking if the given name is on the list of transient jobs
            if (this.projectsToBeCreatedTransient.contains(name)) {
                this.projectsToBeCreatedTransient.remove(name);
                return new InheritanceProject(parent, name, true);
            } else {
                return new InheritanceProject(parent, name, false);
            }
        }

        public ListBoxModel doFillCreationClassItems() {
            ListBoxModel names = new ListBoxModel();
            for (CreationClass cl : ProjectCreationEngine.instance.getCreationClasses()) {
                names.add(cl.name);
            }
            //And also add an empty one, to select NO mating
            names.add("<None Specified>", "");
            return names;
        }

        /**
         * Wrapper around {@link DescriptorImpl#doFillCreationClassItems()} to
         * account for slightly different field names used by
         * {@link InheritanceViewAction}'s groovy scripts.
         */
        public ListBoxModel doFillProjectClassItems() {
            return this.doFillCreationClassItems();
        }

        public ListBoxModel doFillUserDesiredVersionItems() {
            ListBoxModel verBox = new ListBoxModel();

            InheritanceProject ip = this.getConfiguredProject();
            if (ip != null) {
                for (Version v : ip.getVersions()) {
                    verBox.add(v.toString(), v.id.toString());
                }
            } else {
                log.warning("Could not fetch or resolve project name");
            }

            return verBox;
        }

        /**
         * This method identifies the project under configuration.
         * 
         * It first tries to do that by asking the request itself for its
         * ancestor; but if that is unavailable it looks at the HTTP request
         * to check for the name of the project under configuration.
         * It then tries to retrieve the object associated with that name.
         */
        public InheritanceProject getConfiguredProject(StaplerRequest req) {
            //Fetching the current request from the user
            if (req == null) {
                return null;
            }

            //Then, trying to fetch an ancestor
            InheritanceProject ip = req.findAncestorObject(InheritanceProject.class);
            if (ip != null) {
                return ip;
            }

            //If that failed; trying to decode the URL to get the project name
            String uri = req.getRequestURI();
            if (uri == null || uri.length() == 0) {
                return null;
            }
            Matcher m = urlJobPattern.matcher(uri);
            if (m == null || !m.find()) {
                return null;
            }
            String pName = m.group(1);
            if (pName == null || pName.length() == 0) {
                return null;
            }

            //Now that we have the name, we try to match it to a Project
            return InheritanceProject.getProjectByName(pName);
        }

        protected InheritanceProject getConfiguredProject() {
            return this.getConfiguredProject(Stapler.getCurrentRequest());
        }

        public synchronized void addProjectToBeCreatedTransient(String name) {
            //TODO: Do not allow this set to grow too long in case of error
            this.projectsToBeCreatedTransient.add(name);
        }

        public synchronized void dropProjectToBeCreatedTransient(String name) {
            this.projectsToBeCreatedTransient.remove(name);
        }
    }
}