View Javadoc
1   /*
2   Copyright (c) 2018 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.io.IOException;
20  import java.math.BigDecimal;
21  import java.text.DecimalFormat;
22  import java.time.LocalDateTime;
23  import java.time.ZoneId;
24  import java.time.format.DateTimeFormatter;
25  import java.util.Collection;
26  import java.util.EnumMap;
27  import java.util.Locale;
28  import java.util.Map;
29  import javax.script.Bindings;
30  
31  import com.healthmarketscience.jackcess.DataType;
32  import com.healthmarketscience.jackcess.JackcessException;
33  import com.healthmarketscience.jackcess.expr.EvalContext;
34  import com.healthmarketscience.jackcess.expr.EvalException;
35  import com.healthmarketscience.jackcess.expr.Expression;
36  import com.healthmarketscience.jackcess.expr.Identifier;
37  import com.healthmarketscience.jackcess.expr.LocaleContext;
38  import com.healthmarketscience.jackcess.expr.NumericConfig;
39  import com.healthmarketscience.jackcess.expr.TemporalConfig;
40  import com.healthmarketscience.jackcess.expr.Value;
41  import com.healthmarketscience.jackcess.impl.expr.Expressionator;
42  import com.healthmarketscience.jackcess.impl.expr.ValueSupport;
43  
44  /**
45   *
46   * @author James Ahlborn
47   */
48  public abstract class BaseEvalContext implements EvalContext
49  {
50    /** map of all non-string data types */
51    private static final Map<DataType,Value.Type> TYPE_MAP =
52      new EnumMap<>(DataType.class);
53  
54    static {
55      TYPE_MAP.put(DataType.BOOLEAN,Value.Type.LONG);
56      TYPE_MAP.put(DataType.BYTE,Value.Type.LONG);
57      TYPE_MAP.put(DataType.INT,Value.Type.LONG);
58      TYPE_MAP.put(DataType.LONG,Value.Type.LONG);
59      TYPE_MAP.put(DataType.MONEY,Value.Type.DOUBLE);
60      TYPE_MAP.put(DataType.FLOAT,Value.Type.DOUBLE);
61      TYPE_MAP.put(DataType.DOUBLE,Value.Type.DOUBLE);
62      TYPE_MAP.put(DataType.SHORT_DATE_TIME,Value.Type.DATE_TIME);
63      TYPE_MAP.put(DataType.NUMERIC,Value.Type.BIG_DEC);
64      TYPE_MAP.put(DataType.BIG_INT,Value.Type.BIG_DEC);
65    }
66  
67    private final DBEvalContext _dbCtx;
68    private Expression _expr;
69  
70    protected BaseEvalContext(DBEvalContext dbCtx) {
71      _dbCtx = dbCtx;
72    }
73  
74    void setExpr(Expressionator.Type exprType, String exprStr) {
75      _expr = new RawExpr(exprType, exprStr);
76    }
77  
78    protected DatabaseImpl getDatabase() {
79      return _dbCtx.getDatabase();
80    }
81  
82    @Override
83    public Locale getLocale() {
84      return _dbCtx.getLocale();
85    }
86  
87    @Override
88    public TemporalConfig getTemporalConfig() {
89      return _dbCtx.getTemporalConfig();
90    }
91  
92    @Override
93    public DateTimeFormatter createDateFormatter(String formatStr) {
94      return _dbCtx.createDateFormatter(formatStr);
95    }
96  
97    @Override
98    public ZoneId getZoneId() {
99      return _dbCtx.getZoneId();
100   }
101 
102   @Override
103   public NumericConfig getNumericConfig() {
104     return _dbCtx.getNumericConfig();
105   }
106 
107   @Override
108   public DecimalFormat createDecimalFormat(String formatStr) {
109     return _dbCtx.createDecimalFormat(formatStr);
110   }
111 
112   @Override
113   public float getRandom(Integer seed) {
114     return _dbCtx.getRandom(seed);
115   }
116 
117   @Override
118   public Value.Type getResultType() {
119     return null;
120   }
121 
122   @Override
123   public Value getThisColumnValue() {
124     throw new UnsupportedOperationException();
125   }
126 
127   @Override
128   public Value getIdentifierValue(Identifier identifier) {
129     throw new UnsupportedOperationException();
130   }
131 
132   @Override
133   public Bindings getBindings() {
134     return _dbCtx.getBindings();
135   }
136 
137   @Override
138   public Object get(String key) {
139     return _dbCtx.getBindings().get(key);
140   }
141 
142   @Override
143   public void put(String key, Object value) {
144     _dbCtx.getBindings().put(key, value);
145   }
146 
147   public Object eval() throws IOException {
148     try {
149       return _expr.eval(this);
150     } catch(Exception e) {
151       String msg = withErrorContext(e.getMessage());
152       throw new JackcessException(msg, e);
153     }
154   }
155 
156   public void collectIdentifiers(Collection<Identifier> identifiers) {
157     _expr.collectIdentifiers(identifiers);
158   }
159 
160   @Override
161   public String toString() {
162     return _expr.toString();
163   }
164 
165   protected Value toValue(Object val, DataType dType) {
166     try {
167       // expression engine always uses LocalDateTime, so force that date/time
168       // type
169       val = ColumnImpl.toInternalValue(dType, val, getDatabase(),
170                                        ColumnImpl.LDT_DATE_TIME_FACTORY);
171       if(val == null) {
172         return ValueSupport.NULL_VAL;
173       }
174 
175       Value.Type vType = toValueType(dType);
176       switch(vType) {
177       case STRING:
178         return ValueSupport.toValue(val.toString());
179       case DATE:
180       case TIME:
181       case DATE_TIME:
182         return ValueSupport.toValue(vType, (LocalDateTime)val);
183       case LONG:
184         Integer i = ((val instanceof Integer) ? (Integer)val :
185                      ((Number)val).intValue());
186         return ValueSupport.toValue(i);
187       case DOUBLE:
188         Double d = ((val instanceof Double) ? (Double)val :
189                     ((Number)val).doubleValue());
190         return ValueSupport.toValue(d);
191       case BIG_DEC:
192         BigDecimal bd = ColumnImpl.toBigDecimal(val, getDatabase());
193         return ValueSupport.toValue(bd);
194       default:
195         throw new RuntimeException("Unexpected type " + vType);
196       }
197     } catch(IOException e) {
198       throw new EvalException("Failed converting value to type " + dType, e);
199     }
200   }
201 
202   /**
203    * @return whether a default value which is not an expression may be taken
204    *         as a literal here.  A column type with no Value.Type of its own,
205    *         such as GUID, reads as a string, but it holds no free text
206    */
207   protected boolean allowLiteralDefaultValue() {
208     return true;
209   }
210 
211   public static Value.Type toValueType(DataType dType) {
212     Value.Type type = TYPE_MAP.get(dType);
213     return ((type == null) ? Value.Type.STRING : type);
214   }
215 
216   protected abstract String withErrorContext(String msg);
217 
218   private class RawExpr implements Expression
219   {
220     private final Expressionator.Type _exprType;
221     private final String _exprStr;
222 
223     private RawExpr(Expressionator.Type exprType, String exprStr) {
224       _exprType = exprType;
225       _exprStr = exprStr;
226     }
227 
228     private Expression getExpr() {
229       // when the expression is parsed we replace the raw version
230       Expression expr = Expressionator.parse(
231           _exprType, _exprStr, getResultType(), _dbCtx,
232           allowLiteralDefaultValue());
233       _expr = expr;
234       return expr;
235     }
236 
237     @Override
238     public Object eval(EvalContext ctx) {
239       return getExpr().eval(ctx);
240     }
241 
242     @Override
243     public String toDebugString(LocaleContext ctx) {
244       return getExpr().toDebugString(ctx);
245     }
246 
247     @Override
248     public String toRawString() {
249       return _exprStr;
250     }
251 
252     @Override
253     public String toCleanString(LocaleContext ctx) {
254       return getExpr().toCleanString(ctx);
255     }
256 
257     @Override
258     public boolean isConstant() {
259       return getExpr().isConstant();
260     }
261 
262     @Override
263     public void collectIdentifiers(Collection<Identifier> identifiers) {
264       getExpr().collectIdentifiers(identifiers);
265     }
266 
267     @Override
268     public String toString() {
269       return toRawString();
270     }
271   }
272 }