| /* Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.xmlbeans; |
| |
| import javax.xml.namespace.QName; |
| import java.util.*; |
| |
| /** |
| * Used to build {@link QNameSet QNameSets}. |
| */ |
| public class QNameSetBuilder implements QNameSetSpecification, java.io.Serializable |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private boolean _inverted; |
| private Set<String> _includedURIs; |
| private Set<QName> _excludedQNames; |
| private Set<QName> _includedQNames; |
| |
| /** |
| * Constructs an empty QNameSetBuilder. |
| */ |
| public QNameSetBuilder() |
| { |
| _inverted = false; |
| _includedURIs = new HashSet<>(); |
| _excludedQNames = new HashSet<>(); |
| _includedQNames = new HashSet<>(); |
| } |
| |
| /** |
| * Constructs a QNameSetBuilder whose initial contents are given by |
| * another QNameSetSpecification. |
| * @param set the QNameSetSpecification to copy |
| */ |
| public QNameSetBuilder(QNameSetSpecification set) |
| { |
| Set<String> includedURIs = set.includedURIs(); |
| if (includedURIs != null) |
| { |
| _inverted = false; |
| _includedURIs = new HashSet<>(includedURIs); |
| _excludedQNames = new HashSet<>(set.excludedQNamesInIncludedURIs()); |
| _includedQNames = new HashSet<>(set.includedQNamesInExcludedURIs()); |
| } |
| else |
| { |
| _inverted = true; |
| _includedURIs = new HashSet<>(set.excludedURIs()); |
| _excludedQNames = new HashSet<>(set.includedQNamesInExcludedURIs()); |
| _includedQNames = new HashSet<>(set.excludedQNamesInIncludedURIs()); |
| } |
| } |
| |
| /** |
| * Constructs a QNameSetBuilder whose inital contents are given by |
| * the four sets. Exactly one of either excludedURIs or includedURIs must |
| * be non-null. |
| * |
| * @param excludedURIs the finite set of namespace URI strings to exclude from the set, or null if this set is infinite |
| * @param includedURIs the finite set of namespace URI strings to include in the set, or null if this set is infinite |
| * @param excludedQNamesInIncludedURIs the finite set of exceptional QNames to exclude from the included namespaces |
| * @param includedQNamesInExcludedURIs the finite set of exceptional QNames to include that are in the excluded namespaces |
| */ |
| public QNameSetBuilder(Set<String> excludedURIs, Set<String> includedURIs, Set<QName> excludedQNamesInIncludedURIs, Set<QName> includedQNamesInExcludedURIs) |
| { |
| if (includedURIs != null && excludedURIs == null) |
| { |
| _inverted = false; |
| _includedURIs = new HashSet<>(includedURIs); |
| _excludedQNames = new HashSet<>(excludedQNamesInIncludedURIs); |
| _includedQNames = new HashSet<>(includedQNamesInExcludedURIs); |
| } |
| else if (excludedURIs != null && includedURIs == null) |
| { |
| _inverted = true; |
| _includedURIs = new HashSet<>(excludedURIs); |
| _excludedQNames = new HashSet<>(includedQNamesInExcludedURIs); |
| _includedQNames = new HashSet<>(excludedQNamesInIncludedURIs); |
| } |
| else |
| throw new IllegalArgumentException("Exactly one of excludedURIs and includedURIs must be null"); |
| } |
| |
| |
| /** |
| * Constructs a QNameSetBuilder whose initial contents are given |
| * as a list of namespace URIs, using the same format used by wildcards |
| * in XSD files. |
| * |
| * @param str a wildcard namespace specification string such as "##any", |
| * "##other", "##local", "##targetNamespace", or a space-separated |
| * list of URIs. |
| * @param targetURI the current targetNamespace |
| */ |
| public QNameSetBuilder(String str, String targetURI) |
| { |
| this(); |
| |
| if (str == null) |
| str = "##any"; |
| |
| String[] uri = splitList(str); |
| for (int i = 0; i < uri.length; i++) |
| { |
| String adduri = uri[i]; |
| if (adduri.startsWith("##")) |
| { |
| if (adduri.equals("##other")) |
| { |
| if (targetURI == null) |
| throw new IllegalArgumentException(); |
| QNameSetBuilder temp = new QNameSetBuilder(); |
| temp.addNamespace(targetURI); |
| temp.addNamespace(""); |
| temp.invert(); |
| addAll(temp); |
| continue; |
| } |
| else if (adduri.equals("##any")) |
| { |
| clear(); |
| invert(); |
| continue; |
| } |
| else if (uri[i].equals("##targetNamespace")) |
| { |
| if (targetURI == null) |
| throw new IllegalArgumentException(); |
| adduri = targetURI; |
| } |
| else if (uri[i].equals("##local")) |
| { |
| adduri = ""; |
| } |
| } |
| addNamespace(adduri); |
| } |
| } |
| |
| /** |
| * Local xml names are created using "" as the namespace. |
| */ |
| private static String nsFromName(QName QName) |
| { |
| String ns = QName.getNamespaceURI(); |
| return ns == null ? "" : ns; |
| } |
| |
| private static final String[] EMPTY_STRINGARRAY = new String[0]; |
| |
| private static boolean isSpace(char ch) |
| { |
| switch (ch) |
| { |
| case ' ': |
| case '\t': |
| case '\r': |
| case '\n': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private static String[] splitList(String s) |
| { |
| if (s.length() == 0) |
| return EMPTY_STRINGARRAY; |
| |
| List<String> result = new ArrayList<>(); |
| int i = 0; |
| int start = 0; |
| for (;;) |
| { |
| while (i < s.length() && isSpace(s.charAt(i))) |
| i += 1; |
| if (i >= s.length()) |
| return result.toArray(EMPTY_STRINGARRAY); |
| start = i; |
| while (i < s.length() && !isSpace(s.charAt(i))) |
| i += 1; |
| result.add(s.substring(start, i)); |
| } |
| } |
| |
| /** |
| * Remove all xml names from qnameset whose namespace matches the uri. |
| */ |
| private static void removeAllMatchingNs(String uri, Set<QName> qnameset) |
| { |
| for (Iterator<QName> i = qnameset.iterator(); i.hasNext(); ) |
| { |
| if (uri.equals(nsFromName(i.next()))) |
| i.remove(); |
| } |
| } |
| |
| /** |
| * Remove all xml names from qnameset whose namespace is in the |
| * first set of uris but not the second. |
| */ |
| private static void removeAllMatchingFirstOnly(Set<String> setFirst, Set<String> setSecond, Set<QName> qnameset) |
| { |
| for (Iterator<QName> i = qnameset.iterator(); i.hasNext(); ) |
| { |
| String ns = nsFromName(i.next()); |
| if (setFirst.contains(ns) && !setSecond.contains(ns)) |
| i.remove(); |
| } |
| } |
| |
| /** |
| * Remove all xml names from qnameset whose namespace is in both |
| * sets of uris. |
| */ |
| private static void removeAllMatchingBoth(Set<String> setFirst, Set<String> setSecond, Set<QName> qnameset) |
| { |
| for (Iterator<QName> i = qnameset.iterator(); i.hasNext(); ) |
| { |
| String ns = nsFromName(i.next()); |
| if (setFirst.contains(ns) && setSecond.contains(ns)) |
| i.remove(); |
| } |
| } |
| |
| /** |
| * Remove all xml names from qnameset whose namespace is in neither |
| * set of uris. |
| */ |
| private static void removeAllMatchingNeither(Set<String> setFirst, Set<String> setSecond, Set<QName> qnameset) |
| { |
| for (Iterator<QName> i = qnameset.iterator(); i.hasNext(); ) |
| { |
| String ns = nsFromName(i.next()); |
| if (!setFirst.contains(ns) && !setSecond.contains(ns)) |
| i.remove(); |
| } |
| } |
| |
| /** |
| * True if this ModelTransitionSet contains the given qname. |
| */ |
| public boolean contains(QName name) |
| { |
| boolean in = _includedURIs.contains(nsFromName(name)) ? |
| !_excludedQNames.contains(name) : |
| _includedQNames.contains(name); |
| return _inverted ^ in; |
| } |
| |
| /** |
| * True if this ModelTransitionSet contains all QNames. |
| */ |
| public boolean isAll() |
| { |
| return _inverted && _includedURIs.size() == 0 && _includedQNames.size() == 0; |
| } |
| |
| /** |
| * True if this ModelTransitionSet contains no QNames. |
| */ |
| public boolean isEmpty() |
| { |
| return !_inverted && _includedURIs.size() == 0 && _includedQNames.size() == 0; |
| } |
| |
| /** |
| * Returns a new QNameSet that is the intersection of this one and another. |
| */ |
| public QNameSet intersect(QNameSetSpecification set) |
| { |
| QNameSetBuilder result = new QNameSetBuilder(this); |
| result.restrict(set); |
| return result.toQNameSet(); |
| } |
| |
| /** |
| * Returns a new QNameSet that is the union of this one and another. |
| */ |
| public QNameSet union(QNameSetSpecification set) |
| { |
| QNameSetBuilder result = new QNameSetBuilder(this); |
| result.addAll(set); |
| return result.toQNameSet(); |
| } |
| |
| /** |
| * Returns a new QNameSet that is the inverse of this one. |
| */ |
| public QNameSet inverse() |
| { |
| return QNameSet.forSets(includedURIs(), excludedURIs(), includedQNamesInExcludedURIs(), excludedQNamesInIncludedURIs()); |
| } |
| |
| /** |
| * True if the parameter is a subset of this set. |
| */ |
| public boolean containsAll(QNameSetSpecification set) |
| { |
| if (!_inverted && set.excludedURIs() != null) |
| return false; |
| |
| return inverse().isDisjoint(set); |
| } |
| |
| /** |
| * True if the given set is disjoint from this one. |
| */ |
| public boolean isDisjoint(QNameSetSpecification set) |
| { |
| if (_inverted && set.excludedURIs() != null) |
| return false; |
| |
| if (_inverted) |
| return isDisjointImpl(set, this); |
| else |
| return isDisjointImpl(this, set); |
| } |
| |
| private boolean isDisjointImpl(QNameSetSpecification set1, QNameSetSpecification set2) |
| { |
| Set<String> includeURIs = set1.includedURIs(); |
| Set<String> otherIncludeURIs = set2.includedURIs(); |
| if (otherIncludeURIs != null) |
| { |
| for (Iterator<String> i = includeURIs.iterator(); i.hasNext(); ) |
| { |
| if (otherIncludeURIs.contains(i.next())) |
| return false; |
| } |
| } |
| else |
| { |
| Set<String> otherExcludeURIs = set2.excludedURIs(); |
| for (Iterator<String> i = includeURIs.iterator(); i.hasNext(); ) |
| { |
| if (!otherExcludeURIs.contains(i.next())) |
| return false; |
| } |
| } |
| |
| for (Iterator<QName> i = set1.includedQNamesInExcludedURIs().iterator(); i.hasNext(); ) |
| { |
| if (set2.contains(i.next())) |
| return false; |
| } |
| |
| if (includeURIs.size() > 0) |
| for (Iterator<QName> i = set2.includedQNamesInExcludedURIs().iterator(); i.hasNext(); ) |
| { |
| if (set1.contains(i.next())) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Clears this QNameSetBuilder |
| */ |
| public void clear() |
| { |
| _inverted = false; |
| _includedURIs.clear(); |
| _excludedQNames.clear(); |
| _includedQNames.clear(); |
| } |
| |
| /** |
| * Inverts this QNameSetBuilder. |
| */ |
| public void invert() |
| { |
| _inverted = !_inverted; |
| } |
| |
| /** |
| * Adds a single QName to this QNameSetBuilder. |
| */ |
| public void add(QName qname) |
| { |
| if (!_inverted) |
| addImpl(qname); |
| else |
| removeImpl(qname); |
| } |
| |
| /** |
| * Adds an entire namespace URI of QNames to this QNameSetBuilder. |
| * The empty string is used to signify the (local) no-namespace. |
| */ |
| public void addNamespace(String uri) |
| { |
| if (!_inverted) |
| addNamespaceImpl(uri); |
| else |
| removeNamespaceImpl(uri); |
| } |
| |
| /** |
| * Adds the contents of another QNameSet to this QNameSetBuilder. |
| */ |
| public void addAll(QNameSetSpecification set) |
| { |
| if (_inverted) |
| removeAllImpl(set.includedURIs(), set.excludedURIs(), set.includedQNamesInExcludedURIs(), set.excludedQNamesInIncludedURIs()); |
| else |
| addAllImpl(set.includedURIs(), set.excludedURIs(), set.includedQNamesInExcludedURIs(), set.excludedQNamesInIncludedURIs()); |
| } |
| |
| /** |
| * Removes the given qname from this QNameSetBuilder. |
| */ |
| public void remove(QName qname) |
| { |
| if (_inverted) |
| addImpl(qname); |
| else |
| removeImpl(qname); |
| } |
| |
| /** |
| * Removes an entire namespace URI from this QNameSetBuilder. |
| */ |
| public void removeNamespace(String uri) |
| { |
| if (_inverted) |
| addNamespaceImpl(uri); |
| else |
| removeNamespaceImpl(uri); |
| } |
| |
| /** |
| * Removes all contents of a given QNameSet from this QNameSetBuilder. |
| */ |
| public void removeAll(QNameSetSpecification set) |
| { |
| if (_inverted) |
| addAllImpl(set.includedURIs(), set.excludedURIs(), set.includedQNamesInExcludedURIs(), set.excludedQNamesInIncludedURIs()); |
| else |
| removeAllImpl(set.includedURIs(), set.excludedURIs(), set.includedQNamesInExcludedURIs(), set.excludedQNamesInIncludedURIs()); |
| } |
| |
| /** |
| * Restricts the contents of this QNameSetBuilder to be a subset of the |
| * given QNameSet. In other words, computes an intersection. |
| */ |
| public void restrict(QNameSetSpecification set) |
| { |
| if (_inverted) |
| addAllImpl(set.excludedURIs(), set.includedURIs(), set.excludedQNamesInIncludedURIs(), set.includedQNamesInExcludedURIs()); |
| else |
| removeAllImpl(set.excludedURIs(), set.includedURIs(), set.excludedQNamesInIncludedURIs(), set.includedQNamesInExcludedURIs()); |
| } |
| |
| /** |
| * Implementation of add(qname) that ignores inversion. |
| */ |
| private void addImpl(QName qname) |
| { |
| if (_includedURIs.contains(nsFromName(qname))) |
| _excludedQNames.remove(qname); |
| else |
| _includedQNames.add(qname); |
| } |
| |
| /** |
| * Implementation of add(ns) that ignores inversion. |
| */ |
| private void addNamespaceImpl(String uri) |
| { |
| if (_includedURIs.contains(uri)) |
| { |
| removeAllMatchingNs(uri, _excludedQNames); |
| } |
| else |
| { |
| removeAllMatchingNs(uri, _includedQNames); |
| _includedURIs.add(uri); |
| } |
| } |
| |
| /** |
| * Implementation of add(set) that ignores inversion. |
| */ |
| private void addAllImpl(Set<String> includedURIs, Set<String> excludedURIs, Set<QName> includedQNames, Set<QName> excludedQNames) |
| { |
| boolean exclude = (excludedURIs != null); |
| Set<String> specialURIs = exclude ? excludedURIs : includedURIs; |
| |
| for (Iterator<QName> i = _excludedQNames.iterator(); i.hasNext(); ) |
| { |
| QName name = i.next(); |
| String uri = nsFromName(name); |
| if ((exclude ^ specialURIs.contains(uri)) && !excludedQNames.contains(name)) |
| i.remove(); |
| } |
| |
| for (Iterator<QName> i = excludedQNames.iterator(); i.hasNext(); ) |
| { |
| QName name = i.next(); |
| String uri = nsFromName(name); |
| if (!_includedURIs.contains(uri) && !_includedQNames.contains(name)) |
| _excludedQNames.add(name); |
| } |
| |
| for (Iterator<QName> i = includedQNames.iterator(); i.hasNext(); ) |
| { |
| QName name = i.next(); |
| String uri = nsFromName(name); |
| if (!_includedURIs.contains(uri)) |
| _includedQNames.add(name); |
| else |
| _excludedQNames.remove(name); |
| } |
| |
| if (!exclude) |
| { |
| removeAllMatchingFirstOnly(includedURIs, _includedURIs, _includedQNames); |
| _includedURIs.addAll(includedURIs); |
| } |
| else |
| { |
| removeAllMatchingNeither(excludedURIs, _includedURIs, _includedQNames); |
| for (Iterator<String> i = _includedURIs.iterator(); i.hasNext(); ) |
| { |
| String uri = i.next(); |
| if (!excludedURIs.contains(uri)) |
| i.remove(); |
| } |
| |
| for (Iterator<String> i = excludedURIs.iterator(); i.hasNext(); ) |
| { |
| String uri = i.next(); |
| if (!_includedURIs.contains(uri)) |
| _includedURIs.add(uri); |
| else |
| _includedURIs.remove(uri); |
| } |
| Set<QName> temp = _excludedQNames; |
| _excludedQNames = _includedQNames; |
| _includedQNames = temp; |
| _inverted = !_inverted; |
| } |
| } |
| |
| /** |
| * Implementation of remove(qname) that ignores inversion. |
| */ |
| private void removeImpl(QName qname) |
| { |
| if (_includedURIs.contains(nsFromName(qname))) |
| _excludedQNames.add(qname); |
| else |
| _includedQNames.remove(qname); |
| } |
| |
| /** |
| * Implementation of remove(ns) that ignores inversion. |
| */ |
| private void removeNamespaceImpl(String uri) |
| { |
| if (_includedURIs.contains(uri)) |
| { |
| removeAllMatchingNs(uri, _excludedQNames); |
| _includedURIs.remove(uri); |
| } |
| else |
| { |
| removeAllMatchingNs(uri, _includedQNames); |
| } |
| } |
| |
| /** |
| * Implementation of remove(set) that ignores inversion. |
| */ |
| private void removeAllImpl(Set<String> includedURIs, Set<String> excludedURIs, Set<QName> includedQNames, Set<QName> excludedQNames) |
| { |
| boolean exclude = (excludedURIs != null); |
| Set<String> specialURIs = exclude ? excludedURIs : includedURIs; |
| |
| for (Iterator<QName> i = _includedQNames.iterator(); i.hasNext(); ) |
| { |
| QName name = i.next(); |
| String uri = nsFromName(name); |
| if (exclude ^ specialURIs.contains(uri)) |
| { |
| if (!excludedQNames.contains(name)) |
| i.remove(); |
| } |
| else |
| { |
| if (includedQNames.contains(name)) |
| i.remove(); |
| } |
| } |
| |
| for (Iterator<QName> i = includedQNames.iterator(); i.hasNext(); ) |
| { |
| QName name = i.next(); |
| String uri = nsFromName(name); |
| if (_includedURIs.contains(uri)) |
| _excludedQNames.add(name); |
| } |
| |
| for (Iterator<QName> i = excludedQNames.iterator(); i.hasNext(); ) |
| { |
| QName name = i.next(); |
| String uri = nsFromName(name); |
| if (_includedURIs.contains(uri) && !_excludedQNames.contains(name)) |
| _includedQNames.add(name); |
| } |
| |
| if (exclude) |
| { |
| removeAllMatchingFirstOnly(_includedURIs, excludedURIs, _excludedQNames); |
| } |
| else |
| { |
| removeAllMatchingBoth(_includedURIs, includedURIs, _excludedQNames); |
| } |
| |
| for (Iterator<String> i = _includedURIs.iterator(); i.hasNext(); ) |
| { |
| if (exclude ^ specialURIs.contains(i.next())) |
| i.remove(); |
| } |
| } |
| |
| public Set<String> excludedURIs() |
| { |
| if (_inverted) return Collections.unmodifiableSet(_includedURIs); |
| return null; |
| } |
| |
| public Set<String> includedURIs() |
| { |
| if (!_inverted) return _includedURIs; |
| return null; |
| } |
| |
| public Set<QName> excludedQNamesInIncludedURIs() |
| { |
| return Collections.unmodifiableSet(_inverted ? _includedQNames : _excludedQNames); |
| } |
| |
| public Set<QName> includedQNamesInExcludedURIs() |
| { |
| return Collections.unmodifiableSet(_inverted ? _excludedQNames : _includedQNames); |
| } |
| |
| private String prettyQName(QName name) |
| { |
| if (name.getNamespaceURI() == null) |
| return name.getLocalPart(); |
| return name.getLocalPart() + "@" + name.getNamespaceURI(); |
| } |
| |
| /** |
| * Returns a string representation useful for debugging, subject to change. |
| */ |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("QNameSetBuilder"); |
| sb.append(_inverted ? "-(" : "+("); |
| for (Iterator<String> i = _includedURIs.iterator(); i.hasNext(); ) |
| { |
| sb.append("+*@"); |
| sb.append(i.next()); |
| sb.append(", "); |
| } |
| for (Iterator<QName> i = _excludedQNames.iterator(); i.hasNext(); ) |
| { |
| sb.append("-"); |
| sb.append(prettyQName(i.next())); |
| sb.append(", "); |
| } |
| for (Iterator<QName> i = _includedQNames.iterator(); i.hasNext(); ) |
| { |
| sb.append("+"); |
| sb.append(prettyQName(i.next())); |
| sb.append(", "); |
| } |
| int index = sb.lastIndexOf(", "); |
| if (index > 0) |
| sb.setLength(index); |
| sb.append(')'); |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns a {@link QNameSet} equivalent to the current state of this |
| * QNameSetBuilder. |
| */ |
| public QNameSet toQNameSet() |
| { |
| return QNameSet.forSpecification(this); |
| } |
| } |