/*
* RapidMiner
*
* Copyright (C) 2001-2014 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.gui.renderer;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import javax.swing.Icon;
import javax.swing.ImageIcon;
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 org.xml.sax.SAXException;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.operator.IOObject;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.Tools;
import com.rapidminer.tools.WebServiceTools;
/**
* The renderer service is the basic provider for all registered renderers. All {@link IOObject}s which want to provide
* a Renderer for visualization and reporting must place an entry in the
* <code>ioobjects.xml</xml> file in order to allow
* for renderer retrieval.
*
* @author Ingo Mierswa, Nils Woehler
*/
public class RendererService {
/** Used in {@link RendererService#class2IconMap}. */
private static class IconData {
private final String iconName;
private final Icon icon;
public IconData(String iconName, Icon icon) {
this.iconName = iconName;
this.icon = icon;
}
public Icon getIcon() {
return icon;
}
public String getIconName() {
return iconName;
}
}
private static final IconData ICON_DEFAULT = new IconData("data.png", SwingTools.createIcon("16/data.png"));
private static Set<String> objectNames = new TreeSet<String>();
/**
* Maps names of IOObjects to lists of renderers that can render this object. These instances are shared!
*/
private static Map<String, List<Renderer>> objectRenderers = new HashMap<String, List<Renderer>>();
/** Maps names of IOObjects to lists of renderer classes that can render this object. */
private static Map<String, Map<String, Class<? extends Renderer>>> rendererNameToRendererClasses = new HashMap<String, Map<String, Class<? extends Renderer>>>();
private static Map<String, Class<? extends IOObject>> objectClasses = new HashMap<String, Class<? extends IOObject>>();
/** Set of names of reportable objects. */
private static Set<String> reportableMap = new HashSet<String>();
private static Map<Class<?>, String> class2NameMap = new HashMap<Class<?>, String>();
private static Map<Class<? extends IOObject>, IconData> class2IconMap = new HashMap<Class<? extends IOObject>, IconData>();
private static boolean isInitialized = false;
public static void init() {
URL url = Tools.getResource("ioobjects.xml");
init(url);
init("ioobjects.xml", url, RendererService.class.getClassLoader());
}
public static void init(URL ioObjectsURL) {
init(ioObjectsURL.getFile(), ioObjectsURL, RendererService.class.getClassLoader());
}
public static void init(String name, InputStream in) {
init(name, in, RendererService.class.getClassLoader());
}
public static void init(String name, URL ioObjectsURL, ClassLoader classLoader) {
InputStream in = null;
try {
if (ioObjectsURL != null) {
in = WebServiceTools.openStreamFromURL(ioObjectsURL);
init(name, in, classLoader);
}
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.renderer.RendererService.initializing_io_object_description_from_plugin_error",
name, e),
e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// do nothing
}
}
}
}
public static void init(String rendererFileName, InputStream in, ClassLoader classLoader) {
LogService.getRoot().log(Level.CONFIG, "com.rapidminer.gui.renderer.RendererService.loading_renderers", rendererFileName);
try {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
Element ioObjectsElement = document.getDocumentElement();
if (ioObjectsElement.getTagName().equals("ioobjects")) {
NodeList ioObjectNodes = ioObjectsElement.getElementsByTagName("ioobject");
for (int i = 0; i < ioObjectNodes.getLength(); i++) {
Node ioObjectNode = ioObjectNodes.item(i);
if (ioObjectNode instanceof Element) {
Element ioObjectElement = (Element) ioObjectNode;
String name = ioObjectElement.getAttribute("name");
String className = ioObjectElement.getAttribute("class");
String reportableString = "true";
if (ioObjectElement.hasAttribute("reportable")) {
reportableString = ioObjectElement.getAttribute("reportable");
}
boolean reportable = Tools.booleanValue(reportableString, true);
String icon = null;
if (ioObjectElement.hasAttribute("icon")) {
icon = ioObjectElement.getAttribute("icon");
}
NodeList rendererNodes = ioObjectElement.getElementsByTagName("renderer");
List<String> renderers = new LinkedList<String>();
for (int k = 0; k < rendererNodes.getLength(); k++) {
Node rendererNode = rendererNodes.item(k);
if (rendererNode instanceof Element) {
Element rendererElement = (Element) rendererNode;
String rendererName = rendererElement.getTextContent();
renderers.add(rendererName);
}
}
registerRenderers(name, className, reportable, icon, renderers, classLoader);
}
}
isInitialized = true;
} else {
LogService.getRoot().log(Level.WARNING, "com.rapidminer.gui.renderer.RendererService.initializing_io_object_description_tag_error");
}
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.renderer.RendererService.initializing_io_object_description_parsing_error",
e),
e);
} catch (javax.xml.parsers.ParserConfigurationException e) {
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.renderer.RendererService.initializing_io_object_description_error",
e),
e);
} catch (SAXException e) {
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.renderer.RendererService.initializing_io_object_description_parsing_error",
e),
e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// do nothing
}
}
}
}
/**
* This method remains for compatibility reasons. But one should use
* {@link #registerRenderers(String, String, boolean, String, List, ClassLoader)} in order to assign an icon to the
* ioobject class.
*/
@Deprecated
public static void registerRenderers(String reportableNames, String className, boolean reportable, List<String> rendererClassNames, ClassLoader classLoader) {
registerRenderers(reportableNames, className, reportable, null, rendererClassNames, classLoader);
}
@SuppressWarnings("unchecked")
public static void registerRenderers(String reportableName, String className, boolean reportable, String iconName, List<String> rendererClassNames, ClassLoader classLoader) {
objectNames.add(reportableName);
try {
Class<? extends IOObject> clazz = (Class<? extends IOObject>) Class.forName(className, true, classLoader);
List<Renderer> renderers = new LinkedList<Renderer>();
Map<String, Class<? extends Renderer>> rendererClassMap = new HashMap<String, Class<? extends Renderer>>();
for (String rendererClassName : rendererClassNames) {
Class<? extends Renderer> rendererClass;
try {
rendererClass = (Class<? extends Renderer>) Class.forName(rendererClassName, true, classLoader);
} catch (Exception e) { // should be unnecessary in most cases, because plugin loader contains core
// classes
rendererClass = (Class<? extends Renderer>) Class.forName(rendererClassName);
}
Renderer renderer = rendererClass.newInstance();
renderers.add(renderer);
rendererClassMap.put(renderer.getName(), rendererClass);
}
rendererNameToRendererClasses.put(reportableName, rendererClassMap);
objectRenderers.put(reportableName, renderers);
objectClasses.put(reportableName, clazz);
class2NameMap.put(clazz, reportableName);
if (reportable) {
reportableMap.add(reportableName);
}
// try to create icon
if (iconName != null && !iconName.isEmpty()) {
ImageIcon icon = SwingTools.createIcon("16/" + iconName);
if (icon != null)
class2IconMap.put(clazz, new IconData(iconName, icon));
}
} catch (Throwable e) {
//LogService.getRoot().log(Level.WARNING, "Cannot register renderer: " + e, e);
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.renderer.RendererService.registering_renderer_error",
e),
e);
}
}
public static Set<String> getAllRenderableObjectNames() {
return objectNames;
}
public static Set<String> getAllReportableObjectNames() {
Set<String> result = new TreeSet<String>();
for (String name : objectNames) {
if (reportableMap.contains(name)) {
result.add(name);
}
}
return result;
}
/**
* Returns the Reportable name for objects of the given class.
*
* @return the name of the IOObject as specified in the ioobjectsXXX.xml file. Returns null if the object is not registered
*/
public static String getName(Class<?> clazz) {
String result = class2NameMap.get(clazz);
if (result == null) {
Set<Class<?>> candidates = new HashSet<Class<?>>();
//fetch candidates
for (Class<?> candidate : class2NameMap.keySet()) {
if (candidate.isAssignableFrom(clazz)) {
candidates.add(candidate);
}
}
//iterate over candidates and extract candidates for renderables
boolean dominatedClassesFound = true;
while (dominatedClassesFound) {
dominatedClassesFound = false;
List<Class<?>> dominatedList = new LinkedList<Class<?>>();
//iterate over all candidate pairs and add all candidates that are dominated by other candidates into a list
for (Class<?> candidate : candidates) {
for (Class<?> comperable : candidates) {
if (candidate != comperable) {
if (!candidate.isAssignableFrom(comperable)) {
dominatedList.add(candidate);
dominatedClassesFound = true;
}
}
}
}
//if dominates classes have been found set them as new candidates for the next iteration
if (dominatedClassesFound) {
candidates.clear();
candidates.addAll(dominatedList);
}
}
//this loop should break with only one candidate left, BUT: theoretically there can be more than one
//if this is the case, log an error...
if (candidates.size() > 1) {
LogService.getRoot().log(Level.INFO, "com.rapidminer.gui.renderer.RendererService.more_than_one_renderable_candidate_for_the_result_of_classname", clazz.getName());
}
//and show the first candidate found
if (candidates.isEmpty()) {
return null;
} else {
return class2NameMap.get(candidates.toArray()[0]);
}
}
return result;
}
/**
* This returns the highest super class of the report type with the given name.
*/
public static Class<? extends IOObject> getClass(String name) {
return objectClasses.get(name);
}
/**
* Returns a list of renderers defined for this IOObject name (as returned by {@link #getName(Class)} for the
* respective object). It is recommended to use {@link #getRenderers(IOObject)} instead.
* */
public static List<Renderer> getRenderers(String reportableName) {
List<Renderer> renderers = objectRenderers.get(reportableName);
if (renderers != null)
return renderers;
return new LinkedList<Renderer>();
}
/** Returns a list of shared (i.e. not thread-safe!) renderers defined for this IOObject. */
public static List<Renderer> getRenderers(IOObject ioo) {
String reportableName = RendererService.getName(ioo.getClass());
return getRenderers(reportableName);
}
public static Renderer getRenderer(String reportableName, String rendererName) {
List<Renderer> renderers = getRenderers(reportableName);
for (Renderer renderer : renderers) {
if (renderer.getName().equals(rendererName)) {
return renderer;
}
}
return null;
}
/**
* This returns the icon registered for the given class or a default icon, if nothing has been registered.
*/
public static Icon getIcon(Class<? extends IOObject> objectClass) {
return getIconData(objectClass).getIcon();
}
/**
* This returns the icon name registered for the given class or a default icon, if nothing has been registered.
*/
public static String getIconName(Class<? extends IOObject> objectClass) {
return getIconData(objectClass).getIconName();
}
private static IconData getIconData(Class<? extends IOObject> objectClass) {
if (objectClass == null)
return ICON_DEFAULT;
IconData icon = class2IconMap.get(objectClass);
if (icon == null) {
for (Entry<Class<? extends IOObject>, IconData> renderableClassEntry : class2IconMap.entrySet()) {
if (renderableClassEntry.getKey().isAssignableFrom(objectClass)) {
class2IconMap.put(objectClass, renderableClassEntry.getValue());
return renderableClassEntry.getValue();
}
}
return ICON_DEFAULT;
}
return icon;
}
/**
* Creates a new renderer for the given object.
*
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static Renderer createRenderer(IOObject ioobject, String rendererName) {
String reportableName = getName(ioobject.getClass());
Map<String, Class<? extends Renderer>> rendererClassMap = rendererNameToRendererClasses.get(reportableName);
if (rendererClassMap == null) {
throw new IllegalArgumentException("Illegal reportable name: " + rendererName);
}
Class<? extends Renderer> rendererClass = rendererClassMap.get(rendererName);
if (rendererClass == null) {
throw new IllegalArgumentException("Illegal renderer name: " + rendererName);
}
try {
return rendererClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create renderer: " + e, e);
}
}
/**
* This returns whether the {@link RendererService} has already been initialized at least once.
* This holds true even if only the core objects have been registered.
*/
public static boolean isInitialized() {
return isInitialized;
}
}