diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java index f865c9566d..05f3de1394 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java +++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,36 +16,38 @@ package org.springframework.util.xml; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.List; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import org.springframework.util.Assert; /** - * Simple {@code javax.xml.namespace.NamespaceContext} implementation. Follows the standard - * {@code NamespaceContext} contract, and is loadable via a {@code java.util.Map} or - * {@code java.util.Properties} object + * Simple {@code javax.xml.namespace.NamespaceContext} implementation. + * Follows the standard {@code NamespaceContext} contract, and is loadable + * via a {@code java.util.Map} or {@code java.util.Properties} object * * @author Arjen Poutsma + * @author Juergen Hoeller * @since 3.0 */ public class SimpleNamespaceContext implements NamespaceContext { - private Map prefixToNamespaceUri = new HashMap(); + private final Map prefixToNamespaceUri = new HashMap(); - private Map> namespaceUriToPrefixes = new HashMap>(); + private final Map> namespaceUriToPrefixes = new HashMap>(); private String defaultNamespaceUri = ""; + @Override public String getNamespaceURI(String prefix) { - Assert.notNull(prefix, "prefix is null"); + Assert.notNull(prefix, "No prefix given"); if (XMLConstants.XML_NS_PREFIX.equals(prefix)) { return XMLConstants.XML_NS_URI; } @@ -53,29 +55,46 @@ public class SimpleNamespaceContext implements NamespaceContext { return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; } else if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { - return defaultNamespaceUri; + return this.defaultNamespaceUri; } - else if (prefixToNamespaceUri.containsKey(prefix)) { - return prefixToNamespaceUri.get(prefix); + else if (this.prefixToNamespaceUri.containsKey(prefix)) { + return this.prefixToNamespaceUri.get(prefix); } return ""; } @Override public String getPrefix(String namespaceUri) { - List prefixes = getPrefixesInternal(namespaceUri); - return prefixes.isEmpty() ? null : (String) prefixes.get(0); + Set prefixes = getPrefixesSet(namespaceUri); + return (!prefixes.isEmpty() ? prefixes.iterator().next() : null); } @Override public Iterator getPrefixes(String namespaceUri) { - return getPrefixesInternal(namespaceUri).iterator(); + return getPrefixesSet(namespaceUri).iterator(); } + private Set getPrefixesSet(String namespaceUri) { + Assert.notNull(namespaceUri, "No namespaceUri given"); + if (this.defaultNamespaceUri.equals(namespaceUri)) { + return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX); + } + else if (XMLConstants.XML_NS_URI.equals(namespaceUri)) { + return Collections.singleton(XMLConstants.XML_NS_PREFIX); + } + else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) { + return Collections.singleton(XMLConstants.XMLNS_ATTRIBUTE); + } + else { + Set prefixes = this.namespaceUriToPrefixes.get(namespaceUri); + return (prefixes != null ? Collections.unmodifiableSet(prefixes) : Collections.emptySet()); + } + } + + /** - * Sets the bindings for this namespace context. The supplied map must consist of string key value pairs. - * - * @param bindings the bindings + * Set the bindings for this namespace context. + * The supplied map must consist of string key value pairs. */ public void setBindings(Map bindings) { for (Map.Entry entry : bindings.entrySet()) { @@ -84,8 +103,7 @@ public class SimpleNamespaceContext implements NamespaceContext { } /** - * Binds the given namespace as default namespace. - * + * Bind the given namespace as default namespace. * @param namespaceUri the namespace uri */ public void bindDefaultNamespaceUri(String namespaceUri) { @@ -93,70 +111,62 @@ public class SimpleNamespaceContext implements NamespaceContext { } /** - * Binds the given prefix to the given namespace. - * - * @param prefix the namespace prefix + * Bind the given prefix to the given namespace. + * @param prefix the namespace prefix * @param namespaceUri the namespace uri */ public void bindNamespaceUri(String prefix, String namespaceUri) { Assert.notNull(prefix, "No prefix given"); Assert.notNull(namespaceUri, "No namespaceUri given"); if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { - defaultNamespaceUri = namespaceUri; + this.defaultNamespaceUri = namespaceUri; } else { - prefixToNamespaceUri.put(prefix, namespaceUri); - getPrefixesInternal(namespaceUri).add(prefix); + this.prefixToNamespaceUri.put(prefix, namespaceUri); + Set prefixes = this.namespaceUriToPrefixes.get(namespaceUri); + if (prefixes == null) { + prefixes = new LinkedHashSet(); + this.namespaceUriToPrefixes.put(namespaceUri, prefixes); + } + prefixes.add(prefix); } } - /** Removes all declared prefixes. */ - public void clear() { - prefixToNamespaceUri.clear(); - } - /** - * Returns all declared prefixes. - * - * @return the declared prefixes + * Remove the given prefix from this context. + * @param prefix the prefix to be removed */ - public Iterator getBoundPrefixes() { - return prefixToNamespaceUri.keySet().iterator(); - } - - private List getPrefixesInternal(String namespaceUri) { - if (defaultNamespaceUri.equals(namespaceUri)) { - return Collections.singletonList(XMLConstants.DEFAULT_NS_PREFIX); - } - else if (XMLConstants.XML_NS_URI.equals(namespaceUri)) { - return Collections.singletonList(XMLConstants.XML_NS_PREFIX); - } - else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) { - return Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE); + public void removeBinding(String prefix) { + if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { + this.defaultNamespaceUri = ""; } - else { - List list = namespaceUriToPrefixes.get(namespaceUri); - if (list == null) { - list = new ArrayList(); - namespaceUriToPrefixes.put(namespaceUri, list); + else if (prefix != null) { + String namespaceUri = this.prefixToNamespaceUri.remove(prefix); + if (namespaceUri != null) { + Set prefixes = this.namespaceUriToPrefixes.get(namespaceUri); + if (prefixes != null) { + prefixes.remove(prefix); + if (prefixes.isEmpty()) { + this.namespaceUriToPrefixes.remove(namespaceUri); + } + } } - return list; } } /** - * Removes the given prefix from this context. - * - * @param prefix the prefix to be removed + * Remove all declared prefixes. */ - public void removeBinding(String prefix) { - if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { - defaultNamespaceUri = ""; - } - else { - String namespaceUri = prefixToNamespaceUri.remove(prefix); - List prefixes = getPrefixesInternal(namespaceUri); - prefixes.remove(prefix); - } + public void clear() { + this.prefixToNamespaceUri.clear(); + this.namespaceUriToPrefixes.clear(); + } + + /** + * Return all declared prefixes. + */ + public Iterator getBoundPrefixes() { + return this.prefixToNamespaceUri.keySet().iterator(); } + } diff --git a/spring-core/src/test/java/org/springframework/util/xml/SimpleNamespaceContextTests.java b/spring-core/src/test/java/org/springframework/util/xml/SimpleNamespaceContextTests.java index 80759270d2..4d191f676a 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/SimpleNamespaceContextTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/SimpleNamespaceContextTests.java @@ -16,101 +16,182 @@ package org.springframework.util.xml; -import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.Set; import javax.xml.XMLConstants; import org.junit.Test; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +/** + * @author Arjen Poutsma + * @author Leo Arnold + */ public class SimpleNamespaceContextTests { - private final SimpleNamespaceContext context = new SimpleNamespaceContext() {{ - bindNamespaceUri("prefix", "namespaceURI"); - }}; + private final String unboundPrefix = "unbound"; + private final String prefix = "prefix"; + private final String namespaceUri = "http://Namespace-name-URI"; + private final String additionalNamespaceUri = "http://Additional-namespace-name-URI"; + private final String unboundNamespaceUri = "http://Unbound-namespace-name-URI"; + private final String defaultNamespaceUri = "http://Default-namespace-name-URI"; + + private final SimpleNamespaceContext context = new SimpleNamespaceContext(); + + @Test(expected = IllegalArgumentException.class) + public void getNamespaceURI_withNull() throws Exception { + context.getNamespaceURI(null); + } @Test public void getNamespaceURI() { - assertEquals("Invalid namespaceURI for default namespace", "", - context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX)); - String defaultNamespaceUri = "defaultNamespace"; - context.bindNamespaceUri(XMLConstants.DEFAULT_NS_PREFIX, defaultNamespaceUri); - assertEquals("Invalid namespaceURI for default namespace", defaultNamespaceUri, - context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX)); - assertEquals("Invalid namespaceURI for bound prefix", "namespaceURI", context.getNamespaceURI("prefix")); - assertEquals("Invalid namespaceURI for unbound prefix", "", context.getNamespaceURI("unbound")); - assertEquals("Invalid namespaceURI for namespace prefix", XMLConstants.XML_NS_URI, - context.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); - assertEquals("Invalid namespaceURI for attribute prefix", XMLConstants.XMLNS_ATTRIBUTE_NS_URI, - context.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); + context.bindNamespaceUri(XMLConstants.XMLNS_ATTRIBUTE, additionalNamespaceUri); + assertThat("Always returns \"http://www.w3.org/2000/xmlns/\" for \"xmlns\"", + context.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE), is(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); + context.bindNamespaceUri(XMLConstants.XML_NS_PREFIX, additionalNamespaceUri); + assertThat("Always returns \"http://www.w3.org/XML/1998/namespace\" for \"xml\"", + context.getNamespaceURI(XMLConstants.XML_NS_PREFIX), is(XMLConstants.XML_NS_URI)); + + assertThat("Returns \"\" for an unbound prefix", context.getNamespaceURI(unboundPrefix), + is(XMLConstants.NULL_NS_URI)); + context.bindNamespaceUri(prefix, namespaceUri); + assertThat("Returns the bound namespace URI for a bound prefix", context.getNamespaceURI(prefix), + is(namespaceUri)); + + assertThat("By default returns URI \"\" for the default namespace prefix", + context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX), is(XMLConstants.NULL_NS_URI)); + context.bindDefaultNamespaceUri(defaultNamespaceUri); + assertThat("Returns the set URI for the default namespace prefix", + context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX), is(defaultNamespaceUri)); + } + + @Test(expected = IllegalArgumentException.class) + public void getPrefix_withNull() throws Exception { + context.getPrefix(null); } @Test public void getPrefix() { - assertEquals("Invalid prefix for default namespace", XMLConstants.DEFAULT_NS_PREFIX, context.getPrefix("")); - assertEquals("Invalid prefix for bound namespace", "prefix", context.getPrefix("namespaceURI")); - assertNull("Invalid prefix for unbound namespace", context.getPrefix("unbound")); - assertEquals("Invalid prefix for namespace", XMLConstants.XML_NS_PREFIX, - context.getPrefix(XMLConstants.XML_NS_URI)); - assertEquals("Invalid prefix for attribute namespace", XMLConstants.XMLNS_ATTRIBUTE, - context.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); + assertThat("Always returns \"xmlns\" for \"http://www.w3.org/2000/xmlns/\"", + context.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI), is(XMLConstants.XMLNS_ATTRIBUTE)); + assertThat("Always returns \"xml\" for \"http://www.w3.org/XML/1998/namespace\"", + context.getPrefix(XMLConstants.XML_NS_URI), is(XMLConstants.XML_NS_PREFIX)); + + assertThat("Returns null for an unbound namespace URI", context.getPrefix(unboundNamespaceUri), + is(nullValue())); + context.bindNamespaceUri("prefix1", namespaceUri); + context.bindNamespaceUri("prefix2", namespaceUri); + assertThat("Returns a prefix for a bound namespace URI", context.getPrefix(namespaceUri), + anyOf(is("prefix1"), is("prefix2"))); + } + + @Test(expected = IllegalArgumentException.class) + public void getPrefixes_withNull() throws Exception { + context.getPrefixes(null); + } + + @Test(expected = UnsupportedOperationException.class) + public void getPrefixes_IteratorIsNotModifiable() throws Exception { + context.bindNamespaceUri(prefix, namespaceUri); + Iterator iterator = context.getPrefixes(namespaceUri); + iterator.remove(); } @Test public void getPrefixes() { - assertPrefixes("", XMLConstants.DEFAULT_NS_PREFIX); - assertPrefixes("namespaceURI", "prefix"); - assertFalse("Invalid prefix for unbound namespace", context.getPrefixes("unbound").hasNext()); - assertPrefixes(XMLConstants.XML_NS_URI, XMLConstants.XML_NS_PREFIX); - assertPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE); + assertThat("Returns only \"xmlns\" for \"http://www.w3.org/2000/xmlns/\"", + getItemSet(context.getPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)), + is(makeSet(XMLConstants.XMLNS_ATTRIBUTE))); + assertThat("Returns only \"xml\" for \"http://www.w3.org/XML/1998/namespace\"", + getItemSet(context.getPrefixes(XMLConstants.XML_NS_URI)), is(makeSet(XMLConstants.XML_NS_PREFIX))); + + assertThat("Returns empty iterator for unbound prefix", context.getPrefixes("unbound Namespace URI").hasNext(), + is(false)); + context.bindNamespaceUri("prefix1", namespaceUri); + context.bindNamespaceUri("prefix2", namespaceUri); + assertThat("Returns all prefixes (and only those) bound to the namespace URI", + getItemSet(context.getPrefixes(namespaceUri)), is(makeSet("prefix1", "prefix2"))); } - @Test - public void multiplePrefixes() { - context.bindNamespaceUri("prefix1", "namespace"); - context.bindNamespaceUri("prefix2", "namespace"); - Iterator iterator = context.getPrefixes("namespace"); - assertNotNull("getPrefixes returns null", iterator); - assertTrue("iterator is empty", iterator.hasNext()); - String result = iterator.next(); - assertTrue("Invalid prefix", result.equals("prefix1") || result.equals("prefix2")); - assertTrue("iterator is empty", iterator.hasNext()); - result = iterator.next(); - assertTrue("Invalid prefix", result.equals("prefix1") || result.equals("prefix2")); - assertFalse("iterator contains more than two values", iterator.hasNext()); + @Test(expected = IllegalArgumentException.class) + public void bindNamespaceUri_withNullNamespaceUri() { + context.bindNamespaceUri("prefix", null); } - private void assertPrefixes(String namespaceUri, String prefix) { - Iterator iterator = context.getPrefixes(namespaceUri); - assertNotNull("getPrefixes returns null", iterator); - assertTrue("iterator is empty", iterator.hasNext()); - String result = iterator.next(); - assertEquals("Invalid prefix", prefix, result); - assertFalse("iterator contains multiple values", iterator.hasNext()); + @Test(expected = IllegalArgumentException.class) + public void bindNamespaceUri_withNullPrefix() { + context.bindNamespaceUri(null, namespaceUri); + } + + @Test + public void bindNamespaceUri() { + context.bindNamespaceUri(prefix, namespaceUri); + assertThat("The Namespace URI was bound to the prefix", context.getNamespaceURI(prefix), is(namespaceUri)); + assertThat("The prefix was bound to the namespace URI", getItemSet(context.getPrefixes(namespaceUri)), + hasItem(prefix)); } @Test - public void getBoundPrefixes() throws Exception { - Iterator iterator = context.getBoundPrefixes(); - assertNotNull("getPrefixes returns null", iterator); - assertTrue("iterator is empty", iterator.hasNext()); - String result = iterator.next(); - assertEquals("Invalid prefix", "prefix", result); - assertFalse("iterator contains multiple values", iterator.hasNext()); + public void getBoundPrefixes() { + context.bindNamespaceUri("prefix1", namespaceUri); + context.bindNamespaceUri("prefix2", namespaceUri); + context.bindNamespaceUri("prefix3", additionalNamespaceUri); + assertThat("Returns all bound prefixes", getItemSet(context.getBoundPrefixes()), + is(makeSet("prefix1", "prefix2", "prefix3"))); } @Test - public void setBindings() throws Exception { - context.setBindings(Collections.singletonMap("prefix", "namespace")); - assertEquals("Invalid namespace uri", "namespace", context.getNamespaceURI("prefix")); + public void clear() { + context.bindNamespaceUri("prefix1", namespaceUri); + context.bindNamespaceUri("prefix2", namespaceUri); + context.bindNamespaceUri("prefix3", additionalNamespaceUri); + context.clear(); + assertThat("All bound prefixes were removed", context.getBoundPrefixes().hasNext(), is(false)); + assertThat("All bound namespace URIs were removed", context.getPrefixes(namespaceUri).hasNext(), is(false)); } @Test - public void removeBinding() throws Exception { - context.removeBinding("prefix"); - assertNull("Invalid prefix for unbound namespace", context.getPrefix("prefix")); + public void removeBinding() { + context.removeBinding(unboundPrefix); + + context.bindNamespaceUri(prefix, namespaceUri); + context.removeBinding(prefix); + assertThat("Returns default namespace URI for removed prefix", context.getNamespaceURI(prefix), + is(XMLConstants.NULL_NS_URI)); + assertThat("#getPrefix returns null when all prefixes for a namespace URI were removed", + context.getPrefix(namespaceUri), is(nullValue())); + assertThat("#getPrefixes returns an empty iterator when all prefixes for a namespace URI were removed", + context.getPrefixes(namespaceUri).hasNext(), is(false)); + + context.bindNamespaceUri("prefix1", additionalNamespaceUri); + context.bindNamespaceUri("prefix2", additionalNamespaceUri); + context.removeBinding("prefix1"); + assertThat("Prefix was unbound", context.getNamespaceURI("prefix1"), is(XMLConstants.NULL_NS_URI)); + assertThat("#getPrefix returns a bound prefix after removal of another prefix for the same namespace URI", + context.getPrefix(additionalNamespaceUri), is("prefix2")); + assertThat("Prefix was removed from namespace URI", getItemSet(context.getPrefixes(additionalNamespaceUri)), + is(makeSet("prefix2"))); + } + + + private Set getItemSet(Iterator iterator) { + Set itemSet = new HashSet(); + while (iterator.hasNext()) { + itemSet.add(iterator.next()); + } + return itemSet; + } + + private Set makeSet(String... items) { + Set itemSet = new HashSet(); + for (String item : items) { + itemSet.add(item); + } + return itemSet; } }