/** * 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.expression; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import com.rapidminer.example.Attribute; import com.rapidminer.example.Example; import com.rapidminer.example.ExampleSet; import com.rapidminer.operator.ports.metadata.AttributeMetaData; import com.rapidminer.operator.ports.metadata.ExampleSetMetaData; import com.rapidminer.tools.I18N; import com.rapidminer.tools.expression.FunctionInput.Category; /** * A {@link Resolver} for {@link Example}s from an {@link ExampleSet}. Should be constructed with an * ExampleSet or its meta data ({@link ExampleSetMetaData}). To evaluate an {@link Expression} for * all the {@link Example}s call {{@link #bind(Example)} before evaluating the expression. * {@link #bind(Example)} can be used in parallel. * * @author Gisa Schaefer * @since 6.5.0 */ public class ExampleResolver implements Resolver { /** * A thread local {@link Example} in order to allow to split the evaluation of * {@link Expression}s over more than one thread. */ private final ThreadLocal<Example> exampleThreadLocal = new ThreadLocal<Example>(); private final ExampleSetMetaData metaData; public static final String KEY_ATTRIBUTES = I18N.getGUIMessage("gui.dialog.function_input.regular_attributes"); public static final String KEY_SPECIAL_ATTRIBUTES = I18N.getGUIMessage("gui.dialog.function_input.special_attributes"); /** * Creates an {@link ExampleResolver} that can bind {@link Example}s that have the same meta * data as the given exampleSet. * * @param exampleSet * the {@link ExampleSet} from which this resolver should bind examples, cannot be * {@code null} */ public ExampleResolver(ExampleSet exampleSet) { if (exampleSet == null) { throw new IllegalArgumentException("exampleSet must not be null"); } this.metaData = new ExampleSetMetaData(exampleSet, false, false); } /** * Creates an {@link ExampleResolver} that can bind {@link Example}s that have the given meta * data. * * @param metaData * the meta data for which the resolver is used, cannot be {@code null} */ public ExampleResolver(ExampleSetMetaData metaData) { if (metaData == null) { throw new IllegalArgumentException("metaData must not be null"); } this.metaData = metaData; } /** * Sets the current Example to example. This Example must have the same meta data as what this * {@link ExampleResolver} was constructed with. The current example is thread local so this * method can be called in parallel. * <p> * Don't forget to call {@link #unbind()} once the expression parser function has been evaluated * to avoid memory leaks. * * @param example * an example with the same meta data as the resolver was constructed with */ public void bind(Example example) { exampleThreadLocal.set(example); } /** * Removes the binding of the current example for this {@link ExampleResolver}. Make sure to * call this method only after the expression parser function has been evaluated. Otherwise this * might result in an undefined state. */ public void unbind() { exampleThreadLocal.remove(); } /** * Adds a new {@link AttributeMetaData} object to the current {@link ExampleSetMetaData}. * * @param amd * the new attribute meta data to add */ public void addAttributeMetaData(AttributeMetaData amd) { this.metaData.addAttribute(amd); } @Override public Collection<FunctionInput> getAllVariables() { Collection<AttributeMetaData> metaDataAttributes = metaData.getAllAttributes(); List<FunctionInput> functionInputs = new ArrayList<>(metaDataAttributes.size()); for (AttributeMetaData amd : metaDataAttributes) { if (amd.isSpecial()) { functionInputs.add(new FunctionInput(Category.DYNAMIC, KEY_SPECIAL_ATTRIBUTES, amd.getName(), amd.getValueType(), amd.getRole())); } else { functionInputs .add(new FunctionInput(Category.DYNAMIC, KEY_ATTRIBUTES, amd.getName(), amd.getValueType(), null)); } } return functionInputs; } @Override public ExpressionType getVariableType(String variableName) { AttributeMetaData attributeMetaData = metaData.getAttributeByName(variableName); if (attributeMetaData == null) { return null; } int ontologyValueType = attributeMetaData.getValueType(); return ExpressionType.getExpressionType(ontologyValueType); } @Override public String getStringValue(String variableName) { if (!(getVariableType(variableName) == ExpressionType.STRING)) { throw new IllegalStateException("the variable " + variableName + " does not have a String value"); } Example example = getNonNullExample(); Attribute attribute = example.getAttributes().get(variableName); if (Double.isNaN(example.getValue(attribute))) { return null; } else { return example.getNominalValue(attribute); } } /** * Gets the thread local example and checks that it is not {@code null}. */ private Example getNonNullExample() { Example example = exampleThreadLocal.get(); if (example == null) { throw new IllegalStateException("no example was bound"); } return example; } @Override public double getDoubleValue(String variableName) { if (!(getVariableType(variableName) == ExpressionType.DOUBLE || getVariableType(variableName) == ExpressionType.INTEGER)) { throw new IllegalStateException("the variable " + variableName + " does not have a double value"); } Example example = getNonNullExample(); Attribute attribute = example.getAttributes().get(variableName); if (getVariableType(variableName) == ExpressionType.INTEGER) { return Math.floor(example.getNumericalValue(attribute)); } else { return example.getNumericalValue(attribute); } } @Override public boolean getBooleanValue(String variableName) { throw new IllegalStateException("Examples never have boolean values"); } @Override public Date getDateValue(String variableName) { if (!(getVariableType(variableName) == ExpressionType.DATE)) { throw new IllegalStateException("the variable " + variableName + " does not have a date value"); } Example example = getNonNullExample(); Attribute attribute = example.getAttributes().get(variableName); if (Double.isNaN(example.getValue(attribute))) { return null; } else { return example.getDateValue(attribute); } } }