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 }