Source code: com/sun/xacml/attr/YearMonthDurationAttribute.java
1
2 /*
3 * @(#)YearMonthDurationAttribute.java
4 *
5 * Copyright 2003-2004 Sun Microsystems, Inc. All Rights Reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistribution of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 *
13 * 2. Redistribution in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
18 * be used to endorse or promote products derived from this software without
19 * specific prior written permission.
20 *
21 * This software is provided "AS IS," without a warranty of any kind. ALL
22 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
23 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
24 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
25 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
26 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
27 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
28 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
29 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
30 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
31 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32 *
33 * You acknowledge that this software is not designed or intended for use in
34 * the design, construction, operation or maintenance of any nuclear facility.
35 */
36
37 package com.sun.xacml.attr;
38
39 import com.sun.xacml.ParsingException;
40
41 import java.math.BigInteger;
42
43 import java.net.URI;
44
45 import java.util.regex.Pattern;
46 import java.util.regex.PatternSyntaxException;
47 import java.util.regex.Matcher;
48
49 import org.w3c.dom.Node;
50
51
52 /**
53 * Representation of an xf:yearMonthDuration value. This class supports parsing
54 * xd:yearMonthDuration values. All objects of this class are immutable and
55 * thread-safe. The <code>Date</code> objects returned are not, but
56 * these objects are cloned before being returned.
57 *
58 * @since 1.0
59 * @author Steve Hanna
60 */
61 public class YearMonthDurationAttribute extends AttributeValue
62 {
63 /**
64 * Official name of this type
65 */
66 public static final String identifier =
67 "http://www.w3.org/TR/2002/WD-xquery-operators-20020816#" +
68 "yearMonthDuration";
69
70 /**
71 * URI version of name for this type
72 * <p>
73 * This field is initialized by a static initializer so that
74 * we can catch any exceptions thrown by URI(String) and
75 * transform them into a RuntimeException, since this should
76 * never happen but should be reported properly if it ever does.
77 */
78 private static URI identifierURI;
79
80 /**
81 * RuntimeException that wraps an Exception thrown during the
82 * creation of identifierURI, null if none.
83 */
84 private static RuntimeException earlyException;
85
86 /**
87 * Static initializer that initializes the identifierURI
88 * class field so that we can catch any exceptions thrown
89 * by URI(String) and transform them into a RuntimeException.
90 * Such exceptions should never happen but should be reported
91 * properly if they ever do.
92 */
93 static {
94 try {
95 identifierURI = new URI(identifier);
96 } catch (Exception e) {
97 earlyException = new IllegalArgumentException();
98 earlyException.initCause(e);
99 }
100 };
101
102 /**
103 * Regular expression for yearMonthDuration (a la java.util.regex)
104 */
105 private static final String patternString =
106 "(\\-)?P((\\d+)?Y)?((\\d+)?M)?";
107
108 /**
109 * The index of the capturing group for the negative sign.
110 */
111 private static final int GROUP_SIGN = 1;
112
113 /**
114 * The index of the capturing group for the number of years.
115 */
116 private static final int GROUP_YEARS = 3;
117
118 /**
119 * The index of the capturing group for the number of months.
120 */
121 private static final int GROUP_MONTHS = 5;
122
123 /**
124 * Static BigInteger values. We only use these if one of
125 * the components is bigger than Integer.MAX_LONG and we
126 * want to detect overflow, so we don't initialize these
127 * until they're needed.
128 */
129 private static BigInteger big12;
130 private static BigInteger bigMaxLong;
131
132 /**
133 * A shared Pattern object, only initialized if needed
134 */
135 private static Pattern pattern;
136
137 /**
138 * Negative flag. true if duration is negative, false otherwise
139 */
140 private boolean negative;
141
142 /**
143 * Number of years
144 */
145 private long years;
146
147 /**
148 * Number of months
149 */
150 private long months;
151
152 /**
153 * Total number of months (used for equals)
154 */
155 private long totalMonths;
156
157 /**
158 * Cached encoded value (null if not cached yet).
159 */
160 private String encodedValue = null;
161
162 /**
163 * Creates a new <code>YearMonthDurationAttribute</code> that represents
164 * the duration supplied.
165 *
166 * @param negative true if the duration is negative, false otherwise
167 * @param years the number of years in the duration (must be positive)
168 * @param months the number of months in the duration (must be positive)
169 * @throws IllegalArgumentException if the total number of months
170 * exceeds Long.MAX_LONG or the number
171 * of months or years is negative
172 */
173 public YearMonthDurationAttribute(boolean negative, long years,
174 long months)
175 throws IllegalArgumentException {
176 super(identifierURI);
177
178 // Shouldn't happen, but just in case...
179 if (earlyException != null)
180 throw earlyException;
181
182 this.negative = negative;
183 this.years = years;
184 this.months = months;
185
186 // Convert all the components except nanoseconds to milliseconds
187
188 // If any of the components is big (too big to be an int),
189 // use the BigInteger class to do the math so we can detect
190 // overflow.
191 if ((years > Integer.MAX_VALUE) || (months > Integer.MAX_VALUE)) {
192 if (big12 == null) {
193 big12 = BigInteger.valueOf(12);
194 bigMaxLong = BigInteger.valueOf(Long.MAX_VALUE);
195 }
196 BigInteger bigMonths = BigInteger.valueOf(months);
197 BigInteger bigYears = BigInteger.valueOf(years);
198
199 BigInteger bigTotal = bigYears.multiply(big12).add(bigMonths);
200
201 // If the result is bigger than Long.MAX_VALUE, we have an
202 // overflow. Indicate an error (should be a processing error,
203 // since it can be argued that we should handle gigantic
204 // values for this).
205 if (bigTotal.compareTo(bigMaxLong) == 1)
206 throw new IllegalArgumentException("total number of " +
207 "months " +
208 "exceeds Long.MAX_VALUE");
209 // If no overflow, convert to a long.
210 totalMonths = bigTotal.longValue();
211 if (negative)
212 totalMonths = - totalMonths;
213 } else {
214 // The numbers are small, so do it the fast way.
215 totalMonths = ((years * 12) + months) * (negative ? -1 : 1);
216 }
217 }
218
219 /**
220 * Returns a new <code>YearMonthDurationAttribute</code> that represents
221 * the xf:yearMonthDuration at a particular DOM node.
222 *
223 * @param root the <code>Node</code> that contains the desired value
224 * @return a new <code>YearMonthDurationAttribute</code> representing the
225 * appropriate value
226 * @throws ParsingException if any problems occurred while parsing
227 */
228 public static YearMonthDurationAttribute getInstance(Node root)
229 throws ParsingException
230 {
231 return getInstance(root.getFirstChild().getNodeValue());
232 }
233
234 /**
235 * Returns the long value for the capturing group groupNumber.
236 * This method takes a Matcher that has been used to match a
237 * Pattern against a String, fetches the value for the specified
238 * capturing group, converts that value to an long, and returns
239 * the value. If that group did not match, 0 is returned.
240 * If the matched value is not a valid long, NumberFormatException
241 * is thrown.
242 *
243 * @param matcher the Matcher from which to fetch the group
244 * @param groupNumber the group number to fetch
245 * @return the long value for that groupNumber
246 * @throws NumberFormatException if the string value for that
247 * groupNumber is not a valid long
248 */
249 private static long parseGroup(Matcher matcher, int groupNumber)
250 throws NumberFormatException {
251 long groupLong = 0;
252
253 if (matcher.start(groupNumber) != -1) {
254 String groupString = matcher.group(groupNumber);
255 groupLong = Long.parseLong(groupString);
256 }
257 return groupLong;
258 }
259
260 /**
261 * Returns a new <code>YearMonthDurationAttribute</code> that represents
262 * the xf:yearMonthDuration value indicated by the string provided.
263 *
264 * @param value a string representing the desired value
265 *
266 * @return a new <code>YearMonthDurationAttribute</code> representing the
267 * desired value
268 *
269 * @throws ParsingException if any problems occurred while parsing
270 */
271 public static YearMonthDurationAttribute getInstance(String value)
272 throws ParsingException
273 {
274 boolean negative = false;
275 long years = 0;
276 long months = 0;
277
278 // Compile the pattern, if not already done.
279 if (pattern == null) {
280 try {
281 pattern = Pattern.compile(patternString);
282 } catch (PatternSyntaxException e) {
283 // This should never happen
284 throw new ParsingException("unexpected pattern syntax error");
285 }
286 }
287
288 // See if the value matches the pattern.
289 Matcher matcher = pattern.matcher(value);
290 boolean matches = matcher.matches();
291
292 // If not, syntax error!
293 if (!matches) {
294 throw new ParsingException("Syntax error in yearMonthDuration");
295 }
296
297 // If the negative group matched, the value is negative.
298 if (matcher.start(GROUP_SIGN) != -1)
299 negative = true;
300
301 try {
302 // If the years group matched, parse that value.
303 years = parseGroup(matcher, GROUP_YEARS);
304
305 // If the months group matched, parse that value.
306 months = parseGroup(matcher, GROUP_MONTHS);
307 } catch (NumberFormatException e) {
308 // If we run into a number that's too big to be a long
309 // that's an error. Really, it's a processing error,
310 // since one can argue that we should handle that.
311 throw new ParsingException("Unable to handle number size");
312 }
313
314 // If parsing went OK, create a new YearMonthDurationAttribute
315 // object and return it.
316 return new YearMonthDurationAttribute(negative, years, months);
317 }
318
319 /**
320 * Returns true if the duration is negative.
321 *
322 * @return true if the duration is negative, false otherwise
323 */
324 public boolean isNegative() {
325 return negative;
326 }
327
328 /**
329 * Gets the number of years.
330 *
331 * @return the number of years
332 */
333 public long getYears() {
334 return years;
335 }
336
337 /**
338 * Gets the number of months.
339 *
340 * @return the number of months
341 */
342 public long getMonths() {
343 return months;
344 }
345
346 /**
347 * Returns true if the input is an instance of this class and if its
348 * value equals the value contained in this class.
349 *
350 * @param o the object to compare
351 *
352 * @return true if this object and the input represent the same value
353 */
354 public boolean equals(Object o) {
355 if (! (o instanceof YearMonthDurationAttribute))
356 return false;
357
358 YearMonthDurationAttribute other = (YearMonthDurationAttribute)o;
359
360 return (totalMonths == other.totalMonths);
361 }
362
363 /**
364 * Returns the hashcode value used to index and compare this object with
365 * others of the same type. Typically this is the hashcode of the backing
366 * data object.
367 *
368 * @return the object's hashcode value
369 */
370 public int hashCode() {
371 return (int) totalMonths ^ (int) (totalMonths >> 32);
372 }
373
374 /**
375 * Converts to a String representation.
376 *
377 * @return the String representation
378 */
379 public String toString() {
380 StringBuffer sb = new StringBuffer();
381 sb.append("YearMonthDurationAttribute: [\n");
382 sb.append(" Negative: " + negative);
383 sb.append(" Years: " + years);
384 sb.append(" Months: " + months);
385 sb.append("]");
386
387 return sb.toString();
388 }
389
390 /**
391 * Encodes the value in a form suitable for including in XML data like
392 * a request or an obligation. This must return a value that could in
393 * turn be used by the factory to create a new instance with the same
394 * value.
395 *
396 * @return a <code>String</code> form of the value
397 */
398 public String encode() {
399 if (encodedValue != null)
400 return encodedValue;
401
402 // Length is variable
403 StringBuffer buf = new StringBuffer(10);
404
405 if (negative)
406 buf.append('-');
407 buf.append('P');
408 if ((years != 0) || (months == 0)) {
409 buf.append(Long.toString(years));
410 buf.append('Y');
411 }
412 if (months != 0) {
413 buf.append(Long.toString(months));
414 buf.append('M');
415 }
416
417 encodedValue = buf.toString();
418
419 return encodedValue;
420 }
421 }