blob: 4f374baaa402dae59b613e489bb560b068d666f3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.metatype.impl;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import org.eclipse.osgi.util.NLS;
import org.osgi.service.log.LogService;
import org.osgi.service.metatype.AttributeDefinition;
public class ValueTokenizer {
private static final char DELIMITER = ',';
private static final char ESCAPE = '\\';
private final LogService logger;
private final List<String> values = new ArrayList<String>();
/*
* Constructor of class ValueTokenizer
*/
public ValueTokenizer(String values_str, LogService logger) {
this.logger = logger;
if (values_str == null)
return;
// The trick is to strip out unescaped whitespace characters before and
// after the input string as well as before and after each
// individual token within the input string without losing any escaped
// whitespace characters. Whitespace between two non-whitespace
// characters may or may not be escaped. Also, any character may be
// escaped. The escape character is '\'. The delimiter is ','.
StringBuffer buffer = new StringBuffer();
// Loop over the characters within the input string and extract each
// value token.
for (int i = 0; i < values_str.length(); i++) {
char c1 = values_str.charAt(i);
switch (c1) {
case DELIMITER :
// When the delimiter is encountered, add the extracted
// token to the result and prepare the buffer to receive the
// next token.
values.add(buffer.toString());
buffer.delete(0, buffer.length());
break;
case ESCAPE :
// When the escape is encountered, add the immediately
// following character to the token, unless the end of the
// input has been reached. Note this will result in loop
// counter 'i' being incremented twice, once here and once
// at the end of the loop.
if (i + 1 < values_str.length()) {
buffer.append(values_str.charAt(++i));
}
// If the ESCAPE character occurs as the last character
// of the string, ignore it.
break;
default :
// For all other characters, add them to the current token
// unless dealing with unescaped whitespace at the beginning
// or end. We know the whitespace is unescaped because it
// would have been handled in the ESCAPE case otherwise.
if (Character.isWhitespace(c1)) {
// Ignore unescaped whitespace at the beginning of the
// token.
if (buffer.length() == 0) {
continue;
}
// If the whitespace is not at the beginning, look
// forward, starting with the next character, to see if
// it's in the middle or at the end. Unescaped
// whitespace in the middle is okay.
for (int j = i + 1; j < values_str.length(); j++) {
// Keep looping until the end of the string is
// reached or a non-whitespace character other than
// the escape is seen.
char c2 = values_str.charAt(j);
if (!Character.isWhitespace(c2)) {
// If the current character is not the DELIMITER, all whitespace
// characters are significant and should be added to the token.
// Otherwise, they're at the end and should be ignored. But watch
// out for an escape character at the end of the input. Ignore it
// and any previous insignificant whitespace if it exists.
if (c2 == ESCAPE && j + 1 >= values_str.length()) {
continue;
}
if (c2 != DELIMITER) {
buffer.append(values_str.substring(i, j));
}
// Let loop counter i catch up with the inner loop but keep in
// mind it will still be incremented at the end of the outer loop.
i = j - 1;
break;
}
}
} else {
// For non-whitespace characters.
buffer.append(c1);
}
}
}
// Don't forget to add the last token.
values.add(buffer.toString());
}
/*
* Method to return values as Vector.
*/
public Collection<String> getValues() {
return Collections.unmodifiableList(values);
}
/*
* Method to return values as String[] or null.
*/
public String[] getValuesAsArray() {
if (values.isEmpty()) {
return null;
}
return values.toArray(new String[values.size()]);
}
public String getValuesAsString() {
if (values.isEmpty()) {
return null;
}
if (values.size() == 1) {
return values.get(0);
}
StringBuffer buffer = new StringBuffer(values.get(0));
for (int i = 1; i < values.size(); i++) {
buffer.append(',');
buffer.append(values.get(i));
}
return buffer.toString();
}
private boolean validateString(AttributeDefinitionImpl ad, String value) {
// Try validating the string length.
try {
// All or nothing. If either min or max is not null and cannot be
// parsed as an integer, do the string comparison instead.
Integer min = ad._minValue == null ? null : Integer.valueOf((String) ad._minValue);
Integer max = ad._maxValue == null ? null : Integer.valueOf((String) ad._maxValue);
if (min != null && value.length() < min)
return true;
if (max != null && value.length() > max)
return true;
} catch (NumberFormatException e) {
// Either min or max was not an integer. Do a string comparison.
if ((ad._minValue != null && value.compareTo((String) ad._minValue) < 0) || (ad._maxValue != null && value.compareTo((String) ad._maxValue) > 0))
return true;
}
return false;
}
public String validate(AttributeDefinitionImpl ad) {
// An empty list means the original value was null. Null is never valid.
if (values.isEmpty()) {
return MetaTypeMsg.NULL_IS_INVALID;
}
String s = null;
try {
// A value must match the cardinality.
int cardinality = Math.abs(ad.getCardinality());
// If the cardinality is zero, the value must contain one and only one token.
if (cardinality == 0) {
if (values.size() != 1) {
return NLS.bind(MetaTypeMsg.CARDINALITY_VIOLATION, new Object[] {getValuesAsString(), values.size(), 1, 1});
}
}
// Otherwise, the number of tokens must be between 0 and cardinality, inclusive.
else if (values.size() > cardinality) {
return NLS.bind(MetaTypeMsg.CARDINALITY_VIOLATION, new Object[] {getValuesAsString(), values.size(), 0, cardinality});
}
// Now inspect each token.
for (Iterator<String> i = values.iterator(); i.hasNext();) {
s = i.next();
// If options were declared and the value does not match one of them, the value is not valid.
if (!ad._values.isEmpty() && !ad._values.contains(s)) {
return NLS.bind(MetaTypeMsg.VALUE_OUT_OF_OPTION, s);
}
// Check the type. Also check the range if min or max were declared.
boolean rangeError = false;
switch (ad._dataType) {
case AttributeDefinition.PASSWORD :
case AttributeDefinition.STRING :
rangeError = validateString(ad, s);
break;
case AttributeDefinition.INTEGER :
Integer intVal = Integer.valueOf(s);
if (ad._minValue != null && intVal.compareTo((Integer) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && intVal.compareTo((Integer) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.LONG :
Long longVal = Long.valueOf(s);
if (ad._minValue != null && longVal.compareTo((Long) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && longVal.compareTo((Long) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.DOUBLE :
Double doubleVal = Double.valueOf(s);
if (ad._minValue != null && doubleVal.compareTo((Double) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && doubleVal.compareTo((Double) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.BOOLEAN :
// Any string can be converted into a boolean via Boolean.valueOf(String).
// Seems unnecessary to impose any further restrictions.
break;
case AttributeDefinition.CHARACTER :
Character charVal = Character.valueOf(s.charAt(0));
if (ad._minValue != null && charVal.compareTo((Character) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && charVal.compareTo((Character) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.FLOAT :
Float floatVal = Float.valueOf(s);
if (ad._minValue != null && floatVal.compareTo((Float) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && floatVal.compareTo((Float) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.SHORT :
Short shortVal = Short.valueOf(s);
if (ad._minValue != null && shortVal.compareTo((Short) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && shortVal.compareTo((Short) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.BYTE :
Byte byteVal = Byte.valueOf(s);
if (ad._minValue != null && byteVal.compareTo((Byte) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && byteVal.compareTo((Byte) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.BIGDECIMAL :
BigDecimal bigDecVal = new BigDecimal(s);
if (ad._minValue != null && bigDecVal.compareTo((BigDecimal) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && bigDecVal.compareTo((BigDecimal) ad._maxValue) > 0) {
rangeError = true;
}
break;
case AttributeDefinition.BIGINTEGER :
BigInteger bigIntVal = new BigInteger(s);
if (ad._minValue != null && bigIntVal.compareTo((BigInteger) ad._minValue) < 0) {
rangeError = true;
} else if (ad._maxValue != null && bigIntVal.compareTo((BigInteger) ad._maxValue) > 0) {
rangeError = true;
}
break;
default :
throw new IllegalStateException();
}
if (rangeError) {
return (NLS.bind(MetaTypeMsg.VALUE_OUT_OF_RANGE, s));
}
}
// No problems detected
return ""; //$NON-NLS-1$
} catch (NumberFormatException e) {
return NLS.bind(MetaTypeMsg.VALUE_NOT_A_NUMBER, s);
} catch (Exception e) {
String message = NLS.bind(MetaTypeMsg.EXCEPTION_MESSAGE, e.getClass().getName(), e.getMessage());
logger.log(LogService.LOG_DEBUG, message, e);
return message;
}
}
}