blob: 7465ab8b3820591179f8d472c7724b2c4cfa122a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.olingo.server.tecsvc.processor.queryoptions.expression.operation;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Locale;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.tecsvc.processor.queryoptions.expression.operand.TypedOperand;
import org.apache.olingo.server.tecsvc.processor.queryoptions.expression.operand.VisitorOperand;
import org.apache.olingo.server.tecsvc.processor.queryoptions.expression.primitive.EdmNull;
public class BinaryOperator {
private static final int FACTOR_SECOND_INT = 1000;
private static final BigDecimal FACTOR_SECOND = new BigDecimal(1000);
private static final BigInteger EDM_SBYTE_MIN = BigInteger.valueOf(Byte.MIN_VALUE);
private static final BigInteger EDN_SBYTE_MAX = BigInteger.valueOf(Byte.MAX_VALUE);
private static final BigInteger EDM_BYTE_MIN = BigInteger.ZERO;
private static final BigInteger EDM_BYTE_MAX = BigInteger.valueOf(((Byte.MAX_VALUE * 2) + 1));
private static final BigInteger EDM_INT16_MIN = BigInteger.valueOf(Short.MIN_VALUE);
private static final BigInteger EDM_INT16_MAX = BigInteger.valueOf(Short.MAX_VALUE);
private static final BigInteger EDM_INT32_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
private static final BigInteger EDM_INT32_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigInteger EDM_INT64_MIN = BigInteger.valueOf(Long.MIN_VALUE);
private static final BigInteger EDM_INT64_MAX = BigInteger.valueOf(Long.MAX_VALUE);
private static final BigDecimal EDM_SINGLE_MIN = BigDecimal.valueOf(Float.MIN_VALUE);
private static final BigDecimal EDM_SINGLE_MAX = BigDecimal.valueOf(Float.MAX_VALUE);
private static final int EQUALS = 0;
private static final int LESS_THAN = -1;
private static final int GREATER_THAN = 1;
protected static final OData oData;
protected static final EdmPrimitiveType primString;
protected static final EdmPrimitiveType primBoolean;
protected static final EdmPrimitiveType primDateTimeOffset;
protected static final EdmPrimitiveType primDate;
protected static final EdmPrimitiveType primTimeOfDay;
protected static final EdmPrimitiveType primDuration;
protected static final EdmPrimitiveType primSByte;
protected static final EdmPrimitiveType primByte;
protected static final EdmPrimitiveType primInt16;
protected static final EdmPrimitiveType primInt32;
protected static final EdmPrimitiveType primInt64;
protected static final EdmPrimitiveType primDecimal;
protected static final EdmPrimitiveType primSingle;
protected static final EdmPrimitiveType primDouble;
static {
oData = OData.newInstance();
primString = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String);
primBoolean = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean);
primDateTimeOffset = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.DateTimeOffset);
primDate = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Date);
primTimeOfDay = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.TimeOfDay);
primDuration = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration);
primSByte = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.SByte);
primByte = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte);
primInt16 = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int16);
primInt32 = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int32);
primInt64 = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int64);
primDecimal = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal);
primSingle = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Single);
primDouble = oData.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double);
}
private TypedOperand right;
private TypedOperand left;
public BinaryOperator(final VisitorOperand leftOperand, final VisitorOperand rightOperand)
throws ODataApplicationException {
left = leftOperand.asTypedOperand();
right = rightOperand.asTypedOperand();
left = left.castToCommonType(right);
right = right.castToCommonType(left);
}
public VisitorOperand andOperator() throws ODataApplicationException {
Boolean result = null;
if (left.is(primBoolean) && right.is(primBoolean)) {
if (Boolean.TRUE.equals(left.getValue()) && Boolean.TRUE.equals(right.getValue())) {
result = true;
} else if (Boolean.FALSE.equals(left.getValue()) || Boolean.FALSE.equals(right.getValue())) {
result = false;
}
return new TypedOperand(result, primBoolean);
} else {
throw new ODataApplicationException("Add operator needs two binary operands",
HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
}
}
public VisitorOperand orOperator() throws ODataApplicationException {
Boolean result = null;
if (left.is(primBoolean) && right.is(primBoolean)) {
if (Boolean.TRUE.equals(left.getValue()) || Boolean.TRUE.equals(right.getValue())) {
result = true;
} else if (Boolean.FALSE.equals(left.getValue()) && Boolean.FALSE.equals(right.getValue())) {
result = false;
}
return new TypedOperand(result, primBoolean);
} else {
throw new ODataApplicationException("Or operator needs two binary operands",
HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
}
}
public VisitorOperand equalsOperator() {
final boolean result = isBinaryComparisonNecessary() && binaryComparison(EQUALS);
return new TypedOperand(result, primBoolean);
}
public VisitorOperand notEqualsOperator() {
final VisitorOperand equalsOperator = equalsOperator();
return new TypedOperand(!(Boolean) equalsOperator.getValue(), primBoolean);
}
private boolean isBinaryComparisonNecessary() {
// binaryComparison() need to be called, if both operand are either null or not null
return !(left.isNull() ^ right.isNull());
}
public VisitorOperand greaterEqualsOperator() {
final boolean result = isBinaryComparisonNecessary() && binaryComparison(GREATER_THAN, EQUALS);
return new TypedOperand(result, primBoolean);
}
public VisitorOperand greaterThanOperator() {
final boolean result = isBinaryComparisonNecessary() && binaryComparison(GREATER_THAN);
return new TypedOperand(result, primBoolean);
}
public VisitorOperand lessEqualsOperator() {
final boolean result = isBinaryComparisonNecessary() && binaryComparison(LESS_THAN, EQUALS);
return new TypedOperand(result, primBoolean);
}
public VisitorOperand lessThanOperator() {
final boolean result = isBinaryComparisonNecessary() && binaryComparison(LESS_THAN);
return new TypedOperand(result, primBoolean);
}
private boolean binaryComparison(final int... expect) {
int result;
if (left.isNull() && right.isNull()) {
result = 0; // null is equals to null
} else {
// left and right are not null!
if (left.isIntegerType()) {
result = left.getTypedValue(BigInteger.class).compareTo(right.getTypedValue(BigInteger.class));
} else if (left.isDecimalType()) {
result = left.getTypedValue(BigDecimal.class).compareTo(right.getTypedValue(BigDecimal.class));
} else {
result = left.getValue().equals(right.getValue()) ? 0 : 1;
}
}
for (int expectedValue : expect) {
if (expectedValue == result) {
return true;
}
}
return false;
}
public VisitorOperand arithmeticOperator(final BinaryOperatorKind operator) throws ODataApplicationException {
if (left.isNull() || right.isNull()) {
return new TypedOperand(new Object(), EdmNull.getInstance());
} else {
if (left.isIntegerType()) {
final BigInteger result = integerArithmeticOperation(operator);
return new TypedOperand(result, determineResultType(result, left));
} else if (left.isDecimalType()) {
final BigDecimal result = decimalArithmeticOperation(operator);
return new TypedOperand(result, determineResultType(result, left));
} else if (left.is(primDate, primDuration, primDateTimeOffset)) {
return dateArithmeticOperation(operator);
} else {
throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(),
Locale.ROOT);
}
}
}
private EdmType determineResultType(final Number arithmeticResult, final TypedOperand leftOperand) {
// Left and right operand have the same typed, so it is enough to check the type of the left operand
if (leftOperand.isDecimalType()) {
final BigDecimal value = (BigDecimal) arithmeticResult;
if (value.compareTo(EDM_SINGLE_MIN) >= 0 && value.compareTo(EDM_SINGLE_MAX) <= 0) {
return primSingle;
} else {
return primDouble;
}
} else {
final BigInteger value = (BigInteger) arithmeticResult;
if (value.compareTo(EDN_SBYTE_MAX) <= 0 && value.compareTo(EDM_SBYTE_MIN) >= 0) {
return primSByte;
}
if (value.compareTo(EDM_BYTE_MAX) <= 0 && value.compareTo(EDM_BYTE_MIN) >= 0) {
return primByte;
}
if (value.compareTo(EDM_INT16_MAX) <= 0 && value.compareTo(EDM_INT16_MIN) >= 0) {
return primInt16;
}
if (value.compareTo(EDM_INT32_MAX) <= 0 && value.compareTo(EDM_INT32_MIN) >= 0) {
return primInt32;
}
if (value.compareTo(EDM_INT64_MAX) <= 0 && value.compareTo(EDM_INT64_MIN) >= 0) {
return primInt64;
}
// Choose double instead single because precision is higher (52 bits instead of 23)
return primDouble;
}
}
private VisitorOperand dateArithmeticOperation(final BinaryOperatorKind operator) throws ODataApplicationException {
VisitorOperand result = null;
if (left.is(primDate)) {
if (right.is(primDate) && operator == BinaryOperatorKind.SUB) {
long millis = left.getTypedValue(Calendar.class).getTimeInMillis()
- left.getTypedValue(Calendar.class).getTimeInMillis();
result = new TypedOperand(new BigDecimal(millis).divide(FACTOR_SECOND), primDuration);
} else if (right.is(primDuration) && operator == BinaryOperatorKind.ADD) {
long millis = left.getTypedValue(Calendar.class).getTimeInMillis()
+ (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT);
result = new TypedOperand(new Timestamp(millis), primDateTimeOffset);
} else if (right.is(primDuration) && operator == BinaryOperatorKind.SUB) {
long millis = left.getTypedValue(Calendar.class).getTimeInMillis()
- (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT);
result = new TypedOperand(new Timestamp(millis), primDateTimeOffset);
}
} else if (left.is(primDuration)) {
if (right.is(primDuration) && operator == BinaryOperatorKind.ADD) {
long seconds = left.getTypedValue(BigDecimal.class).longValue()
+ right.getTypedValue(BigDecimal.class).longValue();
result = new TypedOperand(new BigDecimal(seconds), primDuration);
} else if (right.is(primDuration) && operator == BinaryOperatorKind.SUB) {
long seconds = left.getTypedValue(BigDecimal.class).longValue()
- right.getTypedValue(BigDecimal.class).longValue();
result = new TypedOperand(new BigDecimal(seconds), primDuration);
}
} else if (left.is(primDateTimeOffset)) {
if (right.is(primDuration) && operator == BinaryOperatorKind.ADD) {
long millis = left.getTypedValue(Timestamp.class).getTime()
+ (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT);
result = new TypedOperand(new Timestamp(millis), primDateTimeOffset);
} else if (right.is(primDuration) && operator == BinaryOperatorKind.SUB) {
long millis = left.getTypedValue(Timestamp.class).getTime()
- (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT);
result = new TypedOperand(new Timestamp(millis), primDateTimeOffset);
} else if (right.is(primDateTimeOffset) && operator == BinaryOperatorKind.SUB) {
long millis = left.getTypedValue(Timestamp.class).getTime()
- right.getTypedValue(Timestamp.class).getTime();
result = new TypedOperand(new BigDecimal(millis).divide(FACTOR_SECOND), primDuration);
}
}
if (result == null) {
throw new ODataApplicationException("Invalid operation / operand", HttpStatusCode.BAD_REQUEST.getStatusCode(),
Locale.ROOT);
} else {
return result;
}
}
private BigDecimal decimalArithmeticOperation(final BinaryOperatorKind operator) throws ODataApplicationException {
final BigDecimal left = this.left.getTypedValue(BigDecimal.class);
final BigDecimal right = this.right.getTypedValue(BigDecimal.class);
switch (operator) {
case ADD:
return left.add(right);
case DIV:
return left.divide(left);
case MUL:
return left.multiply(right);
case SUB:
return left.subtract(right);
default:
throw new ODataApplicationException("Operator not valid", HttpStatusCode.BAD_REQUEST.getStatusCode(),
Locale.ROOT);
}
}
private BigInteger integerArithmeticOperation(final BinaryOperatorKind operator) throws ODataApplicationException {
final BigInteger left = this.left.getTypedValue(BigInteger.class);
final BigInteger right = this.right.getTypedValue(BigInteger.class);
switch (operator) {
case ADD:
return left.add(right);
case DIV:
return left.divide(right);
case MUL:
return left.multiply(right);
case SUB:
return left.subtract(right);
case MOD:
return left.mod(right);
default:
throw new ODataApplicationException("Operator not valid", HttpStatusCode.BAD_REQUEST.getStatusCode(),
Locale.ROOT);
}
}
}