提交 000a7c33 编写于 作者: K kohsuke

Extension points can be now sorted.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@30071 71c3de6d-444a-0410-be80-ed276b4c234a
上级 6253fb1b
......@@ -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<T extends Describable<T>, D extends Descrip
private final Class<T> describableType;
protected DescriptorExtensionList(Hudson hudson, Class<T> 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<T extends Describable<T>, D extends Descrip
* Loading the descriptors in this case means filtering the descriptor from the master {@link ExtensionList}.
*/
@Override
protected List<D> load() {
List r = new ArrayList();
for( Descriptor d : hudson.getExtensionList(Descriptor.class) ) {
protected List<ExtensionComponent<D>> load() {
List<ExtensionComponent<D>> r = new ArrayList<ExtensionComponent<D>>();
for( ExtensionComponent<Descriptor> 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<T extends Describable<T>, D extends Descrip
/**
* Stores manually registered Descriptor instances. Keyed by the {@link Describable} type.
*/
private static final Memoizer<Class,CopyOnWriteArrayList> legacyDescriptors = new Memoizer<Class,CopyOnWriteArrayList>() {
private static final Memoizer<Class,CopyOnWriteArrayList<ExtensionComponent<Descriptor>>> legacyDescriptors = new Memoizer<Class,CopyOnWriteArrayList<ExtensionComponent<Descriptor>>>() {
public CopyOnWriteArrayList compute(Class key) {
return new CopyOnWriteArrayList();
}
};
private static <T extends Describable<T>> CopyOnWriteArrayList<ExtensionComponent<Descriptor<T>>> getLegacyDescriptors(Class<T> type) {
return (CopyOnWriteArrayList)legacyDescriptors.get(type);
}
/**
* List up all the legacy instances currently in use.
*/
public static Iterable<Descriptor> listLegacyInstances() {
return new Iterable<Descriptor>() {
public Iterator<Descriptor> iterator() {
return new FlattenIterator<Descriptor,CopyOnWriteArrayList>(legacyDescriptors.values()) {
protected Iterator expand(CopyOnWriteArrayList v) {
return v.iterator();
return new AdaptedIterator<ExtensionComponent<Descriptor>,Descriptor>(
new FlattenIterator<ExtensionComponent<Descriptor>,CopyOnWriteArrayList<ExtensionComponent<Descriptor>>>(legacyDescriptors.values()) {
protected Iterator<ExtensionComponent<Descriptor>> expand(CopyOnWriteArrayList<ExtensionComponent<Descriptor>> v) {
return v.iterator();
}
}) {
protected Descriptor adapt(ExtensionComponent<Descriptor> item) {
return item.getInstance();
}
};
}
......
/*
* 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<T> implements Comparable<ExtensionComponent<T>> {
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<T> that) {
double a = this.ordinal();
double b = that.ordinal();
if (a>b) return -1;
if (a<b) return 1;
return 0;
}
}
......@@ -27,7 +27,10 @@ import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
import hudson.model.Hudson;
import hudson.model.Descriptor;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.util.Collections;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.List;
......@@ -52,6 +55,15 @@ import java.lang.reflect.Method;
* @since 1.286
*/
public abstract class ExtensionFinder implements ExtensionPoint {
/**
* @deprecated as of 1.356
* Use and implement {@link #find(Class, Hudson)} that allows us to put some metadata.
*/
@Restricted(NoExternalUse.class)
public <T> Collection<T> findExtensions(Class<T> 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 <T> Collection<T> findExtensions(Class<T> type, Hudson hudson);
public abstract <T> Collection<ExtensionComponent<T>> find(Class<T> 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 <T> Collection<T> findExtensions(Class<T> type, Hudson hudson) {
List<T> result = new ArrayList<T>();
public <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson) {
List<ExtensionComponent<T>> result = new ArrayList<ExtensionComponent<T>>();
ClassLoader cl = hudson.getPluginManager().uberClassLoader;
for (IndexItem<Extension,Object> 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<T>(type.cast(instance),item.annotation()));
}
} catch (InstantiationException e) {
LOGGER.log(Level.WARNING, "Failed to load "+item.className(),e);
......
......@@ -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<T> extends AbstractList<T> {
* Once discovered, extensions are retained here.
*/
@CopyOnWrite
private volatile List<T> extensions;
private volatile List<ExtensionComponent<T>> 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<T> legacyInstances;
private final CopyOnWriteArrayList<ExtensionComponent<T>> legacyInstances;
protected ExtensionList(Hudson hudson, Class<T> extensionType) {
this(hudson,extensionType,new CopyOnWriteArrayList<T>());
this(hudson,extensionType,new CopyOnWriteArrayList<ExtensionComponent<T>>());
}
/**
......@@ -89,7 +89,7 @@ public class ExtensionList<T> extends AbstractList<T> {
* 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<T> extensionType, CopyOnWriteArrayList<T> legacyStore) {
protected ExtensionList(Hudson hudson, Class<T> extensionType, CopyOnWriteArrayList<ExtensionComponent<T>> legacyStore) {
this.hudson = hudson;
this.extensionType = extensionType;
this.legacyInstances = legacyStore;
......@@ -109,11 +109,22 @@ public class ExtensionList<T> extends AbstractList<T> {
@Override
public Iterator<T> iterator() {
// we need to intercept mutation, so for now don't allow Iterator.remove
return Iterators.readOnly(ensureLoaded().iterator());
return new AdaptedIterator<ExtensionComponent<T>,T>(Iterators.readOnly(ensureLoaded().iterator())) {
protected T adapt(ExtensionComponent<T> item) {
return item.getInstance();
}
};
}
/**
* Gets the same thing as the 'this' list represents, except as {@link ExtensionComponent}s.
*/
public List<ExtensionComponent<T>> 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<T> extends AbstractList<T> {
public synchronized boolean remove(Object o) {
legacyInstances.remove(o);
if(extensions!=null) {
List<T> r = new ArrayList<T>(extensions);
List<ExtensionComponent<T>> r = new ArrayList<ExtensionComponent<T>>(extensions);
for (Iterator<ExtensionComponent<T>> itr = r.iterator(); itr.hasNext();) {
ExtensionComponent<T> c = itr.next();
if (c.getInstance().equals(o))
itr.remove();
}
r.remove(o);
extensions = sort(r);
}
......@@ -146,11 +163,11 @@ public class ExtensionList<T> extends AbstractList<T> {
*/
@Override
public synchronized boolean add(T t) {
legacyInstances.add(t);
legacyInstances.add(new ExtensionComponent<T>(t));
// if we've already filled extensions, add it
if(extensions!=null) {
List<T> r = new ArrayList<T>(extensions);
r.add(t);
List<ExtensionComponent<T>> r = new ArrayList<ExtensionComponent<T>>(extensions);
r.add(new ExtensionComponent<T>(t));
extensions = sort(r);
}
return true;
......@@ -181,7 +198,7 @@ public class ExtensionList<T> extends AbstractList<T> {
return hudson.getExtensionList(ExtensionFinder.class);
}
private List<T> ensureLoaded() {
private List<ExtensionComponent<T>> ensureLoaded() {
if(extensions!=null)
return extensions; // already loaded
if(Hudson.getInstance().getInitLevel().compareTo(InitMilestone.PLUGINS_PREPARED)<0)
......@@ -189,7 +206,7 @@ public class ExtensionList<T> extends AbstractList<T> {
synchronized (this) {
if(extensions==null) {
List<T> r = load();
List<ExtensionComponent<T>> r = load();
r.addAll(legacyInstances);
extensions = sort(r);
}
......@@ -200,13 +217,20 @@ public class ExtensionList<T> extends AbstractList<T> {
/**
* Loads all the extensions.
*/
protected List<T> load() {
protected List<ExtensionComponent<T>> load() {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,"Loading ExtensionList: "+extensionType, new Throwable());
List<T> r = new ArrayList<T>();
for (ExtensionFinder finder : finders())
r.addAll(finder.findExtensions(extensionType, hudson));
List<ExtensionComponent<T>> r = new ArrayList<ExtensionComponent<T>>();
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>(t));
}
}
return r;
}
......@@ -217,21 +241,9 @@ public class ExtensionList<T> extends AbstractList<T> {
* <p>
* The implementation should copy a list, do a sort, and return the new instance.
*/
protected List<T> sort(List<T> r) {
r = new ArrayList<T>(r);
Collections.sort(r,new Comparator<T>() {
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<r) return 1;
return 0;
}
});
protected List<ExtensionComponent<T>> sort(List<ExtensionComponent<T>> r) {
r = new ArrayList<ExtensionComponent<T>>(r);
Collections.sort(r);
return r;
}
......
......@@ -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 <T> Collection<T> findExtensions(Class<T> type, Hudson hudson) {
public <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson) {
if (type==CLICommand.class)
return (List<T>)discover(hudson);
return (List)discover(hudson);
else
return Collections.emptyList();
}
......@@ -80,9 +81,9 @@ public class CLIRegisterer extends ExtensionFinder {
return null;
}
private List<CLICommand> discover(final Hudson hudson) {
private List<ExtensionComponent<CLICommand>> discover(final Hudson hudson) {
LOGGER.fine("Listing up @CLIMethod");
List<CLICommand> r = new ArrayList<CLICommand>();
List<ExtensionComponent<CLICommand>> r = new ArrayList<ExtensionComponent<CLICommand>>();
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<CLICommand>(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);
}
......
......@@ -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<Publisher,Descriptor<Publisher>>
implements Comparator<Descriptor<Publisher>> {
implements Comparator<ExtensionComponent<Descriptor<Publisher>>> {
public DescriptorExtensionListImpl(Hudson hudson) {
super(hudson,Publisher.class);
}
@Override
protected List<Descriptor<Publisher>> sort(List<Descriptor<Publisher>> r) {
List<Descriptor<Publisher>> copy = new ArrayList<Descriptor<Publisher>>(r);
protected List<ExtensionComponent<Descriptor<Publisher>>> sort(List<ExtensionComponent<Descriptor<Publisher>>> r) {
List<ExtensionComponent<Descriptor<Publisher>>> copy = new ArrayList<ExtensionComponent<Descriptor<Publisher>>>(r);
Collections.sort(copy,this);
return copy;
}
public int compare(Descriptor<Publisher> lhs, Descriptor<Publisher> rhs) {
return classify(lhs)-classify(rhs);
public int compare(ExtensionComponent<Descriptor<Publisher>> lhs, ExtensionComponent<Descriptor<Publisher>> rhs) {
int r = classify(lhs.getInstance())-classify(rhs.getInstance());
if (r!=0) return r;
return lhs.compareTo(rhs);
}
/**
......
......@@ -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 <T> Collection<T> findExtensions(Class<T> type, Hudson hudson) {
public <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson) {
TestEnvironment env = TestEnvironment.get();
List<T> result = new ArrayList<T>();
List<ExtensionComponent<T>> result = new ArrayList<ExtensionComponent<T>>();
ClassLoader cl = hudson.getPluginManager().uberClassLoader;
for (IndexItem<TestExtension,Object> 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<T>(type.cast(instance)));
}
} catch (InstantiationException e) {
LOGGER.log(Level.WARNING, "Failed to load "+item.className(),e);
......
......@@ -150,4 +150,36 @@ public class ExtensionListTest extends HudsonTestCase {
LIST = new DescriptorList<Fish>();
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<Car> list = hudson.getExtensionList(Car.class);
assertEquals("honda",list.get(0).name);
assertEquals("mazda",list.get(1).name);
assertEquals("toyota",list.get(2).name);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册