/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.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.ArrayList; 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.ProcessRootOperator; import com.rapidminer.operator.ports.IncompatibleMDClassException; import com.rapidminer.operator.ports.Port; import com.rapidminer.operator.ports.Ports; import com.rapidminer.operator.ports.metadata.MetaData; import com.rapidminer.operator.tools.OperatorCreationHook; import com.rapidminer.tools.documentation.OperatorDocBundle; import com.rapidminer.tools.documentation.OperatorDocumentation; import com.rapidminer.tools.documentation.XMLOperatorDocBundle; import com.rapidminer.tools.plugin.Plugin; /** * 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 GenericOperatorFactory} 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 Studio 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 Studio 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<>(); private static final List<OperatorCreationHook> operatorCreationHooks = new LinkedList<>(); /** * Maps operator keys as defined in the OperatorsCore.xml to operator descriptions. */ private static final Map<String, OperatorDescription> KEYS_TO_DESCRIPTIONS = new HashMap<>(); /** Set of all Operator classes registered. */ private static final Set<Class<? extends Operator>> REGISTERED_OPERATOR_CLASSES = new HashSet<>(); /** 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<>(); /** Maps deprecated operator names to new names. */ private static final Map<String, String> DEPRECATION_MAP = new HashMap<>(); private static final GroupTreeRoot groupTreeRoot = new GroupTreeRoot(); public static void init() { URL mainOperators = getMainOperators(); if (mainOperators == null) { LogService.getRoot().log(Level.SEVERE, "com.rapidminer.tools.OperatorService.main_operator_descripton_file_not_found", new Object[] { Tools.RESOURCE_PREFIX, OPERATORS_XML }); } else { registerOperators(mainOperators, null, null); } // additional operators from starting parameter String additionalOperators = System.getProperty(RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL); // check whether the bug that System.getProperty(key) returns key instead of null appears if (additionalOperators != null && additionalOperators.contains(RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL)) { return; } if (additionalOperators != null && !additionalOperators.isEmpty()) { if (!RapidMiner.getExecutionMode().canAccessFilesystem()) { LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.OperatorService.execution_mode_does_not_permitting_accessing_file_system", new Object[] { RapidMiner.getExecutionMode(), additionalOperators }); } else { LogService.getRoot().log(Level.INFO, "com.rapidminer.tools.OperatorService.loading_additional_operators", new Object[] { RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL, additionalOperators }); String[] additionalOperatorFileNames = additionalOperators.split(File.pathSeparator); for (String additionalOperatorFileName : additionalOperatorFileNames) { File additionalOperatorFile = new File(additionalOperatorFileName); if (additionalOperatorFile.exists()) { try (FileInputStream in = new FileInputStream(additionalOperatorFile)) { OperatorService.registerOperators(additionalOperatorFile.getPath(), in, null); } catch (IOException e) { LogService.getRoot().log(Level.SEVERE, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.reading_additional_operator_file_error", additionalOperatorFile), e); } } else { LogService.getRoot().log(Level.SEVERE, "com.rapidminer.tools.OperatorService.operator_description_file_not_found", additionalOperatorFileName); } } } } // loading operators from plugins Plugin.registerAllPluginOperators(); // add parent folder as operator tag addParentFolderOperatorTags(); LogService.getRoot().log(Level.FINE, "com.rapidminer.tools.OperatorService.number_of_registered_operator_classes_and_descriptions", new Object[] { REGISTERED_OPERATOR_CLASSES.size(), KEYS_TO_DESCRIPTIONS.size(), DEPRECATION_MAP.size() }); } /** * Adds parent folder name of each operator to the operator tags. */ private static void addParentFolderOperatorTags() { for (String operatorKey : getOperatorKeys()) { OperatorDescription operatorDescription = getOperatorDescription(operatorKey); if (operatorDescription == null) { continue; } if (ProcessRootOperator.class.equals(operatorDescription.getOperatorClass())) { // no tags for the root process continue; } GroupTree subTree = groupTreeRoot.findGroup(operatorDescription.getGroup()); if (subTree == null) { continue; } String groupName = subTree.getName(); if (groupName != null && !groupName.trim().isEmpty()) { if (!operatorDescription.getTags().contains(groupName.trim())) { OperatorDocumentation operatorDocumentation = operatorDescription.getOperatorDocumentation(); ArrayList<String> updatedTags = new ArrayList<>(operatorDocumentation.getTags().size() + 1); updatedTags.addAll(operatorDocumentation.getTags()); updatedTags.add(groupName.trim()); operatorDocumentation.setTags(updatedTags); } } } } /** * Refreshes all operator descriptions including the reloading of operator icons. */ public static void refreshOperatorDescriptions() { for (OperatorDescription desc : KEYS_TO_DESCRIPTIONS.values()) { desc.refresh(); } } public static void registerOperators(URL operatorsXML, ClassLoader classLoader, Plugin plugin) { InputStream inputStream = null; try { if (operatorsXML != null) { inputStream = operatorsXML.openStream(); } } catch (IOException e) { // LogService.getRoot().log(Level.WARNING, // "Cannot open stream to operator description file " + operatorsXML + ": " + e, e); LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.opening_stream_to_operator_description_file_error", 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 + "'."); LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.OperatorService.loading_operators", name); String version = null; Document document = null; try { document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(operatorsXML); if (!document.getDocumentElement().getTagName().toLowerCase().equals("operators")) { LogService.getRoot().log(Level.SEVERE, "com.rapidminer.tools.OperatorService.operator_description_file_outermost_tag", name); return; } version = document.getDocumentElement().getAttribute("version"); if (version.startsWith("5.") || version.startsWith("6.") || version.startsWith("7.")) { parseOperators(document, classLoader, provider); } else { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_description_file_wrong_version", name, version)); } } catch (Exception e) { LogService.getRoot().log(Level.SEVERE, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_description_file_reading_error", name, e.getMessage()), e); return; } finally { try { operatorsXML.close(); } catch (IOException e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.error_closing_stream", e.getMessage()), e); } } } 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; String providerName; if (provider == null) { providerName = "RapidMiner core"; } else { providerName = provider.getName(); } LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.OperatorService.operators_no_attached_documention", providerName); } else { bundle = XMLOperatorDocBundle.load(classLoader, docBundle); } GroupTree root = groupTreeRoot; /* * Check whether the operators should be placed in the Extensions top level folder */ if (provider != null && provider.useExtensionTreeRoot()) { // get or create the Extensions group root = groupTreeRoot.getOrCreateSubGroup(OperatorDescription.EXTENSIONS_GROUP_IDENTIFIER, bundle); // get or create the Extension name group root = root.getOrCreateSubGroup(provider.getName(), bundle); } parseOperators(root, 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")) { String extensionId = "RapidMiner Core"; if (provider != null) { extensionId = provider.getExtensionId(); } try { String groupKey = currentGroup.getFullyQualifiedKey(); OperatorDescription desc = new OperatorDescription(groupKey, childElement, classLoader, provider, bundle); desc.setUseExtensionTreeRoot(provider != null ? provider.useExtensionTreeRoot() : false); 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, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_description_creating_error", extensionId, XMLTools.getTagContents(childElement, "key", false)), e); } catch (NoClassDefFoundError e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_description_creating_error", extensionId, XMLTools.getTagContents(childElement, "key", false)), e); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_registering_error", extensionId, XMLTools.getTagContents(childElement, "key", false)), e); } catch (AbstractMethodError e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_registering_error", extensionId, 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, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_registering_error", extensionId, XMLTools.getTagContents(childElement, "key", false)), e); } } else if (childElement.getTagName().equals("factory")) { String factoryClassName = childElement.getTextContent().trim(); if (factoryClassName == null || factoryClassName.isEmpty()) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.OperatorService.malformed_operator_descriptor_factory"); } else { Class<?> factoryClass = null; try { factoryClass = Class.forName(factoryClassName, true, classLoader); } catch (Throwable e) { // LogService.getRoot().warning("Operator factory class '" + // factoryClassName + "' not found!"); LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.operator_factory_class_not_found", factoryClassName), e); } if (factoryClass != null) { if (GenericOperatorFactory.class.isAssignableFrom(factoryClass)) { GenericOperatorFactory factory = null; try { factory = (GenericOperatorFactory) factoryClass.newInstance(); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.OperatorService.operator_instantiating_error", 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, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.failed_to_register_oprator", e), e); } LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.OperatorService.creating_operators_from_factory", factoryClassName); try { if (factory != null) { factory.registerOperators(classLoader, provider); } } catch (Exception e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.error_registering_oprators_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, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.failed_to_register_oprator", e), e); } } else { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.OperatorService.malformed_operator_descriptor_subclasses", factoryClassName); } } } } else if (childElement.getTagName().equals("icon")) { // why do we ignore this? } else { throw new XMLException("Illegal tag in operator descrioption file: " + childElement.getTagName()); } } } } /** * 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.getKey()); if (oldDescription != null) { // LogService.getRoot().warning("Operator key '" + description.getKey() + // "' was already registered for class " + // oldDescription.getOperatorClass().getName() + ". Overwriting with " + // description.getOperatorClass() + "."); LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.OperatorService.operator_key_already_registered", new Object[] { description.getKey(), oldDescription.getOperatorClass().getName(), 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); } /** * <strong>Experimental method.</strong> Unregisters all operators for the given Plugin. */ public static void unregisterAll(Plugin plugin) { for (String opKey : new HashSet<>(OperatorService.getOperatorKeys())) { OperatorDescription desc = OperatorService.getOperatorDescription(opKey); if (desc.getProvider() == plugin) { unregisterOperator(desc); } } } /** * 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<>(); for (Port port : ports.getAllPorts()) { try { if (port.getMetaData(MetaData.class) != null) { result.add(port.getMetaData(MetaData.class).getObjectClass()); } } catch (IncompatibleMDClassException e) { // cannot happen since MetaData is the base class for all meta data } } 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 search through all operator descriptions. */ public static OperatorDescription[] getOperatorDescriptions(Class<?> clazz) { if (clazz == null) { return new OperatorDescription[0]; } List<OperatorDescription> result = new ArrayList<>(1); for (OperatorDescription current : KEYS_TO_DESCRIPTIONS.values()) { if (current.getOperatorClass().equals(clazz)) { result.add(current); } } return result.toArray(new OperatorDescription[result.size()]); } /** * 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> */ @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<>(); 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); } 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.clearProperty(RapidMiner.PROPERTY_RAPIDMINER_OPERATORS_ADDITIONAL); 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 + "."); LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.OperatorService.main_operator_descriptor_overrideen", 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); LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.OperatorService.error_in_operation_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<>(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<>(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<>(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(); } } } }