View Javadoc
1   /*
2   Copyright (c) 2016 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.expr;
18  
19  import java.math.BigDecimal;
20  import java.text.DecimalFormatSymbols;
21  
22  import com.healthmarketscience.jackcess.expr.EvalException;
23  import com.healthmarketscience.jackcess.expr.LocaleContext;
24  import com.healthmarketscience.jackcess.expr.Value;
25  import org.apache.commons.lang3.StringUtils;
26  
27  /**
28   *
29   * @author James Ahlborn
30   */
31  public class StringValue extends BaseValue
32  {
33    private static final Object NOT_A_NUMBER = new Object();
34  
35    private final String _val;
36    private Object _num;
37  
38    public StringValue(String val)
39    {
40      _val = val;
41    }
42  
43    @Override
44    public Type getType() {
45      return Type.STRING;
46    }
47  
48    @Override
49    public Object get() {
50      return _val;
51    }
52  
53    @Override
54    public boolean getAsBoolean(LocaleContext ctx) {
55      // ms access seems to treat strings as "true"
56      return true;
57    }
58  
59    @Override
60    public String getAsString(LocaleContext ctx) {
61      return _val;
62    }
63  
64    @Override
65    public Integer getAsLongInt(LocaleContext ctx) {
66      return roundToLongInt(ctx);
67    }
68  
69    @Override
70    public Double getAsDouble(LocaleContext ctx) {
71      return getNumber(ctx).doubleValue();
72    }
73  
74    @Override
75    public BigDecimal getAsBigDecimal(LocaleContext ctx) {
76      return getNumber(ctx);
77    }
78  
79    @Override
80    public Value getAsDateTimeValue(LocaleContext ctx) {
81      Value dateValue = DefaultDateFunctions.stringToDateValue(ctx, _val);
82  
83      if(dateValue == null) {
84        // see if string can be coerced to number and then to value date (note,
85        // numberToDateValue may return null for out of range numbers)
86        try {
87          dateValue = DefaultDateFunctions.numberToDateValue(
88              getNumber(ctx).doubleValue());
89        } catch(EvalException ignored) {
90          // not a number, not a date/time
91        }
92  
93        if(dateValue == null) {
94          throw invalidConversion(Type.DATE_TIME);
95        }
96      }
97  
98      // TODO, for now, we can't cache the date value becuase it could be an
99      // "implicit" date which would need to be re-calculated on each call
100     return dateValue;
101   }
102 
103   protected BigDecimal getNumber(LocaleContext ctx) {
104     if(_num instanceof BigDecimal) {
105       return (BigDecimal)_num;
106     }
107     if(_num == null) {
108       // see if it is parseable as a number
109       try {
110         // ignore extraneous whitespace whitespace and handle "&[hH]" or
111         // "&[oO]" prefix (only supports integers)
112         String tmpVal = _val.trim();
113         if(tmpVal.length() > 0) {
114 
115           if(tmpVal.charAt(0) != ValueSupport.NUMBER_BASE_PREFIX) {
116             // convert to standard numeric support for parsing
117             tmpVal = toCanonicalNumberFormat(ctx, tmpVal);
118             _num = ValueSupport.normalize(new BigDecimal(tmpVal));
119             return (BigDecimal)_num;
120           }
121 
122           // parse as hex/octal symbolic value
123           if(ValueSupport.HEX_PAT.matcher(tmpVal).matches()) {
124             return parseIntegerString(tmpVal, 16);
125           } else if(ValueSupport.OCTAL_PAT.matcher(tmpVal).matches()) {
126             return parseIntegerString(tmpVal, 8);
127           }
128 
129           // fall through to NaN
130         }
131       } catch(NumberFormatException nfe) {
132         // fall through to NaN...
133       }
134       _num = NOT_A_NUMBER;
135     }
136     throw invalidConversion(Type.DOUBLE);
137   }
138 
139   private BigDecimal parseIntegerString(String tmpVal, int radix) {
140     _num = new BigDecimal(ValueSupport.parseIntegerString(tmpVal, radix));
141     return (BigDecimal)_num;
142   }
143 
144   private static String toCanonicalNumberFormat(LocaleContext ctx, String tmpVal)
145   {
146     // convert to standard numeric format:
147     // - discard any grouping separators
148     // - convert decimal separator to '.'
149     DecimalFormatSymbols syms = ctx.getNumericConfig().getDecimalFormatSymbols();
150     char groupSepChar = syms.getGroupingSeparator();
151     tmpVal = StringUtils.remove(tmpVal, groupSepChar);
152 
153     char decSepChar = syms.getDecimalSeparator();
154     if((decSepChar != ValueSupport.CANON_DEC_SEP) && (tmpVal.indexOf(decSepChar) >= 0)) {
155       tmpVal = tmpVal.replace(decSepChar, ValueSupport.CANON_DEC_SEP);
156     }
157 
158     return tmpVal;
159   }
160 }