cdc.applic.expressions.parsing.SItemsParsing Maven / Gradle / Ivy
Show all versions of cdc-applic-expressions Show documentation
package cdc.applic.expressions.parsing;
import java.util.ArrayList;
import java.util.List;
import cdc.applic.expressions.SyntacticException;
import cdc.applic.expressions.content.BooleanSet;
import cdc.applic.expressions.content.BooleanValue;
import cdc.applic.expressions.content.IntegerRange;
import cdc.applic.expressions.content.IntegerSet;
import cdc.applic.expressions.content.IntegerValue;
import cdc.applic.expressions.content.Range;
import cdc.applic.expressions.content.RealRange;
import cdc.applic.expressions.content.RealSet;
import cdc.applic.expressions.content.RealValue;
import cdc.applic.expressions.content.SItem;
import cdc.applic.expressions.content.StringSet;
import cdc.applic.expressions.content.StringValue;
import cdc.applic.expressions.content.UncheckedSet;
import cdc.applic.expressions.content.Value;
import cdc.util.lang.Checks;
/**
* Utilities to convert the string representation of a set content to {@link SItem SItems}.
*
* @author Damien Carbonne
*/
public final class SItemsParsing {
private SItemsParsing() {
}
/**
* Creates a {@link SyntacticException} corresponding to an unexpected token.
*
* @param content The set content.
* @param token The {@link Token}.
* @return A new {@link SyntacticException}.
*/
private static SyntacticException unexpectedToken(String content,
Token token) {
return new SyntacticException(SyntacticException.Detail.UNEXPECTED_TOKEN,
"[" + content + "] unexpected token: " + token);
}
/**
* Creates a {@link SyntacticException} corresponding to an unexpected end.
*
* @param content The set content.
* @return A new {@link SyntacticException}.
*/
private static SyntacticException unexpectedEnd(String content) {
return new SyntacticException(SyntacticException.Detail.UNEXPECTED_END,
"[" + content + "] unexpected end");
}
private enum Status {
/** At parsing start */
START,
/** After Range */
RANGE,
/** After (first) value */
VALUE,
/** After values Separator */
VALUES_SEP,
/** After Range symbol */
TO
}
/**
* Creates a {@link Value} from a {@link Token}.
*
* @param token The {@link Token}.
* @param expression The expression that contains the token.
* @return A {@link Value} corresponding to {@code token}.
* @throws SyntacticException When {@code token} can not be converted to a {@link Value}.
*/
public static Value createValue(Token token,
String expression) {
try {
return createValue(token);
} catch (final IllegalArgumentException e) {
throw new SyntacticException(SyntacticException.Detail.UNEXPECTED_TOKEN,
e.getMessage(),
expression);
}
}
/**
* Creates a {@link Value} from a {@link Token}.
*
* @param token The {@link Token}.
* @return A {@link Value} corresponding to {@code token}.
* @throws IllegalArgumentException When {@code token} can not be converted to a {@link Value}.
*/
private static Value createValue(Token token) {
switch (token.getType()) {
case ESCAPED_TEXT:
case TEXT:
return StringValue.of(token.getUnescapedText(), false);
case INTEGER:
return IntegerValue.of(token.getUnescapedText());
case REAL:
return RealValue.of(token.getUnescapedText());
case TRUE:
return BooleanValue.TRUE;
case FALSE:
return BooleanValue.FALSE;
default:
throw new IllegalArgumentException("Can not create a value with " + token);
}
}
/**
* Creates a {@link Range} from 2 {@link Value Values}.
*
* @param low The lower bound {@link Value}.
* @param high The higher bound {@link Value}.
* @param expression The expression that contains the 2 values.
* @return A new {@link Range} from {@code low} and {@code high}.
* @throws SyntacticException When {@code low} and {@code high} can not be converted to a {@link Range}.
*/
public static Range createRange(Value low,
Value high,
String expression) {
try {
return createRange(low, high);
} catch (final IllegalArgumentException e) {
throw new SyntacticException(SyntacticException.Detail.INVALID_RANGE,
e.getMessage(),
expression);
}
}
/**
* Creates a {@link Range} from 2 {@link Value Values}.
*
* @param low The lower bound {@link Value}.
* @param high The higher bound {@link Value}.
* @return A new {@link Range} from {@code low} and {@code high}.
* @throws IllegalArgumentException When {@code low} and {@code high} can not be converted to a {@link Range}.
*/
public static Range createRange(Value low,
Value high) {
if (low.getClass().equals(high.getClass())) {
if (IntegerValue.class.equals(low.getClass())) {
return IntegerRange.of(((IntegerValue) low).getNumber(),
((IntegerValue) high).getNumber());
} else if (RealValue.class.equals(low.getClass())) {
return RealRange.of(((RealValue) low).getNumber(),
((RealValue) high).getNumber());
} else {
throw new IllegalArgumentException("Can not create a range with " + low + " and " + high);
}
} else {
throw new IllegalArgumentException("Can not create a range with " + low + " and " + high);
}
}
/**
* Creates a {@link List} of {@link BooleanValue BooleanValues} from a set content.
*
* @param content The content.
* @return A {@link List} of {@link BooleanValue BooleanValues} from {@code content}.
* @throws SyntacticException When {@code content} can not be parsed as the content of a {@link BooleanSet}.
*/
public static List toBooleanValues(String content) {
Checks.isNotNull(content, "content");
final List result = new ArrayList<>();
final Tokenizer tokenizer = new Tokenizer();
tokenizer.init(content);
Status status = Status.START;
while (tokenizer.hasMoreTokens()) {
final Token token = tokenizer.nextToken(false);
final BooleanValue value;
switch (token.getType()) {
case FALSE:
if (status == Status.VALUE) {
throw unexpectedToken(content, token);
} else {
value = BooleanValue.FALSE;
status = Status.VALUE;
}
break;
case TRUE:
if (status == Status.VALUE) {
throw unexpectedToken(content, token);
} else {
value = BooleanValue.TRUE;
status = Status.VALUE;
}
break;
case ITEMS_SEP:
if (status == Status.VALUES_SEP || status == Status.START) {
throw unexpectedToken(content, token);
} else {
value = null;
status = Status.VALUES_SEP;
}
break;
default:
throw unexpectedToken(content, token);
}
if (value != null && !result.contains(value)) {
result.add(value);
}
}
if (status == Status.VALUES_SEP) {
throw unexpectedEnd(content);
}
return result;
}
/**
* Creates a {@link List} of {@link StringValue StringValues} from a set content.
*
* @param content The content.
* @return A {@link List} of {@link StringValue StringValues} from {@code content}.
* @throws SyntacticException When {@code content} can not be parsed as the content of a {@link StringSet}.
*/
public static List toStringValues(String content) {
final List result = new ArrayList<>();
Status status = Status.START;
final Tokenizer tokenizer = new Tokenizer();
tokenizer.init(content);
while (tokenizer.hasMoreTokens()) {
final Token token = tokenizer.nextToken(false);
final StringValue value;
switch (token.getType()) {
case ESCAPED_TEXT:
case TEXT:
if (status == Status.VALUE) {
throw unexpectedToken(content, token);
} else {
value = StringValue.of(token.getUnescapedText(), false);
status = Status.VALUE;
}
break;
case ITEMS_SEP:
if (status == Status.VALUES_SEP || status == Status.START) {
throw unexpectedToken(content, token);
} else {
value = null;
status = Status.VALUES_SEP;
}
break;
default:
throw unexpectedToken(content, token);
}
if (value != null && !result.contains(value)) {
result.add(value);
}
}
if (status == Status.VALUES_SEP) {
throw unexpectedEnd(content);
}
return result;
}
/**
* Creates a {@link List} of {@link IntegerRange IntegerRanges} from a set content.
*
* Note: single values are interpreted as ranges.
*
* @param content The content.
* @return A {@link List} of {@link IntegerRange IntegerRanges} from {@code content}.
* @throws SyntacticException When {@code content} can not be parsed as the content of an {@link IntegerSet}.
*/
public static List toIntegerRanges(String content) {
final List result = new ArrayList<>();
Status status = Status.START;
int low = 0;
final Tokenizer tokenizer = new Tokenizer();
tokenizer.init(content);
while (tokenizer.hasMoreTokens()) {
final Token token = tokenizer.nextToken(false);
switch (token.getType()) {
case INTEGER:
if (status == Status.VALUE || status == Status.RANGE) {
throw unexpectedToken(content, token);
} else {
final String text = token.getUnescapedText();
final IntegerValue value = IntegerValue.of(text);
final int number = value.getNumber();
if (status == Status.START || status == Status.VALUES_SEP) {
low = number;
status = Status.VALUE;
} else { // status == Status.TO
result.add(IntegerRange.of(low, number));
status = Status.RANGE;
}
}
break;
case ITEMS_SEP:
if (status == Status.VALUE) {
result.add(IntegerRange.of(low));
status = Status.VALUES_SEP;
} else if (status == Status.RANGE) {
status = Status.VALUES_SEP;
} else {
throw unexpectedToken(content, token);
}
break;
case TO:
if (status == Status.VALUE) {
status = Status.TO;
} else {
throw unexpectedToken(content, token);
}
break;
default:
throw unexpectedToken(content, token);
}
}
if (status == Status.VALUE) {
result.add(IntegerRange.of(low));
} else if (status == Status.RANGE) {
// Ignore
} else if (status != Status.START) {
throw unexpectedEnd(content);
}
return result;
}
/**
* Creates a {@link List} of {@link RealRange RealRanges} from a set content.
*
* Note: single values are interpreted as ranges.
*
* @param content The content.
* @return A {@link List} of {@link RealRange RealRanges} from {@code content}.
* @throws SyntacticException When {@code content} can not be parsed as the content of a {@link RealSet}.
*/
public static List toRealRanges(String content) {
final List result = new ArrayList<>();
Status status = Status.START;
double low = 0.0;
final Tokenizer tokenizer = new Tokenizer();
tokenizer.init(content);
while (tokenizer.hasMoreTokens()) {
final Token token = tokenizer.nextToken(false);
switch (token.getType()) {
case REAL:
if (status == Status.VALUE || status == Status.RANGE) {
throw unexpectedToken(content, token);
} else {
final String text = token.getUnescapedText();
final RealValue value = RealValue.of(text);
final double number = value.getNumber();
if (status == Status.START || status == Status.VALUES_SEP) {
low = number;
status = Status.VALUE;
} else { // status == Status.TO
result.add(RealRange.of(low, number));
status = Status.RANGE;
}
}
break;
case ITEMS_SEP:
if (status == Status.VALUE) {
result.add(RealRange.of(low));
status = Status.VALUES_SEP;
} else if (status == Status.RANGE) {
status = Status.VALUES_SEP;
} else {
throw unexpectedToken(content, token);
}
break;
case TO:
if (status == Status.VALUE) {
status = Status.TO;
} else {
throw unexpectedToken(content, token);
}
break;
default:
throw unexpectedToken(content, token);
}
}
if (status == Status.VALUE) {
result.add(RealRange.of(low));
} else if (status == Status.RANGE) {
// Ignore
} else if (status != Status.START) {
throw unexpectedEnd(content);
}
return result;
}
/**
* Creates a {@link List} of {@link SItem SItems} from a set content.
*
* @param content The content.
* @return A {@link List} of {@link SItem SItems} from {@code content}.
* @throws SyntacticException When {@code content} can not be parsed as the content of an {@link UncheckedSet}.
*/
public static List toSItems(String content) {
final List result = new ArrayList<>();
Status status = Status.START;
Value previousValue = null;
final Tokenizer tokenizer = new Tokenizer();
tokenizer.init(content);
while (tokenizer.hasMoreTokens()) {
final Token token = tokenizer.nextToken(false);
switch (token.getType()) {
case TEXT:
case ESCAPED_TEXT:
case TRUE:
case FALSE:
case INTEGER:
case REAL:
if (status == Status.VALUE) {
throw unexpectedToken(content, token);
} else if (status == Status.START || status == Status.RANGE || status == Status.VALUES_SEP) {
previousValue = createValue(token, content);
status = Status.VALUE;
} else { // status == Status.TO
final Value value = createValue(token, content);
final Range range = createRange(previousValue, value, content);
result.add(range);
status = Status.RANGE;
}
break;
case ITEMS_SEP:
if (status == Status.VALUE) {
result.add(previousValue);
status = Status.VALUES_SEP;
} else if (status == Status.RANGE) {
// Ignore
} else {
throw unexpectedToken(content, token);
}
break;
case TO:
if (status == Status.VALUE) {
status = Status.TO;
} else {
throw unexpectedToken(content, token);
}
break;
default:
throw unexpectedToken(content, token);
}
}
if (status == Status.VALUE) {
result.add(previousValue);
} else if (status == Status.RANGE) {
// Ignore
} else if (status != Status.START) {
throw unexpectedEnd(content);
}
return result;
}
}