BUG-1589 Exclude xml* prefixes from RandomPrefix 40/10240/1
authorMaros Marsalek <mmarsale@cisco.com>
Mon, 25 Aug 2014 09:50:32 +0000 (11:50 +0200)
committerMaros Marsalek <mmarsale@cisco.com>
Mon, 25 Aug 2014 09:52:17 +0000 (11:52 +0200)
Change-Id: Ief1807e0124a1d54ff737dd1c24fcea21f0574ed
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefix.java
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/RandomPrefixTest.java

index 0251678389a8c29cd3d989fc6e9d0a1a227ba78a..00fc7d708e03f0000428439865055e7afc58b6ce 100644 (file)
@@ -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<URI, String> prefixes = new HashMap<>();
 
-    Iterable<Entry<URI, String>> 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<URI, String> prefixes = HashBiMap.create();
+
+    Iterable<Map.Entry<URI, String>> 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();
+    }
 }
index eb32e4d0cb1b91f90a8af783179b8b7266f82087..95d8ac6bea14e6587a616cb018019b2e01ac2b7b 100644 (file)
@@ -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<String> 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<String> 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));
+
+    }
 }