/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 net.jini.core.constraint;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
/**
* Combines two or more constraint alternatives into a single overall
* constraint. The semantics of this aggregate constraint are that at least
* one of the individual constraint alternatives must be satisfied. The
* alternatives do not have to be instances of the same type, but they
* cannot themselves be <code>ConstraintAlternatives</code> instances.
* <p>
* Note that this class implements {@link RelativeTimeConstraint} even though
* the constraint elements might not implement
* <code>RelativeTimeConstraint</code>.
* <p>
* An instance containing an exhaustive list of alternatives (for example,
* an instance containing both <code>ClientAuthentication.YES</code> and
* <code>ClientAuthentication.NO</code>) serves no useful purpose, as a
* requirement or as a preference. A <i>don't care</i> condition should
* be expressed by the <i>absence</i> of constraints.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public final class ConstraintAlternatives
implements RelativeTimeConstraint, Serializable
{
private static final long serialVersionUID = 7214615235302870613L;
/**
* @serialField constraints InvocationConstraint[]
* The alternative constraints.
*/
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("constraints", InvocationConstraint[].class,
true)
};
/**
* The alternative constraints.
*/
private final InvocationConstraint[] constraints;
/**
* Indicates whether any of the constraints are based on relative time.
*/
private transient boolean rel = false;
/**
* Creates an instance containing the specified alternative constraints,
* with duplicate constraints removed. The argument passed to this
* constructor is neither modified nor retained; subsequent changes to
* that argument have no effect on the instance created.
*
* @param constraints the alternative constraints
* @throws NullPointerException if the argument is <code>null</code> or
* any element is <code>null</code>
* @throws IllegalArgumentException if any of the elements are instances
* of <code>ConstraintAlternatives</code>, or if fewer than two elements
* remain after duplicate constraints are removed
*/
public ConstraintAlternatives(InvocationConstraint[] constraints) {
this.constraints =
reduce((InvocationConstraint[]) constraints.clone());
setRelative();
}
/**
* Creates an instance containing the specified alternative constraints,
* with duplicate constraints removed. The argument passed to this
* constructor is neither modified nor retained; subsequent changes to
* that argument have no effect on the instance created.
*
* @param c the alternative constraints
* @throws NullPointerException if the argument is <code>null</code> or
* any element is <code>null</code>
* @throws IllegalArgumentException if any of the elements are instances
* of <code>ConstraintAlternatives</code>, or if the elements are not all
* instances of <code>InvocationConstraint</code>, or if fewer than two
* elements remain after duplicate constraints are removed
*/
public ConstraintAlternatives(Collection c) {
try {
constraints = reduce((InvocationConstraint[]) c.toArray(
new InvocationConstraint[c.size()]));
} catch (ArrayStoreException e) {
throw new IllegalArgumentException(
"element of collection is not an InvocationConstraint");
}
setRelative();
}
/**
* Returns a constraint representing the specified alternative constraints,
* with duplicate constraints removed. If a single constraint remains after
* duplicates are removed, then that constraint is returned, otherwise an
* instance of <code>ConstraintAlternatives</code> containing the remaining
* constraints is returned. The argument passed to this method is neither
* modified nor retained; subsequent changes to that argument have no
* effect on the instance created.
*
* @param constraints the alternative constraints
* @return a constraint representing the specified alternative constraints,
* with duplicate constraints removed
* @throws NullPointerException if the argument is <code>null</code> or
* any element is <code>null</code>
* @throws IllegalArgumentException if the argument is empty, or if any
* of the elements are instances of <code>ConstraintAlternatives</code>
*/
public static InvocationConstraint create(
InvocationConstraint[] constraints)
{
return reduce((InvocationConstraint[]) constraints.clone(), false);
}
/**
* Returns a constraint representing the specified alternative constraints,
* with duplicate constraints removed. If a single constraint remains after
* duplicates are removed, then that constraint is returned, otherwise an
* instance of <code>ConstraintAlternatives</code> containing the remaining
* constraints is returned. The argument passed to this method is neither
* modified nor retained; subsequent changes to that argument have no
* effect on the instance created.
*
* @param c the alternative constraints
* @return a constraint representing the specified alternative constraints,
* with duplicate constraints removed
* @throws NullPointerException if the argument is <code>null</code> or
* any element is <code>null</code>
* @throws IllegalArgumentException if the argument is empty, or if any
* of the elements are instances of <code>ConstraintAlternatives</code>,
* or if the elements are not all instances of
* <code>InvocationConstraint</code>
*/
public static InvocationConstraint create(Collection c) {
try {
return reduce((InvocationConstraint[]) c.toArray(
new InvocationConstraint[c.size()]),
false);
} catch (ArrayStoreException e) {
throw new IllegalArgumentException(
"element of collection is not an InvocationConstraint");
}
}
/**
* Creates a constraint containing the specified alternative constraints,
* and computes the rel field if allAbs is false.
*/
private ConstraintAlternatives(InvocationConstraint[] constraints,
boolean allAbs)
{
this.constraints = constraints;
if (!allAbs) {
setRelative();
}
}
/**
* Sets the rel field to true if any of the constraints are relative.
*/
private void setRelative() {
for (int i = constraints.length; --i >= 0; ) {
if (constraints[i] instanceof RelativeTimeConstraint) {
rel = true;
return;
}
}
}
/**
* Returns true if any of the constraints are relative, false otherwise.
*/
boolean relative() {
return rel;
}
/**
* Verifies that the array is non-empty, and that the elements are all
* non-null and not ConstraintAlternatives instances. Removes duplicates
* and returns a single constraint if there's only one left, otherwise
* returns an ConstraintAlternatives containing the remaining constraints.
* The argument is modified in place.
*/
private static InvocationConstraint reduce(
InvocationConstraint[] constraints,
boolean allAbs)
{
verify(constraints, 1);
int n = reduce0(constraints);
if (n == 1) {
return constraints[0];
}
return new ConstraintAlternatives(
(InvocationConstraint[]) Constraint.trim(constraints, n),
allAbs);
}
/**
* Verifies that the array has at least min elements, and that the
* elements are all non-null and not ConstraintAlternatives instances.
*/
private static void verify(InvocationConstraint[] constraints, int min) {
if (constraints.length < min) {
throw new IllegalArgumentException(
"cannot create constraint with " +
(min == 1 ? "no" : ("less than " + min)) +
" elements");
}
for (int i = constraints.length; --i >= 0; ) {
InvocationConstraint c = constraints[i];
if (c == null) {
throw new NullPointerException("elements cannot be null");
} else if (c instanceof ConstraintAlternatives) {
throw new IllegalArgumentException(
"elements cannot be ConstraintAlternatives instances");
}
}
}
/**
* Verifies that the array has at least 2 elements, and that the elements
* are all non-null and not ConstraintAlternatives instances, removes
* duplicates, modifying the array in place, verifies that there are still
* at least 2 elements, and returns an array containing the remaining
* elements.
*/
private static InvocationConstraint[] reduce(
InvocationConstraint[] constraints)
{
verify(constraints, 2);
int n = reduce0(constraints);
if (n == 1) {
throw new IllegalArgumentException(
"reduced to less than 2 elements");
}
return (InvocationConstraint[]) Constraint.trim(constraints, n);
}
/**
* Eliminates duplicates, modifying the array in place, and returns
* the resulting number of elements.
*/
private static int reduce0(InvocationConstraint[] constraints) {
int i = 0;
for (int j = 0; j < constraints.length; j++) {
InvocationConstraint c = constraints[j];
if (!Constraint.contains(constraints, i, c)) {
constraints[i++] = c;
}
}
return i;
}
/**
* Returns an immutable set of all of the constraints. Any attempt to
* modify this set results in an {@link UnsupportedOperationException}
* being thrown.
*
* @return an immutable set of all of the constraints
*/
public Set elements() {
return new ArraySet(constraints);
}
/**
* Returns the elements, without copying.
*/
InvocationConstraint[] getConstraints() {
return constraints;
}
/**
* Returns a constraint equal to the result of taking the constraints in
* this instance, replacing each constraint that is an instance of
* {@link RelativeTimeConstraint} with the result of invoking that
* constraint's <code>makeAbsolute</code> method with the specified base
* time, and invoking the <code>create</code> method of this class with
* the revised collection of constraints.
*/
public InvocationConstraint makeAbsolute(long baseTime) {
if (!rel) {
return this;
}
InvocationConstraint[] vals =
new InvocationConstraint[constraints.length];
for (int i = vals.length; --i >= 0; ) {
InvocationConstraint c = constraints[i];
if (c instanceof RelativeTimeConstraint) {
c = ((RelativeTimeConstraint) c).makeAbsolute(baseTime);
}
vals[i] = c;
}
return reduce(vals, true);
}
/**
* Returns a hash code value for this object.
*/
public int hashCode() {
return Constraint.hash(constraints);
}
/**
* Two instances of this class are equal if they have the same constraints
* (ignoring order).
*/
public boolean equals(Object obj) {
return (obj instanceof ConstraintAlternatives &&
Constraint.equal(constraints,
((ConstraintAlternatives) obj).constraints));
}
/**
* Returns a string representation of this object.
*/
public String toString() {
return "ConstraintAlternatives" + Constraint.toString(constraints);
}
/**
* Verifies that there are at least two constraints, that none are
* <code>null</code> and none are instances of this class, and that
* there are no duplicates.
*
* @throws InvalidObjectException if there are less than two constraints,
* or any constraint is <code>null</code> or an instance of this class,
* or if there are duplicates
*/
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();
if (constraints == null) {
throw new InvalidObjectException(
"cannot create constraint with no elements");
}
try {
verify(constraints, 2);
} catch (RuntimeException e) {
if (e instanceof NullPointerException ||
e instanceof IllegalArgumentException)
{
InvalidObjectException ee =
new InvalidObjectException(e.getMessage());
ee.initCause(e);
throw ee;
}
throw e;
}
for (int i = constraints.length; --i >= 0; ) {
if (Constraint.contains(constraints, i, constraints[i])) {
throw new InvalidObjectException(
"cannot create constraint with duplicate elements");
}
}
setRelative();
}
}