blob: c423e1bc09978a7a4e51abe0293a22cc687fb712 [file] [log] [blame]
/* 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.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collections;
import java.util.Arrays;
/**
* This interface represents a lattice of finite and infinite sets of QNames.
* The lattice the minimal one that is closed under union, intersection, and
* inverse, and contains individual QNames as well as entire namespaces.
* Here is a summary of the two kinds of QNameSets:
* <ul>
* <li>A QNameSet can cover a finite set of namespaces, additionally including a finite
* set of QNames outside those namespaces, and with the exception of
* a finite set of QNames excluded from those namespaes:
* <ul>
* <li>excludedQNamesInIncludedURIs == the set of excluded QNames from coveredURIs namespaces
* <li>excludedURIs == null
* <li>includedURIs == the set of covered namespace URIs
* <li>includedQNamesInExcludedURIs == set of additional QNames outside coveredURIs namespaces
* </ul>
* </li>
* <li>A QNameSet can cover all namespaces except for a finite number of excluded ones,
* additionally including a finite set of QNames within the excluded namespaces,
* and with the exception of a finite set of QNames outside the excluded namespaces:
* <ul>
* <li>excludedQNamesInIncludedURIs == the set of excluded QNames outside uncoveredURIs namespaces
* <li>excludedURIs == the set of uncovered namespace URIs
* <li>includedURIs == null
* <li>includedQNamesInExcludedURIs == set of additional QNames from uncoveredURIs namespaces
* </ul>
* </li>
* </ul>
* <p>
* Notice that a finite set of QNames is a degenerate case of the first
* category outlined above:
* <ul>
* <li>A QnameSet can contain a finite number of QNames:
* <ul>
* <li>excludedQNamesInIncludedURIs == empty set
* <li>excludedURIs == null
* <li>includedURIs == empty set
* <li>includedQNamesInExcludedURIs == set of included QNames
* </ul>
* </li>
* </ul>
*
* @see QNameSetBuilder
*/
public final class QNameSet implements QNameSetSpecification, java.io.Serializable
{
private static final long serialVersionUID = 1L;
private final boolean _inverted;
private final Set _includedURIs;
private final Set _excludedQNames;
private final Set _includedQNames;
/**
* The empty QNameSet.
*/
public static final QNameSet EMPTY = new QNameSet(null, Collections.EMPTY_SET, Collections.EMPTY_SET, Collections.EMPTY_SET);
/**
* The QNameSet containing all QNames.
*/
public static final QNameSet ALL = new QNameSet(Collections.EMPTY_SET, null, Collections.EMPTY_SET, Collections.EMPTY_SET);
/**
* The QNameSet containing all QNames in the local (no-)namespace.
*/
public static final QNameSet LOCAL = new QNameSet(null, Collections.singleton(""), Collections.EMPTY_SET, Collections.EMPTY_SET);
/**
* The QNameSet containing all QNames except for those in the local (no-)namespace.
*/
public static final QNameSet NONLOCAL = new QNameSet(Collections.singleton(""), null, Collections.EMPTY_SET, Collections.EMPTY_SET);
/**
* Private function to minimize object creation when copying sets.
*/
private static Set minSetCopy(Set original)
{
if (original == null)
return null;
if (original.isEmpty())
return Collections.EMPTY_SET;
if (original.size() == 1)
return Collections.singleton(original.iterator().next());
return new HashSet(original);
}
/**
* Returns a QNameSet based on the given sets of excluded URIs,
* included URIs, excluded QNames in included namespaces, and included
* QNames in excluded namespaces.
*
* @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 excludedQNamesInIncludedURIs the finite set of exceptional QNames to include that are in the excluded namespaces
*
* @return the constructed QNameSet
*/
public static QNameSet forSets(Set excludedURIs, Set includedURIs, Set excludedQNamesInIncludedURIs, Set includedQNamesInExcludedURIs)
{
if ((excludedURIs != null) == (includedURIs != null))
throw new IllegalArgumentException("Exactly one of excludedURIs and includedURIs must be null");
if (excludedURIs == null && includedURIs.isEmpty() && includedQNamesInExcludedURIs.isEmpty())
return EMPTY;
if (includedURIs == null && excludedURIs.isEmpty() && excludedQNamesInIncludedURIs.isEmpty())
return ALL;
if (excludedURIs == null && includedURIs.size() == 1 && includedURIs.contains("") &&
includedQNamesInExcludedURIs.isEmpty() && excludedQNamesInIncludedURIs.isEmpty())
return LOCAL;
if (includedURIs == null && excludedURIs.size() == 1 && excludedURIs.contains("") &&
excludedQNamesInIncludedURIs.isEmpty() && includedQNamesInExcludedURIs.isEmpty())
return NONLOCAL;
return new QNameSet(
minSetCopy(excludedURIs),
minSetCopy(includedURIs),
minSetCopy(excludedQNamesInIncludedURIs),
minSetCopy(includedQNamesInExcludedURIs));
}
/**
* Returns a QNameSet based on the given array of included QNames
*
* @param includedQNames the array of included QNames
*/
public static QNameSet forArray(QName[] includedQNames)
{
if (includedQNames == null)
throw new IllegalArgumentException("includedQNames cannot be null");
return new QNameSet(null, Collections.EMPTY_SET, Collections.EMPTY_SET, new HashSet(Arrays.asList(includedQNames)));
}
/**
* Returns a QNameSet with the same contents as the given
* QNameSetSpecification.
* @return the copied QNameSet
*/
public static QNameSet forSpecification(QNameSetSpecification spec)
{
if (spec instanceof QNameSet)
return (QNameSet)spec;
return QNameSet.forSets(spec.excludedURIs(), spec.includedURIs(), spec.excludedQNamesInIncludedURIs(), spec.includedQNamesInExcludedURIs());
}
/**
* Returns a QNameSet corresponding to the given wildcard namespace string.
* This is a space-separated list of URIs, plus special tokens as specified
* in the XML Schema specification (##any, ##other, ##targetNamespace, ##local).
* @return the constructed QNameSet
*/
public static QNameSet forWildcardNamespaceString(String wildcard, String targetURI)
{
return QNameSet.forSpecification(new QNameSetBuilder(wildcard, targetURI));
}
/**
* Returns a QNameSet containing only the given QName.
* @return the constructed QNameSet
*/
public static QNameSet singleton(QName name)
{
return new QNameSet(null, Collections.EMPTY_SET, Collections.EMPTY_SET, Collections.singleton(name));
}
/**
* Constructs a QNameSetBuilder whose contents are given by
* the four sets.
*
* This constuctor is PRIVATE because it uses the given
* sets directly, and it trusts its callers to set only immutable values.
* This constructor is is only called by the static builder methods on
* QNameSet: those methods are all careful assign only unchanging sets.
*/
private QNameSet(Set excludedURIs, Set includedURIs, Set excludedQNamesInIncludedURIs, Set includedQNamesInExcludedURIs)
{
if (includedURIs != null && excludedURIs == null)
{
_inverted = false;
_includedURIs = includedURIs;
_excludedQNames = excludedQNamesInIncludedURIs;
_includedQNames = includedQNamesInExcludedURIs;
}
else if (excludedURIs != null && includedURIs == null)
{
_inverted = true;
_includedURIs = excludedURIs;
_excludedQNames = includedQNamesInExcludedURIs;
_includedQNames = excludedQNamesInIncludedURIs;
}
else
throw new IllegalArgumentException("Exactly one of excludedURIs and includedURIs must be null");
}
/**
* Local xml names are hased using "" as the namespace.
*/
private static String nsFromName(QName xmlName)
{
String ns = xmlName.getNamespaceURI();
return ns == null ? "" : ns;
}
/**
* 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.isEmpty() && _includedQNames.isEmpty();
}
/**
* True if this ModelTransitionSet contains no QNames.
*/
public boolean isEmpty()
{
return !_inverted && _includedURIs.isEmpty() && _includedQNames.isEmpty();
}
/**
* Returns a new QNameSet that is the intersection of this one and another.
* @param set the set to insersect with
* @return the intersection
*/
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.
* @param set the set to union with
* @return the union
*/
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()
{
if (this == EMPTY)
return ALL;
if (this == ALL)
return EMPTY;
if (this == LOCAL)
return NONLOCAL;
if (this == NONLOCAL)
return LOCAL;
return new QNameSet(includedURIs(), excludedURIs(), includedQNamesInExcludedURIs(), excludedQNamesInIncludedURIs());
}
/**
* True if the given set is a subset of this one.
* @param set the set to test
* @return true if this contains all QNames contained by the given set
*/
public boolean containsAll(QNameSetSpecification set)
{
// a.contains(b) == a.inverse.isDisjoint(b)
if (!_inverted && set.excludedURIs() != null)
return false;
return inverse().isDisjoint(set);
}
/**
* True if the given set is disjoint from this one.
* @param set the set to test
* @return true if the set is disjoint from this set
*/
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 includeURIs = set1.includedURIs();
Set otherIncludeURIs = set2.includedURIs();
if (otherIncludeURIs != null)
{
for (Iterator i = includeURIs.iterator(); i.hasNext(); )
{
if (otherIncludeURIs.contains(i.next()))
return false;
}
}
else
{
Set otherExcludeURIs = set2.excludedURIs();
for (Iterator i = includeURIs.iterator(); i.hasNext(); )
{
if (!otherExcludeURIs.contains(i.next()))
return false;
}
}
for (Iterator i = set1.includedQNamesInExcludedURIs().iterator(); i.hasNext(); )
{
if (set2.contains((QName)i.next()))
return false;
}
if (includeURIs.size() > 0)
for (Iterator i = set2.includedQNamesInExcludedURIs().iterator(); i.hasNext(); )
{
if (set1.contains((QName)i.next()))
return false;
}
return true;
}
/**
* Namespaces that are fully excluded from the set except for a finite
* number of individual QName exceptions. Returns null if this set is infinite.
* @return the set of excluded namespace URI strings
*/
public Set excludedURIs()
{
if (_inverted) return Collections.unmodifiableSet(_includedURIs);
return null;
}
/**
* Namespaces that are fully included in set except for a finite
* number of individual QName exceptions. Returns null if this set is infinite.
* @return the set of included namespace URI strings
*/
public Set includedURIs()
{
if (!_inverted) return _includedURIs;
return null;
}
/**
* The set of QNames excluded from the set even though they are within
* a namespace that is otherwise fully included in the set.
* @return the set of excluded QNames from within includedURI namespaces
*/
public Set excludedQNamesInIncludedURIs()
{
return Collections.unmodifiableSet(_inverted ? _includedQNames : _excludedQNames);
}
/**
* The set of QNames included in the set even though they are within
* a namespace that is otherwise fully included in the set.
* @return the set of included QNames from within excludedURI namespaces
*/
public Set 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("QNameSet");
sb.append(_inverted ? "-(" : "+(");
for (Iterator i = _includedURIs.iterator(); i.hasNext(); )
{
sb.append("+*@");
sb.append(i.next());
sb.append(", ");
}
for (Iterator i = _excludedQNames.iterator(); i.hasNext(); )
{
sb.append("-");
sb.append(prettyQName((QName)i.next()));
sb.append(", ");
}
for (Iterator i = _includedQNames.iterator(); i.hasNext(); )
{
sb.append("+");
sb.append(prettyQName((QName)i.next()));
sb.append(", ");
}
int index = sb.lastIndexOf(", ");
if (index > 0)
sb.setLength(index);
sb.append(')');
return sb.toString();
}
}