/*
* 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.ObjectStreamField;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
/**
* An immutable aggregation of constraints into a set of requirements and a
* set of preferences. A requirement is a mandatory constraint that must be
* satisfied for the invocation. A preference is a desired constraint, to be
* satisfied if possible, but it will not be satisfied if it conflicts with a
* requirement. If two preferences conflict, it is arbitrary as to which
* one will be satisfied. If a constraint is not understood, due to lack of
* knowledge of the type of the constraint or the contents of the constraint,
* then the constraint cannot be satisfied.
* <p>
* Note that it is possible for an instance of this class to contain both
* requirements that conflict with each other, and preferences that conflict
* with each other and with requirements.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public final class InvocationConstraints implements Serializable {
private static final long serialVersionUID = -3363161199079334224L;
/**
* @serialField reqs InvocationConstraint[] The requirements.
* @serialField prefs InvocationConstraint[] The preferences.
*/
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("reqs", InvocationConstraint[].class, true),
new ObjectStreamField("prefs", InvocationConstraint[].class, true)
};
/** An empty array */
private static final InvocationConstraint[] empty =
new InvocationConstraint[0];
/**
* Bit set in the rel field if there are relative requirements.
*/
private static final int REL_REQS = 1;
/**
* Bit set in the rel field if there are relative preferences.
*/
private static final int REL_PREFS = 2;
/**
* An empty instance, one that has no requirements and no preferences.
*/
public static final InvocationConstraints EMPTY =
new InvocationConstraints((InvocationConstraint) null, null);
/**
* The requirements.
*/
private InvocationConstraint[] reqs;
/**
* The preferences.
*/
private InvocationConstraint[] prefs;
/**
* Flags indicating whether any requirements and/or preferences are
* based on relative time.
*/
private transient int rel = 0;
/**
* Creates an instance that has the first constraint, <code>req</code>,
* added as a requirement if it is a non-<code>null</code> value, and has
* the second constraint, <code>pref</code>, added as a preference if it
* is a non-<code>null</code> value and is not a duplicate of the
* requirement.
*
* @param req a requirement, or <code>null</code>
* @param pref a preference, or <code>null</code>
*/
public InvocationConstraints(InvocationConstraint req,
InvocationConstraint pref)
{
if (req != null) {
reqs = new InvocationConstraint[]{req};
}
if (pref != null) {
prefs = new InvocationConstraint[]{pref};
}
reduce();
}
/**
* Creates an instance that has all of the constraints from the first
* array, <code>reqs</code>, added as requirements if the array is a
* non-<code>null</code> value, and has all of the constraints from
* the second array, <code>prefs</code>, added as preferences if the
* array is a non-<code>null</code> value. Duplicate requirements,
* duplicate preferences, and preferences that are duplicates of
* requirements are all removed. The arguments passed to the constructor
* are neither modified nor retained; subsequent changes to those
* arguments have no effect on the instance created.
*
* @param reqs requirements, or <code>null</code>
* @param prefs preferences, or <code>null</code>
* @throws NullPointerException if any element of an argument is
* <code>null</code>
*/
public InvocationConstraints(InvocationConstraint[] reqs,
InvocationConstraint[] prefs)
{
if (reqs != null) {
this.reqs = (InvocationConstraint[]) reqs.clone();
}
if (prefs != null) {
this.prefs = (InvocationConstraint[]) prefs.clone();
}
reduce();
}
/**
* Creates an instance that has all of the constraints from the first
* collection, <code>reqs</code>, added as requirements if the collection
* is a non-<code>null</code> value, and has all of the constraints from
* the second collection, <code>prefs</code>, added as preferences if the
* collection is a non-<code>null</code> value. Duplicate requirements,
* duplicate preferences, and preferences that are duplicates of
* requirements are all removed. The arguments passed to the constructor
* are neither modified nor retained; subsequent changes to those
* arguments have no effect on the instance created.
*
* @param reqs requirements, or <code>null</code>
* @param prefs preferences, or <code>null</code>
* @throws NullPointerException if any element of an argument is
* <code>null</code>
* @throws IllegalArgumentException if any element of an argument is not
* an instance of <code>InvocationConstraint</code>
*/
public InvocationConstraints(Collection reqs, Collection prefs) {
try {
if (reqs != null) {
this.reqs =
(InvocationConstraint[]) reqs.toArray(
new InvocationConstraint[reqs.size()]);
}
if (prefs != null) {
this.prefs =
(InvocationConstraint[]) prefs.toArray(
new InvocationConstraint[prefs.size()]);
}
} catch (ArrayStoreException e) {
throw new IllegalArgumentException(
"element of collection is not an InvocationConstraint");
}
reduce();
}
/**
* Creates an instance containing the specified requirements and
* preferences. Reqidx and prefidx indicate how many of the initial
* elements in each array are known to have been reduced (meaning they
* came from an existing instance of this class), but prefidx must be
* zero if reqidx is non-zero but reqidx is less than the total number
* of requirements. Rel contains the flags for which constraints are
* relative.
*/
private InvocationConstraints(InvocationConstraint[] reqs,
int reqidx,
InvocationConstraint[] prefs,
int prefidx,
int rel)
{
this.reqs = reqs;
this.prefs = prefs;
reduce(reqidx, prefidx);
this.rel = rel;
}
/**
* Replaces null fields with empty arrays, eliminates duplicates, and
* sets flags indicating which constraints are relative.
*/
private void reduce() {
if (reqs == null) {
reqs = empty;
}
if (prefs == null) {
prefs = empty;
}
reduce(0, 0);
setRelative(reqs, REL_REQS);
setRelative(prefs, REL_PREFS);
}
/**
* Checks for nulls and eliminates duplicates. Reqidx and prefidx
* indicate how many of the initial elements in each array are known to
* have been reduced (meaning they came from an existing instance of this
* class), but prefidx must be zero if reqidx is non-zero but reqidx is
* less than the total number of requirements.
*/
private void reduce(int reqidx, int prefidx) {
for (int i = reqidx; i < reqs.length; i++) {
InvocationConstraint req = reqs[i];
if (req == null) {
throw new NullPointerException("elements cannot be null");
} else if (!Constraint.contains(reqs, reqidx, req)) {
reqs[reqidx++] = req;
}
}
reqs = (InvocationConstraint[]) Constraint.trim(reqs, reqidx);
for (int i = prefidx; i < prefs.length; i++) {
InvocationConstraint pref = prefs[i];
if (pref == null) {
throw new NullPointerException("elements cannot be null");
} else if (!Constraint.contains(prefs, prefidx, pref) &&
!Constraint.contains(reqs, reqs.length, pref))
{
prefs[prefidx++] = pref;
}
}
prefs = (InvocationConstraint[]) Constraint.trim(prefs, prefidx);
}
/**
* Returns true if the specified constraint either implements
* RelativeTimeConstraint or is an instance of ConstraintAlternatives with
* elements that implement RelativeTimeConstraint, and false otherwise.
*/
private static boolean relative(InvocationConstraint c) {
return (c instanceof RelativeTimeConstraint &&
(!(c instanceof ConstraintAlternatives) ||
((ConstraintAlternatives) c).relative()));
}
/**
* Sets the given flag in the rel field if any if the specified
* constraints are relative.
*/
private void setRelative(InvocationConstraint[] constraints, int flag) {
for (int i = constraints.length; --i >= 0; ) {
if (relative(constraints[i])) {
rel |= flag;
return;
}
}
}
/**
* Returns an instance of this class that has all of the requirements from
* each non-<code>null</code> argument added as requirements and has all
* of the preferences from each non-<code>null</code> argument added as
* preferences. Duplicate requirements, duplicate preferences, and
* preferences that are duplicates of requirements are all removed.
*
* @param constraints1 constraints, or <code>null</code>
* @param constraints2 constraints, or <code>null</code>
* @return an instance of this class that has all of the requirements from
* each non-<code>null</code> argument added as requirements and has all
* of the preferences from each non-<code>null</code> argument added as
* preferences
*/
public static InvocationConstraints combine(
InvocationConstraints constraints1,
InvocationConstraints constraints2)
{
if (constraints1 == null || constraints1.isEmpty()) {
return constraints2 == null ? EMPTY : constraints2;
} else if (constraints2 == null || constraints2.isEmpty()) {
return constraints1;
} else if (constraints2.reqs.length > constraints1.reqs.length) {
InvocationConstraints tmp = constraints1;
constraints1 = constraints2;
constraints2 = tmp;
}
int prefidx;
InvocationConstraint[] reqs;
if (constraints2.reqs.length > 0) {
reqs = concat(constraints1.reqs, constraints2.reqs);
prefidx = 0;
} else {
reqs = constraints1.reqs;
prefidx = constraints1.prefs.length;
}
InvocationConstraint[] prefs;
if (constraints1.prefs.length > 0 || constraints2.prefs.length > 0) {
prefs = concat(constraints1.prefs, constraints2.prefs);
} else {
prefs = empty;
}
return new InvocationConstraints(reqs, constraints1.reqs.length,
prefs, prefidx,
constraints1.rel | constraints2.rel);
}
/**
* Returns a new array containing the elements of both arguments.
*/
private static InvocationConstraint[] concat(InvocationConstraint[] arr1,
InvocationConstraint[] arr2)
{
InvocationConstraint[] res =
new InvocationConstraint[arr1.length + arr2.length];
System.arraycopy(arr1, 0, res, 0, arr1.length);
System.arraycopy(arr2, 0, res, arr1.length, arr2.length);
return res;
}
/**
* Converts any relative constraints to absolute time.
*/
private static InvocationConstraint[] makeAbsolute(
InvocationConstraint[] arr,
long baseTime)
{
InvocationConstraint[] narr = new InvocationConstraint[arr.length];
for (int i = arr.length; --i >= 0; ) {
InvocationConstraint c = arr[i];
if (c instanceof RelativeTimeConstraint) {
c = ((RelativeTimeConstraint) c).makeAbsolute(baseTime);
}
narr[i] = c;
}
return narr;
}
/**
* Returns an instance of this class equal to the result of taking the
* requirements and preferences 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 creating a new instance of
* this class with duplicate requirements, duplicate preferences, and
* preferences that are duplicates of requirements all removed.
*
* @param baseTime an absolute time, specified in milliseconds from
* midnight, January 1, 1970 UTC
* @return an instance of this class equal to the result of taking the
* requirements and preferences in this instance, replacing each
* constraint that is an instance of <code>RelativeTimeConstraint</code>
* with the result of invoking that constraint's <code>makeAbsolute</code>
* method with the specified base time, and creating a new instance of
* this class with duplicate requirements, duplicate preferences, and
* preferences that are duplicates of requirements all removed
*/
public InvocationConstraints makeAbsolute(long baseTime) {
if (rel == 0) {
return this;
}
InvocationConstraint[] nreqs;
int reqidx;
if ((rel & REL_REQS) != 0) {
nreqs = makeAbsolute(reqs, baseTime);
reqidx = 0;
} else {
nreqs = reqs;
reqidx = reqs.length;
}
InvocationConstraint[] nprefs;
if ((rel & REL_PREFS) != 0) {
nprefs = makeAbsolute(prefs, baseTime);
} else {
nprefs = (InvocationConstraint[]) prefs.clone();
}
return new InvocationConstraints(nreqs, reqidx, nprefs, 0, 0);
}
/**
* Returns an instance of this class constructed from all of the same
* requirements and preferences as this instance, but with every
* constraint that is an instance of {@link RelativeTimeConstraint}
* replaced by the result of invoking the constraint's
* <code>makeAbsolute</code> method with the current time (as given by
* {@link System#currentTimeMillis System.currentTimeMillis}). Duplicate
* requirements, duplicate preferences, and preferences that are
* duplicates of requirements are all removed.
*
* @return an instance of this class constructed from all of the same
* requirements and preferences as this instance, but with every
* constraint that is an instance of <code>RelativeTimeConstraint</code>
* replaced by the result of invoking the constraint's
* <code>makeAbsolute</code> method with the current time
*/
public InvocationConstraints makeAbsolute() {
if (rel == 0) {
return this;
}
return makeAbsolute(System.currentTimeMillis());
}
/**
* Returns an immutable set of all of the requirements. Any attempt to
* modify this set results in an {@link UnsupportedOperationException}
* being thrown.
*
* @return an immutable set of all of the requirements
*/
public Set requirements() {
return new ArraySet(reqs);
}
/**
* Returns an immutable set of all of the preferences. Any attempt to
* modify this set results in an {@link UnsupportedOperationException}
* being thrown.
*
* @return an immutable set of all of the preferences
*/
public Set preferences() {
return new ArraySet(prefs);
}
/**
* Returns <code>true</code> if the instance has no requirements and no
* preferences; returns <code>false</code> otherwise.
*
* @return <code>true</code> if the instance has no requirements and no
* preferences; <code>false</code> otherwise
*/
public boolean isEmpty() {
return reqs.length == 0 && prefs.length == 0;
}
/**
* Returns a hash code value for this object.
*/
public int hashCode() {
return Constraint.hash(reqs) + Constraint.hash(prefs);
}
/**
* Two instances of this class are equal if they have the same requirements
* and the same preferences. This method is a sufficient substitute for
* {@link net.jini.security.proxytrust.TrustEquivalence#checkTrustEquivalence
* TrustEquivalence.checkTrustEquivalence}.
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof InvocationConstraints)) {
return false;
}
InvocationConstraints sc = (InvocationConstraints)obj;
return (Constraint.equal(reqs, sc.reqs) &&
Constraint.equal(prefs, sc.prefs));
}
/**
* Returns a string representation of this object.
*/
public String toString() {
return ("InvocationConstraints[reqs: " + Constraint.toString(reqs) +
", prefs: " + Constraint.toString(prefs) + "]");
}
/**
* Verifies that there are no <code>null</code> elements and no duplicates.
*
* @throws InvalidObjectException if the requirements or preferences
* arrays are <code>null</code>, or any element is <code>null</code>,
* or if there are duplicate requirements, duplicate preferences, or
* preferences that are duplicates of requirements
*/
/* Also sets the rel field */
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();
verify(reqs);
verify(prefs);
for (int i = prefs.length; --i >= 0; ) {
if (Constraint.contains(reqs, reqs.length, prefs[i])) {
throw new InvalidObjectException(
"cannot create constraint with redundant elements");
}
}
setRelative(reqs, REL_REQS);
setRelative(prefs, REL_REQS);
}
/**
* Verifies that the array is non-null, the elements are all non-null,
* and there are no duplicates.
*/
private static void verify(InvocationConstraint[] constraints)
throws InvalidObjectException
{
if (constraints == null) {
throw new InvalidObjectException("array cannot be null");
}
for (int i = constraints.length; --i >= 0; ) {
InvocationConstraint c = constraints[i];
if (c == null) {
throw new InvalidObjectException("elements cannot be null");
} else if (Constraint.contains(constraints, i, c)) {
throw new InvalidObjectException(
"cannot create constraint with redundant elements");
}
}
}
}