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(); } } }