2 * Copyright (C) 2014 Red Hat
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
9 package org.opendaylight.aaa.idpmapping;
13 import java.util.List;
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
18 enum TokenStorageType {
19 UNKNOWN, CONSTANT, VARIABLE
24 STRING, // java String
28 BOOLEAN, // java Boolean
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.
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.
45 * @author John Dennis <jdennis@redhat.com>
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.
54 * Examples: $foo ${foo} $foo[bar] ${foo[bar] where foo is the variable name and bar is the array
57 * Identifer is any alphabetic followed by alphanumeric or underscore
59 private static final String VARIABLE_PAT = "(?<!\\\\)\\$" + // non-escaped $
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);
69 * Requires only a variable to be present in the string but permits leading and trailing
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);
75 private Object value = null;
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;
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) {
89 this.type = classify(input);
92 this.storageType = TokenStorageType.CONSTANT;
94 this.type = classify(input);
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);
106 return String.format("$%s[%s]", this.name, this.index);
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);
119 this.storageType = TokenStorageType.VARIABLE;
123 this.storageType = TokenStorageType.CONSTANT;
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;
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));
156 Object get(Object index) {
159 if (this.storageType == TokenStorageType.CONSTANT) {
163 if (this.namespace.containsKey(this.name)) {
164 base = this.namespace.get(this.name);
166 throw new UndefinedValueException(String.format("variable '%s' not defined", this.name));
173 if (index == null) { // scalar types
176 if (base instanceof List) {
177 @SuppressWarnings("unchecked")
178 List<Object> list = (List<Object>) base;
181 if (index instanceof Long) {
182 idx = new Integer(((Long) index).intValue());
183 } else if (index instanceof String) {
185 idx = new Integer((String) index);
186 } catch (NumberFormatException e) {
187 throw new InvalidTypeException(
190 "variable '%s' is an array indexed by '%s', however the index cannot be converted to an integer",
194 throw new InvalidTypeException(
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()));
202 value = list.get(idx);
203 } catch (IndexOutOfBoundsException e) {
204 throw new UndefinedValueException(
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));
210 } else if (base instanceof Map) {
211 @SuppressWarnings("unchecked")
212 Map<String, Object> map = (Map<String, Object>) base;
214 if (index instanceof String) {
215 idx = (String) index;
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()));
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",
226 value = map.get(idx);
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()));
234 this.type = classify(value);
238 void set(Object value) {
242 void set(Object value, Object index) {
244 if (this.storageType == TokenStorageType.CONSTANT) {
245 throw new InvalidTypeException("cannot assign to a constant");
252 if (index == null) { // scalar types
253 this.namespace.put(this.name, value);
257 if (this.namespace.containsKey(this.name)) {
258 base = this.namespace.get(this.name);
260 throw new UndefinedValueException(String.format("variable '%s' not defined", this.name));
263 if (base instanceof List) {
264 @SuppressWarnings("unchecked")
265 List<Object> list = (List<Object>) base;
268 if (index instanceof Long) {
269 idx = new Integer(((Long) index).intValue());
270 } else if (index instanceof String) {
272 idx = new Integer((String) index);
273 } catch (NumberFormatException e) {
274 throw new InvalidTypeException(
277 "variable '%s' is an array indexed by '%s', however the index cannot be converted to an integer",
281 throw new InvalidTypeException(
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()));
289 value = list.set(idx, value);
290 } catch (IndexOutOfBoundsException e) {
291 throw new UndefinedValueException(
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));
297 } else if (base instanceof Map) {
298 @SuppressWarnings("unchecked")
299 Map<String, Object> map = (Map<String, Object>) base;
301 if (index instanceof String) {
302 idx = (String) index;
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()));
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",
313 value = map.put(idx, value);
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()));
323 public Object load() {
328 public Object load(Object index) {
329 this.value = get(index);
333 public String getStringValue() {
334 if (this.type == TokenType.STRING) {
335 return (String) this.value;
337 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
338 TokenType.STRING, this.type));
342 public List<Object> getListValue() {
343 if (this.type == TokenType.ARRAY) {
344 @SuppressWarnings("unchecked")
345 List<Object> list = (List<Object>) this.value;
348 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
349 TokenType.ARRAY, this.type));
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;
359 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
360 TokenType.MAP, this.type));
364 public Long getLongValue() {
365 if (this.type == TokenType.INTEGER) {
366 return (Long) this.value;
368 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
369 TokenType.INTEGER, this.type));
373 public Boolean getBooleanValue() {
374 if (this.type == TokenType.BOOLEAN) {
375 return (Boolean) this.value;
377 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
378 TokenType.BOOLEAN, this.type));
382 public Double getDoubleValue() {
383 if (this.type == TokenType.REAL) {
384 return (Double) this.value;
386 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
387 TokenType.REAL, this.type));
391 public Object getNullValue() {
392 if (this.type == TokenType.NULL) {
395 throw new InvalidTypeException(String.format("expected %s value but token type is %s",
396 TokenType.NULL, this.type));
400 public Object getObjectValue() {