/*
* Copyright (c) 2015 Cisco Systems, Inc. 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
*/
package org.opendaylight.yangtools.yang.data.impl.codec;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT16_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT32_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT64_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT8_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT16_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT32_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT64_QNAME;
import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT8_QNAME;
import com.google.common.annotations.Beta;
import com.google.common.base.CharMatcher;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Range;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
/**
* Do not use this class outside of yangtools, its presence does not fall into the API stability contract.
*/
@Beta
public abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T extends TypeDefinition<T>>
extends TypeDefinitionAwareCodec<N, T> {
private static final Pattern INT_PATTERN = Pattern.compile("[+-]?[1-9][0-9]*$");
private static final Pattern HEX_PATTERN = Pattern.compile("[+-]?0[xX][0-9a-fA-F]+");
private static final Pattern OCT_PATTERN = Pattern.compile("[+-]?0[1-7][0-7]*$");
// For up to two characters, this is very fast
private static final CharMatcher X_MATCHER = CharMatcher.anyOf("xX");
private static final String INCORRECT_LEXICAL_REPRESENTATION = "Incorrect lexical representation of integer value: %s."
+ "\nAn integer value can be defined as: "
+ "\n - a decimal number,"
+ "\n - a hexadecimal number (prefix 0x)," + "%n - an octal number (prefix 0)."
+ "\nSigned values are allowed. Spaces between digits are NOT allowed.";
private final List<Range<N>> rangeConstraints;
AbstractIntegerStringCodec(final Optional<T> typeDefinition, final List<RangeConstraint> constraints,
final Class<N> outputClass) {
super(typeDefinition, outputClass);
if (constraints.isEmpty()) {
rangeConstraints = Collections.emptyList();
} else {
final List<Range<N>> builder = new ArrayList<>(constraints.size());
for (final RangeConstraint yangConstraint : constraints) {
builder.add(createRange(yangConstraint.getMin(), yangConstraint.getMax()));
}
rangeConstraints = builder;
}
}
public static AbstractIntegerStringCodec<?, IntegerTypeDefinition> from(final IntegerTypeDefinition type) {
// FIXME: this is not necessary with yang.model.util.type
IntegerTypeDefinition baseType = type;
while (baseType.getBaseType() != null) {
baseType = baseType.getBaseType();
}
final Optional<IntegerTypeDefinition> typeOptional = Optional.of(type);
// FIXME: use DerivedTypes#isInt8() and friends
if (INT8_QNAME.equals(baseType.getQName())) {
return new Int8StringCodec(typeOptional);
} else if (INT16_QNAME.equals(baseType.getQName())) {
return new Int16StringCodec(typeOptional);
} else if (INT32_QNAME.equals(baseType.getQName())) {
return new Int32StringCodec(typeOptional);
} else if (INT64_QNAME.equals(baseType.getQName())) {
return new Int64StringCodec(typeOptional);
} else {
throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
}
}
public static AbstractIntegerStringCodec<?, UnsignedIntegerTypeDefinition> from(
final UnsignedIntegerTypeDefinition type) {
// FIXME: this is not necessary with yang.model.util.type
UnsignedIntegerTypeDefinition baseType = type;
while (baseType.getBaseType() != null) {
baseType = baseType.getBaseType();
}
final Optional<UnsignedIntegerTypeDefinition> typeOptional = Optional.of(type);
// FIXME: use DerivedTypes#isUint8() and friends
if (UINT8_QNAME.equals(baseType.getQName())) {
return new Uint8StringCodec(typeOptional);
} else if (UINT16_QNAME.equals(baseType.getQName())) {
return new Uint16StringCodec(typeOptional);
} else if (UINT32_QNAME.equals(baseType.getQName())) {
return new Uint32StringCodec(typeOptional);
} else if (UINT64_QNAME.equals(baseType.getQName())) {
return new Uint64StringCodec(typeOptional);
} else {
throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
}
}
private Range<N> createRange(final Number yangMin, final Number yangMax) {
final N min = convertValue(yangMin);
final N max = convertValue(yangMax);
return Range.closed(min, max);
}
@Override
public final N deserialize(final String stringRepresentation) {
final int base = provideBase(stringRepresentation);
final N deserialized;
if (base == 16) {
deserialized = deserialize(normalizeHexadecimal(stringRepresentation),base);
} else {
deserialized = deserialize(stringRepresentation,base);
}
validate(deserialized);
return deserialized;
}
private void validate(final N value) {
if (rangeConstraints.isEmpty()) {
return;
}
for (final Range<N> constraint : rangeConstraints) {
if (constraint.contains(value)) {
return;
}
}
throw new IllegalArgumentException("Value '" + value + "' is not in required range " + rangeConstraints);
}
/**
* Deserializes value from supplied string representation
* is supplied radix.
*
* See {@link Integer#parseInt(String, int)} for in-depth
* description about string and radix relationship.
*
* @param stringRepresentation String representation
* @param radix numeric base.
* @return Deserialized value.
*/
abstract N deserialize(String stringRepresentation, int radix);
abstract N convertValue(Number value);
protected static List<RangeConstraint> extractRange(final IntegerTypeDefinition type) {
if (type == null) {
return Collections.emptyList();
}
return type.getRangeConstraints();
}
protected static List<RangeConstraint> extractRange(final UnsignedIntegerTypeDefinition type) {
if (type == null) {
return Collections.emptyList();
}
return type.getRangeConstraints();
}
private static int provideBase(final String integer) {
Preconditions.checkArgument(integer != null, "String representing integer number cannot be NULL");
if (integer.length() == 1 && integer.charAt(0) == '0') {
return 10;
} else if (INT_PATTERN.matcher(integer).matches()) {
return 10;
} else if (HEX_PATTERN.matcher(integer).matches()) {
return 16;
} else if (OCT_PATTERN.matcher(integer).matches()) {
return 8;
} else {
throw new NumberFormatException(String.format(INCORRECT_LEXICAL_REPRESENTATION, integer));
}
}
private static String normalizeHexadecimal(final String hexInt) {
Preconditions.checkArgument(hexInt != null,
"String representing integer number in Hexadecimal format cannot be NULL!");
return X_MATCHER.removeFrom(hexInt);
}
}