/* * The MIT License * * Copyright (c) 2011-2013, CloudBees, Inc., Stephen Connolly. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package jenkins.branch; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.Util; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Job; import hudson.model.TaskListener; import hudson.scm.NullSCM; import hudson.scm.SCM; import java.io.ObjectStreamException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nonnull; import jenkins.model.TransientActionFactory; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMSource; import jenkins.scm.impl.NullSCMSource; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; /** * A source code branch. * Not to be subclassed outside this plugin. */ @ExportedBean public class Branch { /** * The ID of our {@link jenkins.scm.api.SCMSource} */ private final String sourceId; /** * The name of the branch. */ private final SCMHead head; /** * The {@link SCM} for this specific branch. */ private final SCM scm; /** * The properties of this branch. */ private final CopyOnWriteArrayList<BranchProperty> properties = new CopyOnWriteArrayList<BranchProperty>(); /** * The {@link SCMSource#fetchActions(SCMHead, SCMHeadEvent, TaskListener)} for this branch. * The collection is always replaced as a whole. We store the whole collection in an ArrayList so that the XStream * representation is simpler. * @since 2.0 */ private List<Action> actions; /** * Constructs a branch instance. * * @param sourceId the {@link jenkins.scm.api.SCMSource#getId()} * @param head the name of the branch. * @param scm the {@link SCM} for the branch. * @param properties the properties to initiate the branch with. */ public Branch(String sourceId, SCMHead head, SCM scm, List<? extends BranchProperty> properties) { this.sourceId = sourceId; this.head = head; this.scm = scm; this.properties.addAll(properties); this.actions = new ArrayList<>(); } /** * Internal copy constructor for creating dead branches. * @param id the id of the new {@link SCMSource}. * @param scm the new {@link SCM}. * @param b the branch to copy. * @since 2.0 */ private Branch(String id, SCM scm, Branch b) { this.sourceId = id; this.head = b.head; this.scm = scm; this.properties.addAll(b.properties); this.actions = new ArrayList<>(b.actions); } /** * Ensure actions is never null. * * @return the deserialized object. * @throws ObjectStreamException if things go wrong. */ private Object readResolve() throws ObjectStreamException { if (actions == null) { actions = new ArrayList<>(); } return this; } /** * Returns the {@link jenkins.scm.api.SCMSource#getId()} that this branch originates from. * * @return the {@link jenkins.scm.api.SCMSource#getId()} that this branch originates from. */ public String getSourceId() { return sourceId; } /** * Returns the name of the branch. * * @return the name of the branch. */ public String getName() { // TODO this could include a uniquifying prefix defined in BranchSource return head.getName(); } /** * Gets a branch name suitable for use in paths. * * @return {@link #getName} with URL-unsafe characters escaped * @since 0.2-beta-7 */ public String getEncodedName() { return NameEncoder.encode(getName()); } /** * Returns the {@link SCMHead of the branch} * * @return the {@link SCMHead of the branch} */ @Exported public SCMHead getHead() { return head; } /** * Returns the {@link SCM} for the branch. * * @return the {@link SCM} for the branch. */ @Exported public SCM getScm() { return scm; } /** * Tests if a property of a specific type is present. * * @param clazz the specific property type * @return {@code true} if and only if there is a property of the specified type. */ public boolean hasProperty(Class<? extends BranchProperty> clazz) { return getProperty(clazz) != null; } /** * Gets the specific property, or {@code null} if no such property is found. * * @param <T> the type of property. * @param clazz the type of property. * @return the the specific property, or {@code null} if no such property is found. */ @CheckForNull public <T extends BranchProperty> T getProperty(Class<T> clazz) { for (BranchProperty p : properties) { if (clazz.isInstance(p)) { return clazz.cast(p); } } return null; } /** * Gets all the properties. * * @return the properties. */ @NonNull public List<BranchProperty> getProperties() { return properties; } /** * Gets all the actions. * * @return all the actions */ @NonNull public List<Action> getActions() { return actions == null ? Collections.<Action>emptyList() : Collections.unmodifiableList(actions); } /** * Sets the actions atomically. * @param actions the new actions */ /*package*/ void setActions(@NonNull List<Action> actions) { this.actions = new ArrayList<>(actions); } /** * Gets the specific action, or null if no such property is found. * * @param <T> the type of action * @param clazz the type of action. * @return the first action of the requested type or {@code null} if no such action is present. */ @CheckForNull public <T extends Action> T getAction(Class<T> clazz) { if (actions == null) { return null; } for (Action p : actions) { if (clazz.isInstance(p)) { return clazz.cast(p); } } return null; } /** * Returns {@code true} iff the branch is can be built. * * @return {@code true} iff the branch is can be built. */ public boolean isBuildable() { return true; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Branch)) { return false; } Branch branch = (Branch) o; if (sourceId != null ? !sourceId.equals(branch.sourceId) : branch.sourceId != null) { return false; } if (!head.equals(branch.head)) { return false; } return true; } /** * {@inheritDoc} */ @Override public int hashCode() { int result = sourceId != null ? sourceId.hashCode() : 0; result = 31 * result + head.hashCode(); return result; } /** * Represents a dead branch. */ public static class Dead extends Branch { /** * Constructor. * * @param name branch name. * @param properties the initial branch properties */ public Dead(SCMHead name, List<? extends BranchProperty> properties) { super(NullSCMSource.ID, name, new NullSCM(), properties); } /** * Constructor. * * @param b the branch that is now dead. * @since 2.0 */ public Dead(Branch b) { super(NullSCMSource.ID, new NullSCM(), b); } /** * {@inheritDoc} */ @Override public boolean isBuildable() { return false; } } /** * Ensures that the {@link Branch#getActions()} are always present in the {@link Job#getAllActions()}. * NOTE: We need to use a transient action factory as {@link AbstractProject#getActions()} is unmodifiable * and consequently {@link AbstractProject#addAction(Action)} will always fail. * * @since 2.0 */ @Extension public static class TransientJobActionsFactoryImpl extends TransientActionFactory<Job> { /** * {@inheritDoc} */ @Override public Class<Job> type() { return Job.class; } /** * {@inheritDoc} */ @Nonnull @Override public Collection<? extends Action> createFor(@Nonnull Job target) { if (target.getParent() instanceof MultiBranchProject) { MultiBranchProject p = (MultiBranchProject) target.getParent(); BranchProjectFactory factory = p.getProjectFactory(); if (factory.isProject(target)) { Branch b = factory.getBranch(factory.asProject(target)); return b.getActions(); } } return Collections.emptyList(); } } }