/*
* 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 com.sun.jini.discovery;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
import net.jini.core.constraint.ConnectionAbsoluteTime;
import net.jini.core.constraint.ConnectionRelativeTime;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.io.UnsupportedConstraintException;
/**
* Class for processing constraints which apply to the discovery protocol:
* {@link DiscoveryProtocolVersion}, {@link MulticastMaxPacketSize},
* {@link MulticastTimeToLive}, {@link UnicastSocketTimeout},
* {@link net.jini.core.constraint.ConnectionRelativeTime},
* {@link net.jini.core.constraint.ConnectionAbsoluteTime}.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public class DiscoveryConstraints {
/** Method object for the multicastRequest method of this class. */
public static final Method multicastRequestMethod;
/** Method object for the multicastAnnouncement method of this class. */
public static final Method multicastAnnouncementMethod;
/** Method object for the unicastDiscovery method of this class. */
public static final Method unicastDiscoveryMethod;
static {
try {
multicastRequestMethod = DiscoveryConstraints.class.getMethod(
"multicastRequest", null);
multicastAnnouncementMethod = DiscoveryConstraints.class.getMethod(
"multicastAnnouncement", null);
unicastDiscoveryMethod = DiscoveryConstraints.class.getMethod(
"unicastDiscovery", null);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
private static final Set supportedProtocols = new HashSet(2);
static {
supportedProtocols.add(DiscoveryProtocolVersion.ONE);
supportedProtocols.add(DiscoveryProtocolVersion.TWO);
}
private final InvocationConstraints unfulfilled;
private final Set protocolVersions;
private final int preferredProtocolVersion;
private final ConnectionAbsoluteTime connectionAbsoluteTime;
private final MulticastMaxPacketSize maxPacketSize;
private final MulticastTimeToLive timeToLive;
private final UnicastSocketTimeout socketTimeout;
private final int hashcode;
@Override
public int hashCode() {
return hashcode;
}
@Override
public boolean equals(Object o){
if ( o == this ) return true;
if ( o.hashCode() != hashcode) return false;
if ( o instanceof DiscoveryConstraints) {
DiscoveryConstraints that = (DiscoveryConstraints) o;
if ( unfulfilled != null ) {
if ( !unfulfilled.equals(that.unfulfilled) ) return false;
} else if ( unfulfilled != that.unfulfilled) return false;
if ( protocolVersions != null ) {
if ( !protocolVersions.equals(that.protocolVersions) ) return false;
} else if ( protocolVersions!= that.protocolVersions) return false;
if ( connectionAbsoluteTime != null ) {
if ( !connectionAbsoluteTime.equals(that.connectionAbsoluteTime) ) return false;
} else if ( connectionAbsoluteTime != that.connectionAbsoluteTime) return false;
if (preferredProtocolVersion != that.preferredProtocolVersion) return false;
if ( maxPacketSize != null ) {
if ( !maxPacketSize.equals(that.maxPacketSize) ) return false;
} else if ( maxPacketSize != that.maxPacketSize) return false;
if ( timeToLive != null ) {
if ( !timeToLive.equals(that.timeToLive) ) return false;
} else if ( timeToLive != that.timeToLive) return false;
if ( socketTimeout != null ) {
if ( !socketTimeout.equals(that.socketTimeout) ) return false;
} else if ( socketTimeout != that.socketTimeout) return false;
return true;
}
return false;
}
/**
* Empty method which serves as a {@link MethodConstraints} key for looking
* up {@link InvocationConstraints} that apply to multicast requests.
*/
public static void multicastRequest() {
}
/**
* Empty method which serves as a {@link MethodConstraints} key for looking
* up {@link InvocationConstraints} that apply to multicast announcements.
*/
public static void multicastAnnouncement() {
}
/**
* Empty method which serves as a {@link MethodConstraints} key for looking
* up {@link InvocationConstraints} that apply to unicast discovery.
*/
public static void unicastDiscovery() {
}
/**
* Processes the discovery-related constraints in the given set of
* constraints, returning a <code>DiscoveryConstraints</code> instance from
* which the constraint results can be queried. Processing of timeout
* constraints is time dependent, and subsequent invocations of this
* method with the same <code>InvocationConstraints</code> may result in
* <code>DiscoveryConstraint</code> instances with different constraint
* results.
*
* @param constraints the constraints to process
* @return an instance representing processed constraints
* @throws UnsupportedConstraintException if the discovery-related
* constraints contain conflicts, or otherwise cannot be processed
* @throws NullPointerException if <code>constraints</code> is
* <code>null</code>
*
*/
public static DiscoveryConstraints process(
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
return new DiscoveryConstraints(constraints);
}
private DiscoveryConstraints(InvocationConstraints constraints)
throws UnsupportedConstraintException
{
unfulfilled = new InvocationConstraints(
getUnfulfilled(constraints.requirements()),
getUnfulfilled(constraints.preferences()));
ConstraintReducer cr =
new ConstraintReducer(DiscoveryProtocolVersion.class);
protocolVersions = cr.reduce(
new InvocationConstraints(constraints.requirements(), null));
if (!protocolVersions.isEmpty() &&
intersect(protocolVersions, supportedProtocols).isEmpty())
{
throw new UnsupportedConstraintException(
"no supported protocols: " + protocolVersions);
}
preferredProtocolVersion = chooseProtocolVersion(
protocolVersions, cr.reduce(constraints), unfulfilled);
Set s = new MulticastMaxPacketSizeReducer().reduce(constraints);
maxPacketSize = s.isEmpty() ?
null : (MulticastMaxPacketSize) getElement(s);
s = new ConstraintReducer(
MulticastTimeToLive.class).reduce(constraints);
timeToLive = s.isEmpty() ? null : (MulticastTimeToLive) getElement(s);
s = new ConstraintReducer(
UnicastSocketTimeout.class).reduce(constraints);
socketTimeout = s.isEmpty() ?
null : (UnicastSocketTimeout) getElement(s);
InvocationConstraints absConstraints =
new InvocationConstraints(
constraints.requirements(),
constraints.preferences()).makeAbsolute();
s = new ConnectionAbsoluteTimeReducer().reduce(absConstraints);
connectionAbsoluteTime = s.isEmpty() ?
null :
(ConnectionAbsoluteTime) getElement(s);
int hash = 7;
hash = 41 * hash + (this.unfulfilled != null ? this.unfulfilled.hashCode() : 0);
hash = 41 * hash + (this.protocolVersions != null ? this.protocolVersions.hashCode() : 0);
hash = 41 * hash + this.preferredProtocolVersion;
hash = 41 * hash + (this.connectionAbsoluteTime != null ? this.connectionAbsoluteTime.hashCode() : 0);
hash = 41 * hash + (this.maxPacketSize != null ? this.maxPacketSize.hashCode() : 0);
hash = 41 * hash + (this.timeToLive != null ? this.timeToLive.hashCode() : 0);
hash = 41 * hash + (this.socketTimeout != null ? this.socketTimeout.hashCode() : 0);
hashcode = hash;
}
/**
* Returns the protocol version to use for sending multicast requests or
* announcements, or initiating unicast discovery.
*
* @return the protocol version to use
*/
public int chooseProtocolVersion() {
return preferredProtocolVersion;
}
/**
* Checks the protocol version of an incoming multicast request,
* announcement, or unicast discovery attempt, throwing an {@link
* UnsupportedConstraintException} if handling of the given protocol does
* not satisfy the constraints of this instance.
*
* @param version protocol version to check
* @throws UnsupportedConstraintException if handling of the given protocol
* does not satisfy the constraints of this instance
*/
public void checkProtocolVersion(int version)
throws UnsupportedConstraintException
{
if (!(protocolVersions.isEmpty() ||
protocolVersions.contains(
DiscoveryProtocolVersion.getInstance(version))))
{
throw new UnsupportedConstraintException(
"disallowed protocol: " + version);
}
}
/**
* Returns the deadline by which a network connection must be established
* during unicast discovery, or <code>defaultValue</code> if not
* constrained.
*
* @param defaultValue default timeout to return
* @return the deadline for the network connection
* @since 2.1
*/
public long getConnectionDeadline(long defaultValue) {
return connectionAbsoluteTime != null ?
connectionAbsoluteTime.getTime() : defaultValue;
}
/**
* Returns the maximum multicast packet size to allow, or the specified
* default value if not constrained.
*
* @param defaultValue the value to return if the multicast packet size is
* unconstrained
* @return the maximum multicast packet size to allow
*/
public int getMulticastMaxPacketSize(int defaultValue) {
return (maxPacketSize != null) ?
maxPacketSize.getSize() : defaultValue;
}
/**
* Returns the multicast time to live value to use, or the specified
* default value if not constrained.
*
* @param defaultValue the value to return if the multicast time to live
* value is unconstrained
* @return the multicast time to live value to use
*/
public int getMulticastTimeToLive(int defaultValue) {
return (timeToLive != null) ?
timeToLive.getTimeToLive() : defaultValue;
}
/**
* Returns socket read timeout to use for unicast discovery, or the
* specified default value if not constrained.
*
* @param defaultValue the value to return if the socket timeout is
* unconstrained
* @return the socket timeout to use
*/
public int getUnicastSocketTimeout(int defaultValue) {
return (socketTimeout != null) ?
socketTimeout.getTimeout() : defaultValue;
}
/**
* Returns the constraints for this instance which are not, or do not
* contain as alternatives, instances of the "fulfillable" (by this layer)
* constraint types <code>DiscoveryProtocolVersion</code>,
* <code>ConnectionRelativeTime</code>, <code>MulticastMaxPacketSize</code>,
* <code>MulticastTimeToLive</code>, and <code>UnicastSocketTimeout</code>.
* Constraint alternatives containing both fulfillable and unfulfillable
* constraints are treated optimistically--it is assumed that the one of the
* fulfillable alternatives will be satisfied, so the unfulfillable
* alternatives are not included in the returned constraints.
*
* @return the unfulfilled constraints for this instance
*/
public InvocationConstraints getUnfulfilledConstraints() {
return unfulfilled;
}
/**
* Utility for reducing constraints of a given type into a base set of
* alternatives. For each type of constraints that it reduces, this class
* makes the simplifying assumption that failure to reduce constraints of
* that type into a single (perhaps one element) set of alternatives
* represents a constraint conflict--i.e., that two disjoint instances of
* the constraint type cannot be applied to the same operation. While this
* restriction does not hold across all possible constraints, it is
* satisfied by the particular constraint types that DiscoveryConstraints
* handles.
*
* If this utility encounters a set of alternatives containing instances of
* both the type to reduce as well as other constraint types, then it
* ignores the instances of the other types, treating the set as if it can
* only be satisfied by the alternatives of the targeted type. This may in
* some cases cause false positives when detecting conflicts in constraints
* containing alternatives of mixed type; however, it should never result
* in false negatives.
*/
private static class ConstraintReducer {
private final Class targetClass;
/**
* Creates reducer that operates on instances of the given constraint
* class.
*/
ConstraintReducer(Class targetClass) {
this.targetClass = targetClass;
}
/**
* Returns the reduction of the given constraints into a single set of
* alternatives for the target class. Returns an empty set if no
* constraints of the target class are specified. Throws
* UnsupportedConstraintException if the constraints conflict.
*/
Set reduce(InvocationConstraints constraints)
throws UnsupportedConstraintException
{
Set reduced = reduce(null, constraints.requirements(), true);
reduced = reduce(reduced, constraints.preferences(), false);
return (reduced != null) ? reduced : Collections.EMPTY_SET;
}
/**
* Returns the reduction (intersection and compaction) of a new set of
* alternative constraints, all instances of the target class, with a
* previously reduced set (null if no other constraints have been
* reduced yet). Returns an empty set if elements in the sets
* conflict. This method can be overridden by subclasses for
* constraints with particular reduction semantics; the default
* implementation of this method returns the intersection of the two
* sets.
*/
Set reduce0(Set reduced, Set toReduce) {
return (reduced != null) ? intersect(reduced, toReduce) : toReduce;
}
private Set reduce(Set reduced, Set constraints, boolean required)
throws UnsupportedConstraintException
{
for (Iterator i = constraints.iterator(); i.hasNext(); ) {
InvocationConstraint c = (InvocationConstraint) i.next();
Set toReduce = Collections.EMPTY_SET;
if (targetClass.isInstance(c)) {
toReduce = Collections.singleton(c);
} else if (c instanceof ConstraintAlternatives) {
toReduce = getTargetInstances(
((ConstraintAlternatives) c).elements());
}
if (!toReduce.isEmpty()) {
Set s = reduce0(reduced, toReduce);
if (!s.isEmpty()) {
reduced = s;
} else if (required) {
throw new UnsupportedConstraintException(
"constraints conflict: " + constraints);
}
}
}
return reduced;
}
private Set getTargetInstances(Set set) {
Set instances = Collections.EMPTY_SET;
for (Iterator i = set.iterator(); i.hasNext(); ) {
Object obj = i.next();
if (targetClass.isInstance(obj)) {
if (instances.isEmpty()) {
instances = new HashSet();
}
instances.add(obj);
}
}
return instances;
}
}
/*
* Abstract class which reduces constraints that are specified to have
* a max value. Subclasses only have to handle returning the value of the
* constraint and creating a constraint given an input value.
*/
private abstract static class MaxValueReducer
extends ConstraintReducer
{
MaxValueReducer(Class targetClass) {
super(targetClass);
}
abstract long getValue(InvocationConstraint ic);
abstract InvocationConstraint getConstraintInstance(long value);
/*
* Reduces the alternatives in toReduce to the maximum value and then
* chooses the minimum between this value and the one from reduced.
* Invokes abstract method getValue to allow subclasses to return the
* value for specific constraint classes and invokes
* getConstraintInstance to finally return the actual instance given
* the reduced value that has been determined as above.
*/
Set reduce0(Set reduced, Set toReduce) {
long value = 0;
for (Iterator i = toReduce.iterator(); i.hasNext(); ) {
value = Math.max(
value, getValue((InvocationConstraint) i.next()));
}
if (reduced != null) {
value = Math.min(
value,
getValue((InvocationConstraint) getElement(reduced)));
}
return Collections.singleton(getConstraintInstance(value));
}
}
private static class MulticastMaxPacketSizeReducer
extends MaxValueReducer
{
MulticastMaxPacketSizeReducer() {
super(MulticastMaxPacketSize.class);
}
long getValue(InvocationConstraint maxPacketSize) {
return ((MulticastMaxPacketSize) maxPacketSize).getSize();
}
InvocationConstraint getConstraintInstance(long value) {
if (value > Integer.MAX_VALUE) {
// Shouldnt really happen as we are only dealing with
// MulticasatMaxPacketSize constraints which are int
throw new AssertionError("Value too large " + value);
}
return new MulticastMaxPacketSize((int)value);
}
}
private static class ConnectionAbsoluteTimeReducer extends MaxValueReducer {
ConnectionAbsoluteTimeReducer() {
super(ConnectionAbsoluteTime.class);
}
long getValue(InvocationConstraint absTime) {
return ((ConnectionAbsoluteTime) absTime).getTime();
}
InvocationConstraint getConstraintInstance(long value) {
return new ConnectionAbsoluteTime(value);
}
}
private static Set getUnfulfilled(Set constraints) {
Set unfulfilled = new HashSet(constraints.size());
for (Iterator i = constraints.iterator(); i.hasNext(); ) {
InvocationConstraint c = (InvocationConstraint) i.next();
if (c instanceof ConstraintAlternatives) {
Set s = ((ConstraintAlternatives) c).elements();
Set u = getUnfulfilled(s);
if (u.size() == s.size()) {
unfulfilled.add(c);
}
} else if (!(c instanceof DiscoveryProtocolVersion ||
c instanceof MulticastMaxPacketSize ||
c instanceof MulticastTimeToLive ||
c instanceof UnicastSocketTimeout ||
c instanceof ConnectionAbsoluteTime ||
c instanceof ConnectionRelativeTime))
{
unfulfilled.add(c);
}
}
return unfulfilled;
}
private static int chooseProtocolVersion(Set protocolVersions,
Set protocolVersionPrefs,
InvocationConstraints unfulfilled)
{
DiscoveryProtocolVersion bias = unfulfilled.isEmpty() ?
DiscoveryProtocolVersion.ONE : DiscoveryProtocolVersion.TWO;
Set[] sets = { protocolVersionPrefs, protocolVersions };
for (int i = 0; i < sets.length; i++) {
Set s = sets[i];
if (s.contains(bias)) {
return bias.getVersion();
}
if (!(s = intersect(s, supportedProtocols)).isEmpty()) {
return ((DiscoveryProtocolVersion) getElement(s)).getVersion();
}
}
return bias.getVersion();
}
private static Set intersect(Set s1, Set s2) {
Set intersection = Collections.EMPTY_SET;
for (Iterator i = s1.iterator(); i.hasNext(); ) {
Object obj = i.next();
if (s2.contains(obj)) {
if (intersection.isEmpty()) {
intersection = new HashSet();
}
intersection.add(obj);
}
}
return intersection;
}
private static Object getElement(Set s) {
return s.iterator().next();
}
}