BUG-1485: Introduce AbstractRangeGenerator and friends 64/21164/3
authorRobert Varga <rovarga@cisco.com>
Tue, 26 May 2015 20:49:36 +0000 (22:49 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 28 May 2015 07:23:52 +0000 (07:23 +0000)
Adds utility classes for dealing with various range constraints in
generated code. These classes are implemented in pure Java and attempt
to make sure generated code runs as efficiently as possible.

Change-Id: Ie1d05b514937507e225b9bc7559b1949a7e6eb76
Signed-off-by: Robert Varga <rovarga@cisco.com>
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractBigRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractPrimitiveRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractSubIntegerRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BigDecimalRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BigIntegerRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ByteRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/IntegerRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/LongRangeGenerator.java [new file with mode: 0644]
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ShortRangeGenerator.java [new file with mode: 0644]

diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractBigRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractBigRangeGenerator.java
new file mode 100644 (file)
index 0000000..dce97a2
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.sal.java.api.generator;
+
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
+
+abstract class AbstractBigRangeGenerator<T extends Number & Comparable<T>> extends AbstractRangeGenerator<T> {
+    protected AbstractBigRangeGenerator(final Class<T> typeClass) {
+        super(typeClass);
+    }
+
+    private String itemType() {
+        final StringBuilder sb = new StringBuilder("com.google.common.collect.Range<");
+        sb.append(getTypeName()).append('>');
+
+        return sb.toString();
+    }
+
+    private String arrayType() {
+        return new StringBuilder(itemType()).append("[]").toString();
+    }
+
+    @Override
+    protected final String generateRangeCheckerImplementation(final String checkerName, @Nonnull final Collection<RangeConstraint> restrictions) {
+        final StringBuilder sb = new StringBuilder();
+
+        // Field to hold the Range objects in an array
+        sb.append("private static final ").append(arrayType()).append(' ').append(checkerName).append(";\n");
+
+        // Static initializer block for the array
+        sb.append("static {\n");
+        sb.append("    @SuppressWarnings(\"unchecked\")\n");
+        sb.append("    final ").append(arrayType()).append(" a = (").append(arrayType())
+        .append(") java.lang.reflect.Array.newInstance(com.google.common.collect.Range.class, ").append(restrictions.size()).append(");\n");
+
+        int i = 0;
+        for (RangeConstraint r : restrictions) {
+            final String min = format(getValue(r.getMin()));
+            final String max = format(getValue(r.getMax()));
+
+            sb.append("    a[").append(i++).append("] = com.google.common.collect.Range.closed(").append(min).append(", ").append(max).append(");\n");
+        }
+
+        sb.append("    ").append(checkerName).append(" = a;\n");
+        sb.append("}\n\n");
+
+        // Static enforcement method
+        sb.append("private static void ").append(checkerName).append("(final ").append(getTypeName()).append(" value) {\n");
+        sb.append("    for (").append(itemType()).append(" r : ").append(checkerName).append(") {\n");
+        sb.append("        if (r.contains(value)) {\n");
+        sb.append("            return;\n");
+        sb.append("        }\n");
+        sb.append("    }\n");
+        sb.append("\n");
+        sb.append("    throw new IllegalArgumentException(String.format(\"Invalid value %s, expected: %s.\", value, java.util.Arrays.asList(").append(checkerName).append(")));\n");
+        sb.append("}\n\n");
+
+        return sb.toString();
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractPrimitiveRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractPrimitiveRangeGenerator.java
new file mode 100644 (file)
index 0000000..fffe2d1
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.sal.java.api.generator;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractPrimitiveRangeGenerator<T extends Number & Comparable<T>> extends AbstractRangeGenerator<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractPrimitiveRangeGenerator.class);
+    private final T minValue;
+    private final T maxValue;
+
+    protected AbstractPrimitiveRangeGenerator(final Class<T> typeClass, final T minValue, final T maxValue) {
+        super(typeClass);
+        this.minValue = Preconditions.checkNotNull(minValue);
+        this.maxValue = Preconditions.checkNotNull(maxValue);
+    }
+
+    private boolean needsMaximumEnforcement(final T maxToEnforce) {
+        return maxValue.compareTo(maxToEnforce) > 0;
+    }
+
+    private boolean needsMinimumEnforcement(final T minToEnforce) {
+        return minValue.compareTo(minToEnforce) < 0;
+    }
+
+    private final Collection<String> createConditionals(final Collection<RangeConstraint> restrictions) {
+        final Collection<String> ret = new ArrayList<>(restrictions.size());
+
+        for (RangeConstraint r : restrictions) {
+            final T min = getValue(r.getMin());
+            final boolean needMin = needsMinimumEnforcement(min);
+
+            final T max = getValue(r.getMax());
+            final boolean needMax = needsMaximumEnforcement(max);
+
+            if (!needMin && !needMax) {
+                LOG.debug("Type {} indicates [{}, {}] does not require enforcement", getTypeName(), min, max);
+                continue;
+            }
+
+            final StringBuilder sb = new StringBuilder();
+            if (needMin) {
+                sb.append("value >= ").append(format(min));
+                if (needMax) {
+                    sb.append(" && ");
+                }
+            }
+            if (needMax) {
+                sb.append("value <= ").append(format(max));
+            }
+
+            ret.add(sb.toString());
+        }
+
+        return ret;
+    }
+
+    @Override
+    protected final String generateRangeCheckerImplementation(final String checkerName, final Collection<RangeConstraint> restrictions) {
+        final StringBuilder sb = new StringBuilder();
+        final Collection<String> conditionals = createConditionals(restrictions);
+
+        sb.append("private static void ").append(checkerName).append("(final ").append(getTypeName()).append(" value) {\n");
+
+        if (!conditionals.isEmpty()) {
+            for (String c : conditionals) {
+                sb.append("    if (").append(c).append(") {\n");
+                sb.append("        return;\n");
+                sb.append("    }\n");
+            }
+
+            sb.append("    throw new IllegalArgumentException(String.format(\"Invalid value %s does not match any required ranges\", value));\n");
+        }
+
+        sb.append("}\n\n");
+
+        return sb.toString();
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractRangeGenerator.java
new file mode 100644 (file)
index 0000000..3f09cff
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.sal.java.api.generator;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractRangeGenerator<T extends Number & Comparable<T>> {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractRangeGenerator.class);
+    private static final Map<String, AbstractRangeGenerator<?>> GENERATORS;
+
+    private static void addGenerator(final Builder<String, AbstractRangeGenerator<?>> b, final AbstractRangeGenerator<?> generator) {
+        b.put(generator.getTypeClass().getCanonicalName(), generator);
+    }
+
+    static {
+        final Builder<String, AbstractRangeGenerator<?>> b = ImmutableMap.<String, AbstractRangeGenerator<?>> builder();
+        addGenerator(b, new ByteRangeGenerator());
+        addGenerator(b, new ShortRangeGenerator());
+        addGenerator(b, new IntegerRangeGenerator());
+        addGenerator(b, new LongRangeGenerator());
+        addGenerator(b, new BigDecimalRangeGenerator());
+        addGenerator(b, new BigIntegerRangeGenerator());
+        GENERATORS = b.build();
+    }
+
+    private final Class<T> type;
+
+    protected AbstractRangeGenerator(final Class<T> typeClass) {
+        this.type = Preconditions.checkNotNull(typeClass);
+    }
+
+    static AbstractRangeGenerator<?> getInstance(final String canonicalName) {
+        return GENERATORS.get(canonicalName);
+    }
+
+    /**
+     * Return the type's class.
+     *
+     * @return A class object
+     */
+    protected final @Nonnull Class<T> getTypeClass() {
+        return type;
+    }
+
+    /**
+     * Return the type's fully-qualified name.
+     *
+     * @return Fully-qualified name
+     */
+    protected final @Nonnull String getTypeName() {
+        return type.getName();
+    }
+
+    protected final @Nonnull T getValue(final Number value) {
+        if (type.isInstance(value)) {
+            return type.cast(value);
+        }
+
+        LOG.info("Number class conversion from {} to {} may lose precision of {}", value.getClass(), type, value);
+        return convert(value);
+    }
+
+    // FIXME: Once BUG-3399 is fixed, we should never need this
+    @Deprecated
+    protected abstract T convert(final Number value);
+
+    /**
+     * Format a value into a Java-compilable expression which results in the appropriate
+     * type.
+     * @param number Number value
+     * @return Java language string representation
+     */
+    protected abstract @Nonnull String format(final T number);
+
+    /**
+     * Generate the checker method source code.
+     * @param checkerName Name of the checker method.
+     * @param restrictions Restrictions which need to be applied.
+     * @return Method source code.
+     */
+    protected abstract @Nonnull String generateRangeCheckerImplementation(@Nonnull final String checkerName, @Nonnull final Collection<RangeConstraint> restrictions);
+
+    private static String rangeCheckerName(final String member) {
+        final StringBuilder sb = new StringBuilder("check");
+        if (member != null) {
+            sb.append(member);
+        }
+        return sb.append("Range").toString();
+    }
+
+    String generateRangeChecker(@Nullable final String member, @Nonnull final Collection<RangeConstraint> restrictions) {
+        Preconditions.checkArgument(!restrictions.isEmpty(), "Restrictions may not be empty");
+        return generateRangeCheckerImplementation(rangeCheckerName(member), restrictions);
+    }
+
+    String generateRangeCheckerCall(@Nullable final String member, @Nonnull final String valueReference) {
+        return rangeCheckerName(member) + '(' + valueReference + ");\n";
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractSubIntegerRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/AbstractSubIntegerRangeGenerator.java
new file mode 100644 (file)
index 0000000..020da1e
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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.sal.java.api.generator;
+
+import com.google.common.base.Preconditions;
+
+abstract class AbstractSubIntegerRangeGenerator<T extends Number & Comparable<T>> extends AbstractPrimitiveRangeGenerator<T> {
+    private final String castType;
+
+    protected AbstractSubIntegerRangeGenerator(final Class<T> typeClass, final T minValue, final T maxValue, final String castType) {
+        super(typeClass, minValue, maxValue);
+        this.castType = Preconditions.checkNotNull(castType);
+    }
+
+    @Override
+    protected final String format(final T number) {
+        // Make sure the number constant is cast to the corresponding primitive type
+        return '(' + castType + ')' + number;
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BigDecimalRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BigDecimalRangeGenerator.java
new file mode 100644 (file)
index 0000000..30eef9f
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.sal.java.api.generator;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+final class BigDecimalRangeGenerator extends AbstractBigRangeGenerator<BigDecimal> {
+    BigDecimalRangeGenerator() {
+        super(BigDecimal.class);
+    }
+
+    @Override
+    protected String format(final BigDecimal number) {
+        if (BigDecimal.ZERO.equals(number)) {
+            return "java.math.BigDecimal.ZERO";
+        }
+        if (BigDecimal.ONE.equals(number)) {
+            return "java.math.BigDecimal.ONE";
+        }
+        if (BigDecimal.TEN.equals(number)) {
+            return "java.math.BigDecimal.TEN";
+        }
+
+        // FIXME: can we do something better?
+        return "new java.math.BigDecimal(\"" + number + "\")";
+    }
+
+    @Override
+    protected BigDecimal convert(final Number value) {
+        if (value instanceof BigInteger) {
+            return new BigDecimal((BigInteger)value);
+        } else if (value instanceof Byte) {
+            return new BigDecimal(value.intValue());
+        } else if (value instanceof Short) {
+            return new BigDecimal(value.intValue());
+        } else if (value instanceof Integer) {
+            return new BigDecimal(value.intValue());
+        } else {
+            return BigDecimal.valueOf(value.longValue());
+        }
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BigIntegerRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BigIntegerRangeGenerator.java
new file mode 100644 (file)
index 0000000..7c2a4c9
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.sal.java.api.generator;
+
+import java.math.BigInteger;
+
+final class BigIntegerRangeGenerator extends AbstractBigRangeGenerator<BigInteger> {
+    BigIntegerRangeGenerator() {
+        super(BigInteger.class);
+    }
+
+    @Override
+    protected String format(final BigInteger number) {
+        if (BigInteger.ZERO.equals(number)) {
+            return "java.math.BigInteger.ZERO";
+        }
+        if (BigInteger.ONE.equals(number)) {
+            return "java.math.BigInteger.ONE";
+        }
+        if (BigInteger.TEN.equals(number)) {
+            return "java.math.BigInteger.TEN";
+        }
+
+        // Check for conversion to long
+        final long l = number.longValue();
+        if (number.equals(BigInteger.valueOf(l))) {
+            return "java.math.BigInteger.valueOf(" + l + "L)";
+        } else {
+            return "new java.math.BigInteger(\"" + number.toString() + "\")";
+        }
+    }
+
+    @Override
+    protected BigInteger convert(final Number value) {
+        return BigInteger.valueOf(value.longValue());
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ByteRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ByteRangeGenerator.java
new file mode 100644 (file)
index 0000000..4656897
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * 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.sal.java.api.generator;
+
+final class ByteRangeGenerator extends AbstractSubIntegerRangeGenerator<Byte> {
+    ByteRangeGenerator() {
+        super(Byte.class, Byte.MIN_VALUE, Byte.MAX_VALUE, byte.class.getName());
+    }
+
+    @Override
+    protected Byte convert(final Number value) {
+        return value.byteValue();
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/IntegerRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/IntegerRangeGenerator.java
new file mode 100644 (file)
index 0000000..fb23ec8
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.sal.java.api.generator;
+
+final class IntegerRangeGenerator extends AbstractPrimitiveRangeGenerator<Integer> {
+    IntegerRangeGenerator() {
+        super(Integer.class, Integer.MIN_VALUE, Integer.MAX_VALUE);
+    }
+
+    @Override
+    protected final String format(final Integer number) {
+        return number.toString();
+    }
+
+    @Override
+    protected Integer convert(final Number value) {
+        return value.intValue();
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/LongRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/LongRangeGenerator.java
new file mode 100644 (file)
index 0000000..7a26bee
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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.sal.java.api.generator;
+
+final class LongRangeGenerator extends AbstractPrimitiveRangeGenerator<Long> {
+
+    protected LongRangeGenerator() {
+        super(Long.class, Long.MIN_VALUE, Long.MAX_VALUE);
+    }
+
+    @Override
+    protected String format(final Long number) {
+        return number.toString() + 'L';
+    }
+
+    @Override
+    protected Long convert(final Number value) {
+        return value.longValue();
+    }
+}
diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ShortRangeGenerator.java b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ShortRangeGenerator.java
new file mode 100644 (file)
index 0000000..9ad2a83
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * 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.sal.java.api.generator;
+
+final class ShortRangeGenerator extends AbstractSubIntegerRangeGenerator<Short> {
+    ShortRangeGenerator() {
+        super(Short.class, Short.MIN_VALUE, Short.MAX_VALUE, short.class.getName());
+    }
+
+    @Override
+    protected Short convert(final Number value) {
+        return value.shortValue();
+    }
+}