org.jdom2.AttributeList Maven / Gradle / Ivy
Show all versions of jdom Show documentation
/*--
Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer that follows
these conditions in the documentation and/or other materials
provided with the distribution.
3. The name "JDOM" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact .
4. Products derived from this software may not be called "JDOM", nor
may "JDOM" appear in their name, without prior written permission
from the JDOM Project Management .
In addition, we request (but do not require) that you include in the
end-user documentation provided with the redistribution and/or in the
software itself an acknowledgement equivalent to the following:
"This product includes software developed by the
JDOM Project (http://www.jdom.org/)."
Alternatively, the acknowledgment may be graphical using the logos
available at http://www.jdom.org/images/logos.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
This software consists of voluntary contributions made by many
individuals on behalf of the JDOM Project and was originally
created by Jason Hunter and
Brett McLaughlin . For more information
on the JDOM Project, please see .
*/
package org.jdom2;
import java.util.*;
import org.jdom2.internal.ArrayCopy;
/**
* AttributeList
represents legal JDOM
* {@link Attribute}
content.
*
* This class is NOT PUBLIC; users should see it as a simple List
* implementation, although it behaves something like a Set because you cannot
* add duplicate Attributes. An attribute is considered duplicate if it has the
* same Namespace URI and Attribute name as another existing Attribute.
*
* @author Alex Rosen
* @author Philippe Riand
* @author Bradley S. Huffman
* @author Rolf Lear
*/
final class AttributeList extends AbstractList
implements RandomAccess {
/** The initial size to start the backing array. */
private static final int INITIAL_ARRAY_SIZE = 4;
/** The backing array */
private Attribute attributeData[];
/** The current size */
private int size;
/** The parent Element */
private final Element parent;
/**
* Create a new instance of the AttributeList representing parent
* Element's Attributes
*
* @param parent
* Element whose Attributes are to be held
*/
AttributeList(final Element parent) {
this.parent = parent;
}
/**
* Package internal method to support building from sources that are 100%
* trusted.
*
* @param a
* an Attribute to add without any checks
*/
final void uncheckedAddAttribute(final Attribute a) {
a.parent = parent;
ensureCapacity(size + 1);
attributeData[size++] = a;
modCount++;
}
/**
* Check and add attribute to the end of the list or replace an
* existing Attribute
with the same name and
* Namespace
.
*
* @param attribute
* The Attribute
to insert into the list.
* @return true as specified by Collection.add()
.
* @throws IllegalAddException
* if validation rules prevent the add
*/
@Override
public boolean add(final Attribute attribute) {
final int duplicate = indexOfDuplicate(attribute);
if (duplicate < 0) {
add(size(), attribute);
}
else {
set(duplicate, attribute);
}
return true;
}
/**
* Check and add attribute to this list at index.
*
* @param index
* where to add/insert the Attribute
* @param attribute
* Attribute
to add
* @throws IllegalAddException
* if validation rules prevent the add
*/
@Override
public void add(final int index, final Attribute attribute) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index +
" Size: " + size());
}
if (attribute.getParent() != null) {
throw new IllegalAddException(
"The attribute already has an existing parent \"" +
attribute.getParent().getQualifiedName() + "\"");
}
final int duplicate = indexOfDuplicate(attribute);
if (duplicate >= 0) {
throw new IllegalAddException("Cannot add duplicate attribute");
}
final String reason = Verifier.checkNamespaceCollision(attribute, parent);
if (reason != null) {
throw new IllegalAddException(parent, attribute, reason);
}
attribute.setParent(parent);
ensureCapacity(size + 1);
if (index == size) {
attributeData[size++] = attribute;
} else {
System.arraycopy(attributeData, index, attributeData, index + 1,
size - index);
attributeData[index] = attribute;
size++;
}
modCount++;
}
/**
* Add all the Attributes
in collection.
*
* @param collection
* The Collection
of Attributes
to add.
* @return true
if the list was modified as a result of the
* add.
* @throws IllegalAddException
* if validation rules prevent the addAll
*/
@Override
public boolean addAll(final Collection extends Attribute> collection) {
return addAll(size(), collection);
}
/**
* Inserts the Attributes
in collection at the specified
* index in this list.
*
* @param index
* The offset at which to start adding the Attributes
* @param collection
* The Collection
containing the Attributes
* to add.
* @return true
if the list was modified as a result of the
* add.
* @throws IllegalAddException
* if validation rules prevent the addAll
*/
@Override
public boolean addAll(final int index,
final Collection extends Attribute> collection) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index +
" Size: " + size());
}
if (collection == null) {
throw new NullPointerException(
"Can not add a null Collection to AttributeList");
}
final int addcnt = collection.size();
if (addcnt == 0) {
return false;
}
if (addcnt == 1) {
// quick check for single-add.
add(index, collection.iterator().next());
return true;
}
ensureCapacity(size() + addcnt);
final int tmpmodcount = modCount;
boolean ok = false;
int count = 0;
try {
for (Attribute att : collection) {
add(index + count, att);
count++;
}
ok = true;
} finally {
if (!ok) {
while (--count >= 0) {
remove(index + count);
}
modCount = tmpmodcount;
}
}
return true;
}
/**
* Clear the current list.
*/
@Override
public void clear() {
if (attributeData != null) {
while (size > 0) {
size--;
attributeData[size].setParent(null);
attributeData[size] = null;
}
}
modCount++;
}
/**
* Clear the current list and set it to the contents of collection.
*
* @param collection
* The Collection
to use.
* @throws IllegalAddException
* if validation rules prevent the addAll
*/
void clearAndSet(final Collection extends Attribute> collection) {
if (collection == null || collection.isEmpty()) {
clear();
return;
}
// keep a backup in case we need to roll-back...
final Attribute[] old = attributeData;
final int oldSize = size;
final int oldModCount = modCount;
// clear the current system
// we need to detatch before we add so that we don't run in to a problem
// where an attribute in the to-add list is one that we are 'clearing'
// first.
while (size > 0) {
old[--size].setParent(null);
}
size = 0;
attributeData = null;
boolean ok = false;
try {
addAll(0, collection);
ok = true;
} finally {
if (!ok) {
// we have an exception pending....
// restore the old system.
// re-attach the old stuff
attributeData = old;
while (size < oldSize) {
attributeData[size++].setParent(parent);
}
modCount = oldModCount;
}
}
}
/**
* Increases the capacity of this AttributeList
instance, if
* necessary, to ensure that it can hold at least the number of items
* specified by the minimum capacity argument.
*
* @param minCapacity
* the desired minimum capacity.
*/
private void ensureCapacity(final int minCapacity) {
if (attributeData == null) {
attributeData =
new Attribute[Math.max(minCapacity, INITIAL_ARRAY_SIZE)];
return;
} else if (minCapacity < attributeData.length) {
return;
}
// most JVM's allocate memory in multiples of 'double-words', on
// 64-bit it's 16-bytes, on 32-bit it's 8 bytes which all means it makes
// sense to increment the capacity in even values.
attributeData = ArrayCopy.copyOf(attributeData,
((minCapacity + INITIAL_ARRAY_SIZE) >>> 1) << 1);
}
/**
* Retrieve the Attribute
at offset.
*
* @param index
* The position of the Attribute
to retrieve.
* @return The Attribute
at position index.
*/
@Override
public Attribute get(final int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index +
" Size: " + size());
}
return attributeData[index];
}
/**
* Retrieve the Attribute
with the given name and the same
* Namespace
URI as namespace.
*
* @param name
* name of attribute to return
* @param namespace
* indicate what Namespace
URI to consider
* @return the Attribute
, or null if one doesn't exist.
*/
Attribute get(final String name, final Namespace namespace) {
final int index = indexOf(name, namespace);
if (index < 0) {
return null;
}
return attributeData[index];
}
/**
* Return index of the Attribute
with the given name and
* the same Namespace URI as namespace.
*
* @param name
* name of Attribute
to retrieve
* @param namespace
* indicate what Namespace
URI to consider
* @return the index of the attribute that matches the conditions, or
* -1
if there is none.
*/
int indexOf(final String name, final Namespace namespace) {
if (attributeData != null) {
if (namespace == null) {
return indexOf(name, Namespace.NO_NAMESPACE);
}
final String uri = namespace.getURI();
for (int i = 0; i < size; i++) {
final Attribute att = attributeData[i];
if (uri.equals(att.getNamespaceURI()) &&
name.equals(att.getName())) {
return i;
}
}
}
return -1;
}
/**
* Remove the Attribute
at index.
*
* @param index
* The offset of the Attribute
to remove.
* @return The removed Attribute
.
*/
@Override
public Attribute remove(final int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index +
" Size: " + size());
final Attribute old = attributeData[index];
old.setParent(null);
System.arraycopy(attributeData, index + 1, attributeData, index,
size - index - 1);
attributeData[--size] = null; // Let gc do its work
modCount++;
return old;
}
/**
* Remove the Attribute
with the specified name and the same
* URI as namespace.
*
* @param name
* name of Attribute
to remove
* @param namespace
* indicate what Namespace
URI to consider
* @return the true
if attribute was removed,
* false
otherwise
*/
boolean remove(final String name, final Namespace namespace) {
final int index = indexOf(name, namespace);
if (index < 0) {
return false;
}
remove(index);
return true;
}
/**
* Set the Attribute
at index to be attribute.
*
* @param index
* The location to set the value to.
* @param attribute
* The Attribute
to set.
* @return The replaced Attribute
.
* @throws IllegalAddException
* if validation rules prevent the set
*/
@Override
public Attribute set(final int index, final Attribute attribute) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index +
" Size: " + size());
if (attribute.getParent() != null) {
throw new IllegalAddException(
"The attribute already has an existing parent \"" +
attribute.getParent().getQualifiedName() + "\"");
}
final int duplicate = indexOfDuplicate(attribute);
if ((duplicate >= 0) && (duplicate != index)) {
throw new IllegalAddException("Cannot set duplicate attribute");
}
final String reason = Verifier.checkNamespaceCollision(attribute, parent);
if (reason != null) {
throw new IllegalAddException(parent, attribute, reason);
}
final Attribute old = attributeData[index];
old.setParent(null);
attributeData[index] = attribute;
attribute.setParent(parent);
return old;
}
/**
* Return index of attribute with same name and Namespace, or -1 if one
* doesn't exist
*/
private int indexOfDuplicate(final Attribute attribute) {
return indexOf(attribute.getName(), attribute.getNamespace());
}
/**
* Returns an Iterator
over the Attributes
in this
* list in the proper sequence.
*
* @return an iterator.
*/
@Override
public Iterator iterator() {
return new ALIterator();
}
/**
* Return the number of Attributes
in this list
*
* @return The number of Attributes
in this list.
*/
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* Return this list as a String
*/
@Override
public String toString() {
return super.toString();
}
/**
* Unlike the Arrays.binarySearch, this method never expects an
* "already exists" condition, we only ever add, thus there will never
* be a negative insertion-point.
* @param indexes The pointers to search within
* @param len The number of pointers to search within
* @param val The pointer we are checking for.
* @param comp The Comparator to compare with
* @return the insertion point.
*/
private final int binarySearch(final int[] indexes, final int len,
final int val, final Comparator super Attribute> comp) {
int left = 0, mid = 0, right = len - 1, cmp = 0;
final Attribute base = attributeData[val];
while (left <= right) {
mid = (left + right) >>> 1;
cmp = comp.compare(base, attributeData[indexes[mid]]);
if (cmp == 0) {
while (cmp == 0 && mid < right && comp.compare(
base, attributeData[indexes[mid + 1]]) == 0) {
mid++;
}
return mid + 1;
} else if (cmp < 0) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
private void sortInPlace(final int[] indexes) {
// the indexes are a discrete set of values that have no duplicates,
// and describe the relative order of each of them.
// as a result, we can do some tricks....
final int[] unsorted = ArrayCopy.copyOf(indexes, indexes.length);
Arrays.sort(unsorted);
final Attribute[] usc = new Attribute[unsorted.length];
for (int i = 0; i < usc.length; i++) {
usc[i] = attributeData[indexes[i]];
}
// usc contains the content in their pre-sorted order....
for (int i = 0; i < indexes.length; i ++) {
attributeData[unsorted[i]] = usc[i];
}
}
/**
* Sort the attributes using the supplied comparator. The attributes are
* never added using regular mechanisms, so there are never problems with
* detached or already-attached Attributes. The sort happens 'in place'.
*
* If the comparator identifies two (or more) Attributes to be equal, then
* the relative order of those attributes will not be changed.
*
* @param comp The Comparator to use for sorting.
*/
void sort(Comparator super Attribute> comp) {
final int sz = size;
int[] indexes = new int[sz];
for (int i = 0 ; i < sz; i++) {
final int ip = binarySearch(indexes, i, i, comp);
if (ip < i) {
System.arraycopy(indexes, ip, indexes, ip+1, i - ip);
}
indexes[ip] = i;
}
sortInPlace(indexes);
}
/* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * */
/* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * */
/**
* A fast iterator that can beat AbstractList because we can access the data
* directly. This is important because so much code now uses the for-each
* type loop for (Attribute a : element.getAttributes()) {...}
,
* and that uses iterator().
*
* @author Rolf Lear
*/
private final class ALIterator implements Iterator {
// The modCount to expect (or throw ConcurrentModeEx)
private int expect = -1;
// the index of the next Attribute to return.
private int cursor = 0;
// whether it is legal to call remove()
private boolean canremove = false;
private ALIterator() {
expect = modCount;
}
@Override
public boolean hasNext() {
return cursor < size;
}
@Override
public Attribute next() {
if (modCount != expect) {
throw new ConcurrentModificationException("ContentList was " +
"modified outside of this Iterator");
}
if (cursor >= size) {
throw new NoSuchElementException("Iterated beyond the end of " +
"the ContentList.");
}
canremove = true;
return attributeData[cursor++];
}
@Override
public void remove() {
if (modCount != expect) {
throw new ConcurrentModificationException("ContentList was " +
"modified outside of this Iterator");
}
if (!canremove) {
throw new IllegalStateException("Can only remove() content " +
"after a call to next()");
}
AttributeList.this.remove(--cursor);
expect = modCount;
canremove = false;
}
}
}