/* * 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.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.lang.reflect.Method; import java.rmi.RemoteException; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; import net.jini.core.constraint.InvocationConstraints; import net.jini.core.constraint.MethodConstraints; /** * Basic implementation of {@link MethodConstraints}, allowing limited * wildcard matching on method names and parameter types. Methods can be * specified by exact name and parameter types (matching a single method), * by exact name (matching all methods with that name), by name prefix * (matching all methods with names that start with that prefix), by name * suffix (matching all methods with names that end with that suffix), and * by a default that matches all methods. Normally instances of this class * should be obtained from a * {@link net.jini.config.Configuration} rather than being * explicitly constructed. * * @author Sun Microsystems, Inc. * * @since 2.0 */ public final class BasicMethodConstraints implements MethodConstraints, Serializable { private static final long serialVersionUID = 1432234194703790047L; /** * @serialField descs MethodDesc[] The ordered method descriptors. */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("descs", MethodDesc[].class, true) }; /** * The ordered method descriptors. */ private final MethodDesc[] descs; /** * Descriptor for specifying the constraints associated with one or * more methods allowing limited wildcard matching on method names and * parameter types. Methods can be specified by exact name and parameter * types (matching a single method), by exact name (matching all methods * with that name), by name prefix (matching all methods with names that * start with that prefix), by name suffix (matching all methods with * names that end with that suffix), and by a default that matches all * methods. * * @since 2.0 */ public static final class MethodDesc implements Serializable { private static final long serialVersionUID = 6773269226844208999L; /** * @serialField name String * The name of the method, with prefix or suffix '*' permitted * if <code>types</code> is <code>null</code>, or <code>null</code> * for a descriptor that matches all methods (in which case * <code>types</code> must also be <code>null</code>). * @serialField types Class[] * The parameter types for the specified method, or <code>null</code> * for wildcard parameter types. * @serialField constraints InvocationConstraints * The non-empty constraints for the specified method or methods, or * <code>null</code> if there are no constraints. */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("name", String.class), new ObjectStreamField("types", Class[].class, true), new ObjectStreamField("constraints", InvocationConstraints.class) }; /** * The name of the method, with prefix or suffix '*' permitted * if <code>types</code> is <code>null</code>, or <code>null</code> * for a descriptor that matches all methods (in which case * <code>types</code> must also be <code>null</code>. */ final String name; /** * The formal parameter types of the method, in declared order, * or <code>null</code> for wildcard parameter types. */ final Class[] types; /** * The non-empty constraints for the specified method or methods, or * <code>null</code> if there are no constraints. */ final InvocationConstraints constraints; /** * Creates a descriptor that only matches methods with exactly the * specified name and parameter types. The constraints can be * <code>null</code>, which is treated the same as an empty * instance. The array passed to the constructor is neither modified * nor retained; subsequent changes to that array have no effect on * the instance created. * * @param name the name of the method * @param types the formal parameter types of the method, in declared * order * @param constraints the constraints, or <code>null</code> * @throws NullPointerException if <code>name</code> or * <code>types</code> is <code>null</code> or any element of * <code>types</code> is <code>null</code> * @throws IllegalArgumentException if <code>name</code> is not a * syntactically valid method name */ public MethodDesc(String name, Class[] types, InvocationConstraints constraints) { this.name = name; this.types = (Class[]) types.clone(); if (constraints != null && constraints.isEmpty()) { constraints = null; } this.constraints = constraints; check(); } /** * Creates a descriptor that matches all methods with names that * equal the specified name or that match the specified pattern, * regardless of their parameter types. If the specified name starts * with the character '*', then this descriptor matches all methods * with names that end with the rest of the specified name. If the * specified name ends with the character '*', then this descriptor * matches all methods with names that start with the rest of the * specified name. Otherwise, this descriptor matches all methods * with names that equal the specified name. The constraints can be * <code>null</code>, which is treated the same as an empty instance. * * @param name the name of the method, with a prefix or suffix '*' * permitted for pattern matching * @param constraints the constraints, or <code>null</code> * @throws NullPointerException if <code>name</code> is * <code>null</code> * @throws IllegalArgumentException if <code>name</code> does not * match any syntactically valid method name */ public MethodDesc(String name, InvocationConstraints constraints) { this.name = name; this.types = null; if (constraints != null && constraints.isEmpty()) { constraints = null; } this.constraints = constraints; check(); } /** * Verifies that the name is a syntactically valid method name, or * (if types is null) if the name is a syntactically valid method name * with a '*' appended or could be constructed from some syntactically * valid method name containing more than two characters by replacing * the first character of that name with '*', and verifies that none * of the elements of types are null. */ private void check() { boolean star = types == null; int len = name.length(); if (len == 0) { throw new IllegalArgumentException( "method name cannot be empty"); } char c = name.charAt(0); if (!Character.isJavaIdentifierStart(c) && !(star && c == '*' && len > 1)) { throw new IllegalArgumentException("invalid method name"); } if (star && c != '*' && name.charAt(len - 1) == '*') { len--; } while (--len >= 1) { if (!Character.isJavaIdentifierPart(name.charAt(len))) { throw new IllegalArgumentException("invalid method name"); } } if (types != null) { for (int i = types.length; --i >= 0; ) { if (types[i] == null) { throw new NullPointerException("class cannot be null"); } } } } /** * Creates a default descriptor that matches all methods. The * constraints can be <code>null</code>, which is treated the same as * an empty instance. * * @param constraints the constraints, or <code>null</code> */ public MethodDesc(InvocationConstraints constraints) { this.name = null; this.types = null; if (constraints != null && constraints.isEmpty()) { constraints = null; } this.constraints = constraints; } /** * Returns the name of the method, with a prefix or suffix '*' if the * name is a pattern, or <code>null</code> if this descriptor matches * all methods. * * @return the name of the method, with a prefix or suffix '*' if the * name is a pattern, or <code>null</code> if this descriptor matches * all methods */ public String getName() { return name; } /** * Returns the parameter types, or <code>null</code> if this * descriptor matches all parameter types or all methods. Returns a * new non-<code>null</code> array every time it is called. * * @return the parameter types, or <code>null</code> if this * descriptor matches all parameter types or all methods */ public Class[] getParameterTypes() { return types == null ? null : (Class[]) types.clone(); } /** * Returns the constraints as a non-<code>null</code> value. * * @return the constraints as a non-<code>null</code> value */ public InvocationConstraints getConstraints() { return (constraints == null ? InvocationConstraints.EMPTY : constraints); } /** * Returns a hash code value for this object. */ public int hashCode() { int h = 0; if (name != null) { h += name.hashCode(); } if (types != null) { h += hash(types); } if (constraints != null) { h += constraints.hashCode(); } return h; } /** * Two instances of this class are equal if they have the same * name, the same parameter types, and the same constraints. */ public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof MethodDesc)) { return false; } MethodDesc od = (MethodDesc) obj; return ((name == null ? od.name == null : name.equals(od.name)) && Arrays.equals(types, od.types) && (constraints == null ? od.constraints == null : constraints.equals(od.constraints))); } /** * Returns a string representation of this object. */ public String toString() { StringBuffer buf = new StringBuffer("MethodDesc["); toString(buf, true); buf.append(']'); return buf.toString(); } /** * Appends a string representation of this object to the buffer. */ void toString(StringBuffer buf, boolean includeConstraints) { buf.append(name == null ? "default" : name); if (types != null) { buf.append('('); for (int i = 0; i < types.length; i++) { if (i > 0) { buf.append(", "); } buf.append(types[i].getName()); } buf.append(')'); } if (includeConstraints) { buf.append(" => ").append(constraints); } } /** * Verifies that the method name, parameter types, and constraints are * valid. * * @throws InvalidObjectException if <code>types</code> is * non-<code>null</code> and <code>name</code> is either * <code>null</code> or is not a syntactically valid method name; * or if <code>types</code> is <code>null</code> and <code>name</code> * is neither a syntactically valid method name, a syntactically * valid method name with a '*' appended, nor a name constructed from * some syntactically valid method name containing more than two * characters by replacing the first character of that name with '*'; * or if any element of <code>types</code> is <code>null</code>; or * if <code>constraints</code> is non-<code>null</code> but empty */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); if (name == null) { if (types != null) { throw new InvalidObjectException( "cannot have types with null name"); } } else { try { check(); } catch (RuntimeException e) { rethrow(e); } } if (constraints != null && constraints.isEmpty()) { throw new InvalidObjectException( "constraints cannot be empty"); } } } /** * Creates an instance with the specified ordered array of descriptors. * The {@link #getConstraints getConstraints} method searches the * descriptors in the specified order. For any given descriptor in the * array, no preceding descriptor can match at least the same methods as * the given descriptor; that is, more specific descriptors must precede * less specific descriptors. The array passed to the constructor is * neither modified nor retained; subsequent changes to that array have * no effect on the instance created. * * @param descs the descriptors * @throws NullPointerException if the argument is <code>null</code> or * any element of the argument is <code>null</code> * @throws IllegalArgumentException if the descriptors array is empty, or * if any descriptor is preceded by another descriptor that matches at * least the same methods */ public BasicMethodConstraints(MethodDesc[] descs) { this.descs = (MethodDesc[]) descs.clone(); check(); } /** * Throws IllegalArgumentException if the descriptors array is empty, or * if any descriptor is preceded by another descriptor that matches at * least the same methods. Throws NullPointerException if the array or * any element is null. */ private void check() { if (descs.length == 0) { throw new IllegalArgumentException( "must have at least one descriptor"); } for (int i = 0; i < descs.length; i++) { MethodDesc desc = descs[i]; String dname = desc.name; if (dname == null) { if (i < descs.length - 1) { throw new IllegalArgumentException( "default descriptor must be last"); } } else if (dname.charAt(0) == '*') { int dlen = dname.length() + 1; for (int j = 0; j < i; j++) { MethodDesc prev = descs[j]; String pname = prev.name; if (pname.charAt(0) == '*' && pname.regionMatches(1, dname, dlen - pname.length(), pname.length() - 1)) { check(prev, desc); } } } else if (dname.charAt(dname.length() - 1) == '*') { for (int j = 0; j < i; j++) { MethodDesc prev = descs[j]; String pname = prev.name; int plen = pname.length() - 1; if (pname.charAt(plen) == '*' && pname.regionMatches(0, dname, 0, plen)) { check(prev, desc); } } } else { for (int j = 0; j < i; j++) { MethodDesc prev = descs[j]; String pname = prev.name; int plen = pname.length() - 1; if (pname.charAt(0) == '*') { if (dname.regionMatches(dname.length() - plen, pname, 1, plen)) { check(prev, desc); } } else if (pname.charAt(plen) == '*') { if (dname.regionMatches(0, pname, 0, plen)) { check(prev, desc); } } else { if (pname.equals(dname)) { check(prev, desc); } } } } } } /** * Throws IllegalArgumentException if the parameter types of prev cover * those of desc. */ private static void check(MethodDesc prev, MethodDesc desc) { if (prev.types == null || Arrays.equals(prev.types, desc.types)) { StringBuffer buf = new StringBuffer(); prev.toString(buf, false); buf.append(" cannot precede "); desc.toString(buf, false); throw new IllegalArgumentException(buf.toString()); } } /** * Creates an instance that maps all methods to the specified constraints. * The constraints can be <code>null</code>, which is treated the same as * an empty instance. Calling this constructor is equivalent to * constructing an instance of this class with an array containing a * single default descriptor constructed with the specified constraints. * * @param constraints the constraints, or <code>null</code> */ public BasicMethodConstraints(InvocationConstraints constraints) { descs = new MethodDesc[]{new MethodDesc(constraints)}; } /** * Returns the constraints for the specified remote method as a * non-<code>null</code> value. Searches the descriptors in order, and * returns the constraints in the first descriptor that matches the * method, or an empty constraints instance if there is no match. * * @throws NullPointerException {@inheritDoc} */ public InvocationConstraints getConstraints(Method method) { String name = method.getName(); Class[] types = null; InvocationConstraints sc = null; outer: for (int i = 0; i < descs.length; i++) { MethodDesc desc = descs[i]; String dname = desc.name; if (dname == null) { sc = desc.constraints; break; } else if (desc.types != null) { if (!name.equals(dname)) { continue; } if (types == null) { types = method.getParameterTypes(); } if (types.length != desc.types.length) { continue; } for (int j = types.length; --j >= 0; ) { if (types[j] != desc.types[j]) { continue outer; } } sc = desc.constraints; break; } else { int dlen = dname.length() - 1; if (dname.charAt(0) == '*') { if (name.regionMatches(name.length() - dlen, dname, 1, dlen)) { sc = desc.constraints; break; } } else if (dname.charAt(dlen) == '*') { if (name.regionMatches(0, dname, 0, dlen)) { sc = desc.constraints; break; } } else if (name.equals(dname)) { sc = desc.constraints; break; } } } if (sc == null) { sc = InvocationConstraints.EMPTY; } return sc; } /* inherit javadoc */ public Iterator possibleConstraints() { return new Iterator() { private int i = descs.length; private boolean empty = descs[i - 1].name != null; public boolean hasNext() { return i > 0 || empty; } public Object next() { if (i == 0) { if (empty) { empty = false; return InvocationConstraints.EMPTY; } throw new NoSuchElementException("no more elements"); } InvocationConstraints sc = descs[--i].constraints; if (sc == null) { sc = InvocationConstraints.EMPTY; } return sc; } public void remove() { throw new UnsupportedOperationException("immutable object"); } }; } /** * Returns the descriptors. Returns a new non-<code>null</code> array * every time it is called. * * @return the descriptors as a new non-<code>null</code> array */ public MethodDesc[] getMethodDescs() { return (MethodDesc[]) descs.clone(); } /** * Returns a hash code value for this object. */ public int hashCode() { return hash(descs); } /** * Returns a string representation of this object. */ public String toString() { StringBuffer buf = new StringBuffer("BasicMethodConstraints{"); for (int i = 0; i < descs.length; i++) { if (i > 0) { buf.append(", "); } descs[i].toString(buf, true); } buf.append('}'); return buf.toString(); } /** * Two instances of this class are equal if they have the same descriptors * in the same order. */ public boolean equals(Object obj) { return (this == obj || (obj instanceof BasicMethodConstraints && Arrays.equals(descs, ((BasicMethodConstraints) obj).descs))); } /** * Verifies legal descriptor ordering. * * @throws InvalidObjectException if any descriptor is <code>null</code>, * or the descriptors array is empty, or if any descriptor is preceded by * another descriptor that matches at least the same methods */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); try { check(); } catch (RuntimeException e) { rethrow(e); } } /** * Returns the sum of the hash codes of all elements of the given array. */ private static int hash(Object[] elements) { int h = 0; for (int i = elements.length; --i >= 0; ) { h += elements[i].hashCode(); } return h; } /** * If the exception is a NullPointerException or IllegalArgumentException, * wrap it in an InvalidObjectException and throw that, otherwise rethrow * the exception as is. */ private static void rethrow(RuntimeException e) throws InvalidObjectException { if (e instanceof NullPointerException || e instanceof IllegalArgumentException) { InvalidObjectException ee = new InvalidObjectException(e.getMessage()); ee.initCause(e); throw ee; } throw e; } }