/*
* 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.operator;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.swing.ImageIcon;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.io.process.XMLTools;
import com.rapidminer.tools.GenericOperatorFactory;
import com.rapidminer.tools.GroupTree;
import com.rapidminer.tools.OperatorService;
import com.rapidminer.tools.XMLException;
import com.rapidminer.tools.documentation.OperatorDocBundle;
import com.rapidminer.tools.documentation.OperatorDocumentation;
import com.rapidminer.tools.plugin.Plugin;
/**
* Data container for name, class, short name, path and the (very short)
* description of an operator. If the corresponding operator is not marked
* as deprecated the deprecation info string should be null. If the icon
* string is null, the group icon will be used.
*
* @author Ingo Mierswa
*/
public class OperatorDescription implements Comparable<OperatorDescription> {
private static final ImageIcon[] EMPTY_ICONS = new ImageIcon[3];
private final String key;
private final Class<? extends Operator> clazz;
private List<String> replacesDeprecatedKeys;
private final OperatorDocumentation documentation;
private ImageIcon[] icons;
private String fullyQualifiedGroupKey;
/**
* @deprecated Only used for Weka
*/
@Deprecated
private final String deprecationInfo = null;
private String iconName;
private final Plugin provider;
private boolean enabled = true;
/**
* Parses an operator in the RM 5.0 xml standard for operator definitions.
* In contrast to earlier versions, the {@link OperatorDescription} does not
* register themselves on the OperatorTree. This is now handled centralized by the {@link OperatorService}.
*
* @param bundle
*/
@SuppressWarnings("unchecked")
public OperatorDescription(String fullyQualifiedGroupKey, Element element, ClassLoader classLoader, Plugin provider, OperatorDocBundle bundle) throws ClassNotFoundException, XMLException {
this.provider = provider;
this.fullyQualifiedGroupKey = fullyQualifiedGroupKey;
key = XMLTools.getTagContents(element, "key", true);
setIconName(XMLTools.getTagContents(element, "icon"));
Class<?> generatedClass = Class.forName(XMLTools.getTagContents(element, "class", true).trim(), true, classLoader);
this.clazz = (Class<? extends Operator>) generatedClass;
this.documentation = (OperatorDocumentation) bundle.getObject("operator." + key);
if (documentation.getName().equals("")) {
documentation.setName(key);
documentation.setDocumentation("Operator's description is missing in referenced OperatorDoc.");
}
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof Element && ((Element) child).getTagName().equals("replaces")) {
setIsReplacementFor(((Element) child).getTextContent());
}
}
}
/** Constructor for programmatic (non-parsed) creation of OperatorDescriptions, e.g. by a {@link GenericOperatorFactory}. */
public OperatorDescription(String fullyQualifiedGroupKey, String key, Class<? extends Operator> clazz, ClassLoader classLoader, String iconName, Plugin provider) {
this(fullyQualifiedGroupKey, key, clazz, classLoader, iconName, provider, null);
}
/**
* Constructor for programmatic (non-parsed) creation of OperatorDescriptions, e.g. by a {@link GenericOperatorFactory}.
* Additionally this allows to specify an operator documentation bundle where the docu is retrieved from.
* */
public OperatorDescription(String fullyQualifiedGroupKey, String key, Class<? extends Operator> clazz, ClassLoader classLoader, String iconName, Plugin provider, OperatorDocBundle bundle) {
this.key = key;
this.clazz = clazz;
this.fullyQualifiedGroupKey = fullyQualifiedGroupKey;
this.provider = provider;
setIconName(iconName);
if (bundle == null) {
this.documentation = new OperatorDocumentation(key);
} else {
this.documentation = (OperatorDocumentation) bundle.getObject("operator." + key);
if (documentation.getName().equals("")) {
documentation.setName(key);
documentation.setDocumentation("Operator's description is missing in referenced OperatorDoc.");
}
}
}
/**
* Creates a new operator description object. If the corresponding operator is not marked as deprecated the
* deprecation info string should be null. If the icon string is null, the group icon will be used.
*
* @deprecated No I18N support.
*/
@Deprecated
public OperatorDescription(ClassLoader classLoader, String key, String name, String className, String group, String iconName, String deprecationInfo, Plugin provider) throws ClassNotFoundException {
this(classLoader, key, name, className, null, null, group, iconName, deprecationInfo, provider);
}
/**
* Creates an operator description with the given fields.
*
* @deprecated This constructor cannot provide an internationalization mechanism since description
* is not taken from operator documentation bundle.
*/
@SuppressWarnings("unchecked")
@Deprecated
public OperatorDescription(ClassLoader classLoader, String key, String name, String className, String shortDescription, String longDescription, String groupName, String iconName, String deprecationInfo, Plugin provider) throws ClassNotFoundException {
this.key = key;
this.clazz = (Class<? extends Operator>) Class.forName(className, true, classLoader);
this.documentation = new OperatorDocumentation(name);
this.documentation.setSynopsis(shortDescription);
this.documentation.setDocumentation(longDescription);
this.documentation.setDeprecation(deprecationInfo);
this.fullyQualifiedGroupKey = groupName;
this.provider = provider;
setIconName(iconName);
}
/**
* This constructor remains for compatibility reasons. Please use one of the non deprecated alternatives.
*/
@Deprecated
public OperatorDescription(String key, Class<? extends Operator> clazz, GroupTree group, ClassLoader classLoader, String iconName, Plugin provider) {
this(group.getFullyQualifiedKey(), key, clazz, classLoader, iconName, provider);
}
/**
* This constructor remains for compatibility reasons. Please use one of the non deprecated alternatives.
*/
@Deprecated
public OperatorDescription(String key, Class<? extends Operator> clazz, GroupTree groupTree, ClassLoader classLoader, String iconName, Plugin provider, OperatorDocBundle bundle) {
this(groupTree.getFullyQualifiedKey(), key, clazz, classLoader, iconName, provider);
}
public String getName() {
return getOperatorDocumentation().getName();
}
public String getShortName() {
return getOperatorDocumentation().getShortName();
}
public Class<? extends Operator> getOperatorClass() {
return clazz;
}
public String getShortDescription() {
return getOperatorDocumentation().getSynopsis();
}
public String getLongDescriptionHTML() {
OperatorDocumentation operatorDocumentation = getOperatorDocumentation();
if (operatorDocumentation.getDocumentation() != null)
return operatorDocumentation.getDocumentation();
if (operatorDocumentation.getSynopsis() != null)
return operatorDocumentation.getSynopsis();
return "";
}
public OperatorDocumentation getOperatorDocumentation() {
return documentation;
}
/**
* This returns the qualified, dot separated key of the containing group.
*/
public String getGroup() {
return fullyQualifiedGroupKey;
}
/**
* This returns the actual group name as displayed in RapidMiner.
*/
public String getGroupName() {
int pos = fullyQualifiedGroupKey.lastIndexOf(".");
if (pos == -1)
return fullyQualifiedGroupKey;
else
return fullyQualifiedGroupKey.substring(pos + 1);
}
public ImageIcon getIcon() {
return getIcons()[1];
}
public ImageIcon getSmallIcon() {
ImageIcon[] icons2 = this.getIcons();
if (icons2[0] != null) {
return icons2[0];
} else {
return icons2[1];
}
}
public ImageIcon getLargeIcon() {
ImageIcon[] icons2 = this.getIcons();
if (icons2[2] != null) {
return icons2[2];
} else {
return icons2[1];
}
}
public String getAbbreviatedClassName() {
return getOperatorClass().getName().replace("com.rapidminer.operator.", "c.r.o.");
}
public String getDeprecationInfo() {
if (deprecationInfo != null) {
return deprecationInfo;
} else {
return getOperatorDocumentation().getDeprecation();
}
}
public boolean isDeprecated() {
return deprecationInfo != null;
}
public String getProviderName() {
return provider != null ? provider.getName() : OperatorService.RAPID_MINER_CORE_PREFIX;
}
/**
* This defines the namespace of the provider. If is core, OperatorService.RAPID_MINER_CORE_NAMESPACE is returned.
* Otherwise the namespace of the extension is returned as defined by the manifest.xml
*
* @return
*/
public String getProviderNamespace() {
return provider != null ? provider.getExtensionId() : OperatorService.RAPID_MINER_CORE_NAMESPACE;
}
public String getKey() {
if (provider != null) {
return provider.getPrefix() + ":" + this.key;
} else {
return this.key;
}
}
public void disable() {
this.enabled = false;
}
/** Some operators may be disabled, e.g. because they cannot be applied inside an application server (file access etc.) */
public boolean isEnabled() {
return enabled;
}
@Override
public String toString() {
return "key='" + key + "'; name='" + getName() + "'; " + (replacesDeprecatedKeys != null ? "replaces: " + replacesDeprecatedKeys : "") + "; implemented by " + clazz.getName() + "; group: " + fullyQualifiedGroupKey + "; icon: " + iconName;
}
@Override
public int compareTo(OperatorDescription d) {
String myName = this.getName();
String otherName = d.getName();
return myName.compareTo(otherName);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof OperatorDescription)) {
return false;
} else {
OperatorDescription other = (OperatorDescription) o;
return this.getKey().equals(other.getKey());
}
}
@Override
public int hashCode() {
return this.getKey().hashCode();
}
/** Creates a new operator based on the description. Subclasses that want to
* overwrite the creation behavior should override */
public final Operator createOperatorInstance() throws OperatorCreationException {
if (!isEnabled()) {
throw new OperatorCreationException(OperatorCreationException.OPERATOR_DISABLED_ERROR, key + "(" + clazz.getName() + ")", null);
}
Operator operator = null;
try {
operator = createOperatorInstanceByDescription(this);
} catch (InstantiationException e) {
throw new OperatorCreationException(OperatorCreationException.INSTANTIATION_ERROR, key + "(" + clazz.getName() + ")", e);
} catch (IllegalAccessException e) {
throw new OperatorCreationException(OperatorCreationException.ILLEGAL_ACCESS_ERROR, key + "(" + clazz.getName() + ")", e);
} catch (NoSuchMethodException e) {
throw new OperatorCreationException(OperatorCreationException.NO_CONSTRUCTOR_ERROR, key + "(" + clazz.getName() + ")", e);
} catch (java.lang.reflect.InvocationTargetException e) {
throw new OperatorCreationException(OperatorCreationException.CONSTRUCTION_ERROR, key + "(" + clazz.getName() + ")", e);
}
OperatorService.invokeCreationHooks(operator);
return operator;
}
/**
* This method creates the actual instance of the {@link Operator} defined by the
* given {@link OperatorDescription}.
* Subclasses might overwrite this method in order to change the creation behavior or way.
*/
protected Operator createOperatorInstanceByDescription(OperatorDescription description) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
java.lang.reflect.Constructor<? extends Operator> constructor = clazz.getConstructor(new Class[] { OperatorDescription.class });
return constructor.newInstance(new Object[] { description });
}
public void setIsReplacementFor(String opName) {
if (replacesDeprecatedKeys == null) {
replacesDeprecatedKeys = new LinkedList<String>();
}
replacesDeprecatedKeys.add(opName);
}
/** Returns keys of deprecated operators replaced by this operator. */
public List<String> getReplacedKeys() {
if (replacesDeprecatedKeys != null) {
return replacesDeprecatedKeys;
} else {
return Collections.emptyList();
}
}
public String getIconName() {
if (iconName != null) {
return iconName;
}
return null;
}
public void setIconName(String iconName) {
this.iconName = iconName;
if (iconName != null) {
icons = new ImageIcon[3];
icons[0] = SwingTools.createIcon("16/" + iconName);
icons[1] = SwingTools.createIcon("24/" + iconName);
icons[2] = SwingTools.createIcon("48/" + iconName);
} else {
icons = EMPTY_ICONS;
}
}
/**
* This returns true if a specific icon already has been defined in
* this {@link OperatorDescription}. It might be possible, that there's
* no such icon name defined. Since each operator should have an icon,
* it will be read from the containing Group when the {@link OperatorDescription} is
* registered using {@link OperatorService#registerOperator(OperatorDescription)}.
*/
public boolean isIconDefined() {
return iconName != null;
}
private ImageIcon[] getIcons() {
if (icons != null) {
return icons;
// } else {
// return groupTree.getIcons();
}
return null;
}
public Plugin getProvider() {
return provider;
}
protected void setFullyQualifiedGroupKey(String key) {
this.fullyQualifiedGroupKey = key;
}
}