/* * 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.ports.metadata; import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.rapidminer.example.ExampleSet; import com.rapidminer.gui.renderer.RendererService; import com.rapidminer.operator.Annotations; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.ProcessSetupError.Severity; import com.rapidminer.operator.ports.InputPort; import com.rapidminer.operator.ports.OutputPort; import com.rapidminer.tools.RMUrlHandler; /** Meta data about an {@link IOObject}. Includes the specific class of the IOObject plus * a map of key-value pairs specifying more detailed properties. * Additionally, may contain information about which {@link OutputPort} * originally generated this meta data. * * @author Simon Fischer */ public class MetaData implements Serializable { private static final long serialVersionUID = 1L; /** A list of ports that have generated or modified this meta data. */ private transient LinkedList<OutputPort> generationHistory = new LinkedList<OutputPort>(); /** Maps keys (MD_KEY_...) to values. */ private final Map<String,Object> keyValueMap = new HashMap<String,Object>(); private Class<? extends IOObject> dataClass; private Annotations annotations = new Annotations(); public MetaData() { this(IOObject.class); } /** Restores an empty history. * @throws ClassNotFoundException * @throws IOException */ public Object readResolve() throws ObjectStreamException { //in.defaultReadObject(); if (generationHistory == null) { generationHistory = new LinkedList<OutputPort>(); } if (annotations == null) { annotations = new Annotations(); } return this; } public MetaData(Class<? extends IOObject> dataClass) { this(dataClass, Collections.<String,Object>emptyMap()); } public MetaData(Class<? extends IOObject> dataClass, String key, Object value) { this(dataClass, Collections.singletonMap(key, value)); } public MetaData(Class<? extends IOObject> dataClass, Map<String,Object> keyValueMap) { this.dataClass = dataClass; this.keyValueMap.putAll(keyValueMap); } public void addToHistory(OutputPort generator) { this.generationHistory.addFirst(generator); } public List<OutputPort> getGenerationHistory() { return Collections.unmodifiableList(generationHistory); } public String getGenerationHistoryAsHTML() { boolean first = true; StringBuilder b = new StringBuilder(); if (generationHistory != null) { for (OutputPort port : generationHistory) { if (!first) { b.append(" ← "); } b.append("<a href=\""+RMUrlHandler.URL_PREFIX+"operator/"); b.append(port.getPorts().getOwner().getOperator().getName()); b.append("\">"); b.append(port.getSpec()); b.append("</a>"); first = false; } } return b.toString(); } public Class<? extends IOObject> getObjectClass() { return dataClass; } public Object getMetaData(String key) { return keyValueMap.get(key); } public Object putMetaData(String key, Object value) { return keyValueMap.put(key, value); } @Override public MetaData clone() { MetaData clone; try { clone = this.getClass().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); throw new RuntimeException("Cannot clone " + this, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot clone " + this, e); } if (generationHistory == null) clone.generationHistory = new LinkedList<OutputPort>(); else clone.generationHistory = new LinkedList<OutputPort>(this.generationHistory); clone.dataClass = this.getObjectClass(); clone.keyValueMap.putAll(this.keyValueMap); if (this.annotations != null) { clone.annotations.putAll(this.annotations); } return clone; } @Override public String toString() { return getObjectClass().getSimpleName() + (keyValueMap.isEmpty() ? "" : (" hints: " + keyValueMap.toString())); } public String getDescription() { String name = RendererService.getName(dataClass); if (name == null) name = dataClass.getSimpleName(); StringBuilder desc = new StringBuilder(name); if (!keyValueMap.isEmpty()) { desc.append("; "); desc.append(keyValueMap); } if ((annotations != null) && !annotations.isEmpty()) { desc.append("<ul>"); for (String key: annotations.getKeys()) { desc.append("<li><em>").append(key).append(":</em> ").append(annotations.get(key)); } desc.append("</ul>"); } return desc.toString(); } /** Returns true if isData is compatible with this meta data, where <code>this</code> * represents desired meta data and isData represents meta data that was actually delivered.*/ public boolean isCompatible(MetaData isData, CompatibilityLevel level) { return getErrorsForInput(null, isData, level).isEmpty(); } /** Returns a (possibly empty) list of errors specifying in what regard <code>isData</code> * differs from <code>this</code> meta data specification. * * @param inputPort required for generating errors * @param isData the data received by the port */ public Collection<MetaDataError> getErrorsForInput(InputPort inputPort, MetaData isData, CompatibilityLevel level) { if (!this.dataClass.isAssignableFrom(isData.dataClass)) { return Collections.<MetaDataError>singletonList(new InputMissingMetaDataError(inputPort, this.getObjectClass(), isData.getObjectClass())); } Collection<MetaDataError> errors = new LinkedList<MetaDataError>(); if (level == CompatibilityLevel.VERSION_5) { for (Map.Entry<String, Object> entry : this.keyValueMap.entrySet()) { Object isValue = isData.keyValueMap.get(entry.getKey()); if (!entry.getValue().equals(isValue)) { errors.add(new SimpleMetaDataError(Severity.ERROR, inputPort, "general_property_mismatch", new Object[] { entry.getKey(), entry.getValue() })); } } } return errors; } /** * This will return the meta data description of the given IOObject. * If the shortened flag is true, the meta data will be incomplete to * avoid to generate too much data if this is supported by the actual meta data implementation. */ public static MetaData forIOObject(IOObject ioo, boolean shortened) { MetaData result; if (ioo instanceof ExampleSet) { result = new ExampleSetMetaData((ExampleSet)ioo, shortened); } else { result = new MetaData(ioo.getClass()); } result.annotations = new Annotations(ioo.getAnnotations()); return result; } public static MetaData forIOObject(IOObject ioo) { return forIOObject(ioo, false); } public Annotations getAnnotations() { return annotations; } public void setAnnotations(Annotations annotations) { this.annotations = annotations; } }