Skip to content

Package: ValueTokenizer

ValueTokenizer

nameinstructionbranchcomplexitylinemethod
ValueTokenizer(String)
M: 120 C: 0
0%
M: 23 C: 0
0%
M: 13 C: 0
0%
M: 30 C: 0
0%
M: 1 C: 0
0%
getValues()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getValuesAsArray()
M: 13 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
getValuesAsString()
M: 48 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
lambda$validate$0(String, KapuaToption)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
validate(KapuaTad)
M: 497 C: 0
0%
M: 122 C: 0
0%
M: 66 C: 0
0%
M: 81 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2005, 2022 IBM Corporation and others.
3: *
4: * This program and the accompanying materials are made
5: * available under the terms of the Eclipse Public License 2.0
6: * which is available at https://www.eclipse.org/legal/epl-2.0/
7: *
8: * SPDX-License-Identifier: EPL-2.0
9: *
10: * Contributors:
11: * IBM Corporation - initial API and implementation
12: * Eurotech
13: *******************************************************************************/
14: package org.eclipse.kapua.commons.configuration;
15:
16: import org.eclipse.kapua.commons.configuration.metatype.TscalarImpl;
17: import org.eclipse.kapua.model.config.metatype.KapuaTad;
18: import org.slf4j.Logger;
19: import org.slf4j.LoggerFactory;
20:
21: import java.text.MessageFormat;
22: import java.util.ArrayList;
23: import java.util.Collection;
24: import java.util.Collections;
25: import java.util.List;
26:
27: /**
28: * The implementation of this class is inspired from the org.eclipse.equinox.metatype.impl.ValueTokenizer class
29: * in the Eclipse Equinox project.
30: *
31: * @since 1.0
32: */
33: public class ValueTokenizer {
34:
35: private static final Logger LOG = LoggerFactory.getLogger(ValueTokenizer.class);
36:
37: private static final String VALUE_INVALID = "Value is invalid";
38: private static final String NULL_VALUE_INVALID = "Null cannot be validated";
39: private static final String CARDINALITY_VIOLATION = "Cardinality violation: \"{0}\" has {1} value(s) but must have between {2} and {3} value(s).";
40: private static final String VALUE_OUT_OF_OPTION = "Value {0} is out of Option";
41: private static final String VALUE_OUT_OF_RANGE = "Value {0} is out of range";
42: private static final String INTERNAL_ERROR = "Internal error: {0}";
43:
44: private static final char DELIMITER = ',';
45: private static final char ESCAPE = '\\';
46:
47: private final List<String> values = new ArrayList<>();
48:
49: /**
50: * Constructs a tokenizer for a string
51: *
52: * @param valuesStr
53: */
54: public ValueTokenizer(String valuesStr) {
55:
56:• if (valuesStr == null) {
57: return;
58: }
59: // The trick is to strip out unescaped whitespace characters before and
60: // after the input string as well as before and after each
61: // individual token within the input string without losing any escaped
62: // whitespace characters. Whitespace between two non-whitespace
63: // characters may or may not be escaped. Also, any character may be
64: // escaped. The escape character is '\'. The delimiter is ','.
65: StringBuffer buffer = new StringBuffer();
66: // Loop over the characters within the input string and extract each
67: // value token.
68:• for (int i = 0; i < valuesStr.length(); i++) {
69: char c1 = valuesStr.charAt(i);
70:• switch (c1) {
71: case DELIMITER:
72: // When the delimiter is encountered, add the extracted
73: // token to the result and prepare the buffer to receive the
74: // next token.
75: values.add(buffer.toString());
76: buffer.delete(0, buffer.length());
77: break;
78: case ESCAPE:
79: // When the escape is encountered, add the immediately
80: // following character to the token, unless the end of the
81: // input has been reached. Note this will result in loop
82: // counter 'i' being incremented twice, once here and once
83: // at the end of the loop.
84:• if (i + 1 < valuesStr.length()) {
85: buffer.append(valuesStr.charAt(++i));
86: } else {
87: // If the ESCAPE character occurs as the last character
88: // of the string, log the error and ignore it.
89: LOG.error(VALUE_INVALID);
90: }
91: break;
92: default:
93: // For all other characters, add them to the current token
94: // unless dealing with unescaped whitespace at the beginning
95: // or end. We know the whitespace is unescaped because it
96: // would have been handled in the ESCAPE case otherwise.
97:• if (Character.isWhitespace(c1)) {
98: // Ignore unescaped whitespace at the beginning of the
99: // token.
100:• if (buffer.length() == 0) {
101: continue;
102: }
103: // If the whitespace is not at the beginning, look
104: // forward, starting with the next character, to see if
105: // it's in the middle or at the end. Unescaped
106: // whitespace in the middle is okay.
107:• for (int j = i + 1; j < valuesStr.length(); j++) {
108: // Keep looping until the end of the string is
109: // reached or a non-whitespace character other than
110: // the escape is seen.
111: char c2 = valuesStr.charAt(j);
112:• if (!Character.isWhitespace(c2)) {
113: // If the current character is not the DELIMITER, all whitespace
114: // characters are significant and should be added to the token.
115: // Otherwise, they're at the end and should be ignored. But watch
116: // out for an escape character at the end of the input. Ignore it
117: // and any previous insignificant whitespace if it exists.
118:• if (c2 == ESCAPE && j + 1 >= valuesStr.length()) {
119: continue;
120: }
121:• if (c2 != DELIMITER) {
122: buffer.append(valuesStr.substring(i, j));
123: }
124: // Let loop counter i catch up with the inner loop but keep in
125: // mind it will still be incremented at the end of the outer loop.
126: i = j - 1;
127: break;
128: }
129: }
130: } else {
131: // For non-whitespace characters.
132: buffer.append(c1);
133: }
134: }
135: }
136: // Don't forget to add the last token.
137: values.add(buffer.toString());
138: }
139:
140: /**
141: * Return values as Vector.
142: *
143: * @return
144: */
145: public Collection<String> getValues() {
146: return Collections.unmodifiableList(values);
147: }
148:
149: /**
150: * Return values as String[] or null.
151: *
152: * @return
153: */
154: public String[] getValuesAsArray() {
155:• if (values.isEmpty()) {
156: return null;
157: }
158:
159: return values.toArray(new String[0]);
160: }
161:
162: /**
163: * Return values as String
164: *
165: * @return
166: */
167: public String getValuesAsString() {
168:• if (values.isEmpty()) {
169: return null;
170: }
171:
172:• if (values.size() == 1) {
173: return values.get(0);
174: }
175:
176: StringBuilder builder = new StringBuilder(values.get(0));
177:• for (int i = 1; i < values.size(); i++) {
178: builder.append(',')
179: .append(values.get(i));
180: }
181: return builder.toString();
182: }
183:
184: /**
185: * Validate Tad
186: *
187: * @param ad
188: * @return
189: */
190: public String validate(KapuaTad ad) {
191: // An empty list means the original value was null. Null is never valid.
192:• if (values.isEmpty()) {
193: return NULL_VALUE_INVALID;
194: }
195: try {
196: // A value must match the cardinality.
197: int cardinality = Math.abs(ad.getCardinality());
198: // If the cardinality is zero, the value must contain one and only one token.
199:• if (cardinality == 0) {
200:• if (values.size() != 1) {
201: return MessageFormat.format(CARDINALITY_VIOLATION, getValuesAsString(), values.size(), 1, 1);
202: }
203: }
204: // Otherwise, the number of tokens must be between 0 and cardinality, inclusive.
205:• else if (values.size() > cardinality) {
206: return MessageFormat.format(CARDINALITY_VIOLATION, getValuesAsString(), values.size(), 0, cardinality);
207: }
208: // Now inspect each token.
209:• for (String s : values) {
210: // If options were declared and the value does not match one of them, the value is not valid.
211:• if (!ad.getOption().isEmpty() && ad.getOption().stream().filter(option -> s.equals(option.getValue())).count() == 0) {
212: return MessageFormat.format(VALUE_OUT_OF_OPTION, s);
213: }
214: // Check the type. Also check the range if min or max were declared.
215: boolean rangeError = false;
216: Object minVal = null;
217: Object maxVal = null;
218: TscalarImpl adScalarType = TscalarImpl.fromValue(ad.getType().value());
219:• switch (adScalarType) {
220: case PASSWORD:
221: case STRING:
222:• minVal = ad.getMin() == null ? null : Integer.valueOf(ad.getMin());
223:• maxVal = ad.getMax() == null ? null : Integer.valueOf(ad.getMax());
224:• if (minVal != null && s.length() < (Integer) maxVal) {
225: rangeError = true;
226:• } else if (maxVal != null && s.length() > (Integer) maxVal) {
227: rangeError = true;
228: }
229: break;
230: case INTEGER:
231:• minVal = ad.getMin() == null ? null : Integer.valueOf(ad.getMin());
232:• maxVal = ad.getMax() == null ? null : Integer.valueOf(ad.getMax());
233: Integer intVal = Integer.valueOf(s);
234:• if (minVal != null && intVal.compareTo((Integer) minVal) < 0) {
235: rangeError = true;
236:• } else if (maxVal != null && intVal.compareTo((Integer) maxVal) > 0) {
237: rangeError = true;
238: }
239: break;
240: case LONG:
241:• minVal = ad.getMin() == null ? null : Long.valueOf(ad.getMin());
242:• maxVal = ad.getMax() == null ? null : Long.valueOf(ad.getMax());
243: Long longVal = Long.valueOf(s);
244:• if (ad.getMin() != null && longVal.compareTo((Long) minVal) < 0) {
245: rangeError = true;
246:• } else if (maxVal != null && longVal.compareTo((Long) maxVal) > 0) {
247: rangeError = true;
248: }
249: break;
250: case DOUBLE:
251:• minVal = ad.getMin() == null ? null : Double.valueOf(ad.getMin());
252:• maxVal = ad.getMax() == null ? null : Double.valueOf(ad.getMax());
253: Double doubleVal = Double.valueOf(s);
254:• if (minVal != null && doubleVal.compareTo((Double) minVal) < 0) {
255: rangeError = true;
256:• } else if (maxVal != null && doubleVal.compareTo((Double) maxVal) > 0) {
257: rangeError = true;
258: }
259: break;
260: case BOOLEAN:
261: // Any string can be converted into a boolean via Boolean.valueOf(String).
262: // Seems unnecessary to impose any further restrictions.
263: break;
264: case CHAR:
265:• minVal = ad.getMin() == null ? null : ad.getMin().charAt(0);
266:• maxVal = ad.getMax() == null ? null : ad.getMax().charAt(0);
267: Character charVal = s.charAt(0);
268:• if (minVal != null && charVal.compareTo((Character) minVal) < 0) {
269: rangeError = true;
270:• } else if (maxVal != null && charVal.compareTo((Character) maxVal) > 0) {
271: rangeError = true;
272: }
273: break;
274: case FLOAT:
275:• minVal = ad.getMin() == null ? null : Float.valueOf(ad.getMin());
276:• maxVal = ad.getMax() == null ? null : Float.valueOf(ad.getMax());
277: Float floatVal = Float.valueOf(s);
278:• if (minVal != null && floatVal.compareTo((Float) minVal) < 0) {
279: rangeError = true;
280:• } else if (maxVal != null && floatVal.compareTo((Float) maxVal) > 0) {
281: rangeError = true;
282: }
283: break;
284: case SHORT:
285:• minVal = ad.getMin() == null ? null : Short.valueOf(ad.getMin());
286:• maxVal = ad.getMax() == null ? null : Short.valueOf(ad.getMax());
287: Short shortVal = Short.valueOf(s);
288:• if (minVal != null && shortVal.compareTo((Short) minVal) < 0) {
289: rangeError = true;
290:• } else if (maxVal != null && shortVal.compareTo((Short) maxVal) > 0) {
291: rangeError = true;
292: }
293: break;
294: case BYTE:
295:• minVal = ad.getMin() == null ? null : Byte.valueOf(ad.getMin());
296:• maxVal = ad.getMax() == null ? null : Byte.valueOf(ad.getMax());
297: Byte byteVal = Byte.valueOf(s);
298:• if (minVal != null && byteVal.compareTo((Byte) minVal) < 0) {
299: rangeError = true;
300:• } else if (maxVal != null && byteVal.compareTo((Byte) maxVal) > 0) {
301: rangeError = true;
302: }
303: break;
304: default:
305: throw new IllegalStateException();
306: }
307:• if (rangeError) {
308: return MessageFormat.format(VALUE_OUT_OF_RANGE, s);
309: }
310: }
311: // No problems detected
312: return ""; //$NON-NLS-1$
313: } catch (Throwable t) {
314: String message = MessageFormat.format(INTERNAL_ERROR, t.getMessage());
315: LOG.debug(message, t);
316: return message;
317: }
318: }
319: }