From: Robert Varga Date: Mon, 25 Aug 2014 19:17:01 +0000 (+0000) Subject: Merge "BUG-1589 Exclude xml* prefixes from RandomPrefix" X-Git-Tag: release/helium~164 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=d38f557d544ef96a3a379c652248c6812ca35a13;hp=f91fc5cfb52a4c8f725e95df864d85500c568aa7;p=yangtools.git Merge "BUG-1589 Exclude xml* prefixes from RandomPrefix" --- diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefix.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefix.java index 0251678389..00fc7d708e 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefix.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefix.java @@ -7,18 +7,29 @@ */ package org.opendaylight.yangtools.yang.data.impl.codec.xml; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import java.net.URI; -import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ThreadLocalRandom; - import org.opendaylight.yangtools.yang.common.QName; final class RandomPrefix { - final Map prefixes = new HashMap<>(); - Iterable> getPrefixes() { + public static final char STARTING_CHAR = 'a'; + public static final int CHARACTER_RANGE = 26; + public static final int PREFIX_MAX_LENGTH = 4; + + public static final int MAX_COUNTER_VALUE = (int) Math.pow(CHARACTER_RANGE, PREFIX_MAX_LENGTH); + private static final int STARTING_WITH_XML = decode("xml"); + + private int counter = 0; + + // BiMap to make values lookup faster + private final BiMap prefixes = HashBiMap.create(); + + Iterable> getPrefixes() { return prefixes.entrySet(); } @@ -28,22 +39,65 @@ final class RandomPrefix { String encodePrefix(final QName qname) { String prefix = prefixes.get(qname.getNamespace()); - if (prefix == null) { - prefix = qname.getPrefix(); - if (prefix == null || prefix.isEmpty() || prefixes.containsValue(prefix)) { - final ThreadLocalRandom random = ThreadLocalRandom.current(); - do { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 4; i++) { - sb.append((char)('a' + random.nextInt(25))); - } - - prefix = sb.toString(); - } while (prefixes.containsValue(prefix)); - } - - prefixes.put(qname.getNamespace(), prefix); + if (prefix != null) { + return prefix; + } + + // Reuse prefix from QName if possible + final String qNamePrefix = qname.getPrefix(); + + if (!Strings.isNullOrEmpty(qNamePrefix) && !qNamePrefix.startsWith("xml") && !alreadyUsedPrefix(qNamePrefix)) { + prefix = qNamePrefix; + } else { + + do { + // Skip values starting with xml (Expecting only 4 chars max since division is calculated only once) + while (counter == STARTING_WITH_XML + || counter / CHARACTER_RANGE == STARTING_WITH_XML) { + counter++; + } + + // Reset in case of max prefix generated + if (counter >= MAX_COUNTER_VALUE) { + counter = 0; + prefixes.clear(); + } + + prefix = encode(counter); + counter++; + } while (alreadyUsedPrefix(prefix)); } + + prefixes.put(qname.getNamespace(), prefix); return prefix; } + + private boolean alreadyUsedPrefix(final String prefix) { + return prefixes.values().contains(prefix); + } + + @VisibleForTesting + static int decode(final String s) { + int num = 0; + for (final char ch : s.toCharArray()) { + num *= CHARACTER_RANGE; + num += (ch - STARTING_CHAR); + } + return num; + } + + @VisibleForTesting + static String encode(int num) { + if (num == 0) { + return "a"; + } + + final StringBuilder sb = new StringBuilder(); + while (num != 0) { + sb.append(((char) (num % CHARACTER_RANGE + STARTING_CHAR))); + num /= CHARACTER_RANGE; + } + + return sb.reverse().toString(); + } } diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefixTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefixTest.java index eb32e4d0cb..95d8ac6bea 100644 --- a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefixTest.java +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefixTest.java @@ -7,35 +7,83 @@ */ package org.opendaylight.yangtools.yang.data.impl.codec.xml; -import org.junit.Assert; -import org.junit.Before; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.net.URI; +import java.util.Date; +import java.util.List; +import org.hamcrest.CoreMatchers; import org.junit.Test; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.RandomPrefix; +import org.opendaylight.yangtools.yang.common.QNameModule; -/** - * @author tkubas - */ public class RandomPrefixTest { - private RandomPrefix randomPrefix; - - /** - * setup {@link #randomPrefix} instance - */ - @Before - public void setUp() { - randomPrefix = new RandomPrefix(); + + @Test + public void testEncodeDecode() throws Exception { + final List allGenerated = Lists.newArrayList(); + for (int i = 0; i < RandomPrefix.MAX_COUNTER_VALUE; i++) { + final String encoded = RandomPrefix.encode(i); + assertEquals(RandomPrefix.decode(encoded), i); + allGenerated.add(encoded); + } + + assertEquals(allGenerated.size(), RandomPrefix.MAX_COUNTER_VALUE); + assertEquals("zzzz", allGenerated.get(RandomPrefix.MAX_COUNTER_VALUE - 1)); + assertEquals("a", allGenerated.get(0)); + assertEquals("xml", allGenerated.get(RandomPrefix.decode("xml"))); + assertEquals(allGenerated.size(), Sets.newHashSet(allGenerated).size()); + } + + @Test + public void testQNameWithPrefix() throws Exception { + final RandomPrefix a = new RandomPrefix(); + + final List allGenerated = Lists.newArrayList(); + for (int i = 0; i < RandomPrefix.MAX_COUNTER_VALUE; i++) { + final String prefix = RandomPrefix.encode(i); + final URI uri = new URI("localhost:" + prefix); + final QName qName = QName.create(QNameModule.create(uri, new Date()), prefix, "local-name"); + allGenerated.add(a.encodePrefix(qName)); + } + + assertEquals(RandomPrefix.MAX_COUNTER_VALUE, allGenerated.size()); + // We are generating MAX_COUNTER_VALUE + 27 prefixes total, so we should encounter a reset in prefix a start from 0 at some point + // At the end, there should be only 27 values in RandomPrefix cache + assertEquals(27, Iterables.size(a.getPrefixes())); + assertThat(allGenerated, CoreMatchers.not(CoreMatchers.hasItem("xml"))); + assertThat(allGenerated, CoreMatchers.not(CoreMatchers.hasItem("xmla"))); + assertThat(allGenerated, CoreMatchers.not(CoreMatchers.hasItem("xmlz"))); + + assertEquals(2, Iterables.frequency(allGenerated, "a")); } - /** - * Test method for {@link org.opendaylight.yangtools.yang.data.impl.codec.xml.RandomPrefix#encodeQName(QName)}. - */ + @Test - public void testEncodeQName() { - QName node = QName.create("","2013-06-07","node"); - String encodedQName = randomPrefix.encodeQName(node); - Assert.assertNotNull(encodedQName); - Assert.assertTrue("prefix is expected to contain 4 small letters as prefix but result is: "+encodedQName, - encodedQName.matches("[a-z]{4}:node")); + public void test2QNames1Namespace() throws Exception { + final RandomPrefix a = new RandomPrefix(); + + final URI uri = URI.create("localhost"); + final QName qName = QName.create(QNameModule.create(uri, new Date()), "p1", "local-name"); + final QName qName2 = QName.create(QNameModule.create(uri, new Date()), "p2", "local-name"); + + assertEquals(a.encodePrefix(qName), a.encodePrefix(qName2)); } + @Test + public void testQNameNoPrefix() throws Exception { + final RandomPrefix a = new RandomPrefix(); + + final URI uri = URI.create("localhost"); + QName qName = QName.create(uri, new Date(), "local-name"); + assertEquals("a", a.encodePrefix(qName)); + qName = QName.create(QNameModule.create(uri, new Date()), "", "local-name"); + assertEquals("a", a.encodePrefix(qName)); + qName = QName.create(QNameModule.create(URI.create("second"), new Date()), "", "local-name"); + assertEquals("b", a.encodePrefix(qName)); + + } }