9b835cb9032ede2480cd925452cacf3f40856a59
[aaa.git] / aaa-idp-mapping / src / main / java / org / opendaylight / aaa / idpmapping / Token.java
1 /*
2  * Copyright (C) 2014 Red Hat
3  * All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9 package org.opendaylight.aaa.idpmapping;
10
11
12
13 import java.util.List;
14 import java.util.Map;
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
17
18 enum TokenStorageType {
19   UNKNOWN, CONSTANT, VARIABLE
20 }
21
22
23 enum TokenType {
24   STRING, // java String
25   ARRAY, // java List
26   MAP, // java Map
27   INTEGER, // java Long
28   BOOLEAN, // java Boolean
29   NULL, // java null
30   REAL, // java Double
31   UNKNOWN, // undefined
32 }
33
34
35 /**
36  * Rule statements can contain variables or constants, this class
37  * encapsulates those values, enforces type handling and supports
38  * reading and writing of those values.
39  *
40  * Technically at the syntactic level these are not tokens. A token
41  * would have finer granularity such as identifier, operator, etc. I
42  * just couldn't think of a better name for how they're used here and
43  * thought token was a reasonable compromise as a name.
44  *
45  * @author John Dennis <jdennis@redhat.com>
46  */
47
48 class Token {
49
50   /*
51    * Regexp to identify a variable beginning with $ Supports array notation, e.g. $foo[bar] Optional
52    * delimiting braces may be used to separate variable from surrounding text.
53    * 
54    * Examples: $foo ${foo} $foo[bar] ${foo[bar] where foo is the variable name and bar is the array
55    * index.
56    * 
57    * Identifer is any alphabetic followed by alphanumeric or underscore
58    */
59   private static final String VARIABLE_PAT = "(?<!\\\\)\\$" + // non-escaped $
60                                                               // sign
61       "\\{?" + // optional delimiting brace
62       "([a-zA-Z][a-zA-Z0-9_]*)" + // group 1: variable name
63       "(\\[" + // group 2: optional index
64       "([a-zA-Z0-9_]+)" + // group 3: array index
65       "\\])?" + // end optional index
66       "\\}?"; // optional delimiting brace
67   public static final Pattern VARIABLE_RE = Pattern.compile(VARIABLE_PAT);
68   /*
69    * Requires only a variable to be present in the string but permits leading and trailing
70    * whitespace.
71    */
72   private static final String VARIABLE_ONLY_PAT = "^\\s*" + VARIABLE_PAT + "\\s*$";
73   public static final Pattern VARIABLE_ONLY_RE = Pattern.compile(VARIABLE_ONLY_PAT);
74
75   private Object value = null;
76
77   public Map<String, Object> namespace = null;
78   public TokenStorageType storageType = TokenStorageType.UNKNOWN;
79   public TokenType type = TokenType.UNKNOWN;
80   public String name = null;
81   public String index = null;
82
83   Token(Object input, Map<String, Object> namespace) {
84     this.namespace = namespace;
85     if (input instanceof String) {
86       parseVariable((String) input);
87       if (this.storageType == TokenStorageType.CONSTANT) {
88         this.value = input;
89         this.type = classify(input);
90       }
91     } else {
92       this.storageType = TokenStorageType.CONSTANT;
93       this.value = input;
94       this.type = classify(input);
95     }
96   }
97
98   @Override
99   public String toString() {
100     if (this.storageType == TokenStorageType.CONSTANT) {
101       return String.format("%s", this.value);
102     } else if (this.storageType == TokenStorageType.VARIABLE) {
103       if (this.index == null) {
104         return String.format("$%s", this.name);
105       } else {
106         return String.format("$%s[%s]", this.name, this.index);
107       }
108     } else {
109       return "UNKNOWN";
110     }
111   }
112
113   void parseVariable(String string) {
114     Matcher matcher = VARIABLE_ONLY_RE.matcher(string);
115     if (matcher.find()) {
116       String name = matcher.group(1);
117       String index = matcher.group(3);
118
119       this.storageType = TokenStorageType.VARIABLE;
120       this.name = name;
121       this.index = index;
122     } else {
123       this.storageType = TokenStorageType.CONSTANT;
124     }
125   }
126
127   public static TokenType classify(Object value) {
128     TokenType tokenType = TokenType.UNKNOWN;
129     // ordered by expected occurrence
130     if (value instanceof String) {
131       tokenType = TokenType.STRING;
132     } else if (value instanceof List) {
133       tokenType = TokenType.ARRAY;
134     } else if (value instanceof Map) {
135       tokenType = TokenType.MAP;
136     } else if (value instanceof Long) {
137       tokenType = TokenType.INTEGER;
138     } else if (value instanceof Boolean) {
139       tokenType = TokenType.BOOLEAN;
140     } else if (value == null) {
141       tokenType = TokenType.NULL;
142     } else if (value instanceof Double) {
143       tokenType = TokenType.REAL;
144     } else {
145       throw new InvalidRuleException(String.format(
146           "Type must be String, Long, Double, Boolean, List, Map, or null, not %s", value
147               .getClass().getSimpleName(), value));
148     }
149     return tokenType;
150   }
151
152   Object get() {
153     return get(null);
154   }
155
156   Object get(Object index) {
157     Object base = null;
158
159     if (this.storageType == TokenStorageType.CONSTANT) {
160       return this.value;
161     }
162
163     if (this.namespace.containsKey(this.name)) {
164       base = this.namespace.get(this.name);
165     } else {
166       throw new UndefinedValueException(String.format("variable '%s' not defined", this.name));
167     }
168
169     if (index == null) {
170       index = this.index;
171     }
172
173     if (index == null) { // scalar types
174       value = base;
175     } else {
176       if (base instanceof List) {
177         @SuppressWarnings("unchecked")
178         List<Object> list = (List<Object>) base;
179         Integer idx = null;
180
181         if (index instanceof Long) {
182           idx = new Integer(((Long) index).intValue());
183         } else if (index instanceof String) {
184           try {
185             idx = new Integer((String) index);
186           } catch (NumberFormatException e) {
187             throw new InvalidTypeException(
188                 String
189                     .format(
190                         "variable '%s' is an array indexed by '%s', however the index cannot be converted to an integer",
191                         this.name, index));
192           }
193         } else {
194           throw new InvalidTypeException(
195               String
196                   .format(
197                       "variable '%s' is an array indexed by '%s', however the index must be an integer or string not %s",
198                       this.name, index, index.getClass().getSimpleName()));
199         }
200
201         try {
202           value = list.get(idx);
203         } catch (IndexOutOfBoundsException e) {
204           throw new UndefinedValueException(
205               String
206                   .format(
207                       "variable '%s' is an array of size %d indexed by '%s', however the index is out of bounds",
208                       this.name, list.size(), idx));
209         }
210       } else if (base instanceof Map) {
211         @SuppressWarnings("unchecked")
212         Map<String, Object> map = (Map<String, Object>) base;
213         String idx = null;
214         if (index instanceof String) {
215           idx = (String) index;
216         } else {
217           throw new InvalidTypeException(String.format(
218               "variable '%s' is a map indexed by '%s', however the index must be a string not %s",
219               this.name, index, index.getClass().getSimpleName()));
220         }
221         if (!map.containsKey(idx)) {
222           throw new UndefinedValueException(String.format(
223               "variable '%s' is a map indexed by '%s', however the index does not exist",
224               this.name, index));
225         }
226         value = map.get(idx);
227       } else {
228         throw new InvalidTypeException(String.format(
229             "variable '%s' is indexed by '%s', variable must be an array or map, not %s",
230             this.name, index, base.getClass().getSimpleName()));
231
232       }
233     }
234     this.type = classify(value);
235     return value;
236   }
237
238   void set(Object value) {
239     set(value, null);
240   }
241
242   void set(Object value, Object index) {
243
244     if (this.storageType == TokenStorageType.CONSTANT) {
245       throw new InvalidTypeException("cannot assign to a constant");
246     }
247
248     if (index == null) {
249       index = this.index;
250     }
251
252     if (index == null) { // scalar types
253       this.namespace.put(this.name, value);
254     } else {
255       Object base = null;
256
257       if (this.namespace.containsKey(this.name)) {
258         base = this.namespace.get(this.name);
259       } else {
260         throw new UndefinedValueException(String.format("variable '%s' not defined", this.name));
261       }
262
263       if (base instanceof List) {
264         @SuppressWarnings("unchecked")
265         List<Object> list = (List<Object>) base;
266         Integer idx = null;
267
268         if (index instanceof Long) {
269           idx = new Integer(((Long) index).intValue());
270         } else if (index instanceof String) {
271           try {
272             idx = new Integer((String) index);
273           } catch (NumberFormatException e) {
274             throw new InvalidTypeException(
275                 String
276                     .format(
277                         "variable '%s' is an array indexed by '%s', however the index cannot be converted to an integer",
278                         this.name, index));
279           }
280         } else {
281           throw new InvalidTypeException(
282               String
283                   .format(
284                       "variable '%s' is an array indexed by '%s', however the index must be an integer or string not %s",
285                       this.name, index, index.getClass().getSimpleName()));
286         }
287
288         try {
289           value = list.set(idx, value);
290         } catch (IndexOutOfBoundsException e) {
291           throw new UndefinedValueException(
292               String
293                   .format(
294                       "variable '%s' is an array of size %d indexed by '%s', however the index is out of bounds",
295                       this.name, list.size(), idx));
296         }
297       } else if (base instanceof Map) {
298         @SuppressWarnings("unchecked")
299         Map<String, Object> map = (Map<String, Object>) base;
300         String idx = null;
301         if (index instanceof String) {
302           idx = (String) index;
303         } else {
304           throw new InvalidTypeException(String.format(
305               "variable '%s' is a map indexed by '%s', however the index must be a string not %s",
306               this.name, index, index.getClass().getSimpleName()));
307         }
308         if (!map.containsKey(idx)) {
309           throw new UndefinedValueException(String.format(
310               "variable '%s' is a map indexed by '%s', however the index does not exist",
311               this.name, index));
312         }
313         value = map.put(idx, value);
314       } else {
315         throw new InvalidTypeException(String.format(
316             "variable '%s' is indexed by '%s', variable must be an array or map, not %s",
317             this.name, index, base.getClass().getSimpleName()));
318
319       }
320     }
321   }
322
323   public Object load() {
324     this.value = get();
325     return this.value;
326   }
327
328   public Object load(Object index) {
329     this.value = get(index);
330     return this.value;
331   }
332
333   public String getStringValue() {
334     if (this.type == TokenType.STRING) {
335       return (String) this.value;
336     } else {
337       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
338           TokenType.STRING, this.type));
339     }
340   }
341
342   public List<Object> getListValue() {
343     if (this.type == TokenType.ARRAY) {
344       @SuppressWarnings("unchecked")
345       List<Object> list = (List<Object>) this.value;
346       return list;
347     } else {
348       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
349           TokenType.ARRAY, this.type));
350     }
351   }
352
353   public Map<String, Object> getMapValue() {
354     if (this.type == TokenType.MAP) {
355       @SuppressWarnings("unchecked")
356       Map<String, Object> map = (Map<String, Object>) this.value;
357       return map;
358     } else {
359       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
360           TokenType.MAP, this.type));
361     }
362   }
363
364   public Long getLongValue() {
365     if (this.type == TokenType.INTEGER) {
366       return (Long) this.value;
367     } else {
368       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
369           TokenType.INTEGER, this.type));
370     }
371   }
372
373   public Boolean getBooleanValue() {
374     if (this.type == TokenType.BOOLEAN) {
375       return (Boolean) this.value;
376     } else {
377       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
378           TokenType.BOOLEAN, this.type));
379     }
380   }
381
382   public Double getDoubleValue() {
383     if (this.type == TokenType.REAL) {
384       return (Double) this.value;
385     } else {
386       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
387           TokenType.REAL, this.type));
388     }
389   }
390
391   public Object getNullValue() {
392     if (this.type == TokenType.NULL) {
393       return this.value;
394     } else {
395       throw new InvalidTypeException(String.format("expected %s value but token type is %s",
396           TokenType.NULL, this.type));
397     }
398   }
399
400   public Object getObjectValue() {
401     return this.value;
402   }
403
404
405
406 }