/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.dmdl.model;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.MessageFormat;
import com.asakusafw.dmdl.Region;
/**
* Represents literals.
* @since 0.2.0
* @version 0.9.1
*/
public class AstLiteral extends AbstractAstNode implements AstAttributeValue {
private static final char[] ASCII_SPECIAL_ESCAPE = new char[128];
static {
ASCII_SPECIAL_ESCAPE['\b'] = 'b';
ASCII_SPECIAL_ESCAPE['\t'] = 't';
ASCII_SPECIAL_ESCAPE['\n'] = 'n';
ASCII_SPECIAL_ESCAPE['\f'] = 'f';
ASCII_SPECIAL_ESCAPE['\r'] = 'r';
ASCII_SPECIAL_ESCAPE['\\'] = '\\';
ASCII_SPECIAL_ESCAPE['\"'] = '\"';
}
private final Region region;
/**
* The (original) token of this literal.
*/
public final String token;
/**
* The kind of this literal.
*/
public final LiteralKind kind;
/**
* Creates a new instance.
* @param region the region of this node on the enclosing script, or {@code null} if unknown
* @param token the token of this literal
* @param kind the kind of this literal
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public AstLiteral(Region region, String token, LiteralKind kind) {
if (token == null) {
throw new IllegalArgumentException("token must not be null"); //$NON-NLS-1$
}
if (kind == null) {
throw new IllegalArgumentException("kind must not be null"); //$NON-NLS-1$
}
this.region = region;
this.token = token;
this.kind = kind;
}
/**
* Converts the string as the token of DMDL string literal.
* @param string target string
* @return the corresponded DMDL string literal
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public static String quote(String string) {
if (string == null) {
throw new IllegalArgumentException("string must not be null"); //$NON-NLS-1$
}
StringBuilder buf = new StringBuilder();
buf.append('"');
for (char c : string.toCharArray()) {
if (c <= 0x7f && ASCII_SPECIAL_ESCAPE[c] != 0) {
buf.append('\\');
buf.append(ASCII_SPECIAL_ESCAPE[c]);
} else if (Character.isISOControl(c) || Character.isDefined(c) == false) {
buf.append(String.format("\\u%04x", (int) c)); //$NON-NLS-1$
} else {
buf.append(c);
}
}
buf.append('"');
return buf.toString();
}
/**
* Returns the string value of this literal.
* @return the string value
* @throws IllegalStateException if this is not a string literal
*/
public String toStringValue() {
checkKind(LiteralKind.STRING);
if (token.length() >= 2 && token.startsWith("\"") && token.endsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
return EscapeDecoder.scan(token.substring(1, token.length() - 1));
}
throw new IllegalStateException(MessageFormat.format(
"Invalid string value: {0}", //$NON-NLS-1$
token));
}
/**
* Returns the integer value of this literal.
* @return the integer value
* @throws IllegalStateException if this is not an integer literal
*/
public BigInteger toIntegerValue() {
checkKind(LiteralKind.INTEGER);
return new BigInteger(token);
}
/**
* Returns the decimal value of this literal.
* @return the decimal value
* @throws IllegalStateException if this is not a decimal literal
*/
public BigDecimal toDecimalValue() {
checkKind(LiteralKind.DECIMAL);
return new BigDecimal(token);
}
/**
* Returns the boolean value of this literal.
* @return the boolean value
* @throws IllegalStateException if this is not a boolean literal
*/
public boolean toBooleanValue() {
checkKind(LiteralKind.BOOLEAN);
return token.equals("TRUE"); //$NON-NLS-1$
}
/**
* Returns the value of this literal.
* @return the value
* @since 0.9.1
*/
public Object toValue() {
switch (getKind()) {
case BOOLEAN:
return toBooleanValue();
case DECIMAL:
return toDecimalValue();
case INTEGER:
return toIntegerValue();
case STRING:
return toStringValue();
default:
throw new AssertionError(getKind());
}
}
private void checkKind(LiteralKind expected) {
assert expected != null;
if (kind != expected) {
throw new IllegalStateException(MessageFormat.format(
"Inconsistent literal kind: {0}", //$NON-NLS-1$
token));
}
}
@Override
public Region getRegion() {
return region;
}
/**
* Returns the token of this literal.
* @return the token
*/
public String getToken() {
return token;
}
/**
* Returns the kind of this literal.
* @return the literal kind
*/
public LiteralKind getKind() {
return kind;
}
@Override
public <C, R> R accept(C context, AstNode.Visitor<C, R> visitor) {
if (visitor == null) {
throw new IllegalArgumentException("visitor must not be null"); //$NON-NLS-1$
}
R result = visitor.visitLiteral(context, this);
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + kind.hashCode();
result = prime * result + token.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AstLiteral other = (AstLiteral) obj;
if (kind != other.kind) {
return false;
}
if (!token.equals(other.token)) {
return false;
}
return true;
}
}