/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2001,2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
*/
// ServiceLocationAttribute.java : Class for attributes in SLP.
// Author: James Kempf, Erik Guttman
//
package com.sun.slp;
import java.util.*;
import java.io.*;
/**
* The ServiceLocationAttribute class models SLP attributes.
*
* @author James Kempf, Erik Guttman
*/
public class ServiceLocationAttribute extends Object
implements Serializable {
// Characters to escape.
final static String RESERVED = "(),\\!<=>~";
final static String ESCAPED = RESERVED + "*";
final static char ESCAPE = '\\';
final static char CTL_LOWER = (char)0x00;
final static char CTL_UPPER = (char)0x1F;
final static char DEL = (char)0x7F;
// Whitespace chars.
static final String WHITESPACE = " \n\t\r";
static final char SPACE = ' ';
// For character escaping.
static final char COMMA = ',';
static final char PERCENT = '%';
// Bad tag characters.
final private static String BAD_TAG_CHARS = "*\n\t\r";
// For identifying booleans.
final static String TRUE = "true";
final static String FALSE = "false";
//
// Package accessable fields.
//
Vector values = null;
String id = null;
// For V1 compatibility subclass.
ServiceLocationAttribute() {}
/**
* Construct a service location attribute.
*
* @param id The attribute name
* @param values_in Vector of one or more attribute values. Vector
* contents must be uniform in type and one of
* Integer, String, Boolean, or byte[]. If the attribute
* is a keyword attribute, then values_in should be null.
* @exception IllegalArgumentException Thrown if the
* vector contents is not of the right type or
* an argument is null or syntactically incorrect.
*/
public ServiceLocationAttribute(String id_in, Vector values_in)
throws IllegalArgumentException {
Assert.nonNullParameter(id_in, "id");
id = id_in;
if (values_in != null &&
values_in.size() > 0) { // null, empty indicate keyword attribute.
values = (Vector)values_in.clone();
verifyValueTypes(values, false);
}
}
/**
* Construct a service location attribute from a parenthesized expression.
* The syntax is:
*
* exp = "(" id "=" value-list ")" | keyword
* value-list = value | value "," value-list
*
*
* @param exp The expression
* @param dontTypeCheck True if multivalued booleans and vectors
* of varying types are allowed.
* @exception ServiceLocationException If there are any syntax errors.
*/
ServiceLocationAttribute(String exp, boolean allowMultiValuedBooleans)
throws ServiceLocationException {
if (exp == null || exp.length() <= 0) {
new ServiceLocationException(ServiceLocationException.PARSE_ERROR,
"null_string_parameter",
new Object[] {exp});
}
// If start and end paren, then parse out assignment.
if (exp.startsWith("(") && exp.endsWith(")")) {
StringTokenizer tk =
new StringTokenizer(exp.substring(1, exp.length() - 1),
"=",
true);
try {
// Get the tag.
id =
unescapeAttributeString(tk.nextToken(), true);
if (id.length() <= 0) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"null_id",
new Object[] {exp});
}
tk.nextToken(); // get rid of "="
// Gather the rest.
String rest = tk.nextToken("");
// Parse the comma separated list.
values = SrvLocHeader.parseCommaSeparatedListIn(rest, true);
// Convert to objects.
int i, n = values.size();
Class vecClass = null;
for (i = 0; i < n; i++) {
String value = (String)values.elementAt(i);
// Need to determine which type to use.
Object o = evaluate(value);
values.setElementAt(o, i);
}
} catch (NoSuchElementException ex) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"assignment_syntax_err",
new Object[] {exp});
}
verifyValueTypes(values, allowMultiValuedBooleans);
} else {
// Check to make sure there's no parens.
if (exp.indexOf('(') != -1 || exp.indexOf(')') != -1) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"assignment_syntax_err",
new Object[] {exp});
}
// Unescape the keyword.
id = unescapeAttributeString(exp, true);
}
}
static Object evaluate(String value)
throws ServiceLocationException {
Object o = null;
// If it can be converted into an integer, then convert it.
try {
o = Integer.valueOf(value);
} catch (NumberFormatException ex) {
// Wasn't an integer. Try boolean.
if (value.equalsIgnoreCase(TRUE) ||
value.equalsIgnoreCase(FALSE)) {
o = Boolean.valueOf(value);
} else {
// Process the string to remove escapes.
String val = (String)value;
// If it begins with the opaque prefix, treat it as an
// opaque.
if (val.startsWith(Opaque.OPAQUE_HEADER)) {
o = Opaque.unescapeByteArray(val);
} else {
o = unescapeAttributeString(val, false);
}
}
}
return o;
}
//
// Property accessors.
//
/**
* @return A vector of attribute values, or null if the attribute is
* a keyword attribute. If the attribute is single-valued, then
* the vector contains only one object.
*
*/
public Vector getValues() {
if (values == null) {
return null; // keyword case.
}
Vector ret = (Vector)values.clone();
// Need to process Opaques.
int i, n = ret.size();
for (i = 0; i < n; i++) {
Object o = ret.elementAt(i);
if (o instanceof Opaque) {
o = ((Opaque)o).bytes;
}
ret.setElementAt(o, i);
}
return ret;
}
/**
* @return The attribute name.
*/
public String getId() {
return id;
}
/**
* Return an escaped version of the id parameter , suitable for inclusion
* in a query.
*
* @param str The string to escape as an id.
* @return The string with any reserved characters escaped.
* @exception IllegalArgumentException Thrown if the
* string contains bad tag characters.
*/
static public String escapeId(String str)
throws IllegalArgumentException {
String ret = null;
try {
ret = escapeAttributeString(str, true);
} catch (ServiceLocationException ex) {
throw new IllegalArgumentException(ex.getMessage());
}
return ret;
}
/**
* Return an escaped version of the value parameter, suitable for inclusion
* in a query. Opaques are stringified.
*
* @param val The value to escape.
* @return The stringified value.
* @exception IllegalArgumentException Thrown if the object is not
* one of byte[], Integer, Boolean, or String.
*/
static public String escapeValue(Object val)
throws IllegalArgumentException {
// Check type first.
typeCheckValue(val);
// Make Opaque out of byte[].
if (val instanceof byte[]) {
val = new Opaque((byte[])val);
}
return escapeValueInternal(val);
}
// Check type to make sure it's OK.
static private void typeCheckValue(Object obj) {
SLPConfig conf = SLPConfig.getSLPConfig();
Assert.nonNullParameter(obj, "attribute value vector element");
if (obj.equals("")) {
throw
new IllegalArgumentException(
conf.formatMessage("empty_string_value",
new Object[0]));
}
if (!(obj instanceof Integer) && !(obj instanceof Boolean) &&
!(obj instanceof String) && !(obj instanceof byte[])) {
throw
new IllegalArgumentException(
conf.formatMessage("value_type_error",
new Object[0]));
}
}
// We know the value's type is OK, so just escape it.
private static String escapeValueInternal(Object val) {
String s;
// Escape any characters needing it.
if (val instanceof String) {
try {
s = escapeAttributeString((String)val, false);
} catch (ServiceLocationException ex) {
throw
new IllegalArgumentException(ex.getMessage());
}
} else {
s = val.toString();
}
return s;
}
//
// Methods for dealing with the type of attribute values.
//
// Verify the types of incoming attributes.
protected void
verifyValueTypes(Vector values_in, boolean dontTypeCheck) {
SLPConfig conf = SLPConfig.getSLPConfig();
// Make sure the types of objects passed in are acceptable
// and that all objects in the vector have the same type.
int i, n = values_in.size();
Class cls = null;
for (i = 0; i < n; i++) {
Object obj = values_in.elementAt(i);
typeCheckValue(obj);
if (i == 0) {
cls = obj.getClass();
} else if (!cls.equals(obj.getClass()) && !dontTypeCheck) {
throw
new IllegalArgumentException(
conf.formatMessage("type_mismatch_error",
new Object[0]));
}
// If it's a boolean and there's more than one, signal error
// unless multivalued booleans are allowed.
if (!dontTypeCheck && i != 0 && obj instanceof Boolean) {
throw
new IllegalArgumentException(
conf.formatMessage("multivalued_boolean",
new Object[0]));
}
// If it's a byte array, create a Opaque object.
if (obj instanceof byte[]) {
values_in.setElementAt(new Opaque((byte[])obj), i);
} else if (obj instanceof String) {
String val = (String)obj;
// If it's a string and looks like "1" or "true", then
// append a space onto the end.
try {
Object obj2 = evaluate(val);
if (!(obj2 instanceof String)) {
values_in.setElementAt((String)val + " ", i);
}
} catch (ServiceLocationException ex) {
// Ignore for now.
}
}
}
}
//
// Methods for externalizing attributes.
//
/**
* Externalize the attribute into a string that can be written
* to a byte stream. Includes escaping any characters that
* need to be escaped.
*
* @return String with attribute's external representation.
* @exception ServiceLocationException Thrown if the
* string contains unencodable characters.
*/
String externalize()
throws ServiceLocationException {
if (values == null) { // keyword attribute...
return escapeAttributeString(id, true);
}
Vector v = new Vector();
for (Enumeration e = values.elements(); e.hasMoreElements(); ) {
Object o = e.nextElement();
String s = null;
s = escapeValueInternal(o);
v.addElement(s);
}
StringBuffer buf =
new StringBuffer("(" +
escapeAttributeString(id, true) +
"=");
buf.append(SrvLocHeader.vectorToCommaSeparatedList(v));
buf.append(")");
return buf.toString();
}
//
// Escaping and unescaping strings.
//
/**
* Escape any escapable characters to a 2 character escape
* in the attribute string.
*
* @param string The String.
* @param badTag Check for bad tag characters if true.
* @return The escaped string.
* @exception ServiceLocationException Thrown if the string
* contains a character that can't be encoded.
*/
static String escapeAttributeString(String string,
boolean badTag)
throws ServiceLocationException {
StringBuffer buf = new StringBuffer();
int i, n = string.length();
for (i = 0; i < n; i++) {
char c = string.charAt(i);
// Check for bad tag characters first.
if (badTag && BAD_TAG_CHARS.indexOf(c) != -1) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"bad_id_char",
new Object[] {Integer.toHexString(c)});
}
// Escape if the character is reserved.
if (canEscape(c)) {
buf.append(ESCAPE);
String str = escapeChar(c);
// Pad with zero if less than 2 characters.
if (str.length() <= 1) {
str = "0" + str;
}
buf.append(str);
} else {
buf.append(c);
}
}
return buf.toString();
}
/**
* Convert any 2 character escapes to the corresponding characters.
*
* @param string The string to be processed.
* @param badTag Check for bad tag characters if true.
* @return The processed string.
* @exception ServiceLocationException Thrown if an escape
* is improperly formatted.
*/
static String unescapeAttributeString(String string,
boolean badTag)
throws ServiceLocationException {
// Process escapes.
int i, n = string.length();
StringBuffer buf = new StringBuffer(n);
for (i = 0; i < n; i++) {
char c = string.charAt(i);
// Check for escaped characters.
if (c == ESCAPE) {
// Get the next two characters.
if (i >= n - 2) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"nonterminating_escape",
new Object[] {string});
}
i++;
c = unescapeChar(string.substring(i, i+2));
i++;
// Check whether it's reserved.
if (!canEscape(c)) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"char_not_reserved_attr",
new Object[] {new Character(c), string});
}
} else {
// Check whether the character is reserved.
if (isReserved(c)) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"reserved_not_escaped",
new Object[] {new Character(c)});
}
}
// If we need to check for a bad tag character, do so now.
if (badTag && BAD_TAG_CHARS.indexOf(c) != -1) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"bad_id_char",
new Object[] {Integer.toHexString(c)});
}
buf.append(c);
}
return buf.toString();
}
// Return true if the character c can be escaped.
private static boolean canEscape(char c) {
return ((ESCAPED.indexOf(c) != -1) ||
((c >= CTL_LOWER && c <= CTL_UPPER) || c == DEL));
}
// Return true if the character c is reserved.
private static boolean isReserved(char c) {
return ((RESERVED.indexOf(c) != -1) ||
((c >= CTL_LOWER && c <= CTL_UPPER) || c == DEL));
}
/**
* Return a string of integers giving the character's encoding in
* the character set passed in as encoding.
*
* @param c The character to escape.
* @return The character as a string of integers for the encoding.
*/
static String escapeChar(char c) {
byte[] b = null;
try {
b = ("" + c).getBytes(Defaults.UTF8);
} catch (UnsupportedEncodingException ex) {
Assert.slpassert(false, "no_utf8", new Object[0]);
}
int code = 0;
// Assemble the character code.
if (b.length > 3) {
Assert.slpassert(false,
"illegal_utf8",
new Object[] {new Character(c)});
}
code = (int)(b[0] & 0xFF);
if (b.length > 1) {
code = (int)(code | ((b[1] & 0xFF) << 8));
}
if (b.length > 2) {
code = (int)(code | ((b[2] & 0xFF) << 16));
}
String str = Integer.toHexString(code);
return str;
}
/**
* Unescape the character encoded as the string.
*
* @param ch The character as a string of hex digits.
* @return The character.
* @exception ServiceLocationException If the characters can't be
* converted into a hex string.
*/
static char unescapeChar(String ch)
throws ServiceLocationException {
int code = 0;
try {
code = Integer.parseInt(ch, 16);
} catch (NumberFormatException ex) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"not_a_character",
new Object[] {ch});
}
// Convert to bytes.
String str = null;
byte b0 = 0, b1 = 0, b2 = 0, b3 = 0;
byte b[] = null;
b0 = (byte) (code & 0xFF);
b1 = (byte) ((code >> 8) & 0xFF);
b2 = (byte) ((code >> 16) & 0xFF);
b3 = (byte) ((code >> 24) & 0xFF);
// We allow illegal UTF8 encoding so we can decode byte arrays.
if (b3 != 0) {
b = new byte[3];
b[3] = b3;
b[2] = b2;
b[1] = b1;
b[0] = b0;
} else if (b2 != 0) {
b = new byte[3];
b[2] = b2;
b[1] = b1;
b[0] = b0;
} else if (b1 != 0) {
b = new byte[2];
b[1] = b1;
b[0] = b0;
} else {
b = new byte[1];
b[0] = b0;
}
// Make a string out of it.
try {
str = new String(b, Defaults.UTF8);
} catch (UnsupportedEncodingException ex) {
Assert.slpassert(false, "no_utf8", new Object[0]);
}
int len = str.length();
if (str.length() > 1) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"more_than_one",
new Object[] {ch});
}
return (len == 1 ? str.charAt(0):(char)0);
}
/**
* Merge the values in newAttr into the attribute in the hashtable
* if a duplicate attribute, signal error if a type mismatch.
* Both the return vector and hashtable are updated, but the
* newAttr parameter is left unchanged.
*
* @param attr The ServiceLocationAttribute to check.
* @param attrHash A Hashtable containing the attribute tags as
* keys and the attributes as values.
* @param returns A Vector in which to put the attribute when done.
* @param dontTypeCheck If this flag is true, the value vector
* may have two booleans, may
* contain differently typed objects, or the
* function may merge a keyword and nonkeyword
* attribute.
* @exception ServiceLocationException Thrown if a type mismatch
* occurs.
*/
static void
mergeDuplicateAttributes(ServiceLocationAttribute newAttr,
Hashtable attrTable,
Vector returns,
boolean dontTypeCheck)
throws ServiceLocationException {
// Look up the attribute
String tag = newAttr.getId().toLowerCase();
ServiceLocationAttribute attr =
(ServiceLocationAttribute)attrTable.get(tag);
// Don't try this trick with ServerAttributes!
Assert.slpassert((!(attr instanceof ServerAttribute) &&
!(newAttr instanceof ServerAttribute)),
"merge_servattr",
new Object[0]);
// If the attribute isn't in the hashtable, then add to
// vector and hashtable.
if (attr == null) {
attrTable.put(tag, newAttr);
returns.addElement(newAttr);
return;
}
Vector attrNewVals = newAttr.values;
Vector attrVals = attr.values;
// If both keywords, nothing further to do.
if (attrVals == null && attrNewVals == null) {
return;
}
// If we are not typechecking and one is keyword while the other
// is not, then simply merge in the nonkeyword. Otherwise,
// throw a type check exception.
if ((attrVals == null && attrNewVals != null) ||
(attrNewVals == null && attrVals != null)) {
if (dontTypeCheck) {
Vector vals = (attrNewVals != null ? attrNewVals:attrVals);
attr.values = vals;
newAttr.values = vals;
} else {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"attribute_type_mismatch",
new Object[] {newAttr.getId()});
}
} else {
// Merge the two vectors. We type check against the attrVals
// vector, if we are type checking.
int i, n = attrNewVals.size();
Object o = attrVals.elementAt(0);
Class c = o.getClass();
for (i = 0; i < n; i++) {
Object no = attrNewVals.elementAt(i);
// Check for type mismatch, throw exception if
// we are type checking.
if ((c != no.getClass()) && !dontTypeCheck) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"attribute_type_mismatch",
new Object[] {newAttr.getId()});
}
// If we are typechecking, and we get two opposite
// booleans, we need to throw an exception.
if (no instanceof Boolean && !no.equals(o) && !dontTypeCheck) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"boolean_incompat",
new Object[] {newAttr.getId()});
}
// Add the value if it isn't already there.
if (!attrVals.contains(no)) {
attrVals.addElement(no);
}
}
// Set the new attribute's values so they are the same as the old.
newAttr.values = attrVals;
}
}
//
// Object overrides.
//
/**
* Return true if the object equals this attribute.
*/
public boolean equals(Object o) {
if (!(o instanceof ServiceLocationAttribute)) {
return false;
}
if (o == this) {
return true;
}
ServiceLocationAttribute sla = (ServiceLocationAttribute)o;
// check equality of contents, deferring check of all values
Vector vSLA = sla.values;
if (!sla.getId().equalsIgnoreCase(id)) {
return false;
}
if (values == null && vSLA == null) {
return true;
}
if ((values == null && vSLA != null) ||
(values != null && vSLA == null)) {
return false;
}
if (values.size() != vSLA.size()) {
return false;
}
// Check contents.
Object oSLA = vSLA.elementAt(0);
o = values.elementAt(0);
if (o.getClass() != oSLA.getClass()) {
return false;
}
int i, n = vSLA.size();
for (i = 0; i < n; i++) {
oSLA = vSLA.elementAt(i);
if (!values.contains(oSLA)) {
return false;
}
}
return true;
}
/**
* Return a human readable string for the attribute.
*/
public String toString() {
StringBuffer s = new StringBuffer("(");
s.append(id);
if (values != null) {
s.append("=");
int i, n = values.size();
for (i = 0; i < n; i++) {
Object o = values.elementAt(i);
// Identify type.
if (i == 0) {
s.append(o.getClass().getName());
s.append(":");
} else {
s.append(",");
}
// Stringify object.
s.append(o.toString());
}
}
s.append(")");
return s.toString();
}
// Overrides Object.hashCode().
public int hashCode() {
return id.toLowerCase().hashCode();
}
}