diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java index 0e4ee5743d..dc8ed356c6 100644 --- a/core/src/main/java/hudson/DescriptorExtensionList.java +++ b/core/src/main/java/hudson/DescriptorExtensionList.java @@ -28,6 +28,8 @@ import hudson.model.Describable; import hudson.model.Hudson; import hudson.model.ViewDescriptor; import hudson.model.Descriptor.FormException; +import hudson.util.AdaptedIterator; +import hudson.util.CopyOnWriteList; import hudson.util.Memoizer; import hudson.util.Iterators.FlattenIterator; import hudson.slaves.NodeDescriptor; @@ -79,7 +81,7 @@ public class DescriptorExtensionList, D extends Descrip private final Class describableType; protected DescriptorExtensionList(Hudson hudson, Class describableType) { - super(hudson, (Class)Descriptor.class, legacyDescriptors.get(describableType)); + super(hudson, (Class)Descriptor.class, (CopyOnWriteArrayList)getLegacyDescriptors(describableType)); this.describableType = describableType; } @@ -136,16 +138,17 @@ public class DescriptorExtensionList, D extends Descrip * Loading the descriptors in this case means filtering the descriptor from the master {@link ExtensionList}. */ @Override - protected List load() { - List r = new ArrayList(); - for( Descriptor d : hudson.getExtensionList(Descriptor.class) ) { + protected List> load() { + List> r = new ArrayList>(); + for( ExtensionComponent c : hudson.getExtensionList(Descriptor.class).getComponents() ) { + Descriptor d = c.getInstance(); Type subTyping = Types.getBaseClass(d.getClass(), Descriptor.class); if (!(subTyping instanceof ParameterizedType)) { LOGGER.severe(d.getClass()+" doesn't extend Descriptor with a type parameter"); continue; // skip this one } if(Types.erasure(Types.getTypeArgument(subTyping,0))==(Class)describableType) - r.add(d); + r.add((ExtensionComponent)c); } return r; } @@ -153,21 +156,31 @@ public class DescriptorExtensionList, D extends Descrip /** * Stores manually registered Descriptor instances. Keyed by the {@link Describable} type. */ - private static final Memoizer legacyDescriptors = new Memoizer() { + private static final Memoizer>> legacyDescriptors = new Memoizer>>() { public CopyOnWriteArrayList compute(Class key) { return new CopyOnWriteArrayList(); } }; + private static > CopyOnWriteArrayList>> getLegacyDescriptors(Class type) { + return (CopyOnWriteArrayList)legacyDescriptors.get(type); + } + /** * List up all the legacy instances currently in use. */ public static Iterable listLegacyInstances() { return new Iterable() { public Iterator iterator() { - return new FlattenIterator(legacyDescriptors.values()) { - protected Iterator expand(CopyOnWriteArrayList v) { - return v.iterator(); + return new AdaptedIterator,Descriptor>( + new FlattenIterator,CopyOnWriteArrayList>>(legacyDescriptors.values()) { + protected Iterator> expand(CopyOnWriteArrayList> v) { + return v.iterator(); + } + }) { + + protected Descriptor adapt(ExtensionComponent item) { + return item.getInstance(); } }; } diff --git a/core/src/main/java/hudson/ExtensionComponent.java b/core/src/main/java/hudson/ExtensionComponent.java new file mode 100644 index 0000000000..2f7eea3e13 --- /dev/null +++ b/core/src/main/java/hudson/ExtensionComponent.java @@ -0,0 +1,77 @@ +/* + * The MIT License + * + * Copyright (c) 2010, Kohsuke Kawaguchi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package hudson; + +/** + * Discovered {@link Extension} object with a bit of metadata for Hudson. + * This is a plain value object. + * + * @author Kohsuke Kawaguchi + * @since 1.356 + */ +public class ExtensionComponent implements Comparable> { + private final T instance; + private final double ordinal; + + public ExtensionComponent(T instance, double ordinal) { + this.instance = instance; + this.ordinal = ordinal; + } + + public ExtensionComponent(T instance, Extension annotation) { + this(instance,annotation.ordinal()); + } + + public ExtensionComponent(T instance) { + this(instance,0); + } + + /** + * See {@link Extension#ordinal()}. Used to sort extensions. + */ + public double ordinal() { + return ordinal; + } + + /** + * The instance of the discovered extension. + * + * @return never null. + */ + public T getInstance() { + return instance; + } + + /** + * Sort {@link ExtensionComponent}s in the descending order of {@link #ordinal()}. + */ + public int compareTo(ExtensionComponent that) { + double a = this.ordinal(); + double b = that.ordinal(); + if (a>b) return -1; + if (a Collection findExtensions(Class type, Hudson hudson) { + return Collections.emptyList(); + } + /** * Discover extensions of the given type. * @@ -67,8 +79,10 @@ public abstract class ExtensionFinder implements ExtensionPoint { * Hudson whose behalf this extension finder is performing lookup. * @return * Can be empty but never null. + * @since 1.356 + * Older implementations provide {@link #findExtensions(Class, Hudson)} */ - public abstract Collection findExtensions(Class type, Hudson hudson); + public abstract Collection> find(Class type, Hudson hudson); /** * The default implementation that looks for the {@link Extension} marker. @@ -78,8 +92,8 @@ public abstract class ExtensionFinder implements ExtensionPoint { */ @Extension public static final class Sezpoz extends ExtensionFinder { - public Collection findExtensions(Class type, Hudson hudson) { - List result = new ArrayList(); + public Collection> find(Class type, Hudson hudson) { + List> result = new ArrayList>(); ClassLoader cl = hudson.getPluginManager().uberClassLoader; for (IndexItem item : Index.load(Extension.class, Object.class, cl)) { @@ -100,7 +114,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { if(type.isAssignableFrom(extType)) { Object instance = item.instance(); if(instance!=null) - result.add(type.cast(instance)); + result.add(new ExtensionComponent(type.cast(instance),item.annotation())); } } catch (InstantiationException e) { LOGGER.log(Level.WARNING, "Failed to load "+item.className(),e); diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java index 5e519928d9..b764da0b92 100644 --- a/core/src/main/java/hudson/ExtensionList.java +++ b/core/src/main/java/hudson/ExtensionList.java @@ -25,6 +25,7 @@ package hudson; import hudson.init.InitMilestone; import hudson.model.Hudson; +import hudson.util.AdaptedIterator; import hudson.util.DescriptorList; import hudson.util.Memoizer; import hudson.util.Iterators; @@ -36,7 +37,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Vector; -import java.util.Comparator; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -70,16 +70,16 @@ public class ExtensionList extends AbstractList { * Once discovered, extensions are retained here. */ @CopyOnWrite - private volatile List extensions; + private volatile List> extensions; /** * Place to store manually registered instances with the per-Hudson scope. * {@link CopyOnWriteArrayList} is used here to support concurrent iterations and mutation. */ - private final CopyOnWriteArrayList legacyInstances; + private final CopyOnWriteArrayList> legacyInstances; protected ExtensionList(Hudson hudson, Class extensionType) { - this(hudson,extensionType,new CopyOnWriteArrayList()); + this(hudson,extensionType,new CopyOnWriteArrayList>()); } /** @@ -89,7 +89,7 @@ public class ExtensionList extends AbstractList { * omits this uses a new {@link Vector}, making the storage lifespan tied to the life of {@link ExtensionList}. * If the manually registered instances are scoped to VM level, the caller should pass in a static list. */ - protected ExtensionList(Hudson hudson, Class extensionType, CopyOnWriteArrayList legacyStore) { + protected ExtensionList(Hudson hudson, Class extensionType, CopyOnWriteArrayList> legacyStore) { this.hudson = hudson; this.extensionType = extensionType; this.legacyInstances = legacyStore; @@ -109,11 +109,22 @@ public class ExtensionList extends AbstractList { @Override public Iterator iterator() { // we need to intercept mutation, so for now don't allow Iterator.remove - return Iterators.readOnly(ensureLoaded().iterator()); + return new AdaptedIterator,T>(Iterators.readOnly(ensureLoaded().iterator())) { + protected T adapt(ExtensionComponent item) { + return item.getInstance(); + } + }; + } + + /** + * Gets the same thing as the 'this' list represents, except as {@link ExtensionComponent}s. + */ + public List> getComponents() { + return Collections.unmodifiableList(ensureLoaded()); } public T get(int index) { - return ensureLoaded().get(index); + return ensureLoaded().get(index).getInstance(); } public int size() { @@ -124,7 +135,13 @@ public class ExtensionList extends AbstractList { public synchronized boolean remove(Object o) { legacyInstances.remove(o); if(extensions!=null) { - List r = new ArrayList(extensions); + List> r = new ArrayList>(extensions); + + for (Iterator> itr = r.iterator(); itr.hasNext();) { + ExtensionComponent c = itr.next(); + if (c.getInstance().equals(o)) + itr.remove(); + } r.remove(o); extensions = sort(r); } @@ -146,11 +163,11 @@ public class ExtensionList extends AbstractList { */ @Override public synchronized boolean add(T t) { - legacyInstances.add(t); + legacyInstances.add(new ExtensionComponent(t)); // if we've already filled extensions, add it if(extensions!=null) { - List r = new ArrayList(extensions); - r.add(t); + List> r = new ArrayList>(extensions); + r.add(new ExtensionComponent(t)); extensions = sort(r); } return true; @@ -181,7 +198,7 @@ public class ExtensionList extends AbstractList { return hudson.getExtensionList(ExtensionFinder.class); } - private List ensureLoaded() { + private List> ensureLoaded() { if(extensions!=null) return extensions; // already loaded if(Hudson.getInstance().getInitLevel().compareTo(InitMilestone.PLUGINS_PREPARED)<0) @@ -189,7 +206,7 @@ public class ExtensionList extends AbstractList { synchronized (this) { if(extensions==null) { - List r = load(); + List> r = load(); r.addAll(legacyInstances); extensions = sort(r); } @@ -200,13 +217,20 @@ public class ExtensionList extends AbstractList { /** * Loads all the extensions. */ - protected List load() { + protected List> load() { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE,"Loading ExtensionList: "+extensionType, new Throwable()); - List r = new ArrayList(); - for (ExtensionFinder finder : finders()) - r.addAll(finder.findExtensions(extensionType, hudson)); + List> r = new ArrayList>(); + for (ExtensionFinder finder : finders()) { + try { + r.addAll(finder.find(extensionType, hudson)); + } catch (AbstractMethodError e) { + // backward compatibility + for (T t : finder.findExtensions(extensionType, hudson)) + r.add(new ExtensionComponent(t)); + } + } return r; } @@ -217,21 +241,9 @@ public class ExtensionList extends AbstractList { *

* The implementation should copy a list, do a sort, and return the new instance. */ - protected List sort(List r) { - r = new ArrayList(r); - Collections.sort(r,new Comparator() { - public int compare(T lhs, T rhs) { - Extension el = lhs.getClass().getAnnotation(Extension.class); - Extension er = rhs.getClass().getAnnotation(Extension.class); - // Extension used to be Retention.SOURCE, and we may also end up loading extensions from other places - // like Plexus that don't have any annotations, so we need to be defensive - double l = el!=null ? el.ordinal() : 0; - double r = er!=null ? er.ordinal() : 0; - if(l>r) return -1; - if(l> sort(List> r) { + r = new ArrayList>(r); + Collections.sort(r); return r; } diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java index 8af0196ba8..db6f32ba99 100644 --- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java +++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java @@ -24,6 +24,7 @@ package hudson.cli.declarative; import hudson.Extension; +import hudson.ExtensionComponent; import hudson.ExtensionFinder; import hudson.Util; import hudson.cli.CLICommand; @@ -61,9 +62,9 @@ import java.util.logging.Logger; */ @Extension public class CLIRegisterer extends ExtensionFinder { - public Collection findExtensions(Class type, Hudson hudson) { + public Collection> find(Class type, Hudson hudson) { if (type==CLICommand.class) - return (List)discover(hudson); + return (List)discover(hudson); else return Collections.emptyList(); } @@ -80,9 +81,9 @@ public class CLIRegisterer extends ExtensionFinder { return null; } - private List discover(final Hudson hudson) { + private List> discover(final Hudson hudson) { LOGGER.fine("Listing up @CLIMethod"); - List r = new ArrayList(); + List> r = new ArrayList>(); try { for ( final Method m : Util.filter(Index.list(CLIMethod.class, hudson.getPluginManager().uberClassLoader),Method.class)) { @@ -93,7 +94,7 @@ public class CLIRegisterer extends ExtensionFinder { final ResourceBundleHolder res = loadMessageBundle(m); res.format("CLI."+name+".shortDescription"); // make sure we have the resource, to fail early - r.add(new CloneableCLICommand() { + r.add(new ExtensionComponent(new CloneableCLICommand() { @Override public String getName() { return name; @@ -172,7 +173,7 @@ public class CLIRegisterer extends ExtensionFinder { protected int run() throws Exception { throw new UnsupportedOperationException(); } - }); + })); } catch (ClassNotFoundException e) { LOGGER.log(SEVERE,"Failed to process @CLIMethod: "+m,e); } diff --git a/core/src/main/java/hudson/tasks/Publisher.java b/core/src/main/java/hudson/tasks/Publisher.java index 3b6b71c212..7714671b59 100644 --- a/core/src/main/java/hudson/tasks/Publisher.java +++ b/core/src/main/java/hudson/tasks/Publisher.java @@ -25,6 +25,7 @@ package hudson.tasks; import hudson.DescriptorExtensionList; import hudson.Extension; +import hudson.ExtensionComponent; import hudson.Launcher; import hudson.model.Action; import hudson.model.Build; @@ -130,20 +131,22 @@ public abstract class Publisher extends BuildStepCompatibilityLayer implements B * @see DescriptorExtensionList#create(Hudson, Class) */ public static final class DescriptorExtensionListImpl extends DescriptorExtensionList> - implements Comparator> { + implements Comparator>> { public DescriptorExtensionListImpl(Hudson hudson) { super(hudson,Publisher.class); } @Override - protected List> sort(List> r) { - List> copy = new ArrayList>(r); + protected List>> sort(List>> r) { + List>> copy = new ArrayList>>(r); Collections.sort(copy,this); return copy; } - public int compare(Descriptor lhs, Descriptor rhs) { - return classify(lhs)-classify(rhs); + public int compare(ExtensionComponent> lhs, ExtensionComponent> rhs) { + int r = classify(lhs.getInstance())-classify(rhs.getInstance()); + if (r!=0) return r; + return lhs.compareTo(rhs); } /** diff --git a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java index 16a8ad6997..b95937dbde 100644 --- a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java +++ b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java @@ -24,6 +24,7 @@ package org.jvnet.hudson.test; import hudson.Extension; +import hudson.ExtensionComponent; import hudson.ExtensionFinder; import hudson.model.Hudson; import net.java.sezpoz.Index; @@ -46,10 +47,10 @@ import java.util.logging.Logger; @Extension public class TestExtensionLoader extends ExtensionFinder { @Override - public Collection findExtensions(Class type, Hudson hudson) { + public Collection> find(Class type, Hudson hudson) { TestEnvironment env = TestEnvironment.get(); - List result = new ArrayList(); + List> result = new ArrayList>(); ClassLoader cl = hudson.getPluginManager().uberClassLoader; for (IndexItem item : Index.load(TestExtension.class, Object.class, cl)) { @@ -81,7 +82,7 @@ public class TestExtensionLoader extends ExtensionFinder { if(type.isAssignableFrom(extType)) { Object instance = item.instance(); if(instance!=null) - result.add(type.cast(instance)); + result.add(new ExtensionComponent(type.cast(instance))); } } catch (InstantiationException e) { LOGGER.log(Level.WARNING, "Failed to load "+item.className(),e); diff --git a/test/src/test/java/hudson/ExtensionListTest.java b/test/src/test/java/hudson/ExtensionListTest.java index 0d92eea79f..7e755becdd 100644 --- a/test/src/test/java/hudson/ExtensionListTest.java +++ b/test/src/test/java/hudson/ExtensionListTest.java @@ -150,4 +150,36 @@ public class ExtensionListTest extends HudsonTestCase { LIST = new DescriptorList(); assertEquals(0,LIST.size()); } + + public static class Car implements ExtensionPoint { + final String name; + + public Car(String name) { + this.name = name; + } + } + + @Extension(ordinal=1) + public static class Toyota extends Car { + public Toyota() { + super("toyota"); + } + } + + @Extension(ordinal=3) + public static Car honda() { return new Car("honda"); } + + + @Extension(ordinal=2) + public static final Car mazda = new Car("mazda"); + + /** + * Makes sure sorting of the components work as expected. + */ + public void testOrdinals() { + ExtensionList list = hudson.getExtensionList(Car.class); + assertEquals("honda",list.get(0).name); + assertEquals("mazda",list.get(1).name); + assertEquals("toyota",list.get(2).name); + } }