View Javadoc
1   /*
2   Copyright (c) 2017 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  package com.healthmarketscience.jackcess.impl.expr;
17  
18  import java.math.BigDecimal;
19  
20  import com.healthmarketscience.jackcess.expr.EvalContext;
21  import com.healthmarketscience.jackcess.expr.EvalException;
22  import com.healthmarketscience.jackcess.expr.Function;
23  import com.healthmarketscience.jackcess.expr.LocaleContext;
24  import com.healthmarketscience.jackcess.expr.Value;
25  import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
26  import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*;
27  
28  /**
29   *
30   * @author James Ahlborn
31   */
32  public class DefaultTextFunctions
33  {
34    // mask to separate the case conversion value (first two bits) from the char
35    // conversion value for the StrConv() function
36    private static final int STR_CONV_MASK = 0x03;
37  
38    private DefaultTextFunctions() {}
39  
40    static void init() {
41      // dummy method to ensure this class is loaded
42    }
43  
44    public static final Function ASC = registerFunc(new Func1("Asc") {
45      @Override
46      protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
47        String str = param1.getAsString(ctx);
48        int len = str.length();
49        if(len == 0) {
50          throw new EvalException("No characters in string");
51        }
52        int lv = str.charAt(0);
53        if((lv < 0) || (lv > 255)) {
54          throw new EvalException("Character code '" + lv +
55                                          "' out of range ");
56        }
57        return ValueSupport.toValue(lv);
58      }
59    });
60  
61    public static final Function ASCW = registerFunc(new Func1("AscW") {
62      @Override
63      protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
64        String str = param1.getAsString(ctx);
65        int len = str.length();
66        if(len == 0) {
67          throw new EvalException("No characters in string");
68        }
69        int lv = str.charAt(0);
70        return ValueSupport.toValue(lv);
71      }
72    });
73  
74    public static final Function CHR = registerStringFunc(new Func1NullIsNull("Chr") {
75      @Override
76      protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
77        int lv = param1.getAsLongInt(ctx);
78        if((lv < 0) || (lv > 255)) {
79          throw new EvalException("Character code '" + lv +
80                                          "' out of range ");
81        }
82        char[] cs = Character.toChars(lv);
83        return ValueSupport.toValue(new String(cs));
84      }
85    });
86  
87    public static final Function CHRW = registerStringFunc(new Func1NullIsNull("ChrW") {
88      @Override
89      protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
90        int lv = param1.getAsLongInt(ctx);
91        char[] cs = Character.toChars(lv);
92        return ValueSupport.toValue(new String(cs));
93      }
94    });
95  
96    public static final Function STR = registerStringFunc(new Func1NullIsNull("Str") {
97      @Override
98      protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
99        BigDecimal bd = param1.getAsBigDecimal(ctx);
100       String str = bd.toPlainString();
101       if(bd.compareTo(BigDecimal.ZERO) >= 0) {
102         str = " " + str;
103       }
104       return ValueSupport.toValue(str);
105     }
106   });
107 
108   public static final Function INSTR = registerFunc(new FuncVar("InStr", 2, 4) {
109     @Override
110     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
111       int idx = 0;
112       int start = 0;
113       if(params.length > 2) {
114         // 1 based offsets
115         start = params[0].getAsLongInt(ctx) - 1;
116         ++idx;
117       }
118       Value param1 = params[idx++];
119       if(param1.isNull()) {
120         return param1;
121       }
122       String s1 = param1.getAsString(ctx);
123       int s1Len = s1.length();
124       if(s1Len == 0) {
125         return ValueSupport.ZERO_VAL;
126       }
127       Value param2 = params[idx++];
128       if(param2.isNull()) {
129         return param2;
130       }
131       String s2 = param2.getAsString(ctx);
132       int s2Len = s2.length();
133       if(s2Len == 0) {
134         // 1 based offsets
135         return ValueSupport.toValue(start + 1);
136       }
137       boolean ignoreCase = getIgnoreCase(ctx, params, 3);
138       int end = s1Len - s2Len;
139       while(start < end) {
140         if(s1.regionMatches(ignoreCase, start, s2, 0, s2Len)) {
141           // 1 based offsets
142           return ValueSupport.toValue(start + 1);
143         }
144         ++start;
145       }
146       return ValueSupport.ZERO_VAL;
147     }
148   });
149 
150   public static final Function INSTRREV = registerFunc(new FuncVar("InStrRev", 2, 4) {
151     @Override
152     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
153       Value param1 = params[0];
154       if(param1.isNull()) {
155         return param1;
156       }
157       String s1 = param1.getAsString(ctx);
158       int s1Len = s1.length();
159       if(s1Len == 0) {
160         return ValueSupport.ZERO_VAL;
161       }
162       Value param2 = params[1];
163       if(param2.isNull()) {
164         return param2;
165       }
166       String s2 = param2.getAsString(ctx);
167       int s2Len = s2.length();
168       int start = s1Len - 1;
169       if(s2Len == 0) {
170         // 1 based offsets
171         return ValueSupport.toValue(start + 1);
172       }
173       if(params.length > 2) {
174         start = params[2].getAsLongInt(ctx);
175         if(start == -1) {
176           start = s1Len;
177         }
178         // 1 based offsets
179         --start;
180       }
181       boolean ignoreCase = getIgnoreCase(ctx, params, 3);
182       start = Math.min(s1Len - s2Len, start - s2Len + 1);
183       while(start >= 0) {
184         if(s1.regionMatches(ignoreCase, start, s2, 0, s2Len)) {
185           // 1 based offsets
186           return ValueSupport.toValue(start + 1);
187         }
188         --start;
189       }
190       return ValueSupport.ZERO_VAL;
191     }
192   });
193 
194   public static final Function LCASE = registerStringFunc(new Func1NullIsNull("LCase") {
195     @Override
196     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
197       String str = param1.getAsString(ctx);
198       return ValueSupport.toValue(str.toLowerCase());
199     }
200   });
201 
202   public static final Function UCASE = registerStringFunc(new Func1NullIsNull("UCase") {
203     @Override
204     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
205       String str = param1.getAsString(ctx);
206       return ValueSupport.toValue(str.toUpperCase());
207     }
208   });
209 
210   public static final Function LEFT = registerStringFunc(new Func2("Left") {
211     @Override
212     protected Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval2(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
213       if(param1.isNull()) {
214         return param1;
215       }
216       String str = param1.getAsString(ctx);
217       int len = Math.min(str.length(), param2.getAsLongInt(ctx));
218       return ValueSupport.toValue(str.substring(0, len));
219     }
220   });
221 
222   public static final Function RIGHT = registerStringFunc(new Func2("Right") {
223     @Override
224     protected Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval2(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
225       if(param1.isNull()) {
226         return param1;
227       }
228       String str = param1.getAsString(ctx);
229       int strLen = str.length();
230       int len = Math.min(strLen, param2.getAsLongInt(ctx));
231       return ValueSupport.toValue(str.substring(strLen - len, strLen));
232     }
233   });
234 
235   public static final Function MID = registerStringFunc(new FuncVar("Mid", 2, 3) {
236     @Override
237     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
238       Value param1 = params[0];
239       if(param1.isNull()) {
240         return param1;
241       }
242       String str = param1.getAsString(ctx);
243       int strLen = str.length();
244       // 1 based offsets
245       int start = Math.min(strLen, params[1].getAsLongInt(ctx) - 1);
246       int len = Math.min(
247           ((params.length > 2) ? params[2].getAsLongInt(ctx) : strLen),
248           (strLen - start));
249       return ValueSupport.toValue(str.substring(start, start + len));
250     }
251   });
252 
253   public static final Function LEN = registerFunc(new Func1NullIsNull("Len") {
254     @Override
255     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
256       String str = param1.getAsString(ctx);
257       return ValueSupport.toValue(str.length());
258     }
259   });
260 
261   public static final Function LTRIM = registerStringFunc(new Func1NullIsNull("LTrim") {
262     @Override
263     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
264       String str = param1.getAsString(ctx);
265       return ValueSupport.toValue(trim(str, true, false));
266     }
267   });
268 
269   public static final Function RTRIM = registerStringFunc(new Func1NullIsNull("RTrim") {
270     @Override
271     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
272       String str = param1.getAsString(ctx);
273       return ValueSupport.toValue(trim(str, false, true));
274     }
275   });
276 
277   public static final Function TRIM = registerStringFunc(new Func1NullIsNull("Trim") {
278     @Override
279     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
280       String str = param1.getAsString(ctx);
281       return ValueSupport.toValue(trim(str, true, true));
282     }
283   });
284 
285   public static final Function REPLACE = registerStringFunc(new FuncVar("Replace", 3, 6) {
286     @Override
287     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
288       String str = params[0].getAsString(ctx);
289       String searchStr = params[1].getAsString(ctx);
290       String replStr = params[2].getAsString(ctx);
291 
292       int strLen = str.length();
293 
294       int start = getOptionalIntParam(ctx, params, 3, 1) - 1;
295       int count = getOptionalIntParam(ctx, params, 4, -1);
296       boolean ignoreCase = getIgnoreCase(ctx, params, 5);
297 
298       if(start >= strLen) {
299         return ValueSupport.EMPTY_STR_VAL;
300       }
301 
302       int searchLen = searchStr.length();
303       if((searchLen == 0) || (count == 0)) {
304         String result = str;
305         if(start > 0) {
306           result = str.substring(start);
307         }
308         return ValueSupport.toValue(result);
309       }
310 
311       if(count < 0) {
312         count = strLen;
313       }
314 
315       StringBuilder result = new StringBuilder(strLen);
316 
317       int matchCount = 0;
318       for(int i = start; i < strLen; ++i) {
319         if((matchCount < count) &&
320            str.regionMatches(ignoreCase, i, searchStr, 0, searchLen)) {
321           result.append(replStr);
322           ++matchCount;
323           i += searchLen - 1;
324         } else {
325           result.append(str.charAt(i));
326         }
327       }
328 
329       return ValueSupport.toValue(result.toString());
330     }
331   });
332 
333   public static final Function SPACE = registerStringFunc(new Func1("Space") {
334     @Override
335     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
336       int lv = param1.getAsLongInt(ctx);
337       return ValueSupport.toValue(nchars(lv, ' '));
338     }
339   });
340 
341   public static final Function STRCOMP = registerFunc(new FuncVar("StrComp", 2, 3) {
342     @Override
343     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
344       Value param1 = params[0];
345       Value param2 = params[1];
346       if(param1.isNull() || param2.isNull()) {
347         return ValueSupport.NULL_VAL;
348       }
349       String s1 = param1.getAsString(ctx);
350       String s2 = param2.getAsString(ctx);
351       boolean ignoreCase = getIgnoreCase(ctx, params, 2);
352       int cmp = (ignoreCase ?
353                  s1.compareToIgnoreCase(s2) : s1.compareTo(s2));
354       // stupid java doesn't return 1, -1, 0...
355       return ((cmp < 0) ? ValueSupport.NEG_ONE_VAL :
356               ((cmp > 0) ? ValueSupport.ONE_VAL :
357                ValueSupport.ZERO_VAL));
358     }
359   });
360 
361   @SuppressWarnings("deprecation")
362   public static final Function STRCONV = registerStringFunc(new FuncVar("StrConv", 2, 3) {
363     @Override
364     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
365       Value param1 = params[0];
366       if(param1.isNull()) {
367         return ValueSupport.NULL_VAL;
368       }
369 
370       String str = param1.getAsString(ctx);
371       int conversion = params[1].getAsLongInt(ctx);
372       // TODO, for now, ignore locale id...?
373       // int localeId = params[2];
374 
375       int caseConv = STR_CONV_MASK & conversion;
376       int charConv = (~STR_CONV_MASK) & conversion;
377 
378       switch(caseConv) {
379       case 1:
380         // vbUpperCase
381         str = str.toUpperCase();
382         break;
383       case 2:
384         // vbLowerCase
385         str = str.toLowerCase();
386         break;
387       case 3:
388         // vbProperCase
389         str = org.apache.commons.lang3.text.WordUtils.capitalize(
390             str.toLowerCase());
391         break;
392       default:
393         // do nothing
394       }
395 
396       if(charConv != 0) {
397           // 64 = vbUnicode, all java strings are already unicode,so nothing to do
398         if(charConv != 64) {
399           throw new EvalException("Unsupported character conversion " + charConv);
400         }
401       }
402 
403       return ValueSupport.toValue(str);
404     }
405   });
406 
407   public static final Function STRING = registerStringFunc(new Func2("String") {
408     @Override
409     protected Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval2(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
410       if(param1.isNull() || param2.isNull()) {
411         return ValueSupport.NULL_VAL;
412       }
413       int lv = param1.getAsLongInt(ctx);
414       char c = (char)(param2.getAsString(ctx).charAt(0) % 256);
415       return ValueSupport.toValue(nchars(lv, c));
416     }
417   });
418 
419   public static final Function STRREVERSE = registerFunc(new Func1("StrReverse") {
420     @Override
421     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
422       String str = param1.getAsString(ctx);
423       return ValueSupport.toValue(
424           new StringBuilder(str).reverse().toString());
425     }
426   });
427 
428   public static final Function FORMAT = registerStringFunc(new FuncVar("Format", 1, 4) {
429     @Override
430     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
431 
432       Value expr = params[0];
433       if(params.length < 2) {
434         // no formatting, do simple string conversion
435         if(expr.isNull()) {
436           return ValueSupport.NULL_VAL;
437         }
438         return ValueSupport.toValue(expr.getAsString(ctx));
439       }
440 
441       String fmtStr = params[1].getAsString(ctx);
442       int firstDay = DefaultDateFunctions.getFirstDayParam(ctx, params, 2);
443       int firstWeekType = DefaultDateFunctions.getFirstWeekTypeParam(ctx, params, 3);
444 
445       return FormatUtil.format(ctx, expr, fmtStr, firstDay, firstWeekType);
446     }
447   });
448 
449   private static String nchars(int num, char c) {
450     StringBuilder sb = new StringBuilder(num);
451     nchars(sb, num, c);
452     return sb.toString();
453   }
454 
455   static void nchars(StringBuilder sb, int num, char c) {
456     for(int i = 0; i < num; ++i) {
457       sb.append(c);
458     }
459   }
460 
461   private static String trim(String str, boolean doLeft, boolean doRight) {
462     int start = 0;
463     int end = str.length();
464 
465     if(doLeft) {
466       while((start < end) && (str.charAt(start) == ' ')) {
467         ++start;
468       }
469     }
470     if(doRight) {
471       while((start < end) && (str.charAt(end - 1) == ' ')) {
472         --end;
473       }
474     }
475     return str.substring(start, end);
476   }
477 
478   private static boolean getIgnoreCase(EvalContext ctx, Value[] params, int idx) {
479     boolean ignoreCase = true;
480     if(params.length > idx) {
481       ignoreCase = doIgnoreCase(ctx, params[idx]);
482     }
483     return ignoreCase;
484   }
485 
486   private static boolean doIgnoreCase(LocaleContext ctx, Value paramCmp) {
487     int cmpType = paramCmp.getAsLongInt(ctx);
488     switch(cmpType) {
489     case -1:
490       // vbUseCompareOption -> default is binary
491     case 0:
492       // vbBinaryCompare
493       return false;
494     case 1:
495       // vbTextCompare
496       return true;
497     default:
498       // vbDatabaseCompare -> unsupported
499       throw new EvalException("Unsupported compare type " + cmpType);
500     }
501   }
502 
503 
504 }