*/
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();
}
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();
+ }
}
*/
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));
+
+ }
}