Report ErrorType.APPLICATION from codecs
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / codec / AbstractIntegerStringCodec.java
index b7dfa00bd02ab37a29f2e2ab88a3dddb12e886e4..ac69cab0cf8a15bcc09fc81418607eacce15af05 100644 (file)
@@ -7,29 +7,38 @@
  */
 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 static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Optional;
-import com.google.common.collect.Range;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Matcher;
+import com.google.common.collect.RangeSet;
+import java.util.Optional;
 import java.util.regex.Pattern;
-import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+import org.opendaylight.yangtools.yang.data.api.codec.YangInvalidValueException;
+import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
-import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
-
-abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T extends TypeDefinition<T>> extends TypeDefinitionAwareCodec<N, T>{
+import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
+
+/**
+ * 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 RangeRestrictedTypeDefinition<T, N>> 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]+");
@@ -38,159 +47,98 @@ abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T ex
     // 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 RangeConstraint<N> rangeConstraint;
 
+    AbstractIntegerStringCodec(final T typeDefinition, final Optional<RangeConstraint<N>> constraint,
+            final Class<N> outputClass) {
+        super(requireNonNull(typeDefinition), outputClass);
+        rangeConstraint = constraint.orElse(null);
+    }
 
-    private final List<Range<N>> rangeConstraints;
+    public static @NonNull AbstractIntegerStringCodec<Byte, Int8TypeDefinition> from(final Int8TypeDefinition type) {
+        return new Int8StringCodec(type);
+    }
 
-    protected AbstractIntegerStringCodec(final Optional<T> typeDefinition, final List<RangeConstraint> constraints , final Class<N> outputClass) {
-        super(typeDefinition, outputClass);
-        if(constraints.isEmpty()) {
-            rangeConstraints = Collections.emptyList();
-        } else {
-            final ArrayList<Range<N>> builder = new ArrayList<>(constraints.size());
-            for(final RangeConstraint yangConstraint : constraints) {
-                builder.add(createRange(yangConstraint.getMin(),yangConstraint.getMax()));
-            }
-            rangeConstraints = builder;
-        }
+    public static @NonNull AbstractIntegerStringCodec<Short, Int16TypeDefinition> from(final Int16TypeDefinition type) {
+        return new Int16StringCodec(type);
+    }
 
+    public static @NonNull AbstractIntegerStringCodec<Integer, Int32TypeDefinition> from(
+            final Int32TypeDefinition type) {
+        return new Int32StringCodec(type);
     }
 
-    static TypeDefinitionAwareCodec<?, IntegerTypeDefinition> from(final IntegerTypeDefinition type) {
-        final Optional<IntegerTypeDefinition> typeOptional = Optional.of(type);
-        IntegerTypeDefinition baseType = type;
-        while(baseType.getBaseType() != null) {
-            baseType = baseType.getBaseType();
-        }
-        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);
-        }
-        throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
+    public static @NonNull AbstractIntegerStringCodec<Long, Int64TypeDefinition> from(final Int64TypeDefinition type) {
+        return new Int64StringCodec(type);
     }
 
-    static TypeDefinitionAwareCodec<?, UnsignedIntegerTypeDefinition> from(final UnsignedIntegerTypeDefinition type) {
-        final Optional<UnsignedIntegerTypeDefinition> typeOptional = Optional.of(type);
-        UnsignedIntegerTypeDefinition baseType = type;
-        while(baseType.getBaseType() != null) {
-            baseType = baseType.getBaseType();
-        }
-        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);
-        }
-        throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
+    public static @NonNull AbstractIntegerStringCodec<Uint8, Uint8TypeDefinition> from(final Uint8TypeDefinition type) {
+        return new Uint8StringCodec(type);
     }
 
-    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);
+    public static @NonNull AbstractIntegerStringCodec<Uint16, Uint16TypeDefinition> from(
+            final Uint16TypeDefinition type) {
+        return new Uint16StringCodec(type);
     }
 
-    @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;
+    public static @NonNull AbstractIntegerStringCodec<Uint32, Uint32TypeDefinition> from(
+            final Uint32TypeDefinition type) {
+        return new Uint32StringCodec(type);
     }
 
+    public static @NonNull AbstractIntegerStringCodec<Uint64, Uint64TypeDefinition> from(
+            final Uint64TypeDefinition type) {
+        return new Uint64StringCodec(type);
+    }
 
-    private void validate(final N value) {
-        if (rangeConstraints.isEmpty()) {
-            return;
-        }
-        for (final Range<N> constraint : rangeConstraints) {
-            if (constraint.contains(value)) {
-                return;
+    @Override
+    protected final N deserializeImpl(final String product) {
+        final int base = provideBase(product);
+        final String stringRepresentation = base != 16 ? product : X_MATCHER.removeFrom(product);
+        final N deserialized = verifyNotNull(deserialize(stringRepresentation, base));
+        if (rangeConstraint != null) {
+            final RangeSet<N> ranges = rangeConstraint.getAllowedRanges();
+            if (!ranges.contains(deserialized)) {
+                throw new YangInvalidValueException(ErrorType.APPLICATION, rangeConstraint,
+                    "Value '" + deserialized + "'  is not in required ranges " + ranges);
             }
         }
-        throw new IllegalArgumentException("Value '" + value + "'  is not in required range " + rangeConstraints);
+        return deserialized;
+    }
+
+    @Override
+    protected final String serializeImpl(final N input) {
+        return input.toString();
     }
 
     /**
-     * Deserializes value from supplied string representation
-     * is supplied radix.
-     *
-     * See {@link Integer#parseInt(String, int)} for in-depth
-     * description about string and radix relationship.
+     * 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.
      */
-    protected abstract N deserialize(String stringRepresentation, int radix);
+    protected abstract @NonNull N deserialize(@NonNull String stringRepresentation, int radix);
 
-    protected 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();
+    protected static <N extends Number & Comparable<N>> Optional<RangeConstraint<N>> extractRange(
+            final RangeRestrictedTypeDefinition<?, N> type) {
+        return type == null ? Optional.empty() : type.getRangeConstraint();
     }
 
     private static int provideBase(final String integer) {
-        if (integer == null) {
-            throw new IllegalArgumentException("String representing integer number cannot be NULL");
-        }
-
-        if ((integer.length() == 1) && (integer.charAt(0) == '0')) {
+        if ((integer.length() == 1 && integer.charAt(0) == '0') || INT_PATTERN.matcher(integer).matches()) {
             return 10;
-        }
-
-        final Matcher intMatcher = INT_PATTERN.matcher(integer);
-        if (intMatcher.matches()) {
-            return 10;
-        }
-        final Matcher hexMatcher = HEX_PATTERN.matcher(integer);
-        if (hexMatcher.matches()) {
+        } else if (HEX_PATTERN.matcher(integer).matches()) {
             return 16;
-        }
-        final Matcher octMatcher = OCT_PATTERN.matcher(integer);
-        if (octMatcher.matches()) {
+        } else if (OCT_PATTERN.matcher(integer).matches()) {
             return 8;
+        } else {
+            throw new NumberFormatException("Incorrect lexical representation of integer value: " + integer + ".\n"
+                        + "An integer value can be defined as:\n"
+                        + "  - a decimal number,\n"
+                        + "  - a hexadecimal number (prefix 0x)," + "%n  - an octal number (prefix 0).\n"
+                        + "Signed values are allowed. Spaces between digits are NOT allowed.");
         }
-        final String formatedMessage =
-                String.format(INCORRECT_LEXICAL_REPRESENTATION, integer);
-        throw new NumberFormatException(formatedMessage);
-    }
-
-    private static String normalizeHexadecimal(final String hexInt) {
-        if (hexInt == null) {
-            throw new IllegalArgumentException(
-                    "String representing integer number in Hexadecimal format cannot be NULL!");
-        }
-
-        return X_MATCHER.removeFrom(hexInt);
     }
 }