001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krms.framework.engine.expression; 017 018import org.apache.commons.lang.ObjectUtils; 019import org.kuali.rice.krms.api.engine.IncompatibleTypeException; 020 021import java.lang.reflect.Constructor; 022import java.lang.reflect.InvocationTargetException; 023import java.math.BigDecimal; 024import java.math.BigInteger; 025 026/** 027 * The default {@link ComparisonOperator}. If no other {@link EngineComparatorExtension} have been configured to handle 028 * a type, the DefaultComparisonOperator will be used. At the moment the DefaultComparisonOperator is also the default 029 * {@link StringCoercionExtension} for coercing types. 030 * 031 * @author Kuali Rice Team (rice.collab@kuali.org) 032 */ 033 034public class DefaultComparisonOperator implements EngineComparatorExtension, StringCoercionExtension { 035 036 @Override 037 public int compare(Object lhs, Object rhs) { 038 039 if (lhs == null && rhs == null) { 040 return 0; 041 } else if (lhs == null) { 042 return -1; 043 } else if (rhs == null) { 044 return 1; 045 } 046 047 if (rhs instanceof String && !(lhs instanceof String)) { 048 rhs = coerceStringOperand(lhs, rhs.toString()); 049 } else if (lhs instanceof String && !(rhs instanceof String)) { 050 lhs = coerceStringOperand(rhs, lhs.toString()); 051 } 052 053 054 if (ObjectUtils.equals(lhs, rhs)) { 055 return 0; 056 } 057 058 if (lhs instanceof Comparable && rhs instanceof Comparable) { 059 int result = ((Comparable)lhs).compareTo(rhs); 060 return result; 061 } 062 else { 063 throw new IncompatibleTypeException("DefaultComparisonOperator could not compare values", lhs, rhs.getClass()); 064 } 065 } 066 067 @Override 068 public boolean canCompare(Object lhs, Object rhs) { 069 try { 070 compare(lhs, rhs); 071 return true; 072 } catch (Exception e) { 073 return false; 074 } 075 } 076 077 /** 078 * 079 * @param objectArg 080 * @param stringArg 081 * @return Object 082 * @throws IncompatibleTypeException 083 */ 084 private Object coerceStringOperand(Object objectArg, String stringArg) { 085 Object result = stringArg; 086 if (objectArg != null && stringArg != null) { 087 if (!(objectArg instanceof String)) { 088 result = coerceHelper(objectArg, stringArg, Double.class, Float.class, Long.class, Integer.class, Boolean.class); 089 090 if (result instanceof String) { // was coercion successful? 091 if (objectArg instanceof BigDecimal) { 092 try { 093 result = BigDecimal.valueOf(Double.valueOf(stringArg.toString())); 094 } catch (NumberFormatException e) { 095 throw new IncompatibleTypeException("Could not coerce String to BigDecimal" + this, stringArg, objectArg.getClass()); 096 } 097 } else if (objectArg instanceof BigInteger) { 098 try { 099 result = BigInteger.valueOf(Long.valueOf(stringArg.toString())); 100 } catch (NumberFormatException e) { 101 throw new IncompatibleTypeException("Could not coerce String to BigInteger" + this, stringArg, objectArg.getClass()); 102 } 103 } else { 104 throw new IncompatibleTypeException("Could not compare values for operator " + this, objectArg, stringArg.getClass()); 105 } 106 } 107 } 108 } 109 return result; 110 } 111 112 /** 113 * 114 * @param objectArg 115 * @param stringArg 116 * @param clazzes 117 * @return The object of one of the given types, whose value is stringArg 118 */ 119 private Object coerceHelper(Object objectArg, String stringArg, Class<?> ... clazzes) { 120 for (Class clazz : clazzes) { 121 if (clazz.isInstance(objectArg)) { 122 try { 123 return clazz.getMethod("valueOf", String.class).invoke(null, stringArg); 124 } catch (NumberFormatException e) { 125 throw new IncompatibleTypeException("Could not coerce String to " + 126 clazz.getSimpleName() + " " + this, stringArg, objectArg.getClass()); 127 } catch (NoSuchMethodException e) { 128 throw new IncompatibleTypeException("Could not coerce String to " + 129 clazz.getSimpleName() + " " + this, stringArg, objectArg.getClass()); 130 } catch (InvocationTargetException e) { 131 throw new IncompatibleTypeException("Could not coerce String to " + 132 clazz.getSimpleName() + " " + this, stringArg, objectArg.getClass()); 133 } catch (IllegalAccessException e) { 134 throw new IncompatibleTypeException("Could not coerce String to " + 135 clazz.getSimpleName() + " " + this, stringArg, objectArg.getClass()); 136 } 137 } 138 } 139 return stringArg; 140 } 141 142 @Override 143 public Object coerce(String type, String value) { 144 try { 145 Class clazz = Class.forName(type); 146 // Constructor that takes string a bit more generic than the coerceRhs 147 Constructor constructor = clazz.getConstructor(new Class[]{String.class}); 148 Object propObject = constructor.newInstance(value); 149 return propObject; 150 } catch (Exception e) { 151 return null; // TODO EGHM dev log? 152 } 153 } 154 155 @Override 156 public boolean canCoerce(String type, String value) { 157 return coerce(type, value) != null; 158 } 159}