/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.tools; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.URL; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.rapidminer.RapidMiner; import com.rapidminer.io.process.XMLTools; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorCreationException; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.ports.Port; import com.rapidminer.operator.ports.Ports; import com.rapidminer.operator.tools.OperatorCreationHook; import com.rapidminer.tools.documentation.OperatorDocBundle; import com.rapidminer.tools.documentation.XMLOperatorDocBundle; import com.rapidminer.tools.plugin.Plugin; import com.sun.corba.se.spi.orb.OperationFactory; /** * This class maintains all registered operators in the current context. * There exists a listener concept that will alert all listeners, if new operators are added * or removed. * * It provides convenience methods for creating new {@link Operator}s. See the description of * the {@link #createOperator(Class)} method. Please mind that {@link Operator}s that are * built from an {@link OperationFactory} cannot be constructed with this method. Please use {@link #createOperator(String)} method instead, * that can be passed the {@link Operator}'s key. * * * <p> * This class also reads the xml definitions of the RapidMiner Core and Extension operators. These descriptions are entries in a XML file * like OperatorsCore.xml. * </p> * * @author Ingo Mierswa, Simon Fischer, Sebastian Land */ public class OperatorService { /** * The interface for all Listener to the {@link OperatorService}. * * @author Sebastian Land */ public static interface OperatorServiceListener { /** * This will be called if an operator is registered. * * ATTENTION!!! * You must ensure that bundle might be null! * */ public void operatorRegistered(OperatorDescription description, OperatorDocBundle bundle); /** * This method will be called if an operator is removed. */ public void operatorUnregistered(OperatorDescription description); } public static final String RAPID_MINER_CORE_PREFIX = "RapidMiner Core"; public static final String RAPID_MINER_CORE_NAMESPACE = "core"; private static final String OPERATORS_XML = "OperatorsCore.xml"; private static final LinkedList<WeakReference<OperatorServiceListener>> listeners = new LinkedList<WeakReference<OperatorServiceListener>>(); private static final List<OperatorCreationHook> operatorCreationHooks = new LinkedList<OperatorCreationHook>(); /** * Maps operator keys as defined in the OperatorsCore.xml to operator descriptions. */ private static final Map<String, OperatorDescription> KEYS_TO_DESCRIPTIONS = new HashMap<String, OperatorDescription>(); /** Set of all Operator classes registered. */ private static final Set<Class<? extends Operator>> REGISTERED_OPERATOR_CLASSES = new HashSet<Class<? extends Operator>>(); /** The Map for all IO objects (maps short names on classes). */ private static final Map<String, Class<? extends IOObject>> IO_OBJECT_NAME_MAP = new TreeMap<String, Class<? extends IOObject>>(); /** Maps deprecated operator names to new names. */ private static final Map<String, String> DEPRECATION_MAP = new HashMap<String, String>(); private static final GroupTreeRoot groupTreeRoot = new GroupTreeRoot(); public static void init() { URL mainOperators = getMainOperators(); if (mainOperators == null) { LogService.getRoot().severe("Cannot find main operator description file " + Tools.RESOURCE_PREFIX + OPERATORS_XML + "."); } else { registerOperators(mainOperators, null, null); } // additional operators from starting parameter String additionalOperators = System.getProperty(RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL); if (additionalOperators != null && !additionalOperators.isEmpty()) { if (!RapidMiner.getExecutionMode().canAccessFilesystem()) { LogService.getRoot().config("Execution mode " + RapidMiner.getExecutionMode() + " does not permit accessing the file system. Ignoring additional operator description files '" + additionalOperators + "'."); } else { LogService.getRoot().info("Loading additional operators specified by RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL (" + additionalOperators + ")"); String[] additionalOperatorFileNames = additionalOperators.split(File.pathSeparator); for (String additionalOperatorFileName : additionalOperatorFileNames) { File additionalOperatorFile = new File(additionalOperatorFileName); if (additionalOperatorFile.exists()) { FileInputStream in = null; try { in = new FileInputStream(additionalOperatorFile); OperatorService.registerOperators(additionalOperatorFile.getPath(), in, null); } catch (IOException e) { LogService.getRoot().log(Level.SEVERE, "Cannot read '" + additionalOperatorFile + "'.", e); } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } } else { LogService.getRoot().severe("Cannot find operator description file '" + additionalOperatorFileName + "'"); } } } } // loading operators from plugins Plugin.registerAllPluginOperators(); LogService.getRoot().config("Number of registered operator classes: " + REGISTERED_OPERATOR_CLASSES.size() + "; number of registered operator descriptions: " + KEYS_TO_DESCRIPTIONS.size() + "; number of replacements: " + DEPRECATION_MAP.size()); } public static void registerOperators(URL operatorsXML, ClassLoader classLoader, Plugin plugin) { InputStream inputStream; try { inputStream = operatorsXML.openStream(); } catch (IOException e) { LogService.getRoot().log(Level.WARNING, "Cannot open stream to operator description file " + operatorsXML + ": " + e, e); return; } registerOperators(OPERATORS_XML, inputStream, null, plugin); } /** * Registers all operators from a given XML input stream. Closes the stream. */ public static void registerOperators(String name, InputStream operatorsXML, ClassLoader classLoader) { registerOperators(name, operatorsXML, classLoader, null); } public static void registerOperators(String name, InputStream operatorsXML, ClassLoader classLoader, Plugin provider) { // register operators if (classLoader == null) { classLoader = OperatorService.class.getClassLoader(); } LogService.getRoot().config("Loading operators from '" + name + "'."); String version = null; Document document = null; try { document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(operatorsXML); if (!document.getDocumentElement().getTagName().toLowerCase().equals("operators")) { LogService.getRoot().severe("Operator description file '" + name + "': outermost tag must be <operators>!"); return; } version = document.getDocumentElement().getAttribute("version"); if (version.equals("5.0")) { parseOperators(document, classLoader, provider); // } else { // parseOperatorsPre5(document, classLoader, provider); } } catch (Exception e) { LogService.getRoot().log(Level.SEVERE, "Cannot read operator description file '" + name + "': no valid XML: " + e.getMessage(), e); return; } finally { try { operatorsXML.close(); } catch (IOException e) { e.printStackTrace(); } } } private static void parseOperators(Document document, ClassLoader classLoader, Plugin provider) throws XMLException, OperatorCreationException { String docBundle = document.getDocumentElement().getAttribute("docbundle"); OperatorDocBundle bundle; if (docBundle == null || docBundle.isEmpty()) { bundle = null; LogService.getRoot().warning("Operators for " + provider.getName() + " don't have an attached documentation."); } else { bundle = XMLOperatorDocBundle.load(classLoader, docBundle); } parseOperators(groupTreeRoot, document.getDocumentElement(), classLoader, provider, bundle); } private static void parseOperators(GroupTree currentGroup, Element groupElement, ClassLoader classLoader, Plugin provider, OperatorDocBundle bundle) throws XMLException, OperatorCreationException { NodeList children = groupElement.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child instanceof Element) { Element childElement = (Element) child; if (childElement.getTagName().equals("group")) { String name = childElement.getAttribute("key"); String icon = XMLTools.getTagContents(childElement, "icon"); GroupTree newTree; if (name != null && !name.isEmpty()) { newTree = currentGroup.getOrCreateSubGroup(name, bundle); } else { newTree = currentGroup; } if (icon != null && icon.length() > 0) { newTree.setIconName(icon); } else { if (newTree.getIconName() == null || newTree.getIconName().length() == 0) newTree.setIconName(currentGroup.getIconName()); } parseOperators(newTree, childElement, classLoader, provider, bundle); } else if (childElement.getTagName().equals("operator")) { try { OperatorDescription desc = new OperatorDescription(currentGroup.getFullyQualifiedKey(), childElement, classLoader, provider, bundle); registerOperator(desc, bundle); if (desc.getReplacedKeys() != null) { for (String replaces : desc.getReplacedKeys()) { DEPRECATION_MAP.put(replaces, desc.getKey()); } } } catch (ClassNotFoundException e) { LogService.getRoot().log(Level.WARNING, "Cannot create operator description: " + provider.getExtensionId() + ":" + XMLTools.getTagContents(childElement, "key", false), e); } catch (NoClassDefFoundError e) { LogService.getRoot().log(Level.WARNING, "Cannot create operator description: " + provider.getExtensionId() + ":" + XMLTools.getTagContents(childElement, "key", false), e); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, "Failed to register operator: " + provider.getExtensionId() + ":" + XMLTools.getTagContents(childElement, "key", false), e); } catch (AbstractMethodError e) { LogService.getRoot().log(Level.WARNING, "Failed to register operator: " + provider.getExtensionId() + ":" + XMLTools.getTagContents(childElement, "key", false), e); } catch (Throwable e) { // Yes, this is evil. However, it is the only way we can prevent errors due to // incompatible RapidMiner / extension updates LogService.getRoot().log(Level.SEVERE, "Failed to register operator: " + provider.getExtensionId() + ":" + XMLTools.getTagContents(childElement, "key", false), e); } } else if (childElement.getTagName().equals("factory")) { String factoryClassName = childElement.getTextContent(); if (factoryClassName == null || factoryClassName.isEmpty()) { LogService.getRoot().warning("Malformed operator descriptor: <factory> tag must contain class name!"); } else { Class factoryClass = null; try { factoryClass = Class.forName(factoryClassName, true, classLoader); } catch (ClassNotFoundException e) { LogService.getRoot().warning("Operator factory class '" + factoryClassName + "' not found!"); } if (factoryClass != null) { if (GenericOperatorFactory.class.isAssignableFrom(factoryClass)) { GenericOperatorFactory factory = null; try { factory = (GenericOperatorFactory) factoryClass.newInstance(); } catch (Exception e) { LogService.getRoot().warning("Cannot instantiate operator factory class '" + factoryClass.getName() + "'!"); } catch (Throwable e) { // Yes, this is evil. However, it is the only way we can prevent errors due to // incompatible RapidMiner / extension updates LogService.getRoot().log(Level.SEVERE, "Failed to register operator: " + e, e); } LogService.getRoot().config("Creating operators from factory " + factoryClassName); try { factory.registerOperators(classLoader, provider); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, "Error registering operators from " + factoryClass.getName() + e, e); } catch (Throwable e) { // Yes, this is evil. However, it is the only way we can prevent errors due to // incompatible RapidMiner / extension updates LogService.getRoot().log(Level.SEVERE, "Failed to register operator: " + e, e); } } else { LogService.getRoot().warning("Malformed operator descriptor: Only subclasses of GenericOperatorFactory may be defined as class, was '" + factoryClassName + "'!"); } } } } else if (childElement.getTagName().equals("icon")) { // why do we ignore this? } else { throw new XMLException("Illegal tag in operator descrioption file: " + childElement.getTagName()); } } } } // private static void parseOperatorsPre5(Document document, ClassLoader classLoader, Plugin provider) { // // operators // NodeList operatorTags = document.getDocumentElement().getElementsByTagName("operator"); // for (int i = 0; i < operatorTags.getLength(); i++) { // Element currentElement = (Element) operatorTags.item(i); // try { // parseOperatorPre5(currentElement, classLoader, provider); // } catch (Throwable e) { // Attr currentNameAttr = currentElement.getAttributeNode("name"); // if (currentNameAttr != null) { // LogService.getRoot().log(Level.WARNING, "Cannot register '" + currentNameAttr.getValue() + "': " + e, e); // } else { // LogService.getRoot().log(Level.WARNING, "Cannot register '" + currentElement + "': " + e, e); // } // } // } // } // // /** // * Registers an operator description from an XML tag (operator description // * file, mostly operators.xml). // * // * Warning suppressed because of old style creation of OperatorDescription // */ // @SuppressWarnings("deprecation") // private static void parseOperatorPre5(Element operatorTag, ClassLoader classLoader, Plugin provider) throws Exception { // Attr nameAttr = operatorTag.getAttributeNode("name"); // Attr classAttr = operatorTag.getAttributeNode("class"); // if (nameAttr == null) // throw new Exception("Missing name for <operator> tag"); // if (classAttr == null) // throw new Exception("Missing class for <operator> tag"); // // String name = nameAttr.getValue(); // // String deprecationString = operatorTag.getAttribute("deprecation"); // String group = operatorTag.getAttribute("group"); // String icon = operatorTag.getAttribute("icon"); // if (icon.isEmpty()) { // icon = null; // } // // String names[] = name.split(","); // int i = 0; // for (String opName : names) { // if (i > 0) { // deprecationString = "Replaced by " + names[0] + "."; // DEPRECATION_MAP.put(opName, names[0]); // // OperatorDescription replacement = getOperatorDescription(names[0]); // replacement.setIsReplacementFor(opName); // } // String name1 = opName.trim(); // OperatorDescription description = new OperatorDescription(classLoader, name1, name1, classAttr.getValue(), group, icon, // deprecationString, provider); // // add to group // try { // registerOperator(description); // } catch (Exception e) { // LogService.getRoot().log(Level.WARNING, "Failed to register operator "+description.getKey()+": "+e, e); // } // i++; // } // } /** * This method does the same as {@link #registerOperator(OperatorDescription, OperatorDocBundle))}, * but without an {@link OperatorDocBundle} groups will not have a name or icon. This method remains for * compatibility of older extensions. */ @Deprecated public static void registerOperator(OperatorDescription description) throws OperatorCreationException { registerOperator(description, null); } /** * Registers the given operator description. Please note that two different * descriptions must not have the same name. Otherwise the second * description overwrite the first in the description map. * * If there's no icon defined for the given {@link OperatorDescription}, * the group icon will be set here. * * @param bundle * might be null. If existing will be used for GroupCreation / Icon settings * @throws OperatorCreationException */ public static void registerOperator(OperatorDescription description, OperatorDocBundle bundle) throws OperatorCreationException { // check if this operator was not registered earlier OperatorDescription oldDescription = KEYS_TO_DESCRIPTIONS.get(description.getName()); if (oldDescription != null) { LogService.getRoot().warning("Operator key '" + description.getKey() + "' was already registered for class " + oldDescription.getOperatorClass().getName() + ". Overwriting with " + description.getOperatorClass() + "."); } // check if icon already was set. if (!description.isIconDefined()) { description.setIconName(groupTreeRoot.findOrCreateGroup(description.getGroup(), bundle).getIconName()); } // register in maps KEYS_TO_DESCRIPTIONS.put(description.getKey(), description); REGISTERED_OPERATOR_CLASSES.add(description.getOperatorClass()); // TODO: Check if still necessary. Operator currentOperator = description.createOperatorInstance(); currentOperator.assumePreconditionsSatisfied(); currentOperator.transformMetaData(); checkIOObjects(currentOperator.getInputPorts()); checkIOObjects(currentOperator.getOutputPorts()); // inform listener invokeOperatorRegisteredListener(description, bundle); } /** * This method can be used to dynamically remove Operators from the number of defined operators. */ public static void unregisterOperator(OperatorDescription description) { KEYS_TO_DESCRIPTIONS.remove(description.getKey()); REGISTERED_OPERATOR_CLASSES.remove(description.getOperatorClass()); // inform all listener including GroupTree invokeOperatorUnregisteredListener(description); } /** * Checks if the classes generated by these ports are already registered and * registers them if not. */ private static void checkIOObjects(Ports<? extends Port> ports) { List<Class<? extends IOObject>> result = new LinkedList<Class<? extends IOObject>>(); for (Port port : ports.getAllPorts()) { if (port.getMetaData() != null) { result.add(port.getMetaData().getObjectClass()); } } registerIOObjects(result); } /** Checks if the given classes are already registered and adds them if not. */ public static void registerIOObjects(Collection<Class<? extends IOObject>> objects) { for (Class<? extends IOObject> currentClass : objects) { String current = currentClass.getName(); IO_OBJECT_NAME_MAP.put(current.substring(current.lastIndexOf(".") + 1), currentClass); } } /** Returns a sorted set of all short IO object names. */ public static Set<String> getIOObjectsNames() { //TODO: Check if this can be replaced! //return RendererService.getAllRenderableObjectNames(); return IO_OBJECT_NAME_MAP.keySet(); } /** Returns the class for the short name of an IO object. */ public static Class<? extends IOObject> getIOObjectClass(String name) { // TODO: CHECK // assert (IO_OBJECT_NAME_MAP.get(name).equals(RendererService.getClass(name))); // // return RendererService.getClass(name); return IO_OBJECT_NAME_MAP.get(name); } /** * Returns a collection of all operator keys. Use {@link #getOperatorKeys()} instead. */ @Deprecated public static Set<String> getOperatorNames() { return KEYS_TO_DESCRIPTIONS.keySet(); } /** * Returns a collection of all operator keys. */ public static Set<String> getOperatorKeys() { return KEYS_TO_DESCRIPTIONS.keySet(); } /** * Returns the group hierarchy of all operators. * This will automatically reflect changes on the registered Operators. * You might register as listener to {@link OperatorService} in order * to receive registration or unregistration eventsevents */ public static GroupTree getGroups() { return groupTreeRoot; } // ================================================================================ // Operator Factory Methods // ================================================================================ /** * Returns the operator descriptions for the operators which uses the given * class. Performs a linear seach through all operator descriptions. */ public static OperatorDescription[] getOperatorDescriptions(Class clazz) { List<OperatorDescription> result = new LinkedList<OperatorDescription>(); for (OperatorDescription current : KEYS_TO_DESCRIPTIONS.values()) { if (current.getOperatorClass().equals(clazz)) result.add(current); } OperatorDescription[] resultArray = new OperatorDescription[result.size()]; result.toArray(resultArray); return resultArray; } /** * Returns the operator description for a given key from the * operators.xml file, e.g. "read_csv" for the Read CSV operator. */ public static OperatorDescription getOperatorDescription(String key) { return KEYS_TO_DESCRIPTIONS.get(key); } /** * Use this method to create an operator from the given class name (from * operator description file operators.xml, not from the Java class name). * For most operators, is is recommended to use the method {@link #createOperator(Class)} which can be checked during compile time. * This is, however, not possible for some generic operators like the Weka * operators. In that case, you have to use this method with the argument * from the operators.xml file, e.g. <tt>createOperator("J48")</tt> for a J48 decision tree learner. */ public static Operator createOperator(String typeName) throws OperatorCreationException { OperatorDescription description = getOperatorDescription(typeName); if (description == null) throw new OperatorCreationException(OperatorCreationException.NO_DESCRIPTION_ERROR, typeName, null); return createOperator(description); } /** Use this method to create an operator of a given description object. */ public static Operator createOperator(OperatorDescription description) throws OperatorCreationException { return description.createOperatorInstance(); } /** * <p> * Use this method to create an operator from an operator class. This is the only method which ensures operator existence checks during * compile time (and not during runtime) and the usage of this method is therefore the recommended way for operator creation. * </p> * * <p> * It is, however, not possible to create some generic operators with this method (this mainly applies to the Weka operators). Please * use the method {@link #createOperator(String)} for those generic operators. * </p> * * <p> * If you try to create a generic operator with this method, the OperatorDescription will not be unique for the given class and an * OperatorCreationException is thrown. * </p> * * <p> * Please note that is is not necessary to cast the operator to the desired class. * </p> * * TODO: can we remove the suppress warning here? */ @SuppressWarnings("unchecked") public static <T extends Operator> T createOperator(Class<T> clazz) throws OperatorCreationException { OperatorDescription[] descriptions = getOperatorDescriptions(clazz); if (descriptions.length == 0) { throw new OperatorCreationException(OperatorCreationException.NO_DESCRIPTION_ERROR, clazz.getName(), null); } else if (descriptions.length > 1) { List<OperatorDescription> nonDeprecated = new LinkedList<OperatorDescription>(); for (OperatorDescription od : descriptions) { if (od.getDeprecationInfo() == null) { nonDeprecated.add(od); } } if (nonDeprecated.size() > 1) { throw new OperatorCreationException(OperatorCreationException.NO_UNIQUE_DESCRIPTION_ERROR, clazz.getName(), null); } else { return (T) nonDeprecated.get(0).createOperatorInstance(); } } else { return (T) descriptions[0].createOperatorInstance(); } } /** * Returns a replacement if the given operator class is deprecated, and null * otherwise. * The deprecated Key is the key with that this operator was used in RapidMiner 4.x */ public static String getReplacementForDeprecatedClass(String deprecatedKey) { return DEPRECATION_MAP.get(deprecatedKey); } /** Specifies a list of files to be loaded as operator descriptors. */ public static void setAdditionalOperatorDescriptors(String... files) { if (files == null || files.length == 0) { System.setProperty(RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL, null); return; } StringBuffer buf = new StringBuffer(); boolean first = true; for (String file : files) { if (!first) { buf.append(File.pathSeparator); } else { first = false; } buf.append(file); } System.setProperty(RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL, buf.toString()); } /** Returns the main operator description file (XML). */ private static URL getMainOperators() { String resource; String operatorsXML = System.getProperty(RapidMiner.PROPERTY_RAPIDMINER_INIT_OPERATORS); if (operatorsXML != null) { resource = operatorsXML; LogService.getRoot().config("Main operator descriptor overrideen by system property. Using " + operatorsXML + "."); } else { resource = "/" + Tools.RESOURCE_PREFIX + OPERATORS_XML; } return OperatorService.class.getResource(resource); } /* * OperatorCreationHooks */ /** * This method adds an {@link OperatorCreationHook} that will be informed whenever an {@link Operator} instance is created. */ public static void addOperatorCreationHook(OperatorCreationHook operatorCreationHook) { operatorCreationHooks.add(operatorCreationHook); } /** * This method must be called by each {@link OperatorDescription#createOperatorInstance()} call. * It is used for example for statistics purpose. */ public static void invokeCreationHooks(Operator operator) { for (OperatorCreationHook hook : operatorCreationHooks) { try { hook.operatorCreated(operator); } catch (RuntimeException e) { LogService.getRoot().log(Level.WARNING, "Error in operator creation hook: " + e, e); } } } /* * Listener to changes in available Operators */ /** * This method can be used to add an listener to the OperatorService that is * informed whenever the set of available operators changes. * Internally WeakReferences are used so that there's no need to deregister listeners. */ public static void addOperatorServiceListener(OperatorServiceListener listener) { listeners.add(new WeakReference<OperatorServiceListener>(listener)); } /** * This method will inform all listeners of a change in the available operators. */ private static void invokeOperatorRegisteredListener(OperatorDescription description, OperatorDocBundle bundle) { List<WeakReference<OperatorServiceListener>> listenersCopy = new LinkedList<WeakReference<OperatorServiceListener>>(listeners); for (WeakReference<OperatorServiceListener> listenerRef : listenersCopy) { OperatorServiceListener operatorServiceListener = listenerRef.get(); if (operatorServiceListener != null) operatorServiceListener.operatorRegistered(description, bundle); } Iterator<WeakReference<OperatorServiceListener>> iterator = listenersCopy.iterator(); while (iterator.hasNext()) { OperatorServiceListener operatorServiceListener = iterator.next().get(); if (operatorServiceListener == null) iterator.remove(); } } /** * This method will inform all listeners of a change in the available operators. */ private static void invokeOperatorUnregisteredListener(OperatorDescription description) { List<WeakReference<OperatorServiceListener>> listenersCopy = new LinkedList<WeakReference<OperatorServiceListener>>(listeners); for (WeakReference<OperatorServiceListener> listenerRef : listenersCopy) { OperatorServiceListener operatorServiceListener = listenerRef.get(); if (operatorServiceListener != null) operatorServiceListener.operatorUnregistered(description); } Iterator<WeakReference<OperatorServiceListener>> iterator = listenersCopy.iterator(); while (iterator.hasNext()) { OperatorServiceListener operatorServiceListener = iterator.next().get(); if (operatorServiceListener == null) iterator.remove(); } } }