/* * 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 com.cloudbees.hudson.plugins.folder.AbstractFolder; import com.cloudbees.hudson.plugins.folder.AbstractFolderDescriptor; import com.cloudbees.hudson.plugins.folder.ChildNameGenerator; import com.cloudbees.hudson.plugins.folder.FolderIconDescriptor; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.ExtensionList; import hudson.Util; import hudson.model.Descriptor; import hudson.model.Job; import hudson.model.Run; import hudson.model.TopLevelItem; import hudson.model.TopLevelItemDescriptor; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.WeakHashMap; import javax.annotation.CheckForNull; import jenkins.model.Jenkins; import jenkins.scm.api.SCMSourceDescriptor; import org.jvnet.tiger_types.Types; /** * <p>The {@link Descriptor} for {@link MultiBranchProject}s.</p> * * <p>Compatible {@link hudson.scm.SCM}s displayed by {@link jenkins.scm.impl.SingleSCMSource} (via their * {@link hudson.scm.SCMDescriptor}) can be defined by overriding {@link #isApplicable(Descriptor)}:</p> * <pre> * @Override * public boolean isApplicable(Descriptor descriptor) { * if (descriptor instanceof SCMDescriptor) { * SCMDescriptor d = (SCMDescriptor) descriptor; * // Your logic * } * return super.isApplicable(descriptor); * } * </pre> * * @author Stephen Connolly */ public abstract class MultiBranchProjectDescriptor extends AbstractFolderDescriptor { /** * The base class of the projects that are produced by this factory. * * @since 2.0 */ @NonNull private final Class<? extends Job> projectClass; /** * Explicit constructor to use when type inference fails. * * @param clazz the {@link MultiBranchProject} that this descriptor is for. * @param projectClass the {@link Job} type that the {@link MultiBranchProject} creates. * @since 2.0 */ protected MultiBranchProjectDescriptor(Class<? extends MultiBranchProject<?, ?>> clazz, Class<? extends Job> projectClass) { super(clazz); this.projectClass = projectClass; } /** * Semi explicit constructor to use when the descriptor is not an inner class of the {@link MultiBranchProject}. * * @param clazz the {@link MultiBranchProject} that this descriptor is for. * @since 2.0 */ protected MultiBranchProjectDescriptor(Class<? extends MultiBranchProject<?, ?>> clazz) { super(clazz); projectClass = inferProjectClass(clazz); } /** * Fully inferring constructor to use when the descriptor is an inner class of the {@link MultiBranchProject} * and type parameter inference works because it just should work. * * @since 2.0 */ protected MultiBranchProjectDescriptor() { super(); projectClass = inferProjectClass((Class) clazz); } /** * Infers the base class of projects that the specified {@link MultiBranchProject} creates. * * @param clazz the specified {@link MultiBranchProject}. * @return the base class of project that the specified {@link MultiBranchProject} creates. */ private static Class<? extends Job> inferProjectClass(Class<? extends MultiBranchProject> clazz) { Type bt = Types.getBaseClass(clazz, MultiBranchProject.class); if (bt instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) bt; // this 'p' is the closest approximation of P of MultiBranchProject<P,R>. Class p = Types.erasure(pt.getActualTypeArguments()[0]); if (!Job.class.isAssignableFrom(p)) { throw new AssertionError( "Cannot infer job type produced by " + clazz + " perhaps use the explicit constructor"); } return p; } else { throw new AssertionError( "Cannot infer job type produced by " + clazz + " perhaps use the explicit constructor"); } } /** * Returns the base class of the projects that are produced by this factory. * * @return the base class of the projects that are produced by this factory. * @since 2.0 */ @NonNull public Class<? extends Job> getProjectClass() { return projectClass; } /** * We have to extend {@link TopLevelItemDescriptor} but we want to be able to access {@link #clazz} as a * {@link MultiBranchProject} based type. * * @return the {@link #clazz} */ @SuppressWarnings("unchecked") @NonNull public Class<? extends MultiBranchProject> getClazz() { return clazz.asSubclass(MultiBranchProject.class); } /** * Gets the {@link SCMSourceDescriptor}s. * * @param onlyUserInstantiable {@code true} retains only those {@link jenkins.scm.api.SCMSource} types that * are instantiable by the user. * @return the list of {@link SCMSourceDescriptor}s. */ @SuppressWarnings("unused") // used by stapler @NonNull public List<SCMSourceDescriptor> getSCMSourceDescriptors(boolean onlyUserInstantiable) { return SCMSourceDescriptor.forOwner(getClazz(), onlyUserInstantiable); } /** * Returns the {@link BranchProjectFactoryDescriptor}s. * * @return the {@link BranchProjectFactoryDescriptor}s. */ @SuppressWarnings("unused") // used by stapler @NonNull public List<BranchProjectFactoryDescriptor> getProjectFactoryDescriptors() { List<BranchProjectFactoryDescriptor> result = new ArrayList<BranchProjectFactoryDescriptor>(); for (BranchProjectFactoryDescriptor descriptor : ExtensionList.lookup(BranchProjectFactoryDescriptor.class)) { if (descriptor.isApplicable(getClazz()) && descriptor.getProjectClass().isAssignableFrom(getProjectClass())) { result.add(descriptor); } } return result; } /** * Returns the {@link BranchSource.DescriptorImpl}. * * @return the {@link BranchSource.DescriptorImpl}. */ @SuppressWarnings({"unused", "unchecked"}) // used by stapler @NonNull public Descriptor<BranchSource> getBranchSourceDescriptor() { return Jenkins.getActiveInstance().getDescriptorOrDie(BranchSource.class); } /** * {@inheritDoc} */ @Override public List<FolderIconDescriptor> getIconDescriptors() { return Collections.<FolderIconDescriptor>singletonList( Jenkins.getActiveInstance().getDescriptorByType(MetadataActionFolderIcon.DescriptorImpl.class) ); } /** * {@inheritDoc} */ @Override public boolean isIconConfigurable() { return false; } @SuppressWarnings("unchecked") @Override @NonNull public <I extends TopLevelItem> ChildNameGenerator<AbstractFolder<I>,I> childNameGenerator() { return (ChildNameGenerator<AbstractFolder<I>, I>)ChildNameGeneratorImpl.INSTANCE; } public static class ChildNameGeneratorImpl<P extends Job<P, R> & TopLevelItem, R extends Run<P, R>> extends ChildNameGenerator<MultiBranchProject<P,R>,P> { /*package*/ static final ChildNameGeneratorImpl INSTANCE = new ChildNameGeneratorImpl(); @Override @CheckForNull public String itemNameFromItem(@NonNull MultiBranchProject<P,R> parent, @NonNull P item) { BranchProjectFactory<P, R> factory = parent.getProjectFactory(); if (factory.isProject(item)) { return NameEncoder.encode(factory.getBranch(item).getName()); } String idealName = idealNameFromItem(parent, item); if (idealName != null) { return NameEncoder.encode(idealName); } return null; } @Override @CheckForNull public String dirNameFromItem(@NonNull MultiBranchProject<P,R> parent, @NonNull P item) { BranchProjectFactory<P, R> factory = parent.getProjectFactory(); if (factory.isProject(item)) { return NameMangler.apply(factory.getBranch(item).getName()); } String idealName = idealNameFromItem(parent, item); if (idealName != null) { return NameMangler.apply(idealName); } return null; } @Override @NonNull public String itemNameFromLegacy(@NonNull MultiBranchProject<P, R> parent, @NonNull String legacyDirName) { return NameEncoder.decode(legacyDirName); } @Override @NonNull public String dirNameFromLegacy(@NonNull MultiBranchProject<P, R> parent, @NonNull String legacyDirName) { return NameMangler.apply(NameEncoder.decode(legacyDirName)); } @Override public void recordLegacyName(MultiBranchProject<P, R> parent, P item, String legacyDirName) throws IOException { // no-op because we already tracked the name in Branch.getName() } } }