View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package net.jini.core.constraint;
20  
21  import java.io.IOException;
22  import java.io.InvalidObjectException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectStreamField;
25  import java.io.Serializable;
26  import java.util.Collection;
27  import java.util.Set;
28  import org.apache.river.api.io.AtomicSerial;
29  import org.apache.river.api.io.AtomicSerial.GetArg;
30  
31  /**
32   * Combines two or more constraint alternatives into a single overall
33   * constraint. The semantics of this aggregate constraint are that at least
34   * one of the individual constraint alternatives must be satisfied. The
35   * alternatives do not have to be instances of the same type, but they
36   * cannot themselves be <code>ConstraintAlternatives</code> instances.
37   * <p>
38   * Note that this class implements {@link RelativeTimeConstraint} even though
39   * the constraint elements might not implement
40   * <code>RelativeTimeConstraint</code>.
41   * <p>
42   * An instance containing an exhaustive list of alternatives (for example,
43   * an instance containing both <code>ClientAuthentication.YES</code> and
44   * <code>ClientAuthentication.NO</code>) serves no useful purpose, as a
45   * requirement or as a preference. A <i>don't care</i> condition should
46   * be expressed by the <i>absence</i> of constraints.
47   *
48   * @author Sun Microsystems, Inc.
49   * @since 2.0
50   */
51  @AtomicSerial
52  public final class ConstraintAlternatives
53  				implements RelativeTimeConstraint, Serializable
54  {
55      private static final long serialVersionUID = 7214615235302870613L;
56  
57      /**
58       * @serialField constraints InvocationConstraint[]
59       * The alternative constraints.
60       */
61      private static final ObjectStreamField[] serialPersistentFields = {
62          new ObjectStreamField("constraints", InvocationConstraint[].class,
63  			      true)
64      };
65  
66      /**
67       * The alternative constraints.
68       */
69      private final InvocationConstraint[] constraints;
70  
71      /**
72       * Indicates whether any of the constraints are based on relative time.
73       */
74      private transient boolean rel = false;
75  
76      /**
77       * Creates an instance containing the specified alternative constraints,
78       * with duplicate constraints removed. The argument passed to this
79       * constructor is neither modified nor retained; subsequent changes to
80       * that argument have no effect on the instance created.
81       *
82       * @param constraints the alternative constraints
83       * @throws NullPointerException if the argument is <code>null</code> or
84       * any element is <code>null</code>
85       * @throws IllegalArgumentException if any of the elements are instances
86       * of <code>ConstraintAlternatives</code>, or if fewer than two elements
87       * remain after duplicate constraints are removed
88       */
89      public ConstraintAlternatives(InvocationConstraint[] constraints) {
90  	this.constraints =
91  	    reduce((InvocationConstraint[]) constraints.clone());
92  	setRelative();
93      }
94      
95      /**
96       * AtomicSerial constructor.
97       * 
98       * @param arg atomic deserialization parameter 
99       * @throws java.io.IOException if there are I/O errors while reading from GetArg's
100      *         underlying <code>InputStream</code>
101      * @throws InvalidObjectException if object invariants aren't satisfied.
102      */
103     public ConstraintAlternatives(GetArg arg) throws IOException{
104 	this(validate(arg.get("constraints", null, InvocationConstraint[].class)), false);
105     }
106     
107     private static InvocationConstraint[] validate(InvocationConstraint[] constraints) 
108 	    throws InvalidObjectException{
109 	if (constraints == null) {
110 	    throw new InvalidObjectException(
111 				  "cannot create constraint with no elements");
112 	}
113 	constraints = constraints.clone();
114 	try {
115 	    verify(constraints, 2);
116 	} catch (RuntimeException e) {
117 	    if (e instanceof NullPointerException ||
118 		e instanceof IllegalArgumentException)
119 	    {
120 		InvalidObjectException ee =
121 		    new InvalidObjectException(e.getMessage());
122 		ee.initCause(e);
123 		throw ee;
124 	    }
125 	    throw e;
126 	}
127 	for (int i = constraints.length; --i >= 0; ) {
128 	    if (Constraint.contains(constraints, i, constraints[i])) {
129 		throw new InvalidObjectException(
130 			  "cannot create constraint with duplicate elements");
131 	    }
132 	}
133 	return constraints;
134     }
135 
136     /**
137      * Creates an instance containing the specified alternative constraints,
138      * with duplicate constraints removed. The argument passed to this
139      * constructor is neither modified nor retained; subsequent changes to
140      * that argument have no effect on the instance created.
141      *
142      * @param c the alternative constraints
143      * @throws NullPointerException if the argument is <code>null</code> or
144      * any element is <code>null</code>
145      * @throws IllegalArgumentException if any of the elements are instances
146      * of <code>ConstraintAlternatives</code>, or if the elements are not all
147      * instances of <code>InvocationConstraint</code>, or if fewer than two
148      * elements remain after duplicate constraints are removed
149      */
150     public ConstraintAlternatives(Collection<InvocationConstraint> c) {
151 	try {
152 	    constraints = reduce( c.toArray(
153 					  new InvocationConstraint[c.size()]));
154 	} catch (ArrayStoreException e) {
155 	    throw new IllegalArgumentException(
156 		       "element of collection is not an InvocationConstraint");
157 	}
158 	setRelative();
159     }
160 
161     /**
162      * Returns a constraint representing the specified alternative constraints,
163      * with duplicate constraints removed. If a single constraint remains after
164      * duplicates are removed, then that constraint is returned, otherwise an
165      * instance of <code>ConstraintAlternatives</code> containing the remaining
166      * constraints is returned. The argument passed to this method is neither
167      * modified nor retained; subsequent changes to that argument have no
168      * effect on the instance created.
169      *
170      * @param constraints the alternative constraints
171      * @return a constraint representing the specified alternative constraints,
172      * with duplicate constraints removed
173      * @throws NullPointerException if the argument is <code>null</code> or
174      * any element is <code>null</code>
175      * @throws IllegalArgumentException if the argument is empty, or if any
176      * of the elements are instances of <code>ConstraintAlternatives</code>
177      */
178     public static InvocationConstraint create(
179 					   InvocationConstraint[] constraints)
180     {
181 	return reduce((InvocationConstraint[]) constraints.clone(), false);
182     }
183 
184     /**
185      * Returns a constraint representing the specified alternative constraints,
186      * with duplicate constraints removed. If a single constraint remains after
187      * duplicates are removed, then that constraint is returned, otherwise an
188      * instance of <code>ConstraintAlternatives</code> containing the remaining
189      * constraints is returned. The argument passed to this method is neither
190      * modified nor retained; subsequent changes to that argument have no
191      * effect on the instance created.
192      *
193      * @param c the alternative constraints
194      * @return a constraint representing the specified alternative constraints,
195      * with duplicate constraints removed
196      * @throws NullPointerException if the argument is <code>null</code> or
197      * any element is <code>null</code>
198      * @throws IllegalArgumentException if the argument is empty, or if any
199      * of the elements are instances of <code>ConstraintAlternatives</code>,
200      * or if the elements are not all instances of
201      * <code>InvocationConstraint</code>
202      */
203     public static InvocationConstraint create(Collection<InvocationConstraint> c) {
204 	try {
205 	    return reduce( c.toArray( new InvocationConstraint[c.size()]), false);
206 	} catch (ArrayStoreException e) {
207 	    throw new IllegalArgumentException(
208 		       "element of collection is not an InvocationConstraint");
209 	}
210     }
211 
212     /**
213      * Creates a constraint containing the specified alternative constraints,
214      * and computes the rel field if allAbs is false.
215      */
216     private ConstraintAlternatives(InvocationConstraint[] constraints,
217 				   boolean allAbs)
218     {
219 	this.constraints = constraints;
220 	if (!allAbs) {
221 	    setRelative();
222 	}
223     }
224 
225     /**
226      * Sets the rel field to true if any of the constraints are relative.
227      */
228     private void setRelative() {
229 	for (int i = constraints.length; --i >= 0; ) {
230 	    if (constraints[i] instanceof RelativeTimeConstraint) {
231 		rel = true;
232 		return;
233 	    }
234 	}
235     }
236 
237     /**
238      * Returns true if any of the constraints are relative, false otherwise.
239      */
240     boolean relative() {
241 	return rel;
242     }
243 
244     /**
245      * Verifies that the array is non-empty, and that the elements are all
246      * non-null and not ConstraintAlternatives instances. Removes duplicates
247      * and returns a single constraint if there's only one left, otherwise
248      * returns an ConstraintAlternatives containing the remaining constraints.
249      * The argument is modified in place.
250      */
251     private static InvocationConstraint reduce(
252 					    InvocationConstraint[] constraints,
253 					    boolean allAbs)
254     {
255 	verify(constraints, 1);
256 	int n = reduce0(constraints);
257 	if (n == 1) {
258 	    return constraints[0];
259 	}
260 	return new ConstraintAlternatives(
261 		      (InvocationConstraint[]) Constraint.trim(constraints, n),
262 		      allAbs);
263     }
264 
265     /**
266      * Verifies that the array has at least min elements, and that the
267      * elements are all non-null and not ConstraintAlternatives instances.
268      */
269     private static void verify(InvocationConstraint[] constraints, int min) {
270 	if (constraints.length < min) {
271 	    throw new IllegalArgumentException(
272 				 "cannot create constraint with " +
273 				 (min == 1 ? "no" : ("less than " + min)) +
274 				 " elements");
275 	}
276 	for (int i = constraints.length; --i >= 0; ) {
277 	    InvocationConstraint c = constraints[i];
278 	    if (c == null) {
279 		throw new NullPointerException("elements cannot be null");
280 	    } else if (c instanceof ConstraintAlternatives) {
281 		throw new IllegalArgumentException(
282 			"elements cannot be ConstraintAlternatives instances");
283 	    }
284 	}
285     }
286 
287     /**
288      * Verifies that the array has at least 2 elements, and that the elements
289      * are all non-null and not ConstraintAlternatives instances, removes
290      * duplicates, modifying the array in place, verifies that there are still
291      * at least 2 elements, and returns an array containing the remaining
292      * elements.
293      */
294     private static InvocationConstraint[] reduce(
295 					    InvocationConstraint[] constraints)
296     {
297 	verify(constraints, 2);
298 	int n = reduce0(constraints);
299 	if (n == 1) {
300 	    throw new IllegalArgumentException(
301 					   "reduced to less than 2 elements");
302 	}
303 	return (InvocationConstraint[]) Constraint.trim(constraints, n);
304     }
305 
306     /**
307      * Eliminates duplicates, modifying the array in place, and returns
308      * the resulting number of elements.
309      */
310     private static int reduce0(InvocationConstraint[] constraints) {
311 	int i = 0;
312 	for (int j = 0; j < constraints.length; j++) {
313 	    InvocationConstraint c = constraints[j];
314 	    if (!Constraint.contains(constraints, i, c)) {
315 		constraints[i++] = c;
316 	    }
317 	}
318 	return i;
319     }
320 
321     /**
322      * Returns an immutable set of all of the constraints. Any attempt to
323      * modify this set results in an {@link UnsupportedOperationException}
324      * being thrown.
325      *
326      * @return an immutable set of all of the constraints
327      */
328     public Set<InvocationConstraint> elements() {
329 	return new ArraySet<InvocationConstraint>(constraints);
330     }
331 
332     /**
333      * Returns the elements, without copying.
334      */
335     InvocationConstraint[] getConstraints() {
336 	return constraints;
337     }
338 
339     /**
340      * Returns a constraint equal to the result of taking the constraints in
341      * this instance, replacing each constraint that is an instance of
342      * {@link RelativeTimeConstraint} with the result of invoking that
343      * constraint's <code>makeAbsolute</code> method with the specified base
344      * time, and invoking the <code>create</code> method of this class with
345      * the revised collection of constraints.
346      */
347     public InvocationConstraint makeAbsolute(long baseTime) {
348 	if (!rel) {
349 	    return this;
350 	}
351 	InvocationConstraint[] vals =
352 	    new InvocationConstraint[constraints.length];
353 	for (int i = vals.length; --i >= 0; ) {
354 	    InvocationConstraint c = constraints[i];
355 	    if (c instanceof RelativeTimeConstraint) {
356 		c = ((RelativeTimeConstraint) c).makeAbsolute(baseTime);
357 	    }
358 	    vals[i] = c;
359 	}
360 	return reduce(vals, true);
361     }
362 
363     /**
364      * Returns a hash code value for this object.
365      */
366     public int hashCode() {
367 	return Constraint.hash(constraints);
368     }
369 
370     /**
371      * Two instances of this class are equal if they have the same constraints
372      * (ignoring order).
373      */
374     public boolean equals(Object obj) {
375 	return (obj instanceof ConstraintAlternatives &&
376 		Constraint.equal(constraints,
377 				 ((ConstraintAlternatives) obj).constraints));
378     }
379 
380     /**
381      * Returns a string representation of this object.
382      */
383     public String toString() {
384 	return "ConstraintAlternatives" + Constraint.toString(constraints);
385     }
386 
387     /**
388      * Verifies that there are at least two constraints, that none are
389      * <code>null</code> and none are instances of this class, and that
390      * there are no duplicates.
391      *
392      * @throws InvalidObjectException if there are less than two constraints,
393      * or any constraint is <code>null</code> or an instance of this class,
394      * or if there are duplicates
395      * @param s ObjectInputStream
396      * @throws ClassNotFoundException if class not found.
397      * @throws IOException if a problem occurs during de-serialization.
398      */
399     private void readObject(ObjectInputStream s)
400 	throws IOException, ClassNotFoundException
401     {
402 	s.defaultReadObject();
403 	if (constraints == null) {
404 	    throw new InvalidObjectException(
405 				  "cannot create constraint with no elements");
406 	}
407 	try {
408 	    verify(constraints, 2);
409 	} catch (RuntimeException e) {
410 	    if (e instanceof NullPointerException ||
411 		e instanceof IllegalArgumentException)
412 	    {
413 		InvalidObjectException ee =
414 		    new InvalidObjectException(e.getMessage());
415 		ee.initCause(e);
416 		throw ee;
417 	    }
418 	    throw e;
419 	}
420 	for (int i = constraints.length; --i >= 0; ) {
421 	    if (Constraint.contains(constraints, i, constraints[i])) {
422 		throw new InvalidObjectException(
423 			  "cannot create constraint with duplicate elements");
424 	    }
425 	}
426 	setRelative();
427     }
428 }