diff --git a/extensions/extensions.iml b/extensions/extensions.iml new file mode 100644 index 000000000000..8aa391e2c744 --- /dev/null +++ b/extensions/extensions.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/source/com/intellij/openapi/extensions/AreaInstance.java b/extensions/source/com/intellij/openapi/extensions/AreaInstance.java new file mode 100644 index 000000000000..db6a594d2d90 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/AreaInstance.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author akireyev + */ +public interface AreaInstance { +} diff --git a/extensions/source/com/intellij/openapi/extensions/AreaListener.java b/extensions/source/com/intellij/openapi/extensions/AreaListener.java new file mode 100644 index 000000000000..ded299db81bc --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/AreaListener.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author akireyev + */ +public interface AreaListener { + void areaCreated(String areaClass, AreaInstance areaInstance); + void areaDisposing(String areaClass, AreaInstance areaInstance); +} diff --git a/extensions/source/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java b/extensions/source/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java new file mode 100644 index 000000000000..3cd342f23841 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author AKireyev + */ +public class EPAvailabilityListenerExtension { + public static final String EXTENSION_POINT_NAME = "jetbrains.fabrique.framework.epAvailabilityListener"; + + private String myExtensionPointName; + private String myListenerClass; + + public EPAvailabilityListenerExtension() { + } + + public EPAvailabilityListenerExtension(String extensionPointName, String listenerClass) { + myExtensionPointName = extensionPointName; + myListenerClass = listenerClass; + } + + public String getExtensionPointName() { + return myExtensionPointName; + } + + public void setExtensionPointName(String extensionPointName) { + myExtensionPointName = extensionPointName; + } + + public String getListenerClass() { + return myListenerClass; + } + + public void setListenerClass(String listenerClass) { + myListenerClass = listenerClass; + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/Extension.java b/extensions/source/com/intellij/openapi/extensions/Extension.java new file mode 100644 index 000000000000..9453cb5229e3 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/Extension.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author kir + * + * An extension can implement this interface to get notifications when it is added/removed to {@link ExtensionPoint} + */ +public interface Extension { + void extensionAdded(ExtensionPoint extensionPoint); + void extensionRemoved(ExtensionPoint extensionPoint); +} diff --git a/extensions/source/com/intellij/openapi/extensions/ExtensionPoint.java b/extensions/source/com/intellij/openapi/extensions/ExtensionPoint.java new file mode 100644 index 000000000000..915a09dbb80a --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/ExtensionPoint.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author AKireyev + */ +public interface ExtensionPoint { + String getName(); + AreaInstance getArea(); + + String getBeanClassName(); + + void registerExtension(Object extension); + void registerExtension(Object extension, LoadingOrder order); + + Object[] getExtensions(); + Object getExtension(); + boolean hasExtension(Object extension); + + void unregisterExtension(Object extension); + + void addExtensionPointListener(ExtensionPointListener listener); + void removeExtensionPointListener(ExtensionPointListener extensionPointListener); + + void reset(); + + Class getExtensionClass(); +} diff --git a/extensions/source/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java b/extensions/source/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java new file mode 100644 index 000000000000..6df20949788f --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author AKireyev + */ +public interface ExtensionPointAvailabilityListener { + void extensionPointRegistered(ExtensionPoint extensionPoint); + void extensionPointRemoved(ExtensionPoint extensionPoint); +} diff --git a/extensions/source/com/intellij/openapi/extensions/ExtensionPointListener.java b/extensions/source/com/intellij/openapi/extensions/ExtensionPointListener.java new file mode 100644 index 000000000000..555c8d38268c --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/ExtensionPointListener.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +public interface ExtensionPointListener { + void extensionAdded(Object extension); + void extensionRemoved(Object extension); +} diff --git a/extensions/source/com/intellij/openapi/extensions/Extensions.java b/extensions/source/com/intellij/openapi/extensions/Extensions.java new file mode 100644 index 000000000000..050198016230 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/Extensions.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl; +import org.apache.commons.collections.MultiHashMap; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +// todo: make default area instance a non-null object + +public abstract class Extensions { + private static LogProvider ourLogger = new SimpleLogProvider(); + + public static final String AREA_LISTENER_EXTENSION_POINT = "jetbrains.fabrique.platform.areaListeners"; + + private static Map ourAreaClass2prototypeArea; + private static Map ourAreaInstance2area; + private static MultiHashMap ourAreaClass2instances; + private static Map ourAreaInstance2class; + private static Map ourAreaClass2Configuration; + + public static ExtensionsArea getRootArea() { + return getArea(null); + } + + public static ExtensionsArea getArea(AreaInstance areaInstance) { + init(); + if (!ourAreaInstance2area.containsKey(areaInstance)) { + throw new IllegalArgumentException("No area instantiated for: " + areaInstance); + } + return (ExtensionsArea) ourAreaInstance2area.get(areaInstance); + } + + public static Object[] getExtensions(String extensionPointName) { + return getExtensions(extensionPointName, null); + } + + public static Object[] getExtensions(String extensionPointName, AreaInstance areaInstance) { + ExtensionsArea area = getArea(areaInstance); + assert area != null: "Unable to get area for " + areaInstance; + ExtensionPoint extensionPoint = area.getExtensionPoint(extensionPointName); + assert extensionPoint != null: "Unable to get extension point " + extensionPoint + " for " + areaInstance; + return extensionPoint.getExtensions(); + } + + private static void init() { + if (ourAreaInstance2area == null) { + ourAreaInstance2area = new HashMap(); + ourAreaClass2prototypeArea = new HashMap(); + ourAreaClass2instances = new MultiHashMap(); + ourAreaInstance2class = new HashMap(); + ourAreaClass2Configuration = new HashMap(); + ExtensionsAreaImpl rootArea = new ExtensionsAreaImpl(null, null, null, ourLogger); + ourAreaInstance2area.put(null, rootArea); + ourAreaClass2prototypeArea.put(null, rootArea); + rootArea.registerExtensionPoint(AREA_LISTENER_EXTENSION_POINT, AreaListener.class.getName()); + } + } + + static void reset() { + ourAreaInstance2area = null; + ourAreaClass2instances = null; + ourAreaClass2prototypeArea = null; + ourAreaInstance2class = null; + } + + public static void instantiateArea(String areaClass, AreaInstance areaInstance, AreaInstance parentAreaInstance) { + if (areaClass == null) { + throw new IllegalArgumentException("Should not try to instantiate the root area"); + } + init(); + if (!ourAreaClass2Configuration.containsKey(areaClass)) { + throw new IllegalArgumentException("Area class is not registered: " + areaClass); + } + if (ourAreaInstance2area.containsKey(areaInstance)) { + throw new IllegalArgumentException("Area already instantiated for: " + areaInstance); + } + ExtensionsArea parentArea = getArea(parentAreaInstance); + AreaClassConfiguration configuration = (AreaClassConfiguration)ourAreaClass2Configuration.get(areaClass); + if (!equals(parentArea.getAreaClass(), configuration.getParentClassName())) { + throw new IllegalArgumentException("Wrong parent area. Expected class: " + configuration.getParentClassName() + " actual class: " + parentArea.getAreaClass()); + } + ExtensionsAreaImpl area = new ExtensionsAreaImpl(areaClass, areaInstance, parentArea.getPicoContainer(), ourLogger); + ourAreaInstance2area.put(areaInstance, area); + ourAreaClass2instances.put(areaClass, areaInstance); + ourAreaInstance2class.put(areaInstance, areaClass); + AreaListener[] listeners = getAreaListeners(); + for (int i = 0; i < listeners.length; i++) { + AreaListener listener = listeners[i]; + listener.areaCreated(areaClass, areaInstance); + } + } + + private static AreaListener[] getAreaListeners() { + AreaListener[] listeners = (AreaListener[]) getRootArea().getExtensionPoint(AREA_LISTENER_EXTENSION_POINT).getExtensions(); + return listeners; + } + + public static void registerAreaClass(String areaClass, String parentAreaClass) { + init(); + if (ourAreaClass2Configuration.containsKey(areaClass)) { + // allow duplicate area class registrations if they are the same - fixing duplicate registration in tests is much more trouble + AreaClassConfiguration configuration = (AreaClassConfiguration)ourAreaClass2Configuration.get(areaClass); + if (!equals(configuration.getParentClassName(), parentAreaClass)) { + throw new RuntimeException("Area class already registered: " + areaClass, ((AreaClassConfiguration)ourAreaClass2Configuration.get(areaClass)).getCreationPoint()); + } + else { + return; + } + } + AreaClassConfiguration configuration = new AreaClassConfiguration(areaClass, parentAreaClass); + ourAreaClass2Configuration.put(areaClass, configuration); + } + + public static void disposeArea(AreaInstance areaInstance) { + assert ourAreaInstance2area.containsKey(areaInstance); + if (areaInstance == null) { + throw new IllegalArgumentException("Cannot dispose root area"); + } + + AreaListener[] listeners = getAreaListeners(); + String areaClass = (String) ourAreaInstance2class.get(areaInstance); + if (areaClass == null) { + throw new IllegalArgumentException("Area class is null (area never instantiated?). Instance: " + areaInstance); + } + try { + for (int i = 0; i < listeners.length; i++) { + listeners[i].areaDisposing(areaClass, areaInstance); + } + } finally { + ourAreaInstance2area.remove(areaInstance); + ourAreaClass2instances.remove(ourAreaInstance2class.remove(areaInstance), areaInstance); + ourAreaInstance2class.remove(areaInstance); + } + } + + public static AreaInstance[] getAllAreas() { + init(); + final Set keys = ourAreaInstance2area.keySet(); + return (AreaInstance[]) keys.toArray(new AreaInstance[keys.size()]); + } + + public static AreaInstance[] getAllAreas(String areaClass) { + Collection instances = (Collection) ourAreaClass2instances.get(areaClass); + if (instances != null) { + return (AreaInstance[]) instances.toArray(new AreaInstance[instances.size()]); + } + return new AreaInstance[0]; + } + + public static Object getAreaClass(AreaInstance areaInstance) { + if (areaInstance == null) return null; + + assert ourAreaInstance2class.containsKey(areaInstance); + return ourAreaInstance2class.get(areaInstance); + } + + public static void unregisterAreaClass(String areaClass) { + init(); + assert ourAreaClass2Configuration.containsKey(areaClass) : "Area class is not registered: " + areaClass; + ourAreaClass2Configuration.remove(areaClass); + } + + private static boolean equals(Object object1, Object object2) { + if (object1 == object2) { + return true; + } + if ((object1 == null) || (object2 == null)) { + return false; + } + return object1.equals(object2); + } + + public static void setLogProvider(LogProvider logProvider) { + ourLogger = logProvider; + } + + private static class AreaClassConfiguration { + private String myClassName; + private String myParentClassName; + private Throwable myCreationPoint; + + AreaClassConfiguration(String className, String parentClassName) { + myCreationPoint = new Throwable(); + myClassName = className; + myParentClassName = parentClassName; + } + + public Throwable getCreationPoint() { + return myCreationPoint; + } + + public String getClassName() { + return myClassName; + } + + public String getParentClassName() { + return myParentClassName; + } + } + + public static class SimpleLogProvider implements LogProvider { + public void error(String message) { + new Throwable(message).printStackTrace(); + } + + public void error(String message, Throwable t) { + System.err.println(message); + t.printStackTrace(); + } + + public void error(Throwable t) { + t.printStackTrace(); + } + + public void warn(String message) { + System.err.println(message); + } + + public void warn(String message, Throwable t) { + System.err.println(message); + t.printStackTrace(); + } + + public void warn(Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/ExtensionsArea.java b/extensions/source/com/intellij/openapi/extensions/ExtensionsArea.java new file mode 100644 index 000000000000..2860dace1f8f --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/ExtensionsArea.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +import org.jdom.Element; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoContainer; + +/** + * @author AKireyev + */ +public interface ExtensionsArea { + + void registerExtensionPoint(String extensionPointName, String extensionPointBeanClass); + void unregisterExtensionPoint(String extensionPointName); + + boolean hasExtensionPoint(String extensionPointName); + ExtensionPoint getExtensionPoint(String extensionPointName); + + ExtensionPoint[] getExtensionPoints(); + + void suspendInteractions(); + void resumeInteractions(); + void killPendingInteractions(); + + void addAvailabilityListener(String epName, ExtensionPointAvailabilityListener listener); + + MutablePicoContainer getPicoContainer(); + + void registerExtensionPoint(String pluginName, Element extensionPointElement); + void registerExtension(String pluginName, Element extensionElement); + + void unregisterExtensionPoint(String pluginName, Element extensionPointElement); + + void unregisterExtension(String pluginName, Element extensionElement); + + PicoContainer getPluginContainer(String pluginName); + + String getAreaClass(); +} diff --git a/extensions/source/com/intellij/openapi/extensions/LoadingOrder.java b/extensions/source/com/intellij/openapi/extensions/LoadingOrder.java new file mode 100644 index 000000000000..558874a50bc6 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/LoadingOrder.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +import org.jdom.Element; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Alexander Kireyev + */ +public abstract class LoadingOrder { + public static final LoadingOrder ANY = new LoadingOrder("ANY") { + int findPlace(Orderable[] orderables, int current) { + return DONT_CARE; + } + }; + public static final LoadingOrder FIRST = new LoadingOrder("FIRST") { + int findPlace(Orderable[] orderables, int current) { + return SPECIAL; + } + }; + public static final LoadingOrder LAST = new LoadingOrder("LAST") { + int findPlace(Orderable[] orderables, int current) { + return SPECIAL; + } + }; + + static final int DONT_CARE = -1; + static final int ACCEPTABLE = -2; + static final int SPECIAL = -3; + + private final String myName; // for debug only + private static final String BEFORE_STR = "BEFORE:"; + private static final String AFTER_STR = "AFTER:"; + + private LoadingOrder(String name) { + myName = name; + } + + public String toString() { + return myName; + } + + public static LoadingOrder before(final String id) { + return new BeforeLoadingOrder(id); + } + + public static LoadingOrder after(final String id) { + return new AfterLoadingOrder(id); + } + + abstract int findPlace(Orderable[] orderables, int current); + + public static void sort(Orderable[] orderables) { + Orderable first = null; + Orderable last = null; + List other = new ArrayList(); + for (int i = 0; i < orderables.length; i++) { + Orderable orderable = orderables[i]; + if (orderable.getOrder() == FIRST) { + if (first != null) { + throw new SortingException("More than one 'first' element", new Element[] {first.getDescribingElement(), orderable.getDescribingElement()}); + } + first = orderable; + } + else if (orderable.getOrder() == LAST) { + if (last != null) { + throw new SortingException("More than one 'last' element", new Element[] {last.getDescribingElement(), orderable.getDescribingElement()}); + } + last = orderable; + } + else { + other.add(orderable); + } + } + List result = new ArrayList(); + if (first != null) { + result.add(first); + } + result.addAll(other); + if (last != null) { + result.add(last); + } + + assert result.size() == orderables.length; + + Orderable[] presorted = (Orderable[]) result.toArray(new Orderable[result.size()]); + + int swapCount = 0; + int maxSwaps = presorted.length * presorted.length; + for (int i = 0; i < presorted.length; i++) { + Orderable orderable = presorted[i]; + LoadingOrder order = orderable.getOrder(); + int place = order.findPlace(presorted, i); + if (place == DONT_CARE || place == ACCEPTABLE || place == SPECIAL) { + continue; + } + if (place == 0 && presorted[0].getOrder() == FIRST) { + throw new SortingException("Element attempts to go before the specified first", new Element[] {orderable.getDescribingElement(), presorted[0].getDescribingElement()}); + } + if (place == presorted.length - 1 && presorted[presorted.length - 1].getOrder() == LAST) { + throw new SortingException("Element attempts to go after the specified last", new Element[] {orderable.getDescribingElement(), presorted[presorted.length - 1].getDescribingElement()}); + } + moveTo(presorted, i, place); + if (i > place) { + i = place; + } + else { + i--; + } + swapCount++; + if (swapCount > maxSwaps) { + List allElements = new ArrayList(); + for (int j = 0; j < presorted.length; j++) { + allElements.add(presorted[j].getDescribingElement()); + } + throw new SortingException("Could not satisfy sorting requirements", (Element[]) allElements.toArray(new Element[allElements.size()])); + } + } + + System.arraycopy(presorted, 0, orderables, 0, presorted.length); + } + + private static void moveTo(Orderable[] orderables, int from, int to) { + if (to == from) return; + Orderable movedOrderable = orderables[from]; + if (to > from) { + for (int i = from; i < to; i++) { + orderables[i] = orderables[i + 1]; + } + } + else { + for (int i = from; i > to; i--) { + orderables[i] = orderables[i - 1]; + } + } + orderables[to] = movedOrderable; + } + + public static LoadingOrder readOrder(String orderAttr) { + if (orderAttr != null) { + if ("FIRST".equalsIgnoreCase(orderAttr)) return FIRST; + if ("LAST".equalsIgnoreCase(orderAttr)) return LAST; + if ("ANY".equalsIgnoreCase(orderAttr)) return ANY; + if (orderAttr.toUpperCase().startsWith(BEFORE_STR)) { + return before(orderAttr.substring(BEFORE_STR.length())); + } + if (orderAttr.toUpperCase().startsWith(AFTER_STR)) { + return after(orderAttr.substring(AFTER_STR.length())); + } + } + return ANY; + } + + private static class BeforeLoadingOrder extends LoadingOrder { + private final String myId; + + public BeforeLoadingOrder(String id) { + super(LoadingOrder.BEFORE_STR + id); + myId = id; + } + + int findPlace(Orderable[] orderables, int current) { + for (int i = 0; i < orderables.length; i++) { + Orderable orderable = orderables[i]; + String orderId = orderable.getOrderId(); + if (myId.equals(orderId)) { + if (current < i) { + return ACCEPTABLE; + } + else { + return i; + } + } + } + return DONT_CARE; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BeforeLoadingOrder)) return false; + + final BeforeLoadingOrder beforeLoadingOrder = (BeforeLoadingOrder) o; + + if (!myId.equals(beforeLoadingOrder.myId)) return false; + + return true; + } + + public int hashCode() { + return myId.hashCode(); + } + } + + private static class AfterLoadingOrder extends LoadingOrder { + private final String myId; + + public AfterLoadingOrder(String id) { + super(LoadingOrder.AFTER_STR + id); + myId = id; + } + + int findPlace(Orderable[] orderables, int current) { + for (int i = 0; i < orderables.length; i++) { + Orderable orderable = orderables[i]; + String orderId = orderable.getOrderId(); + if (myId.equals(orderId)) { + if (current > i) { + return ACCEPTABLE; + } + else { + return i; + } + } + } + return DONT_CARE; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AfterLoadingOrder)) return false; + + final AfterLoadingOrder afterLoadingOrder = (AfterLoadingOrder) o; + + if (!myId.equals(afterLoadingOrder.myId)) return false; + + return true; + } + + public int hashCode() { + return myId.hashCode(); + } + } + + public interface Orderable { + String getOrderId(); + LoadingOrder getOrder(); + Element getDescribingElement(); + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/LogProvider.java b/extensions/source/com/intellij/openapi/extensions/LogProvider.java new file mode 100644 index 000000000000..755147ec4904 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/LogProvider.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author Alexander Kireyev + */ +public interface LogProvider { + void error(String message); + void error(String message, Throwable t); + void error(Throwable t); + + void warn(String message); + void warn(String message, Throwable t); + void warn(Throwable t); +} diff --git a/extensions/source/com/intellij/openapi/extensions/PluginAware.java b/extensions/source/com/intellij/openapi/extensions/PluginAware.java new file mode 100644 index 000000000000..ca01c8cd34d7 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/PluginAware.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +/** + * @author akireyev + */ +public interface PluginAware { + void setPluginName(String pluginName); +} diff --git a/extensions/source/com/intellij/openapi/extensions/ReaderConfigurator.java b/extensions/source/com/intellij/openapi/extensions/ReaderConfigurator.java new file mode 100644 index 000000000000..04895ab88e42 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/ReaderConfigurator.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +import com.thoughtworks.xstream.XStream; + +/** + * @author AKireyev + */ +public interface ReaderConfigurator { + void configureReader(XStream xstream); +} diff --git a/extensions/source/com/intellij/openapi/extensions/SortingException.java b/extensions/source/com/intellij/openapi/extensions/SortingException.java new file mode 100644 index 000000000000..4f62fd40a03f --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/SortingException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions; + +import org.jdom.Element; + +/** + * @author Alexander Kireyev + */ +public class SortingException extends RuntimeException { + private Element[] myConflictingElements; + + public SortingException(String message, Element[] conflictingElements) { + super(message); + myConflictingElements = conflictingElements; + } + + public Element[] getConflictingElements() { + return myConflictingElements; + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/AreaDescriptor.java b/extensions/source/com/intellij/openapi/extensions/impl/AreaDescriptor.java new file mode 100644 index 000000000000..108ed27950d4 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/AreaDescriptor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import com.intellij.openapi.extensions.AreaInstance; + +/** + * @author akireyev + */ +class AreaDescriptor { + private String myAreaClass; + private AreaInstance myInstance; + + public AreaDescriptor(String aClass, AreaInstance instance) { + myAreaClass = aClass; + myInstance = instance; + } + + public String getAreaClass() { + return myAreaClass; + } + + public AreaInstance getAreaInstance() { + return myInstance; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AreaDescriptor)) return false; + + final AreaDescriptor areaDescriptor = (AreaDescriptor) o; + + if (myAreaClass != null ? !myAreaClass.equals(areaDescriptor.myAreaClass) : areaDescriptor.myAreaClass != null) return false; + if (myInstance != null ? !myInstance.equals(areaDescriptor.myInstance) : areaDescriptor.myInstance != null) return false; + + return true; + } + + public int hashCode() { + int result; + result = (myAreaClass != null ? myAreaClass.hashCode() : 0); + result = 29 * result + (myInstance != null ? myInstance.hashCode() : 0); + return result; + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/AreaPicoContainer.java b/extensions/source/com/intellij/openapi/extensions/impl/AreaPicoContainer.java new file mode 100644 index 000000000000..56ddc69de573 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/AreaPicoContainer.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoContainer; +import org.picocontainer.ComponentAdapter; +import org.picocontainer.Parameter; +import org.picocontainer.defaults.DefaultPicoContainer; +import org.picocontainer.defaults.AmbiguousComponentResolutionException; +import org.picocontainer.defaults.AbstractPicoVisitor; +import org.picocontainer.alternatives.AbstractDelegatingMutablePicoContainer; + +import java.util.*; + +/** + * @author Alexander Kireyev + */ +public class AreaPicoContainer extends AbstractDelegatingMutablePicoContainer implements MutablePicoContainer { + private ExtensionsAreaImpl myArea; + + public AreaPicoContainer(PicoContainer parentPicoContainer, ExtensionsAreaImpl area) { + super(new DefaultPicoContainer(parentPicoContainer)); + myArea = area; + } + + public ComponentAdapter getComponentAdapter(final Object componentKey) { + final ComponentAdapter[] result = new ComponentAdapter[] { null }; + accept(new EmptyPicoVisitor() { + public void visitComponentAdapter(ComponentAdapter componentAdapter) { + if (componentKey.equals(componentAdapter.getComponentKey())) { + result[0] = componentAdapter; + } + } + }); + if (result[0] != null) { + return result[0]; + } + else { + if (getParent() != null) { + return getParent().getComponentAdapter(componentKey); + } + else { + return null; + } + } + } + + public List getComponentInstances() { + final List result = new ArrayList(); + accept(new EmptyPicoVisitor() { + public void visitContainer(PicoContainer pico) { + result.addAll(pico.getComponentInstances()); + } + }); + return result; + } + + public List getComponentInstancesOfType(final Class type) { + final List result = new ArrayList(); + accept(new EmptyPicoVisitor() { + public void visitContainer(PicoContainer pico) { + result.addAll(pico.getComponentInstancesOfType(type)); + } + }); + return result; + } + + public Object getComponentInstanceOfType(Class componentType) { + List instances = getComponentInstancesOfType(componentType); + if (instances.size() == 0) { + return null; + } + else if (instances.size() == 1) { + return instances.get(0); + } + else { + throw new AmbiguousComponentResolutionException(componentType, instances.toArray(new Object[instances.size()])); + } + } + + public Object getComponentInstance(final Object componentKey) { + if (getParent() != null) { + Object parentInstance = getParent().getComponentInstance(componentKey); + if (parentInstance != null) { + return parentInstance; + } + } + + final Object[] result = new Object[] { null }; + accept(new EmptyPicoVisitor() { + public void visitContainer(PicoContainer pico) { + final boolean[] found = new boolean[] { false }; + pico.accept(new EmptyPicoVisitor() { + public void visitComponentAdapter(ComponentAdapter componentAdapter) { + if (componentKey.equals(componentAdapter.getComponentKey())) { + found[0] = true; + } + } + }); + if (!found[0]) { + return; + } + Object componentInstance = pico.getComponentInstance(componentKey); + if (componentInstance != null) { + result[0] = componentInstance; + } + } + }); + return result[0]; + } + + public ComponentAdapter getComponentAdapterOfType(Class componentType) { + List adapters = getComponentAdaptersOfType(componentType); + if (adapters.size() == 0) { + return null; + } + else if (adapters.size() == 1) { + return (ComponentAdapter) adapters.get(0); + } + else { + Class[] foundClasses = new Class[adapters.size()]; + for (int i = 0; i < foundClasses.length; i++) { + ComponentAdapter componentAdapter = (ComponentAdapter) adapters.get(i); + foundClasses[i] = componentAdapter.getComponentImplementation(); + } + throw new AmbiguousComponentResolutionException(componentType, foundClasses); + } + } + + public Collection getComponentAdapters() { + final List result = new ArrayList(); + if (getParent() != null) { + result.addAll(getParent().getComponentAdapters()); + } + accept(new EmptyPicoVisitor() { + public void visitComponentAdapter(ComponentAdapter componentAdapter) { + result.add(componentAdapter); + } + }); + return result; + } + + public List getComponentAdaptersOfType(final Class componentType) { + final List result = new ArrayList(); + if (getParent() != null) { + result.addAll(getParent().getComponentAdaptersOfType(componentType)); + } + accept(new EmptyPicoVisitor() { + public void visitComponentAdapter(ComponentAdapter componentAdapter) { + if (componentType.isAssignableFrom(componentAdapter.getComponentImplementation())) { + result.add(componentAdapter); + } + } + }); + return result; + } + + public MutablePicoContainer makeChildContainer() { + throw new UnsupportedOperationException("Method makeChildContainer() is not implemented"); + } + + private abstract class EmptyPicoVisitor extends AbstractPicoVisitor { + public void visitContainer(PicoContainer pico) { + } + + public void visitComponentAdapter(ComponentAdapter componentAdapter) { + } + + public void visitParameter(Parameter parameter) { + } + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/ElementConverter.java b/extensions/source/com/intellij/openapi/extensions/impl/ElementConverter.java new file mode 100644 index 000000000000..23b481b9a35f --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/ElementConverter.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import org.jdom.Element; + +/** + * @author Alexander Kireyev + */ +public class ElementConverter implements Converter { + public boolean canConvert(Class aClass) { + return Element.class.isAssignableFrom(aClass); + } + + public void marshal(Object object, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + throw new UnsupportedOperationException("This method is not yet implemented"); + } + + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return hierarchicalStreamReader.peekUnderlyingNode(); + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/ExtensionClassAndAreaInstance.java b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionClassAndAreaInstance.java new file mode 100644 index 000000000000..66edaf603a24 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionClassAndAreaInstance.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +public class ExtensionClassAndAreaInstance { + private final Class myExtensionClass; + private final Object myAreaInstance; + + public ExtensionClassAndAreaInstance(Class extensionClass, Object areaInstance) { + myExtensionClass = extensionClass; + myAreaInstance = areaInstance; + } + + public Class getExtensionClass() { + return myExtensionClass; + } + + public Object getAreaInstance() { + return myAreaInstance; + } + + public boolean equals(Object areaInstance) { + if (this == areaInstance) return true; + if (!(areaInstance instanceof ExtensionClassAndAreaInstance)) return false; + + final ExtensionClassAndAreaInstance extensionClassAndAreaInstance = (ExtensionClassAndAreaInstance) areaInstance; + + if (myAreaInstance != null ? !myAreaInstance.equals(extensionClassAndAreaInstance.myAreaInstance) : extensionClassAndAreaInstance.myAreaInstance != null) return false; + if (!myExtensionClass.equals(extensionClassAndAreaInstance.myExtensionClass)) return false; + + return true; + } + + public int hashCode() { + int result; + result = myExtensionClass.hashCode(); + result = 29 * result + (myAreaInstance != null ? myAreaInstance.hashCode() : 0); + return result; + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java new file mode 100644 index 000000000000..c75cea3b07c3 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import com.intellij.openapi.extensions.LoadingOrder; +import com.intellij.openapi.extensions.PluginAware; +import com.intellij.openapi.extensions.ReaderConfigurator; +import com.thoughtworks.xstream.XStream; +import org.jdom.Element; +import org.picocontainer.PicoContainer; +import org.picocontainer.PicoInitializationException; +import org.picocontainer.PicoIntrospectionException; +import org.picocontainer.defaults.AssignabilityRegistrationException; +import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; +import org.picocontainer.defaults.NotConcreteRegistrationException; + +/** + * @author Alexander Kireyev + */ +public class ExtensionComponentAdapter extends ConstructorInjectionComponentAdapter implements LoadingOrder.Orderable { + private Object myComponentInstance; + private Element myExtensionElement; + private PicoContainer myContainer; + private String myPluginName; + + public ExtensionComponentAdapter(Class implementationClass, Element extensionElement, PicoContainer container, String pluginName) { + super(new Object(), implementationClass); + myExtensionElement = extensionElement; + myContainer = container; + myPluginName = pluginName; + } + + public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException { + assert myContainer == container; + + if (myComponentInstance == null) { + if (!Element.class.equals(getComponentImplementation())) { + XStream xStream = new XStream(new PropertyReflectionProvider()); + xStream.registerConverter(new ElementConverter()); + Object componentInstance = super.getComponentInstance(container); + if (componentInstance instanceof ReaderConfigurator) { + ReaderConfigurator readerConfigurator = (ReaderConfigurator) componentInstance; + readerConfigurator.configureReader(xStream); + } + xStream.alias(myExtensionElement.getName(), componentInstance.getClass()); + myComponentInstance = xStream.unmarshal(new JDomReader(myExtensionElement), componentInstance); + } + else { + myComponentInstance = myExtensionElement; + } + if (myComponentInstance instanceof PluginAware) { + PluginAware pluginAware = (PluginAware) myComponentInstance; + pluginAware.setPluginName(myPluginName); + } + } + + return myComponentInstance; + } + + public Object getExtension() { + return getComponentInstance(myContainer); + } + + public LoadingOrder getOrder() { + String orderAttr = myExtensionElement.getAttributeValue("order"); + return LoadingOrder.readOrder(orderAttr); + } + + public String getOrderId() { + return myExtensionElement.getAttributeValue("id"); + } + + public Element getExtensionElement() { + return myExtensionElement; + } + + public Element getDescribingElement() { + return getExtensionElement(); + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java new file mode 100644 index 000000000000..d5984388d98a --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import com.intellij.openapi.extensions.*; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Array; +import java.util.*; + +import org.jdom.Element; + +/** + * @author AKireyev + */ +public class ExtensionPointImpl implements ExtensionPoint { + private final LogProvider myLogger; + + private String myName; + private String myBeanClassName; + private List myExtensions = new ArrayList(); + private List myLoadedAdapters = new ArrayList(); + private Set myExtensionAdapters = new LinkedHashSet(); + private Set myEPListeners = new LinkedHashSet(); + private SoftReference myExtensionsCache; + private ExtensionsAreaImpl myOwner; + private final AreaInstance myArea; + private Class myExtensionClass; + + public ExtensionPointImpl(String name, String beanClassName, ExtensionsAreaImpl owner, AreaInstance area, LogProvider logger) { + myName = name; + myBeanClassName = beanClassName; + myOwner = owner; + myArea = area; + myLogger = logger; + } + + public String getName() { + return myName; + } + + public AreaInstance getArea() { + return myArea; + } + + public String getBeanClassName() { + return myBeanClassName; + } + + public void registerExtension(Object extension) { + registerExtension(extension, LoadingOrder.ANY); + } + + public void registerExtension(Object extension, LoadingOrder order) { + assert (extension != null) : "Extension cannot be null"; + + myOwner.getMutablePicoContainer().registerComponentInstance(new Object(), extension); + + assert myExtensions.size() == myLoadedAdapters.size(); + + if (LoadingOrder.ANY == order) { + int index = myLoadedAdapters.size(); + if (myLoadedAdapters.size() > 0) { + ExtensionComponentAdapter lastAdapter = (ExtensionComponentAdapter) myLoadedAdapters.get(myLoadedAdapters.size() - 1); + if (lastAdapter.getOrder() == LoadingOrder.LAST) { + index--; + } + } + internalRegisterExtension(extension, new ObjectComponentAdapter(extension, order), index, true); + } + else { + myExtensionAdapters.add(new ObjectComponentAdapter(extension, order)); + processAdapters(); + } + } + + private void internalRegisterExtension(Object extension, ExtensionComponentAdapter adapter, int index, boolean runNotifications) { + myExtensionsCache = null; + + if (myExtensions.contains(extension)) { + myLogger.error("Extension was already added: " + extension); + } + else { + myExtensions.add(index, extension); + myLoadedAdapters.add(index, adapter); + if (runNotifications) { + if (extension instanceof Extension) { + Extension o = (Extension) extension; + try { + o.extensionAdded(this); + } catch (Throwable e) { + myLogger.error(e); + } + } + + notifyListenersOnAdd(extension); + } + } + } + + private void notifyListenersOnAdd(Object extension) { + ExtensionPointListener[] listeners = (ExtensionPointListener[]) myEPListeners.toArray(new ExtensionPointListener[myEPListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + ExtensionPointListener listener = listeners[i]; + try { + listener.extensionAdded(extension); + } catch (Throwable e) { + myLogger.error(e); + } + } + } + + public Object[] getExtensions() { + Object[] result = null; + + processAdapters(); + + if (myExtensionsCache != null) { + result = (Object[]) myExtensionsCache.get(); + } + if (result == null) { + result = myExtensions.toArray((Object[])Array.newInstance(getExtensionClass(), myExtensions.size())); + myExtensionsCache = new SoftReference(result); + } + + return result; + } + + private void processAdapters() { + if (myExtensionAdapters.size() > 0) { + List allAdapters = new ArrayList(myExtensionAdapters.size() + myLoadedAdapters.size()); + allAdapters.addAll(myExtensionAdapters); + allAdapters.addAll(myLoadedAdapters); + myExtensions.clear(); + List loadedAdapters = myLoadedAdapters; + myLoadedAdapters = new ArrayList(); + ExtensionComponentAdapter[] adapters = (ExtensionComponentAdapter[]) allAdapters.toArray(new ExtensionComponentAdapter[myExtensionAdapters.size()]); + LoadingOrder.sort(adapters); + for (int i = 0; i < adapters.length; i++) { + ExtensionComponentAdapter adapter = adapters[i]; + Object extension = adapter.getExtension(); + internalRegisterExtension(extension, adapter, i, !loadedAdapters.contains(adapter)); + } + myExtensionAdapters.clear(); + } + } + + public Object getExtension() { + Object[] extensions = getExtensions(); + if (extensions.length == 0) return null; + + return extensions[0]; + } + + public boolean hasExtension(Object extension) { + processAdapters(); + + return myExtensions.contains(extension); + } + + public void unregisterExtension(Object extension) { + assert (extension != null) : "Extension cannot be null"; + + myOwner.getMutablePicoContainer().unregisterComponentByInstance(extension); + + processAdapters(); + + internalUnregisterExtension(extension); + } + + private void internalUnregisterExtension(Object extension) { + myExtensionsCache = null; + + if (!myExtensions.contains(extension)) { + throw new IllegalArgumentException("Extension to be removed not found: " + extension); + } + int index = myExtensions.indexOf(extension); + myExtensions.remove(index); + myLoadedAdapters.remove(index); + + notifyListenersOnRemove(extension); + + if (extension instanceof Extension) { + Extension o = (Extension) extension; + try { + o.extensionRemoved(this); + } catch (Throwable e) { + myLogger.error(e); + } + } + } + + private void notifyListenersOnRemove(Object extensionObject) { + for (Iterator iterator = myEPListeners.iterator(); iterator.hasNext();) { + ExtensionPointListener listener = (ExtensionPointListener)iterator.next(); + try { + listener.extensionRemoved(extensionObject); + } catch (Throwable e) { + myLogger.error(e); + } + } + } + + public void addExtensionPointListener(ExtensionPointListener listener) { + if (myEPListeners.add(listener)) { + for (Iterator iterator = myExtensions.iterator(); iterator.hasNext();) { + try { + listener.extensionAdded(iterator.next()); + } catch (Throwable e) { + myLogger.error(e); + } + } + } + } + + public void removeExtensionPointListener(ExtensionPointListener listener) { + if (myEPListeners.contains(listener)) { + for (Iterator iterator = myExtensions.iterator(); iterator.hasNext();) { + try { + listener.extensionRemoved(iterator.next()); + } catch (Throwable e) { + myLogger.error(e); + } + } + + myEPListeners.remove(listener); + } + } + + public void reset() { + Object[] extensions = getExtensions(); + for (int i = 0; i < extensions.length; i++) { + Object extension = extensions[i]; + unregisterExtension(extension); + } + } + + public Class getExtensionClass() { + if (myExtensionClass == null) { + try { + myExtensionClass = Class.forName(myBeanClassName); + } + catch (ClassNotFoundException e) { + myExtensionClass = Object.class; + } + } + return myExtensionClass; + } + + public String toString() { + return getName(); + } + + void registerExtensionAdapter(ExtensionComponentAdapter adapter) { + myExtensionAdapters.add(adapter); + } + + private static class ObjectComponentAdapter extends ExtensionComponentAdapter { + private Object myExtension; + private LoadingOrder myLoadingOrder; + + public ObjectComponentAdapter(Object extension, LoadingOrder loadingOrder) { + super(Object.class, null, null, null); + myExtension = extension; + myLoadingOrder = loadingOrder; + } + + public Object getExtension() { + return myExtension; + } + + public LoadingOrder getOrder() { + return myLoadingOrder; + } + + public String getOrderId() { + return null; + } + + public Element getDescribingElement() { + return new Element("RuntimeExtension: " + myExtension); + } + } +} diff --git a/extensions/source/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java new file mode 100644 index 000000000000..fed9f828ff64 --- /dev/null +++ b/extensions/source/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import com.intellij.openapi.extensions.*; +import org.apache.commons.collections.MultiHashMap; +import org.apache.commons.collections.MultiMap; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.output.XMLOutputter; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoContainer; +import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; +import org.picocontainer.defaults.DefaultPicoContainer; + +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Modifier; +import java.util.*; + +public class ExtensionsAreaImpl implements ExtensionsArea { + private final LogProvider myLogger; + + static Map ourDefaultEPs = new HashMap(); + + static { + ourDefaultEPs.put(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME, EPAvailabilityListenerExtension.class.getName()); + } + + private static boolean DEBUG_REGISTRATION = true; + + private MutablePicoContainer myPicoContainer; +// private Map myPluginName2picoContainer = new HashMap(); + private Throwable myCreationTrace = null; + private Map myExtensionPoints = new HashMap(); + private Map myEPTraces = new HashMap(); + private MultiMap myAvailabilityListeners = new MultiHashMap(); + private List mySuspendedListenerActions = new ArrayList(); + private boolean myAvailabilityNotificationsActive = true; + + private final AreaInstance myAreaInstance; + private MultiMap myServiceRegistry = new MultiHashMap(); + private final String myAreaClass; + private Map myServiceClass2ServicesCache = new HashMap(); + private Map myExtensionElement2extension = new HashMap(); + private Map myPluginName2picoContainer = new HashMap(); + + public ExtensionsAreaImpl(String areaClass, AreaInstance areaInstance, PicoContainer parentPicoContainer, LogProvider logger) { + if (DEBUG_REGISTRATION) { + myCreationTrace = new Throwable("Area creation trace"); + } + myAreaClass = areaClass; + myAreaInstance = areaInstance; + myPicoContainer = new AreaPicoContainer(parentPicoContainer, this); + if (areaInstance != null) { + myPicoContainer.registerComponentInstance(areaInstance); + } + initialize(); + myLogger = logger; + } + + public ExtensionsAreaImpl(MutablePicoContainer picoContainer, LogProvider logger) { + this(null, null, picoContainer, logger); + } + + public MutablePicoContainer getPicoContainer() { + return myPicoContainer; + } + + MutablePicoContainer getMutablePicoContainer() { + return myPicoContainer; + } + + public String getAreaClass() { + return myAreaClass; + } + + public void registerExtensionPoint(String pluginName, Element extensionPointElement) { + assert pluginName != null; + String epName = pluginName + '.' + extensionPointElement.getAttributeValue("name"); + String className = extensionPointElement.getAttributeValue("beanClass"); + if (className == null) { + className = extensionPointElement.getAttributeValue("interface"); + } + if (className == null) { + throw new RuntimeException("No class specified for extension point: " + epName); + } + registerExtensionPoint(epName, className); + } + + public void registerExtension(final String pluginName, final Element extensionElement) { + String epName = extractEPName(extensionElement); + ExtensionComponentAdapter adapter; + String implClass = null; + Class extensionClass = getExtensionPoint(epName).getExtensionClass(); + implClass = extensionElement.getAttributeValue("implementation"); + if (extensionClass.isInterface() || Modifier.isAbstract(extensionClass.getModifiers())) { + if (implClass == null) { + throw new RuntimeException("Expected implementation for extension declaration (ep = " + epName + ")"); + } + } + if (implClass != null) { + try { + Class implementationClass = Class.forName(implClass); + adapter = new ExtensionComponentAdapter(implementationClass, extensionElement, getPluginContainer(pluginName), pluginName); + } + catch (ClassNotFoundException e) { + myLogger.warn("Extension implementation class not found: " + implClass); + myExtensionElement2extension.put(extensionElement, null); + return; + } + } + else { + final ExtensionPoint extensionPoint = getExtensionPoint(epName); + adapter = new ExtensionComponentAdapter(extensionPoint.getExtensionClass(), extensionElement, getPluginContainer(pluginName), pluginName); + } + myExtensionElement2extension.put(extensionElement, adapter); + internalGetPluginContainer(pluginName).registerComponent(adapter); + getExtensionPointImpl(epName).registerExtensionAdapter(adapter); + } + + private String extractEPName(final Element extensionElement) { + String epName = extensionElement.getAttributeValue("point"); + if (epName == null) { + Namespace namespace = extensionElement.getNamespace(); + epName = namespace.getURI() + '.' + extensionElement.getName(); + } + return epName; + } + + public PicoContainer getPluginContainer(String pluginName) { + return internalGetPluginContainer(pluginName); + } + + private MutablePicoContainer internalGetPluginContainer(String pluginName) { +// return myPicoContainer; + DefaultPicoContainer pluginContainer = (DefaultPicoContainer) myPluginName2picoContainer.get(pluginName); + if (pluginContainer == null) { + pluginContainer = new DefaultPicoContainer(myPicoContainer); + myPicoContainer.addChildContainer(pluginContainer); + myPluginName2picoContainer.put(pluginName, pluginContainer); + } + return pluginContainer; + } + + private void disposePluginContainer(String pluginName) { + DefaultPicoContainer pluginContainer = (DefaultPicoContainer) myPluginName2picoContainer.remove(pluginName); + if (pluginContainer != null) { + myPicoContainer.removeChildContainer(pluginContainer); + } + } + + public void unregisterExtensionPoint(String pluginName, Element extensionPointElement) { + assert pluginName != null; + String epName = pluginName + '.' + extensionPointElement.getAttributeValue("name"); + unregisterExtensionPoint(epName); + } + + public void unregisterExtension(String pluginName, Element extensionElement) { + String epName = extractEPName(extensionElement); + if (!myExtensionElement2extension.containsKey(extensionElement)) { + XMLOutputter xmlOutputter = new XMLOutputter(); + xmlOutputter.setIndent(" "); + xmlOutputter.setTextNormalize(true); + xmlOutputter.setNewlines(true); + StringWriter stringWriter = new StringWriter(); + try { + xmlOutputter.output(extensionElement, stringWriter); + } + catch (IOException e) { + throw new RuntimeException(e); + } + myLogger.warn(stringWriter.toString()); + throw new IllegalArgumentException("Trying to unregister extension element that was never registered"); + } + ExtensionComponentAdapter adapter = (ExtensionComponentAdapter) myExtensionElement2extension.remove(extensionElement); + if (adapter == null) return; + if (getExtensionPoint(epName).hasExtension(adapter.getExtension())) { + getExtensionPoint(epName).unregisterExtension(adapter.getExtension()); + MutablePicoContainer pluginContainer = internalGetPluginContainer(pluginName); + pluginContainer.unregisterComponent(adapter.getComponentKey()); + if (pluginContainer.getComponentAdapters().size() == 0) { + disposePluginContainer(pluginName); + } + } + } + + public void initialize() { + for (Iterator iterator = ourDefaultEPs.keySet().iterator(); iterator.hasNext();) { + String epName = (String) iterator.next(); + registerExtensionPoint(epName, (String) ourDefaultEPs.get(epName)); + } + getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).addExtensionPointListener(new ExtensionPointListener() { + public void extensionRemoved(Object extension) { + EPAvailabilityListenerExtension epListenerExtension = (EPAvailabilityListenerExtension) extension; + List listeners = (List) myAvailabilityListeners.get(epListenerExtension.getExtensionPointName()); + for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { + Object listener = iterator.next(); + if (listener.getClass().getName().equals(epListenerExtension.getListenerClass())) { + iterator.remove(); + return; + } + } + myLogger.warn("Failed to find EP availability listener: " + epListenerExtension.getListenerClass()); + } + + public void extensionAdded(Object extension) { + EPAvailabilityListenerExtension epListenerExtension = (EPAvailabilityListenerExtension) extension; + try { + String epName = epListenerExtension.getExtensionPointName(); + + ExtensionPointAvailabilityListener listener = (ExtensionPointAvailabilityListener) instantiate(Class.forName(epListenerExtension.getListenerClass())); + addAvailabilityListener(epName, listener); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + public Object instantiate(Class clazz) { + ConstructorInjectionComponentAdapter adapter = new ConstructorInjectionComponentAdapter(System.identityHashCode(new Object()) + "", clazz); + return adapter.getComponentInstance(getPicoContainer()); + } + + public Throwable getCreationTrace() { + return myCreationTrace; + } + + public void addAvailabilityListener(String epName, ExtensionPointAvailabilityListener listener) { + myAvailabilityListeners.put(epName, listener); + if (hasExtensionPoint(epName)) { + notifyAvailableListener(listener, (ExtensionPoint) myExtensionPoints.get(epName)); + } + } + + public void registerExtensionPoint(final String extensionPointName, String extensionPointBeanClass) { + if (hasExtensionPoint(extensionPointName)) { + if (DEBUG_REGISTRATION) { + myLogger.error((Throwable) myEPTraces.get(extensionPointName)); + } + throw new RuntimeException("Duplicate registration for EP: " + extensionPointName); + } + + ExtensionPointImpl extensionPoint = new ExtensionPointImpl(extensionPointName, extensionPointBeanClass, this, myAreaInstance, myLogger); + myExtensionPoints.put(extensionPointName, extensionPoint); + notifyEPRegistered(extensionPoint); + if (DEBUG_REGISTRATION) { + myEPTraces.put(extensionPointName, new Throwable("Original registration for " + extensionPointName)); + } + } + + private void notifyEPRegistered(final ExtensionPoint extensionPoint) { + List listeners = (List) myAvailabilityListeners.get(extensionPoint.getName()); + if (listeners != null) { + for (Iterator i = listeners.iterator(); i.hasNext();) { + final ExtensionPointAvailabilityListener listener = (ExtensionPointAvailabilityListener) i.next(); + notifyAvailableListener(listener, extensionPoint); + } + } + } + + private void notifyAvailableListener(final ExtensionPointAvailabilityListener listener, final ExtensionPoint extensionPoint) { + Runnable action = new Runnable() { + public void run() { + listener.extensionPointRegistered(extensionPoint); + } + }; + if (myAvailabilityNotificationsActive) { + action.run(); + } + else { + mySuspendedListenerActions.add(action); + } + } + + public ExtensionPoint getExtensionPoint(String extensionPointName) { + return getExtensionPointImpl(extensionPointName); + } + + private ExtensionPointImpl getExtensionPointImpl(String extensionPointName) { + if (!hasExtensionPoint(extensionPointName)) { + throw new IllegalArgumentException("Missing extension point: " + extensionPointName + + " in area " + myAreaInstance ); + } + return (ExtensionPointImpl) myExtensionPoints.get(extensionPointName); + } + + public ExtensionPoint[] getExtensionPoints() { + return (ExtensionPoint[]) myExtensionPoints.values().toArray(new ExtensionPoint[myExtensionPoints.size()]); + } + + public void unregisterExtensionPoint(final String extensionPointName) { + ExtensionPoint extensionPoint = (ExtensionPoint) myExtensionPoints.get(extensionPointName); + if (extensionPoint != null) { + extensionPoint.reset(); + myExtensionPoints.remove(extensionPointName); + notifyEPRemoved(extensionPoint); + } + } + + private void notifyEPRemoved(final ExtensionPoint extensionPoint) { + List listeners = (List) myAvailabilityListeners.get(extensionPoint.getName()); + if (listeners != null) { + for (Iterator i = listeners.iterator(); i.hasNext();) { + final ExtensionPointAvailabilityListener listener = (ExtensionPointAvailabilityListener) i.next(); + Runnable action = new Runnable() { + public void run() { + listener.extensionPointRemoved(extensionPoint); + } + }; + if (myAvailabilityNotificationsActive) { + action.run(); + } + else { + mySuspendedListenerActions.add(action); + } + } + } + } + + public boolean hasExtensionPoint(String extensionPointName) { + return myExtensionPoints.containsKey(extensionPointName); + } + + public void suspendInteractions() { + myAvailabilityNotificationsActive = false; + } + + public void resumeInteractions() { + myAvailabilityNotificationsActive = true; + ExtensionPoint[] extensionPoints = getExtensionPoints(); + for (int i = 0; i < extensionPoints.length; i++) { + ExtensionPoint extensionPoint = extensionPoints[i]; + extensionPoint.getExtensions(); // creates extensions from ComponentAdapters + } + for (Iterator i = mySuspendedListenerActions.iterator(); i.hasNext();) { + Runnable action = (Runnable) i.next(); + try { + action.run(); + } + catch (Exception e) { + myLogger.error(e); + } + } + mySuspendedListenerActions.clear(); + } + + public void killPendingInteractions() { + mySuspendedListenerActions.clear(); + } + + private Set collectInterfaces(String className) { + try { + Class serviceClass = Class.forName(className); + Set classes = new HashSet(); + classes.add(serviceClass.getName()); + collectInterfaces(serviceClass, classes); + return classes; + } + catch (ClassNotFoundException e) { + return Collections.EMPTY_SET; + } + } + + private void collectInterfaces(Class serviceClass, Set classes) { + Class[] interfaces = serviceClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + Class anInterface = interfaces[i]; + if (!classes.contains(anInterface.getName())) { + classes.add(anInterface.getName()); + collectInterfaces(anInterface, classes); + } + } + } +} diff --git a/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java b/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java new file mode 100644 index 000000000000..d8089ece2e5f --- /dev/null +++ b/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jmock.cglib.MockObjectTestCase; +import org.picocontainer.defaults.DefaultPicoContainer; + +import java.io.IOException; +import java.io.StringReader; + +import com.intellij.openapi.extensions.LoadingOrder; + +/** + * @author Alexander Kireyev + */ +public class ExtensionComponentAdapterTest extends MockObjectTestCase { + public void testLoadingOrderReading() { + assertEquals(LoadingOrder.ANY, createAdapter("").getOrder()); + assertEquals(LoadingOrder.FIRST, createAdapter("").getOrder()); + assertEquals(LoadingOrder.LAST, createAdapter("").getOrder()); + assertEquals(LoadingOrder.before("test"), createAdapter("").getOrder()); + assertEquals(LoadingOrder.after("test"), createAdapter("").getOrder()); + } + + private ExtensionComponentAdapter createAdapter(String text) { + Element extensionElement = readElement(text); + + ExtensionComponentAdapter adapter = new ExtensionComponentAdapter(Object.class, extensionElement, new DefaultPicoContainer(), ""); + return adapter; + } + + static Element readElement(String text) { + Element extensionElement1 = null; + try { + extensionElement1 = new SAXBuilder().build(new StringReader(text)).getRootElement(); + } + catch (JDOMException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + Element extensionElement = extensionElement1; + return extensionElement; + } + +} diff --git a/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java b/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java new file mode 100644 index 000000000000..b5e57b861c41 --- /dev/null +++ b/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import com.intellij.openapi.extensions.*; +import junit.framework.TestCase; +import org.picocontainer.defaults.DefaultPicoContainer; + +/** + * @author AKireyev + */ +public class ExtensionPointImplTest extends TestCase { + public void testCreate() { + ExtensionPointImpl extensionPoint = buildExtensionPoint(); + assertEquals(ExtensionsImplTest.EXTENSION_POINT_NAME_1, extensionPoint.getName()); + assertEquals(Integer.class.getName(), extensionPoint.getBeanClassName()); + } + + private ExtensionPointImpl buildExtensionPoint() { + return new ExtensionPointImpl(ExtensionsImplTest.EXTENSION_POINT_NAME_1, Integer.class.getName(), buildExtensionArea(), null, new Extensions.SimpleLogProvider()); + } + + private ExtensionsAreaImpl buildExtensionArea() { + return new ExtensionsAreaImpl(new DefaultPicoContainer(), new Extensions.SimpleLogProvider()); + } + + public void testUnregisterObject() { + ExtensionPointImpl extensionPoint = buildExtensionPoint(); + extensionPoint.registerExtension(new Integer(123)); + Object[] extensions = extensionPoint.getExtensions(); + assertEquals(1, extensions.length); + extensionPoint.unregisterExtension(new Integer(123)); + extensions = extensionPoint.getExtensions(); + assertEquals(0, extensions.length); + } + + public void testRegisterUnregister_Extension() { + + final AreaInstance area = new AreaInstance() {}; + final ExtensionPointImpl extensionPoint = new ExtensionPointImpl("an.extension.point", Object.class.getName(), buildExtensionArea(), area, new Extensions.SimpleLogProvider()); + + final boolean[] flags = new boolean[2]; + Extension extension = new Extension() { + public void extensionAdded(ExtensionPoint extensionPoint1) { + assertSame(extensionPoint, extensionPoint1); + assertSame(area, extensionPoint1.getArea()); + flags[0] = true; + } + + public void extensionRemoved(ExtensionPoint extensionPoint1) { + assertSame(extensionPoint, extensionPoint1); + assertSame(area, extensionPoint1.getArea()); + flags[1] = true; + } + }; + + extensionPoint.registerExtension(extension); + assertTrue("Registratioon call is missed", flags[0]); + assertFalse(flags[1]); + + extensionPoint.unregisterExtension(extension); + assertTrue("UnRegistratioon call is missed", flags[1]); + } + + public void testRegisterObject() { + ExtensionPointImpl extensionPoint = buildExtensionPoint(); + extensionPoint.registerExtension(new Integer(123)); + Object[] extensions = extensionPoint.getExtensions(); + assertEquals("One extension", 1, extensions.length); + assertEquals("Correct type", Integer[].class, extensions.getClass()); + assertEquals("Correct object", new Integer(123), extensions[0]); + } + + public void testRegistrationOrder() { + ExtensionPointImpl extensionPoint = buildExtensionPoint(); + extensionPoint.registerExtension(new Integer(123)); + extensionPoint.registerExtension(new Integer(321), LoadingOrder.FIRST); + Object[] extensions = extensionPoint.getExtensions(); + assertEquals("One extension", 2, extensions.length); + assertEquals("Correct object", new Integer(321), extensions[0]); + } + + public void testListener() { + ExtensionPointImpl extensionPoint = buildExtensionPoint(); + final boolean added[] = new boolean[1]; + final boolean removed[] = new boolean[1]; + extensionPoint.addExtensionPointListener(new ExtensionPointListener() { + public void extensionAdded(Object extension) { + added[0] = true; + } + + public void extensionRemoved(Object extension) { + removed[0] = true; + } + }); + assertFalse(added[0]); + assertFalse(removed[0]); + extensionPoint.registerExtension(new Integer(123)); + assertTrue(added[0]); + assertFalse(removed[0]); + added[0] = false; + extensionPoint.unregisterExtension(new Integer(123)); + assertFalse(added[0]); + assertTrue(removed[0]); + } + + public void testLateListener() { + ExtensionPointImpl extensionPoint = buildExtensionPoint(); + final boolean added[] = new boolean[1]; + extensionPoint.registerExtension(new Integer(123)); + assertFalse(added[0]); + extensionPoint.addExtensionPointListener(new ExtensionPointListener() { + public void extensionAdded(Object extension) { + added[0] = true; + } + + public void extensionRemoved(Object extension) { + } + }); + assertTrue(added[0]); + } +} diff --git a/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java b/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java new file mode 100644 index 000000000000..46210eb52d4f --- /dev/null +++ b/extensions/testSource/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import junit.framework.TestCase; +import com.intellij.openapi.extensions.*; +import java.util.Arrays; + +import org.picocontainer.defaults.DefaultPicoContainer; +import org.picocontainer.MutablePicoContainer; + +/** + * @author AKireyev + */ +public class ExtensionsImplTest extends TestCase { + public static final String EXTENSION_POINT_NAME_1 = "ext.point.one"; + public static final String AREA_1 = "the.area.one"; + + protected void tearDown() throws Exception { + ExtensionsTestUtils.hardReset(); + super.tearDown(); //To change body of overriden methods use Options | File Templates. + } + + public void testCreateAndAccess() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + int numEP = extensionsArea.getExtensionPoints().length; + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + assertEquals("Additional EP available", numEP + 1, extensionsArea.getExtensionPoints().length); + assertNotNull("EP by name available", extensionsArea.getExtensionPoint(EXTENSION_POINT_NAME_1)); + } + + public void testInvalidActions() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + try { + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Boolean.class.getName()); + fail("Should not allow duplicate registration"); + } catch (RuntimeException e) { + } + } + + public void testUnregisterEP() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + int numEP = extensionsArea.getExtensionPoints().length; + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + final boolean removed[] = new boolean[1]; + removed[0] = false; + extensionsArea.getExtensionPoint(EXTENSION_POINT_NAME_1).addExtensionPointListener(new ExtensionPointListener() { + public void extensionAdded(Object extension) { + } + + public void extensionRemoved(Object extension) { + removed[0] = true; + } + }); + extensionsArea.getExtensionPoint(EXTENSION_POINT_NAME_1).registerExtension(new Integer(123)); + extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1); + assertTrue("Extension point should be removed", extensionsArea.getExtensionPoints().length == numEP); + assertTrue("Extension point disposed", removed[0]); + } + + public void testAvailabilityListener() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + MyListener.reset(); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + assertEquals(1, MyListener.regcount); + assertEquals(0, MyListener.remcount); + MyListener.reset(); + extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1); + assertEquals(1, MyListener.remcount); + assertEquals(0, MyListener.regcount); + } + + public void testAvailability2Listeners() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + MyListener.reset(); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + assertEquals(2, MyListener.regcount); + assertEquals(0, MyListener.remcount); + MyListener.reset(); + extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1); + assertEquals(2, MyListener.remcount); + assertEquals(0, MyListener.regcount); + } + + public void testAvailabilityListenerAfter() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + MyListener.reset(); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + assertEquals(1, MyListener.regcount); + assertEquals(0, MyListener.remcount); + } + + public void testAvailabilityListenerDelay() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + MyListener.reset(); + extensionsArea.suspendInteractions(); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.resumeInteractions(); + assertEquals(1, MyListener.regcount); + assertEquals(0, MyListener.remcount); + MyListener.reset(); + extensionsArea.suspendInteractions(); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.resumeInteractions(); + assertEquals(1, MyListener.remcount); + assertEquals(0, MyListener.regcount); + } + + public void testKillAvailabilityNotifications() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + MyListener.reset(); + extensionsArea.suspendInteractions(); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.killPendingInteractions(); + extensionsArea.resumeInteractions(); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + } + + public void testListenerAfterResume() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null, new Extensions.SimpleLogProvider()); + extensionsArea.suspendInteractions(); + extensionsArea.resumeInteractions(); + MyListener.reset(); + extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension( + new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName())); + assertEquals(0, MyListener.regcount); + assertEquals(0, MyListener.remcount); + extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName()); + assertEquals(1, MyListener.regcount); + assertEquals(0, MyListener.remcount); + MyListener.reset(); + extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1); + assertEquals(1, MyListener.remcount); + assertEquals(0, MyListener.regcount); + } + + public void testTryPicoContainer() { + DefaultPicoContainer rootContainer = new DefaultPicoContainer(); + rootContainer.registerComponentInstance("plugin1", new DefaultPicoContainer(rootContainer)); + rootContainer.registerComponentInstance("plugin2", new DefaultPicoContainer(rootContainer)); +// rootContainer.registerComponentImplementation("plugin2", DefaultPicoContainer.class); + MutablePicoContainer container1 = (MutablePicoContainer)rootContainer.getComponentInstance("plugin1"); + MutablePicoContainer container2 = (MutablePicoContainer)rootContainer.getComponentInstance("plugin2"); + container1.registerComponentImplementation("component1", MyComponent1.class); + container1.registerComponentImplementation("component1.1", MyComponent1.class); + container2.registerComponentImplementation("component2", MyComponent2.class); + MyInterface1 testInstance = new MyInterface1() { + public void run() { + } + }; + rootContainer.registerComponentInstance(testInstance); + MyComponent1 component1 = (MyComponent1)container1.getComponentInstance("component1"); + assertEquals(testInstance, component1.testObject); + rootContainer.registerComponentInstance("component1", component1); + MyComponent1 component11 = (MyComponent1)container1.getComponentInstance("component1.1"); + rootContainer.registerComponentInstance("component11", component11); + MyComponent2 component2 = (MyComponent2)container2.getComponentInstance("component2"); + assertEquals(testInstance, component2.testObject); + assertTrue(Arrays.asList(component2.comp1).contains(component1)); + assertTrue(Arrays.asList(component2.comp1).contains(component11)); + rootContainer.registerComponentInstance("component2", component2); + rootContainer.registerComponentImplementation(MyTestComponent.class); + MyTestComponent testComponent = (MyTestComponent)rootContainer.getComponentInstance(MyTestComponent.class); + assertTrue(Arrays.asList(testComponent.comp1).contains(component1)); + assertTrue(Arrays.asList(testComponent.comp1).contains(component11)); + assertEquals(component2, testComponent.comp2); + } + + public void testTryPicoContainer2() { + DefaultPicoContainer rootContainer = new DefaultPicoContainer(); + rootContainer.registerComponentImplementation("component1", MyComponent1.class); + rootContainer.registerComponentImplementation("component1.1", MyComponent1.class); + rootContainer.registerComponentImplementation("component2", MyComponent2.class); + rootContainer.registerComponentImplementation(MyTestComponent.class); + MyInterface1 testInstance = new MyInterface1() { + public void run() { + } + }; + rootContainer.registerComponentInstance(testInstance); + MyTestComponent testComponent = (MyTestComponent)rootContainer.getComponentInstance(MyTestComponent.class); + MyComponent2 component2 = (MyComponent2)rootContainer.getComponentInstance("component2"); + MyComponent1 component11 = (MyComponent1)rootContainer.getComponentInstance("component1.1"); + MyComponent1 component1 = (MyComponent1)rootContainer.getComponentInstance("component1"); + assertEquals(testInstance, component1.testObject); + assertEquals(testInstance, component2.testObject); + assertTrue(Arrays.asList(component2.comp1).contains(component1)); + assertTrue(Arrays.asList(component2.comp1).contains(component11)); + assertTrue(Arrays.asList(testComponent.comp1).contains(component1)); + assertTrue(Arrays.asList(testComponent.comp1).contains(component11)); + assertEquals(component2, testComponent.comp2); + } + + public void testExtensionsNamespaces() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(new DefaultPicoContainer(), new Extensions.SimpleLogProvider()); + extensionsArea.registerExtensionPoint("plugin.ep1", TestExtensionClassOne.class.getName()); + extensionsArea.registerExtension("plugin", ExtensionComponentAdapterTest.readElement( + "3")); + extensionsArea.registerExtension("plugin", ExtensionComponentAdapterTest.readElement( + "1")); + extensionsArea.registerExtension("plugin", ExtensionComponentAdapterTest.readElement( + "2")); + ExtensionPoint extensionPoint = extensionsArea.getExtensionPoint("plugin.ep1"); + TestExtensionClassOne[] extensions = (TestExtensionClassOne[]) extensionPoint.getExtensions(); + assertEquals(3, extensions.length); + assertEquals("1", extensions[0].getText()); + assertEquals("2", extensions[1].getText()); + assertEquals("3", extensions[2].getText()); + } + + public void testExtensionsWithOrdering() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(new DefaultPicoContainer(), new Extensions.SimpleLogProvider()); + extensionsArea.registerExtensionPoint("ep1", TestExtensionClassOne.class.getName()); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "3")); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "1")); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "2")); + ExtensionPoint extensionPoint = extensionsArea.getExtensionPoint("ep1"); + TestExtensionClassOne[] extensions = (TestExtensionClassOne[]) extensionPoint.getExtensions(); + assertEquals(3, extensions.length); + assertEquals("1", extensions[0].getText()); + assertEquals("2", extensions[1].getText()); + assertEquals("3", extensions[2].getText()); + } + + public void testExtensionsWithOrderingUpdate() { + ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(new DefaultPicoContainer(), new Extensions.SimpleLogProvider()); + extensionsArea.registerExtensionPoint("ep1", TestExtensionClassOne.class.getName()); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "6")); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "1")); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "3")); + ExtensionPoint extensionPoint = extensionsArea.getExtensionPoint("ep1"); + TestExtensionClassOne[] extensions = (TestExtensionClassOne[]) extensionPoint.getExtensions(); + assertEquals(3, extensions.length); + assertEquals("1", extensions[0].getText()); + assertEquals("3", extensions[1].getText()); + assertEquals("6", extensions[2].getText()); + TestExtensionClassOne extension = new TestExtensionClassOne("xxx"); + extensionPoint.registerExtension(extension); + extensionPoint.unregisterExtension(extension); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "2")); + extensionsArea.registerExtension("", ExtensionComponentAdapterTest.readElement( + "4")); + extensionPoint.registerExtension(new TestExtensionClassOne("5")); + extensions = (TestExtensionClassOne[]) extensionPoint.getExtensions(); + assertEquals(6, extensions.length); + assertEquals("1", extensions[0].getText()); + assertEquals("2", extensions[1].getText()); + assertEquals("3", extensions[2].getText()); + assertEquals("4", extensions[3].getText()); + assertEquals("5", extensions[4].getText()); + assertEquals("6", extensions[5].getText()); + } + + public interface MyInterface1 extends Runnable { + } + + public interface MyInterface2 { + } + + public interface MyInterface3 extends Runnable { + } + + public interface MyInterface4 extends MyInterface1, MyInterface2, MyInterface3 { + } + + public static class MyClass implements MyInterface4 { + public void run() { + } + } + + public static class MyComponent1 { + public MyInterface1 testObject; + + public MyComponent1(MyInterface1 testObject) { + this.testObject = testObject; + } + } + + public static class MyComponent2 { + public MyInterface1 testObject; + public MyComponent1[] comp1; + + public MyComponent2(MyComponent1[] comp1, MyInterface1 testObject) { + this.comp1 = comp1; + this.testObject = testObject; + } + } + + public static class MyTestComponent { + public MyComponent1[] comp1; + public MyComponent2 comp2; + + public MyTestComponent(MyComponent1[] comp1, MyComponent2 comp2) { + this.comp1 = comp1; + this.comp2 = comp2; + } + } + + public static class MyListener implements ExtensionPointAvailabilityListener { + public static int regcount = 0; + public static int remcount = 0; + + public void extensionPointRegistered(ExtensionPoint extensionPoint) { + regcount++; + } + + public void extensionPointRemoved(ExtensionPoint extensionPoint) { + remcount++; + } + + public static void reset() { + regcount = 0; + remcount = 0; + } + } +} diff --git a/extensions/testSource/com/intellij/openapi/extensions/impl/LoadingOrderTest.java b/extensions/testSource/com/intellij/openapi/extensions/impl/LoadingOrderTest.java new file mode 100644 index 000000000000..bdc238bc3b5f --- /dev/null +++ b/extensions/testSource/com/intellij/openapi/extensions/impl/LoadingOrderTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +import org.jmock.cglib.MockObjectTestCase; +import org.jmock.cglib.Mock; +import org.jmock.core.stub.ReturnStub; +import org.jdom.Element; +import org.picocontainer.PicoContainer; +import org.picocontainer.defaults.AssignabilityRegistrationException; + +import java.util.ArrayList; + +import com.intellij.openapi.extensions.LoadingOrder; +import com.intellij.openapi.extensions.SortingException; + +/** + * @author Alexander Kireyev + */ +public class LoadingOrderTest extends MockObjectTestCase { + public void testSimpleSorting() { + ArrayList target = new ArrayList(); + target.add(createElement(LoadingOrder.ANY, null, "any")); + target.add(createElement(LoadingOrder.FIRST, null, "1")); + target.add(createElement(LoadingOrder.LAST, null, "2")); + target.add(createElement(LoadingOrder.ANY, null, "any")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + assertSequence(array, "1anyany2"); + } + + public void testComplexSorting() { + ArrayList target = new ArrayList(); + String idOne = "idone"; + String idTwo = "idTwo"; + target.add(createElement(LoadingOrder.before(idTwo), idOne, "2")); + target.add(createElement(LoadingOrder.FIRST, null, "0")); + target.add(createElement(LoadingOrder.LAST, null, "5")); + target.add(createElement(LoadingOrder.after(idTwo), null, "4")); + target.add(createElement(LoadingOrder.ANY, idTwo, "3")); + target.add(createElement(LoadingOrder.before(idOne), null, "1")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + assertSequence(array, "012345"); + } + + public void testComplexSorting2() { + ArrayList target = new ArrayList(); + String idOne = "idone"; + target.add(createElement(LoadingOrder.before(idOne), null, "2")); + target.add(createElement(LoadingOrder.after(idOne), null, "4")); + target.add(createElement(LoadingOrder.FIRST, null, "1")); + target.add(createElement(LoadingOrder.ANY, idOne, "3")); + target.add(createElement(LoadingOrder.ANY, null, "5")); + target.add(createElement(LoadingOrder.LAST, null, "6")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + assertSequence(array, "123456"); + } + + private void assertSequence(LoadingOrder.Orderable[] array, String expected) { + LoadingOrder.sort(array); + StringBuffer sequence = buildSequence(array); + assertEquals(expected, sequence.toString()); + } + + private StringBuffer buildSequence(LoadingOrder.Orderable[] array) { + StringBuffer sequence = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + LoadingOrder.Orderable adapter = array[i]; + sequence.append(((MyElement)adapter.getDescribingElement()).getID()); + } + return sequence; + } + + public void testFailingSortingBeforeFirst() { + ArrayList target = new ArrayList(); + target.add(createElement(LoadingOrder.ANY, null, "good")); + target.add(createElement(LoadingOrder.FIRST, "first", "bad")); + target.add(createElement(LoadingOrder.LAST, null, "good")); + target.add(createElement(LoadingOrder.before("first"), null, "bad")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + checkSortingFailure(array, 2); + } + + public void testFailingSortingFirst() { + ArrayList target = new ArrayList(); + target.add(createElement(LoadingOrder.ANY, null, "good")); + target.add(createElement(LoadingOrder.FIRST, "first", "bad")); + target.add(createElement(LoadingOrder.LAST, null, "good")); + target.add(createElement(LoadingOrder.FIRST, null, "bad")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + checkSortingFailure(array, 2); + } + + private void checkSortingFailure(LoadingOrder.Orderable[] array, int expectedCount) { + try { + LoadingOrder.sort(array); + fail("Should have failed"); + } + catch (SortingException e) { + Element[] conflictingElements = e.getConflictingElements(); + assertEquals(expectedCount, conflictingElements.length); + for (int i = 0; i < conflictingElements.length; i++) { + MyElement conflictingElement = (MyElement) conflictingElements[i]; + assertEquals("bad", conflictingElement.getID()); + } + } + } + + public void testFailingSortingAfterLast() { + ArrayList target = new ArrayList(); + target.add(createElement(LoadingOrder.after("last"), null, "bad")); + target.add(createElement(LoadingOrder.FIRST, null, "good")); + target.add(createElement(LoadingOrder.LAST, "last", "bad")); + target.add(createElement(LoadingOrder.ANY, null, "good")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + checkSortingFailure(array, 2); + } + + public void testFailingSortingLast() { + ArrayList target = new ArrayList(); + target.add(createElement(LoadingOrder.LAST, null, "bad")); + target.add(createElement(LoadingOrder.FIRST, null, "good")); + target.add(createElement(LoadingOrder.LAST, "last", "bad")); + target.add(createElement(LoadingOrder.ANY, null, "good")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + checkSortingFailure(array, 2); + } + + public void testFailingSortingComplex() { + ArrayList target = new ArrayList(); + target.add(createElement(LoadingOrder.after("2"), "1", "bad")); + target.add(createElement(LoadingOrder.after("3"), "2", "bad")); + target.add(createElement(LoadingOrder.after("1"), "3", "bad")); + LoadingOrder.Orderable[] array = (LoadingOrder.Orderable[]) target.toArray(new LoadingOrder.Orderable[target.size()]); + checkSortingFailure(array, 3); + } + + private LoadingOrder.Orderable createElement(LoadingOrder order, String idString, String elementId) { + Mock mock = new Mock(LoadingOrder.Orderable.class); + mock.stubs().method("getOrder").withNoArguments().will(new ReturnStub(order)); + mock.stubs().method("getOrderId").withNoArguments().will(returnValue(idString)); + mock.stubs().method("getDescribingElement").withNoArguments().will(new ReturnStub(new MyElement(elementId))); + return (LoadingOrder.Orderable) mock.proxy(); + } + + private static class MyElement extends Element { + private String myID; + + public MyElement(String ID) { + myID = ID; + } + + public String getID() { + return myID; + } + } +} diff --git a/extensions/testSource/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java b/extensions/testSource/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java new file mode 100644 index 000000000000..17e75d38e220 --- /dev/null +++ b/extensions/testSource/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package com.intellij.openapi.extensions.impl; + +/** + * @author Alexander Kireyev + */ +public class TestExtensionClassOne { + private String myText; + + public TestExtensionClassOne() { + } + + public TestExtensionClassOne(String text) { + myText = text; + } + + public String getText() { + return myText; + } + + public void setText(String text) { + myText = text; + } +} diff --git a/lib/dev/jmock-1.0.1.jar b/lib/dev/jmock-1.0.1.jar new file mode 100644 index 000000000000..e774359fc19b Binary files /dev/null and b/lib/dev/jmock-1.0.1.jar differ diff --git a/lib/dev/jmock-cglib-1.0.1.jar b/lib/dev/jmock-cglib-1.0.1.jar new file mode 100644 index 000000000000..494fa645b0ea Binary files /dev/null and b/lib/dev/jmock-cglib-1.0.1.jar differ diff --git a/lib/xstream.jar b/lib/xstream.jar new file mode 100644 index 000000000000..d657fa0a35e4 Binary files /dev/null and b/lib/xstream.jar differ