/**
* 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.operator.ports.metadata;
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;
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;
/**
* 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. <br/>
*
* Subclasses representing the meta data for a class T (in particular those defined by plugins),
* should implement a constructor accepting a T and a boolean and be registered with
* {@link MetaDataFactory#registerIOObjectMetaData(Class, Class)}.
*
* @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) {
return MetaDataFactory.getInstance().createMetaDataforIOObject(ioo, shortened);
}
public static MetaData forIOObject(IOObject ioo) {
return forIOObject(ioo, false);
}
public Annotations getAnnotations() {
return annotations;
}
public void setAnnotations(Annotations annotations) {
this.annotations = annotations;
}
}