/* * © Copyright FOCONIS AG, 2014 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * */ package org.openntf.formula; import java.io.Serializable; import java.lang.reflect.Array; import java.util.Collection; import java.util.List; /** * This Valueholder is to hold single or multiple values. * * When evaluating a formula, every String/int/double value is wrapped in a "ValueHolder". The holder has several get-methods to return the * different types. You always must check the datatype before calling one of the getters, because a ValueHolder that contains Strings cannot * return * * The code itself might look strange, but this was done to be as fast as possible * * @author Roland Praml, Foconis AG * */ public abstract class ValueHolder implements Serializable { private static final long serialVersionUID = 8290517470597891417L; /** * These are the possible datatypes. <br> * DOUBLE means 'only doubles', INTEGER means 'only integers'. If you mix Integers and Doubles, the type changes to "NUMBER" */ public enum DataType { ERROR, STRING, KEYWORD_STRING, INTEGER(true), DOUBLE(true), DATETIME, BOOLEAN, _UNSET, OBJECT, UNAVAILABLE; public boolean numeric = false; DataType() { } DataType(final boolean n) { numeric = n; } } private static ValueHolder nothing; private static ValueHolder unavailable; private static ValueHolder defaultVar; protected boolean immutable; // For performance reasons we allow direct access to these members public int size; public DataType dataType = DataType._UNSET; private EvaluateException currentError; // Caches protected static final ValueHolder TRUE; protected static final ValueHolder FALSE; protected static final ValueHolder integerCache[]; protected static final ValueHolder stringCache[]; /** * Initializer to initialize some default ValueHolders */ static { TRUE = new ValueHolderBoolean(1); TRUE.add(Boolean.TRUE); TRUE.immutable = true; FALSE = new ValueHolderBoolean(1); FALSE.add(Boolean.FALSE); FALSE.immutable = true; integerCache = new ValueHolder[256]; stringCache = new ValueHolder[256]; nothing = new ValueHolderObject<Object>(1); nothing.add(""); nothing.immutable = true; defaultVar = new ValueHolderObject<Object>(1); defaultVar.add(""); defaultVar.immutable = true; unavailable = new ValueHolderObject<String>(1); unavailable.add(""); unavailable.dataType = DataType.UNAVAILABLE; unavailable.immutable = true; for (int i = 0; i < 256; i++) { ValueHolder vhn = integerCache[i] = new ValueHolderNumber(1); vhn.add(i - 128); vhn.immutable = true; ValueHolder vho = stringCache[i] = new ValueHolderObject<Object>(1); if (i == 0) { vho.add(""); } else { vho.add(String.valueOf((char) i)); } vho.immutable = true; } } /** * Need for serialization */ public ValueHolder() { } /** * Create a new ValueHolder * * @param clazz * the class to create the ValueHolder * @param size * the size of the ValueHolder. You must specify the size at construction time * @return a valueHOlder */ public static ValueHolder createValueHolder(final Class<?> clazz, final int size) { if (boolean.class.equals(clazz)) return new ValueHolderBoolean(size); if (double.class.equals(clazz)) return new ValueHolderNumber(size); if (int.class.equals(clazz)) return new ValueHolderNumber(size); // the rest of the numeric values in JAVA -> handled as double if (byte.class.equals(clazz)) return new ValueHolderNumber(size); if (char.class.equals(clazz)) return new ValueHolderNumber(size); if (short.class.equals(clazz)) return new ValueHolderNumber(size); if (long.class.equals(clazz)) return new ValueHolderNumber(size); if (float.class.equals(clazz)) return new ValueHolderNumber(size); if (clazz.isPrimitive()) { throw new UnsupportedOperationException("Cannot return objectholder for " + clazz); } if (Number.class.isAssignableFrom(clazz)) return new ValueHolderNumber(size); if (Boolean.class.isAssignableFrom(clazz)) return new ValueHolderBoolean(size); if (Character.class.isAssignableFrom(clazz)) return new ValueHolderNumber(size); return new ValueHolderObject<Object>(size); } /** * thows the current error, if there is one stored in the exception * * @throws EvaluateException * @throws UnavailableException */ protected void throwError() throws EvaluateException { if (currentError != null) throw currentError; } /** * Init a ValueHolder based on a single value or a collection<br> * If possible use one of the special "valueOf" constructors as these are faster * */ public static ValueHolder valueOf(final Object init) { if (init == null) return valueDefault(); if (init instanceof String) return valueOf((String) init); if (init instanceof Integer) return valueOf(((Integer) init).intValue()); if (init instanceof Number) return valueOf(((Number) init).doubleValue()); if (init instanceof Boolean) return valueOf(((Boolean) init).booleanValue()); ValueHolder vh = null; // Array handling and other objects if (init.getClass().isArray()) { int lh = Array.getLength(init); if (lh == 0) return valueDefault(); if (lh == 1) return valueOf(Array.get(init, 0)); for (int i = 0; i < lh; i++) { Object o = Array.get(init, i); if (o != null) { if (vh == null) { vh = createValueHolder(o.getClass(), lh); } vh.add(o); } } } else if (init instanceof Collection) { Collection<?> c = (Collection<?>) init; int lh = c.size(); if (lh == 0) return valueDefault(); if (lh == 1) return valueOf(c.iterator().next()); for (Object o : c) { if (o != null) { if (vh == null) { vh = createValueHolder(o.getClass(), lh); } vh.add(o); } } } else { vh = createValueHolder(init.getClass(), 1); vh.add(init); } if (vh == null) return valueDefault(); vh.immutable = true; return vh; } /** * Initializes a new ValueHolder that holds a RuntimeException * * @param init * the RuntimeException * @return the Valuholder */ public static ValueHolder valueOf(final EvaluateException init) { ValueHolder vh = new ValueHolderObject<Object>(1); vh.dataType = DataType.ERROR; vh.currentError = init; vh.size = 1; vh.immutable = true; return vh; } /** * Initializes a new ValueHolder that holds a integer value. Values -128..128 are cached. * * @param init * the int value * @return the Valuholder */ public static ValueHolder valueOf(final int init) { if (-128 <= init && init < 128) { return integerCache[init + 128]; } ValueHolder vh = new ValueHolderNumber(1); vh.add(init); vh.immutable = true; return vh; } /** * Initializes a new ValueHolder that holds a double value * * @param init * the double * @return the ValueHolder */ public static ValueHolder valueOf(final double init) { ValueHolder vh = new ValueHolderNumber(1); vh.add(init); vh.immutable = true; return vh; } /** * Initializes a new ValueHolder that holds a String value * * @param init * the String * @return the ValueHolder */ public static ValueHolder valueOf(final String init) { if (init == null || init.length() == 0) return stringCache[0]; if (init.length() == 1) { char ch = init.charAt(0); if (0 < ch && ch < 256) return stringCache[ch]; } ValueHolder vh = new ValueHolderObject<String>(1); vh.add(init); vh.immutable = true; return vh; } /** * Initializes a new ValueHolder that contains a DateTime * * @param init * the DateTime * @return the ValueHolder */ public static ValueHolder valueOf(final DateTime init) { ValueHolder vh = new ValueHolderObject<DateTime>(1); vh.add(init); vh.immutable = true; return vh; } /** * Returns one of the two cached Boolean holders that represent TRUE or FALSE * * @param init * the boolean. * @return the ValueHolder */ public static ValueHolder valueOf(final boolean init) { if (init) return TRUE; return FALSE; } /** * Returns the ValueHolder for the default value . * * @return the ValueHolder */ public static ValueHolder valueDefault() { return defaultVar; } public static ValueHolder valueNothing() { return nothing; } /** * Initializes a new ValueHolder that holds a RuntimeException * * @param init * the RuntimeException * @return the Valuholder */ public static ValueHolder valueUnavailable() { return unavailable; } /** * returns the error or null * * @return the error or null */ public EvaluateException getError() { return currentError; } /** * get the nth entry (0=first entry) * * @param i * the position * @return the entry as Object * * @Deprecated if you know the datatype, use the apropriate get-Method! */ @Deprecated public Object get(final int i) { switch (dataType) { case ERROR: return currentError; case DOUBLE: return getDouble(i); case INTEGER: return getInt(i); default: return getObject(i); } } /** * Returns the stored value as Object. * * @param i * the position * @return the value as object */ public abstract Object getObject(final int i); /** * Returns the value at position i as String * * @param i * the position * @return the value as String */ public String getString(final int i) { throw new ClassCastException("STRING expected. Got '" + dataType + "'"); } /** * Returns the value at position i as DateTime */ public DateTime getDateTime(final int i) { throw new ClassCastException("DATETIME expected. Got '" + dataType + "'"); } /** * Returns the value at position i as int */ public int getInt(final int i) { throw new ClassCastException("INTEGER expected. Got '" + dataType + "'"); } /** * Returns the value at position i as double */ public double getDouble(final int i) { throw new ClassCastException("DOUBLE expected. Got '" + dataType + "'"); } /** * Returns the value at position i as boolen */ public boolean getBoolean(final int i) { throw new ClassCastException("BOOLEAN expected. Got '" + dataType + "'"); } /** * Returns TRUE if one of the values is true */ public boolean isTrue(final FormulaContext ctx) { if (ctx.useBooleans) { for (int i = 0; i < size; i++) { if (getBoolean(i)) { return true; } } } else { for (int i = 0; i < size; i++) { if (getInt(i) != 0) { return true; } } } return false; } /** * Adds all values of an other ValueHolder * */ public boolean addAll(final ValueHolder other) { if (dataType == DataType.ERROR) { return false; } else if (other.dataType == DataType.ERROR) { dataType = DataType.ERROR; currentError = other.currentError; size = 1; return true; } else { throw new IllegalArgumentException("Cannot add " + other.dataType + " to " + dataType); } } /** * Adds the value as String. You have to ensure that you have called grow() before! * */ public boolean add(final String obj) { throw new IllegalArgumentException("Cannot mix datatypes " + dataType + " and STRING"); } /** * Adds an integer as value * */ public boolean add(final int value) { throw new IllegalArgumentException("Cannot mix datatypes " + dataType + " and INTEGER"); } /** * Adds a double as value */ public boolean add(final double value) { throw new IllegalArgumentException("Cannot mix datatypes " + dataType + " and DOUBLE"); } public boolean add(final boolean bool) { throw new IllegalArgumentException("Cannot mix datatypes " + dataType + " and BOOLEAN"); } public boolean add(final DateTime bool) { throw new IllegalArgumentException("Cannot mix datatypes " + dataType + " and DATETIME"); } /** * throws an Exception if the ValueHolder is immutable */ protected void checkImmutable() { if (immutable) throw new UnsupportedOperationException("ValueHolder is immutable."); } /** * Add anything as value. Better use the apropriate "add" method. it is faster */ @Deprecated public boolean add(final Object obj) { checkImmutable(); if (dataType == DataType.ERROR) { return false; } if (obj instanceof String) { return add((String) obj); } else if (obj instanceof Integer) { return add(((Integer) obj).intValue()); } else if (obj instanceof Number) { return add(((Number) obj).doubleValue()); } else if (obj instanceof Boolean) { return add(((Boolean) obj).booleanValue()); } else if (obj instanceof DateTime) { return add((DateTime) obj); //} else if (obj instanceof RuntimeException) { // setError((RuntimeException) obj); } else { dataType = DataType.OBJECT; throw new IllegalArgumentException("TODO: Datatype " + obj.getClass() + " is not yet supported"); } //return true; } public static boolean hasMultiValues(final ValueHolder[] params) { if (params != null) { for (int i = 0; i < params.length; i++) { if (params[i].size > 1) return true; } } return false; } public abstract List<Object> toList() throws EvaluateException; public abstract ValueHolder newInstance(int size2); public abstract void swap(int i, int j); public abstract String quoteValue() throws EvaluateException; }