/* * Copyright (c) 2013 Villu Ruusmann * * This file is part of JPMML-Evaluator * * JPMML-Evaluator 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. * * JPMML-Evaluator 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 JPMML-Evaluator. If not, see <http://www.gnu.org/licenses/>. */ package org.jpmml.evaluator; import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.function.Predicate; import org.dmg.pmml.Array; import org.dmg.pmml.DataType; import org.dmg.pmml.Expression; import org.dmg.pmml.Field; import org.dmg.pmml.HasType; import org.dmg.pmml.HasValue; import org.dmg.pmml.HasValueSet; import org.dmg.pmml.OpType; import org.dmg.pmml.PMMLObject; import org.jpmml.model.ToStringHelper; import org.jpmml.model.XPathUtil; /** * <p> * A field value representation that meets the requirements of PMML type system. * </p> * * Type information has two components to it: * <ul> * <li>{@link #getOpType() Operational type}. Determines supported type equality and type comparison operations.</li> * <li>{@link #getDataType() Data type}. Determines supported type conversions.</li> * </ul> * * <p> * A field value is created after a {@link Field field}. * It may be later refined by {@link Expression transformations} and {@link Function functions}. * </p> * * @see FieldValueUtil */ abstract public class FieldValue implements TypeInfo, Serializable { private DataType dataType = null; private Object value = null; FieldValue(DataType dataType, Object value){ setDataType(Objects.requireNonNull(dataType)); setValue(Objects.requireNonNull(value)); } abstract public boolean isValid(); abstract public int compareToValue(Object value); abstract public int compareToValue(FieldValue value); public FieldValue cast(HasType<?> hasType){ DataType dataType = hasType.getDataType(); OpType opType = hasType.getOpType(); boolean equal = true; if(dataType == null){ dataType = getDataType(); } else { equal &= (getDataType()).equals(dataType); } // End if if(opType == null){ opType = getOpType(); } else { equal &= (getOpType()).equals(opType); } // End if if(equal){ return this; } return FieldValue.create(dataType, opType, getValue()); } public FieldValue cast(TypeInfo typeInfo){ DataType dataType = typeInfo.getDataType(); OpType opType = typeInfo.getOpType(); if((getDataType()).equals(dataType) && (getOpType()).equals(opType)){ return this; } return FieldValue.create(typeInfo, getValue()); } /** * <p> * Calculates the order between this value and the reference value. * </p> */ public int compareTo(HasValue<?> hasValue){ return compareToValue(ensureValue(hasValue)); } /** * <p> * Checks if this value is equal to the reference value. * </p> */ public boolean equals(HasValue<?> hasValue){ return equalsValue(ensureValue(hasValue)); } /** * <p> * Checks if this value is contained in the set of reference values. * </p> */ public boolean isIn(HasValueSet<?> hasValueSet){ Array array = hasValueSet.getArray(); if(array == null){ throw new MissingElementException(MissingElementException.formatMessage(XPathUtil.formatElement((Class)hasValueSet.getClass()) + "/" + XPathUtil.formatElement(Array.class)), (PMMLObject)hasValueSet); } // End if if(array instanceof SetHolder){ SetHolder setHolder = (SetHolder)array; return setHolder.contains(getDataType(), getValue()); } List<?> values = ArrayUtil.getContent(array); return values.stream() .anyMatch(value -> equalsValue(value)); } public boolean isIn(Collection<FieldValue> values){ Predicate<FieldValue> predicate = new Predicate<FieldValue>(){ @Override public boolean test(FieldValue value){ if(FieldValueUtil.isMissing(value)){ return false; } return equalsValue(value); } }; return values.stream() .anyMatch(predicate); } public boolean equalsValue(Object value){ value = TypeUtil.parseOrCast(getDataType(), value); return (getValue()).equals(value); } public boolean equalsValue(FieldValue value){ return equalsValue(value.getValue()); } public Collection<?> asCollection(){ return TypeUtil.cast(Collection.class, getValue()); } public String asString(){ return (String)getValue(DataType.STRING); } public Number asNumber(){ Object value = getValue(); if(value instanceof Number){ return (Number)value; } return (Number)getValue(DataType.DOUBLE); } public Integer asInteger(){ return (Integer)getValue(DataType.INTEGER); } public Float asFloat(){ Number number = asNumber(); return number.floatValue(); } /** * Getting the value of a field as {@link Double}: * <pre> * FieldValue value = ...; * Double result = value.asDouble(); * </pre> * * Getting the value of a field as <code>double</code>: * <pre> * FieldValue value = ...; * double result = (value.asNumber()).doubleValue(); * </pre> * * @see #asNumber() */ public Double asDouble(){ Number number = asNumber(); return number.doubleValue(); } public Boolean asBoolean(){ return (Boolean)getValue(DataType.BOOLEAN); } public LocalDateTime asLocalDateTime(){ return (LocalDateTime)getValue(DataType.DATE_TIME); } public LocalDate asLocalDate(){ return (LocalDate)getValue(DataType.DATE); } public LocalTime asLocalTime(){ return (LocalTime)getValue(DataType.TIME); } public ZonedDateTime asZonedDateTime(ZoneId zoneId){ try { LocalDateTime dateTime = asLocalDateTime(); return dateTime.atZone(zoneId); } catch(TypeCheckException tceDateTime){ try { LocalDate localDate = asLocalDate(); LocalTime localTime = LocalTime.MIDNIGHT; return ZonedDateTime.of(localDate, localTime, zoneId); } catch(TypeCheckException tceDate){ // Ignored } try { LocalDate localDate = LocalDate.now(); LocalTime localTime = asLocalTime(); return ZonedDateTime.of(localDate, localTime, zoneId); } catch(TypeCheckException tceTime){ // Ignored } throw tceDateTime; } } Object getValue(DataType dataType){ if((getDataType()).equals(dataType)){ return getValue(); } return TypeUtil.parseOrCast(dataType, getValue()); } @Override public int hashCode(){ return (31 * (getOpType().hashCode() ^ getDataType().hashCode())) + getValue().hashCode(); } @Override public boolean equals(Object object){ if(object instanceof FieldValue){ FieldValue that = (FieldValue)object; return (this.getOpType()).equals(that.getOpType()) && (this.getDataType()).equals(that.getDataType()) && (this.getValue()).equals(that.getValue()); } return false; } @Override public String toString(){ ToStringHelper helper = toStringHelper(); return helper.toString(); } protected ToStringHelper toStringHelper(){ ToStringHelper helper = new ToStringHelper(this) .add("opType", getOpType()) .add("dataType", getDataType()) .add("valid", isValid()) .add("value", getValue()); return helper; } @Override public DataType getDataType(){ return this.dataType; } private void setDataType(DataType dataType){ this.dataType = dataType; } public Object getValue(){ return this.value; } private void setValue(Object value){ this.value = value; } static public FieldValue create(DataType dataType, OpType opType, Object value){ if(dataType == null || opType == null){ throw new IllegalArgumentException(); } switch(opType){ case CONTINUOUS: return ContinuousValue.create(dataType, value); case CATEGORICAL: return CategoricalValue.create(dataType, value); case ORDINAL: return OrdinalValue.create(dataType, (List<?>)null, value); default: throw new IllegalArgumentException(); } } static public FieldValue create(TypeInfo typeInfo, Object value){ if(typeInfo == null){ throw new IllegalArgumentException(); } // End if DataType dataType = typeInfo.getDataType(); OpType opType = typeInfo.getOpType(); switch(opType){ case CONTINUOUS: return ContinuousValue.create(dataType, value); case CATEGORICAL: return CategoricalValue.create(dataType, value); case ORDINAL: List<?> ordering = typeInfo.getOrdering(); return OrdinalValue.create(dataType, ordering, value); default: throw new IllegalArgumentException(); } } static private Object ensureValue(HasValue<?> hasValue){ Object value = hasValue.getValue(); if(value == null){ throw new MissingAttributeException(MissingAttributeException.formatMessage(XPathUtil.formatElement((Class)hasValue.getClass()) + "@value"), (PMMLObject)hasValue); } return value; } static final Integer STATUS_UNKNOWN_VALID = Integer.MAX_VALUE; static final Integer STATUS_MISSING = 0; static final Integer STATUS_UNKNOWN_INVALID = Integer.MIN_VALUE; }