| GroupDescriptor.java |
/*
* $Id: GroupDescriptor.java,v 1.34 2012/03/03 10:41:19 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.common.service;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
/**
* Descriptor for a group of services. Each <code>GroupDescriptor</code> has
* at least 2 members.
*
* @version $Revision: 1.34 $ $Date: 2012/03/03 10:41:19 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
public final class GroupDescriptor extends Descriptor {
/**
* The identifier of the <em>random</em> group type.
*/
public static final String RANDOM_TYPE_ID = "random";
/**
* The identifier of the <em>ordered</em> group type.
*/
public static final String ORDERED_TYPE_ID = "ordered";
/**
* The <em>random</em> group type.
*/
public static final Type RANDOM_TYPE = new Type(RANDOM_TYPE_ID);
/**
* The <em>ordered</em> group type.
*/
public static final Type ORDERED_TYPE = new Type(ORDERED_TYPE_ID);
/**
* Pseudo-random number generator.
*/
private static final Random RANDOM = new Random();
/**
* Constructs a new <code>GroupDescriptor</code>. The members to be
* included must be passed. The array of members cannot contain any
* <code>null</code> elements. It may contain duplicates, though.
*
* <p>Since XINS 1.1.0, the array of members cannot be empty, but needs to
* contain at least 2 descriptors.
*
* @param type
* the type of group, cannot be <code>null</code>.
*
* @param members
* list of members of the group, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>type == null
* || members == null
* || members.length < 2
* || members[<em>n</em>] == null</code>
* (where <code>0 <= <em>n</em> < members.length</code>).
*/
public GroupDescriptor(Type type, Descriptor[] members)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("type", type, "members", members);
int size = members.length;
if (size < 2) {
throw new IllegalArgumentException("members.length (" + size + ") < 2");
}
for (int i = 0; i < size; i++) {
Descriptor d = members[i];
if (d == null) {
throw new IllegalArgumentException("members[" + i + "] == null");
}
}
// Store information
_type = type;
_members = new Descriptor[size];
System.arraycopy(members, 0, _members, 0, size);
// Recursively add all TargetDescriptor instances to the Map
_targetsByCRC = new HashMap();
addTargetsByCRC(members);
}
/**
* Presents this object as a text string.
*
* @return
* this object as a {@link String}, never <code>null</code>.
*/
@Override
public String toString() {
String s = _type._description + " group { " + _members[0];
for (int i = 1; i < _members.length; i++) {
s += "; " + _members[i];
}
s += " }";
return s;
}
/**
* Gets a group type by identifier.
*
* @param identifier
* the identifier for the group, cannot be <code>null</code>.
*
* @return
* the type with the specified identifier, or <code>null</code> if there
* is no matching type.
*
* @throws IllegalArgumentException
* if <code>identifier == null</code>.
*/
public static Type getType(String identifier)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("identifier", identifier);
// Match
if (RANDOM_TYPE_ID.equals(identifier)) {
return RANDOM_TYPE;
} else if (ORDERED_TYPE_ID.equals(identifier)) {
return ORDERED_TYPE;
} else {
return null;
}
}
/**
* Recursively adds all <code>TargetDescriptor</code> instances found in
* the specified list of <code>Descriptor</code>'s to the internal map from
* CRC-32 checksum to <code>TargetDescriptor</code>.
*
* @param members
* the set of {@link Descriptor} instances, cannot be <code>null</code>.
*
* @throws NullPointerException
* if <code>members == null || <em>group</em>.</code>{@link #_members}<code> == null</code>,
* where <em>group</em> is any {@link GroupDescriptor} instance found in
* <code>members</code> (at any level).
*/
private void addTargetsByCRC(Descriptor[] members)
throws NullPointerException {
int size = members.length;
for (int i = 0; i < size; i++) {
Descriptor d = members[i];
// If this is a TargetDescriptor, put it in the map
if (d instanceof TargetDescriptor) {
TargetDescriptor target = (TargetDescriptor) d;
_targetsByCRC.put(new Integer(target.getCRC()), target);
_targetCount++;
// Otherwise it is assumed to be a GroupDescriptor, recurse
} else {
GroupDescriptor group = (GroupDescriptor) d;
addTargetsByCRC(group._members);
}
}
}
/**
* The type of this group. Cannot be <code>null</code>.
*/
private final Type _type;
/**
* The members of this group. Cannot be <code>null</code>.
*/
private final Descriptor[] _members;
/**
* All contained <code>TargetDescriptor</code> instances, by CRC-32. This
* {@link Map} is used by {@link #getTargetByCRC(int)} to lookup a
* {@link TargetDescriptor} by CRC-32 checksum.
*
* <p>This field is initialized by the constructor and can never be
* <code>null</code>.
*/
private final Map _targetsByCRC;
/**
* The total number of targets in this group. The value of this field is
* always >= 1.
*/
private int _targetCount;
/**
* Checks if this descriptor denotes a group of descriptors.
*
* @return
* <code>true</code>, since this descriptor denotes a group.
*/
public boolean isGroup() {
return true;
}
/**
* Iterates over all leaves, the target descriptors.
*
* <p>The returned {@link Iterator} will not support
* {@link Iterator#remove()}. The iterator will only return
* {@link TargetDescriptor} instances, no instances of other classes and
* no <code>null</code> values.
*
* <p>Also, this iterator is guaranteed to return {@link #getTargetCount()}
* instances of class {@link TargetDescriptor}.
*
* @return
* iterator over the leaves, the target descriptors, in this
* descriptor, in the correct order, never <code>null</code>.
*/
public Iterator iterateTargets() {
return iterator();
}
public Iterator<TargetDescriptor> iterator() {
if (_type == RANDOM_TYPE) {
return new RandomIterator();
} else if (_type == ORDERED_TYPE) {
return new OrderedIterator();
} else {
throw Utils.logProgrammingError("Unknown type: " + _type + '.');
}
}
/**
* Counts the total number of target descriptors in/under this descriptor.
*
* @return
* the total number of target descriptors, always >= 2.
*/
public int getTargetCount() {
return _targetCount;
}
/**
* Returns the type of this group.
*
* @return
* the type of this group, not <code>null</code>.
*/
public Type getType() {
return _type;
}
/**
* Returns the members of this group.
*
* @return
* the members of this group as a new array, not <code>null</code>.
*/
public Descriptor[] getMembers() {
int size = _members.length;
Descriptor[] array = new Descriptor[size];
System.arraycopy(_members, 0, array, 0, size);
return array;
}
/**
* Returns the <code>TargetDescriptor</code> that matches the specified
* CRC-32 checksum.
*
* @param crc
* the CRC-32 checksum.
*
* @return
* the {@link TargetDescriptor} that matches the specified checksum, or
* <code>null</code>, if none could be found in this descriptor.
*/
public TargetDescriptor getTargetByCRC(int crc) {
return (TargetDescriptor) _targetsByCRC.get(new Integer(crc));
}
/**
* Type of a group.
*
* @version $Revision: 1.34 $ $Date: 2012/03/03 10:41:19 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
public static final class Type implements Serializable {
/**
* Constructs a new <code>Type</code> with the specified description.
*
* @param description
* the description for this type.
*/
Type(String description) {
_description = description;
}
/**
* The description for this type.
*/
private final String _description;
/**
* Returns a textual representation of this object.
*
* <p>The implementation of this method returns the description for this
* type. However, this is not guaranteed to remain like this.
*
* @return
* a textual representation of this object, never <code>null</code>.
*/
public String toString() {
return _description;
}
}
/**
* Random iterator over the leaf target descriptors contained in this
* group descriptor. Needed for the implementation of
* {@link #iterateTargets()}.
*
* @version $Revision: 1.34 $ $Date: 2012/03/03 10:41:19 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
private final class RandomIterator implements Iterator<TargetDescriptor>, Serializable {
/**
* Constructs a new <code>RandomIterator</code>.
*/
RandomIterator() {
// Copy all members to _remaining
int size = _members.length;
_remaining = new ArrayList(size);
for (int i = 0; i < size; i++) {
_remaining.add(_members[i]);
}
// Pick a member randomly
int index = Math.abs(RANDOM.nextInt() % size);
Descriptor member = (Descriptor) _remaining.remove(index);
// Initialize the current iterator to link to that member's services
_currentIterator = member.iterator();
}
/**
* The set of remaining descriptors. One is removed from a random index
* each time {@link #next()} is called.
*
* <p>This field will be set to <code>null</code> as soon as there are
* no more remaining members. Still {@link #_currentIterator} could have
* more elements.
*/
private List _remaining;
/**
* Current iterator of one of the members.
*
* <p>This field will be set to <code>null</code> as soon as there are
* no more remaining services to be iterated over.
*/
private Iterator<TargetDescriptor> _currentIterator;
/**
* Checks if there is a next element.
*
* @return
* <code>true</code> if there is a next element, <code>false</code>
* if there is not.
*/
public boolean hasNext() {
return _currentIterator != null;
}
/**
* Returns the next element.
*
* @return
* the next element, never <code>null</code>.
*
* @throws NoSuchElementException
* if there is no new element.
*/
public TargetDescriptor next() throws NoSuchElementException {
// Check preconditions
if (_currentIterator == null) {
throw new NoSuchElementException();
}
// Get the next service
TargetDescriptor o = _currentIterator.next();
// Check if this member/iterator has any more
if (! _currentIterator.hasNext()) {
// If there are no remaining members, set _currentIterator to null
if (_remaining == null) {
_currentIterator = null;
} else {
// Pick one of the remaining members
int size = _remaining.size();
int index = (size == 1) ? 0 : Math.abs(RANDOM.nextInt() % size);
Descriptor member = (Descriptor) _remaining.remove(index);
_currentIterator = member.iterateTargets();
// If there are now no additional remaining members, set
// _remaining to null
if (size == 1) {
_remaining = null;
}
}
}
return o;
}
/**
* Removes the element last returned by <code>next()</code> (unsupported
* operation).
*
* @throws UnsupportedOperationException
* always thrown, since this operation is unsupported.
*/
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
/**
* Ordered iterator over the leaf target descriptors contained in this
* group descriptor. Needed for the implementation of
* {@link #iterateTargets()}.
*
* @version $Revision: 1.34 $ $Date: 2012/03/03 10:41:19 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
private final class OrderedIterator implements Iterator<TargetDescriptor>, Serializable {
/**
* Constructs a new <code>OrderedIterator</code>.
*/
OrderedIterator() {
// Copy all members to _remaining
_currentIndex = 0;
// Initialize the current iterator to link to that member's services
_currentIterator = _members[0].iterator();
}
/**
* The current index into the list of members. Will be set to a negative
* value if there are no more members.
*/
private int _currentIndex;
/**
* Current iterator of one of the members.
*
* <p>This field will be set to <code>null</code> as soon as there are
* no more remaining services to be iterated over.
*/
private Iterator<TargetDescriptor> _currentIterator;
/**
* Checks if there is a next element.
*
* @return
* <code>true</code> if there is a next element, <code>false</code>
* if there is not.
*/
public boolean hasNext() {
return (_currentIterator != null) && _currentIterator.hasNext();
}
/**
* Returns the next element.
*
* @return
* the next element, never <code>null</code>.
*
* @throws NoSuchElementException
* if there is no new element.
*/
public TargetDescriptor next() throws NoSuchElementException {
// Check preconditions
if (_currentIterator == null) {
throw new NoSuchElementException();
}
// Get the next service
TargetDescriptor o = _currentIterator.next();
// Check if this member/iterator has any more
if (! _currentIterator.hasNext()) {
// If there are no remaining members, set _currentIterator to null
if (_currentIndex < 0) {
_currentIterator = null;
} else {
_currentIndex++;
if (_currentIndex < _members.length) {
_currentIterator = _members[_currentIndex].iterateTargets();
} else {
_currentIndex = -1;
}
}
}
return o;
}
/**
* Removes the element last returned by <code>next()</code> (unsupported
* operation).
*
* @throws UnsupportedOperationException
* always thrown, since this operation is unsupported.
*/
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
}