View Javadoc
1   /*
2     ServiceLocationAttribute.java
3   
4     (C) Copyright IBM Corp. 2005, 2013
5   
6     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
7     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
8     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
9   
10    You can obtain a current copy of the Eclipse Public License from
11    http://www.opensource.org/licenses/eclipse-1.0.php
12  
13    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
14   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
15   * 
16   * Change History
17   * Flag       Date        Prog         Description
18   *------------------------------------------------------------------------------- 
19   * 1516246    2006-07-22  lupusalex    Integrate SLP client code
20   * 1678915    2007-03-27  lupusalex    Integrated WBEM service discovery via SLP
21   * 1804402    2007-09-28  ebak         IPv6 ready SLP
22   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
23   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
24   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
25   * 2795671    2009-05-22  raman_arora  Add Type to Comparable <T>
26   * 2797696    2009-05-27  raman_arora  Input files use unsafe operations
27   * 2823494    2009-08-03  rgummada     Change Boolean constructor to static
28   * 3022519    2010-06-30  blaschke-oss ServiceLocationAttribute.equals() compares same array
29   * 3022524    2010-06-30  blaschke-oss iSortedValueEntries not serializable in Serializable class
30   * 3023349    2010-07-02  blaschke-oss SLP uses # constructor instead of valueOf
31   *    2650    2013-07-18  blaschke-oss SLP opaque value handling incorrect
32   */
33  
34  package org.metricshub.wbem.sblim.slp;
35  
36  /*-
37   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
38   * WBEM Java Client
39   * ჻჻჻჻჻჻
40   * Copyright 2023 - 2025 MetricsHub
41   * ჻჻჻჻჻჻
42   * Licensed under the Apache License, Version 2.0 (the "License");
43   * you may not use this file except in compliance with the License.
44   * You may obtain a copy of the License at
45   *
46   *      http://www.apache.org/licenses/LICENSE-2.0
47   *
48   * Unless required by applicable law or agreed to in writing, software
49   * distributed under the License is distributed on an "AS IS" BASIS,
50   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
51   * See the License for the specific language governing permissions and
52   * limitations under the License.
53   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
54   */
55  
56  import java.io.ByteArrayOutputStream;
57  import java.io.Serializable;
58  import java.util.Arrays;
59  import java.util.StringTokenizer;
60  import java.util.Vector;
61  import org.metricshub.wbem.sblim.cimclient.GenericExts;
62  import org.metricshub.wbem.sblim.slp.internal.AttributeHandler;
63  import org.metricshub.wbem.sblim.slp.internal.Convert;
64  import org.metricshub.wbem.sblim.slp.internal.SLPString;
65  
66  /**
67   * Service location attribute
68   */
69  public class ServiceLocationAttribute implements Serializable {
70  	private static final long serialVersionUID = -6753246108754657715L;
71  
72  	private Vector<Object> iValues;
73  
74  	private String iId;
75  
76  	/**
77  	 * Construct a service location attribute. Errors in the id or values vector
78  	 * result in an IllegalArgumentException.
79  	 *
80  	 * @param pId
81  	 *            The attribute name. The String can consist of any Unicode
82  	 *            character.
83  	 * @param pValues
84  	 *            A Vector of one or more attribute values. Vector contents must
85  	 *            be uniform in type and one of Integer, String, Boolean, or
86  	 *            byte[]. If the attribute is a keyword attribute, then the
87  	 *            parameter should be null. String values can consist of any
88  	 *            Unicode character.
89  	 */
90  	public ServiceLocationAttribute(String pId, Vector<Object> pValues) {
91  		this.iId = pId;
92  		if (pValues != null && pValues.size() > 0) {
93  			this.iValues = GenericExts.cloneVector(pValues);
94  		}
95  	}
96  
97  	/**
98  	 * Construct a service location attribute from a String.
99  	 *
100 	 * @param pString
101 	 *            The string to parse
102 	 * @throws ServiceLocationException
103 	 *             When the string parsing failed
104 	 */
105 	public ServiceLocationAttribute(String pString) throws ServiceLocationException {
106 		if (pString == null || pString.length() == 0) throw new ServiceLocationException(
107 			ServiceLocationException.PARSE_ERROR,
108 			"Empty or null String is not good for this constructor!"
109 		);
110 
111 		if (pString.startsWith("(") && pString.endsWith(")")) {
112 			int equalPos = pString.indexOf('=');
113 			if (equalPos < 0) throw new ServiceLocationException(
114 				ServiceLocationException.PARSE_ERROR,
115 				"Missing '=' from attribute string: " + pString
116 			);
117 			this.iId = Convert.unescape(pString.substring(1, equalPos));
118 			if (this.iId.length() == 0) throw new ServiceLocationException(
119 				ServiceLocationException.PARSE_ERROR,
120 				"Empty attribute ID in attribute string: " + pString
121 			);
122 			String valueString = pString.substring(equalPos + 1, pString.length() - 1);
123 
124 			parseValueString(valueString);
125 		} else {
126 			if (pString.indexOf('(') >= 0 || pString.indexOf(')') >= 0) throw new ServiceLocationException(
127 				ServiceLocationException.PARSE_ERROR
128 			);
129 			this.iId = Convert.unescape(pString);
130 			this.iValues = null;
131 		}
132 	}
133 
134 	/**
135 	 * Returns an escaped version of the id parameter, suitable for inclusion in
136 	 * a query. Any reserved characters as specified in [7] are escaped using
137 	 * UTF-8 encoding. If any characters in the tag are illegal, throws
138 	 * IllegalArgumentException.
139 	 *
140 	 * @param pId
141 	 *            The attribute id to escape. ServiceLocationException is thrown
142 	 *            if any characters are illegal for an attribute tag.
143 	 * @return The escaped version
144 	 */
145 	public static String escapeId(String pId) {
146 		return Convert.escape(pId, Convert.ATTR_RESERVED);
147 	}
148 
149 	/**
150 	 * Returns a String containing the escaped value parameter as a string,
151 	 * suitable for inclusion in a query. If the parameter is a string, any
152 	 * reserved characters as specified in [7] are escaped using UTF-8 encoding.
153 	 * If the parameter is a byte array, then the escaped string begins with the
154 	 * nonUTF-8 sequence `\ff` and the rest of the string consists of the
155 	 * escaped bytes, which is the encoding for opaque. If the value parameter
156 	 * is a Boolean or Integer, then the returned string contains the object
157 	 * converted into a string. If the value is any type other than String,
158 	 * Integer, Boolean or byte[], an IllegalArgumentException is thrown.
159 	 *
160 	 * @param pValue
161 	 *            The attribute value to be converted into a string and escaped.
162 	 * @return The escaped value
163 	 */
164 	public static String escapeValue(Object pValue) {
165 		return AttributeHandler.escapeValue(pValue);
166 	}
167 
168 	/**
169 	 * Returns a cloned vector of attribute values, or null if the attribute is
170 	 * a keyword attribute. If the attribute is single-valued, then the vector
171 	 * contains only one object.
172 	 *
173 	 * @return The value vector
174 	 *
175 	 */
176 	public Vector<Object> getValues() {
177 		if (this.iValues != null) return GenericExts.cloneVector(this.iValues);
178 		return this.iValues;
179 	}
180 
181 	/**
182 	 * Returns the attribute's name.
183 	 *
184 	 * @return The name (id)
185 	 */
186 	public String getId() {
187 		return this.iId;
188 	}
189 
190 	/*
191 	 * (non-Javadoc)
192 	 *
193 	 * @see java.lang.Object#equals(java.lang.Object)
194 	 *
195 	 * Overrides Object.equals(). Two attributes are equal if their identifiers
196 	 * are equal and their value vectors contain the same number of equal values
197 	 * as determined by the Object equals() method. Values having byte[] type
198 	 * are equal if the contents of all byte arrays in both attribute vectors
199 	 * match. Note that the SLP string matching algorithm [7] MUST NOT be used
200 	 * for comparing attribute identifiers or string values.
201 	 */
202 	@Override
203 	public boolean equals(Object obj) {
204 		if (obj == this) return true;
205 		if (!(obj instanceof ServiceLocationAttribute)) return false;
206 
207 		ServiceLocationAttribute that = (ServiceLocationAttribute) obj;
208 		if (!that.getId().equalsIgnoreCase(this.iId)) return false;
209 
210 		Vector<Object> thatValues = that.iValues;
211 		if (this.iValues == null) return thatValues == null;
212 		if (thatValues == null) return false;
213 		if (this.iValues.size() != thatValues.size()) return false;
214 
215 		ValueEntry[] thisEntries = getSortedValueEntries();
216 		ValueEntry[] thatEntries = that.getSortedValueEntries();
217 
218 		return Arrays.equals(thisEntries, thatEntries);
219 	}
220 
221 	/*
222 	 * (non-Javadoc)
223 	 *
224 	 * @see java.lang.Object#toString()
225 	 *
226 	 * Overrides Object.toString(). The string returned contains a formatted
227 	 * representation of the attribute, giving the attribute's id, values, and
228 	 * the Java type of the values. The returned string is suitable for
229 	 * debugging purposes, but is not in SLP wire format.
230 	 */
231 	@Override
232 	public String toString() {
233 		StringBuffer stringbuffer = new StringBuffer("(");
234 		stringbuffer.append(this.iId);
235 		if (this.iValues != null) {
236 			stringbuffer.append("=");
237 			int size = this.iValues.size();
238 			for (int i = 0; i < size; i++) {
239 				Object obj = this.iValues.elementAt(i);
240 				if (i > 0) {
241 					stringbuffer.append(",");
242 				}
243 				if (obj instanceof byte[]) obj = AttributeHandler.mkOpaqueStr((byte[]) obj);
244 				stringbuffer.append(obj.toString());
245 			}
246 		}
247 		stringbuffer.append(")");
248 		return stringbuffer.toString();
249 	}
250 
251 	private int iHashCode = 0;
252 
253 	private void incHashCode(int pHashCode) {
254 		this.iHashCode *= 31;
255 		this.iHashCode += pHashCode;
256 	}
257 
258 	/*
259 	 * (non-Javadoc)
260 	 *
261 	 * @see java.lang.Object#hashCode()
262 	 *
263 	 * Overrides Object.hashCode(). Hashes on the attribute's identifier.
264 	 */
265 	@Override
266 	public int hashCode() {
267 		if (this.iHashCode == 0) {
268 			this.iHashCode = this.iId.hashCode();
269 			if (this.iValues != null) {
270 				ValueEntry[] valueEntries = getSortedValueEntries();
271 				for (int i = 0; i < valueEntries.length; i++) incHashCode(valueEntries[i].hashCode());
272 			}
273 		}
274 		return this.iHashCode;
275 	}
276 
277 	private void parseValueString(String pStr) throws ServiceLocationException {
278 		StringTokenizer tokenizer = new StringTokenizer(pStr, ",");
279 		this.iValues = new Vector<Object>();
280 		while (tokenizer.hasMoreElements()) {
281 			String valueStr = tokenizer.nextToken();
282 			Object value;
283 			try {
284 				int intVal = Integer.parseInt(valueStr);
285 				value = Integer.valueOf(intVal);
286 			} catch (NumberFormatException e) {
287 				if ("TRUE".equalsIgnoreCase(valueStr)) {
288 					value = Boolean.TRUE;
289 				} else if ("FALSE".equalsIgnoreCase(valueStr)) {
290 					value = Boolean.FALSE;
291 				} else if (valueStr.startsWith("\\FF")) {
292 					value = parseOpaqueStr(valueStr);
293 				} else {
294 					value = Convert.unescape(valueStr);
295 				}
296 			}
297 			this.iValues.add(value);
298 		}
299 	}
300 
301 	private static byte[] parseOpaqueStr(String pStr) throws ServiceLocationException {
302 		if (pStr.length() == 3) throw new ServiceLocationException(
303 			ServiceLocationException.PARSE_ERROR,
304 			"There must be at least three characters after \\FF in opaque string!" + " pStr=" + pStr
305 		);
306 
307 		ByteArrayOutputStream oStr = new ByteArrayOutputStream();
308 		int pos = 3; // skip "\\FF"
309 		int left;
310 		while ((left = pStr.length() - pos) > 0) {
311 			if (left < 3) throw new ServiceLocationException(
312 				ServiceLocationException.PARSE_ERROR,
313 				"Number of characters must be multiple of three after \\FF in opaque string!" + " pStr=" + pStr
314 			);
315 			if (pStr.charAt(pos) != '\\') throw new ServiceLocationException(
316 				ServiceLocationException.PARSE_ERROR,
317 				"Hex value must be preceded by \\ in opaque string!" + " pStr=" + pStr
318 			);
319 			String hexStr = pStr.substring(pos + 1, pos + 3);
320 			pos += 3;
321 			try {
322 				oStr.write(Integer.parseInt(hexStr, 16));
323 			} catch (NumberFormatException e) {
324 				throw new ServiceLocationException(
325 					ServiceLocationException.PARSE_ERROR,
326 					"Failed to parse hex value: " + hexStr + " in opaque string: " + pStr + " !"
327 				);
328 			}
329 		}
330 		return oStr.toByteArray();
331 	}
332 
333 	static class ValueEntry implements Comparable<ValueEntry> {
334 		/**
335 		 * iStr
336 		 */
337 		public String iStr;
338 
339 		/**
340 		 * iValue
341 		 */
342 		public Object iValue;
343 
344 		public int compareTo(ValueEntry o) {
345 			ValueEntry that = o;
346 			return this.iStr.compareTo(that.iStr);
347 		}
348 
349 		@Override
350 		public boolean equals(Object pObj) {
351 			if (this == pObj) return true;
352 			if (!(pObj instanceof ValueEntry)) return false;
353 			ValueEntry that = (ValueEntry) pObj;
354 			if (this.iValue == null) return that.iValue == null;
355 			if (that.iValue == null) return false;
356 			if (!this.iValue.getClass().equals(that.iValue.getClass())) return false;
357 			if (this.iValue instanceof byte[]) return Arrays.equals((byte[]) this.iValue, (byte[]) that.iValue);
358 			if (this.iValue instanceof String) return this.iStr.equals(that.iStr);
359 			return this.iValue.equals(that.iValue);
360 		}
361 
362 		@Override
363 		public int hashCode() {
364 			return this.iStr == null ? 1 : this.iStr.hashCode();
365 		}
366 	}
367 
368 	private transient ValueEntry[] iSortedValueEntries;
369 
370 	/**
371 	 * Used for equals check and hashCode calculation.
372 	 *
373 	 * @param pAttrib
374 	 * @return attribute values in unified order, which is : values are sorted
375 	 *         by theirs toString().
376 	 */
377 	private ValueEntry[] getSortedValueEntries() {
378 		if (this.iValues == null) return null;
379 		if (this.iSortedValueEntries != null) return this.iSortedValueEntries;
380 		this.iSortedValueEntries = new ValueEntry[this.iValues.size()];
381 		for (int i = 0; i < this.iValues.size(); i++) {
382 			ValueEntry entry = new ValueEntry();
383 			this.iSortedValueEntries[i] = entry;
384 			Object value = this.iValues.get(i);
385 			entry.iValue = value;
386 			if (value == null) {
387 				entry.iStr = "";
388 			} else {
389 				if (value instanceof String) {
390 					entry.iStr = SLPString.unify((String) value);
391 				} else if (value instanceof byte[]) {
392 					entry.iStr = AttributeHandler.mkOpaqueStr((byte[]) value);
393 				} else {
394 					entry.iStr = value.toString();
395 				}
396 			}
397 		}
398 		Arrays.sort(this.iSortedValueEntries);
399 		return this.iSortedValueEntries;
400 	}
401 }