001: package net.sf.saxon.value;
002:
003: import net.sf.saxon.expr.XPathContext;
004: import net.sf.saxon.om.FastStringBuffer;
005: import net.sf.saxon.trans.DynamicError;
006: import net.sf.saxon.trans.XPathException;
007: import net.sf.saxon.type.*;
008:
009: import java.io.ByteArrayOutputStream;
010:
011: /**
012: * A value of type xs:base64Binary
013: */
014:
015: public class Base64BinaryValue extends AtomicValue {
016:
017: private byte[] binaryValue;
018:
019: /**
020: * Constructor: create a base64Binary value from a supplied string in base64 encoding
021: */
022:
023: public Base64BinaryValue(CharSequence s) throws XPathException {
024: Base64Decoder decoder = new Base64Decoder();
025: try {
026: decoder.translate(s);
027: } catch (IllegalArgumentException e) {
028: DynamicError err = new DynamicError(e.getMessage());
029: err.setErrorCode("FORG0001");
030: throw err;
031: }
032: binaryValue = decoder.getByteArray();
033: }
034:
035: /**
036: * Constructor: create a base64Binary value from a given array of bytes
037: */
038:
039: public Base64BinaryValue(byte[] value) {
040: this .binaryValue = value;
041: }
042:
043: /**
044: * Get the binary value
045: */
046:
047: public byte[] getBinaryValue() {
048: return binaryValue;
049: }
050:
051: /**
052: * Convert to target data type
053: * @param requiredType an integer identifying the required atomic type
054: * @param context
055: * @return an AtomicValue, a value of the required type; or an ErrorValue
056: */
057:
058: public AtomicValue convertPrimitive(BuiltInAtomicType requiredType,
059: boolean validate, XPathContext context) {
060: switch (requiredType.getPrimitiveType()) {
061: case Type.BASE64_BINARY:
062: case Type.ANY_ATOMIC:
063: case Type.ITEM:
064: return this ;
065: case Type.STRING:
066: return new StringValue(getStringValueCS());
067: case Type.UNTYPED_ATOMIC:
068: return new UntypedAtomicValue(getStringValueCS());
069: case Type.HEX_BINARY:
070: return new HexBinaryValue(binaryValue);
071: default:
072: ValidationException err = new ValidationException(
073: "Cannot convert base64Binary to "
074: + requiredType.getDisplayName());
075: err.setErrorCode("XPTY0004");
076: err.setIsTypeError(true);
077: return new ValidationErrorValue(err);
078: }
079: }
080:
081: /**
082: * Convert to string
083: * @return the canonical representation.
084: */
085:
086: public String getStringValue() {
087: Base64Encoder encoder = new Base64Encoder();
088: encoder.translate(binaryValue);
089: return new String(encoder.getCharArray());
090: }
091:
092: /**
093: * Get the number of octets in the value
094: */
095:
096: public int getLengthInOctets() {
097: return binaryValue.length;
098: }
099:
100: /**
101: * Determine the data type of the exprssion
102: * @return Type.BASE64_BINARY_TYPE
103: * @param th
104: */
105:
106: public ItemType getItemType(TypeHierarchy th) {
107: return Type.BASE64_BINARY_TYPE;
108: }
109:
110: /**
111: * Convert to Java object (for passing to external functions)
112: */
113:
114: public Object convertToJava(Class target, XPathContext context)
115: throws XPathException {
116:
117: if (target.isAssignableFrom(Base64BinaryValue.class)) {
118: return this ;
119: } else if (target == String.class
120: || target == CharSequence.class) {
121: return getStringValue();
122: } else if (target == Object.class) {
123: return getStringValue();
124: } else {
125: Object o = super .convertToJava(target, context);
126: if (o == null) {
127: throw new DynamicError("Conversion of base64Binary to "
128: + target.getName() + " is not supported");
129: }
130: return o;
131: }
132: }
133:
134: /**
135: * Test if the two base64Binary values are equal.
136: */
137:
138: public boolean equals(Object other) {
139: Base64BinaryValue v2;
140: if (other instanceof Base64BinaryValue) {
141: v2 = (Base64BinaryValue) other;
142: } else if (other instanceof AtomicValue) {
143: try {
144: v2 = (Base64BinaryValue) ((AtomicValue) other).convert(
145: Type.BASE64_BINARY, null);
146: } catch (XPathException err) {
147: return false;
148: }
149: } else {
150: return false;
151: }
152: if (binaryValue.length != v2.binaryValue.length) {
153: return false;
154: }
155: ;
156: for (int i = 0; i < binaryValue.length; i++) {
157: if (binaryValue[i] != v2.binaryValue[i]) {
158: return false;
159: }
160: ;
161: }
162: return true;
163: }
164:
165: public int hashCode() {
166: return byteArrayHashCode(binaryValue);
167: }
168:
169: protected static int byteArrayHashCode(byte[] value) {
170: long h = 0;
171: for (int i = 0; i < Math.min(value.length, 64); i++) {
172: h = (h << 1) ^ value[i];
173: }
174: return (int) ((h >> 32) ^ h) & 0xffffffff;
175: }
176:
177: /*
178: *
179: * The contents of this [inner class] are subject to the Netscape Public
180: * License Version 1.1 (the "License"); you may not use this file
181: * except in compliance with the License. You may obtain a copy of
182: * the License at http://www.mozilla.org/NPL/
183: *
184: * Software distributed under the License is distributed on an "AS
185: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
186: * implied. See the License for the specific language governing
187: * rights and limitations under the License.
188: *
189: * The Original Code is mozilla.org code.
190: *
191: * The Initial Developer of the Original Code is Netscape
192: * Communications Corporation. Portions created by Netscape are
193: * Copyright (C) 1999 Netscape Communications Corporation. All
194: * Rights Reserved.
195: *
196: * Contributor(s):
197: */
198:
199: /**
200: * Byte to text encoder using base 64 encoding. To create a base 64
201: * encoding of a byte stream call {@link #translate} for every
202: * sequence of bytes and {@link #getCharArray} to mark closure of
203: * the byte stream and retrieve the text presentation.
204: *
205: * @author Based on code from the Mozilla Directory SDK
206: */
207: private static final class Base64Encoder {
208:
209: private FastStringBuffer out = new FastStringBuffer(256);
210:
211: private int buf = 0; // a 24-bit quantity
212:
213: private int buf_bytes = 0; // how many octets are set in it
214:
215: private char line[] = new char[74]; // output buffer
216:
217: private int line_length = 0; // output buffer fill pointer
218:
219: //static private final byte crlf[] = "\r\n".getBytes();
220:
221: private static final char map[] = { 'A', 'B', 'C', 'D', 'E',
222: 'F', 'G', 'H', // 0-7
223: 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8-15
224: 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16-23
225: 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24-31
226: 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32-39
227: 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40-47
228: 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48-55
229: '4', '5', '6', '7', '8', '9', '+', '/', // 56-63
230: };
231:
232: private void encode_token() {
233: int i = line_length;
234: line[i] = map[0x3F & (buf >> 18)]; // sextet 1 (octet 1)
235: line[i + 1] = map[0x3F & (buf >> 12)]; // sextet 2 (octet 1 and 2)
236: line[i + 2] = map[0x3F & (buf >> 6)]; // sextet 3 (octet 2 and 3)
237: line[i + 3] = map[0x3F & buf]; // sextet 4 (octet 3)
238: line_length += 4;
239: buf = 0;
240: buf_bytes = 0;
241: }
242:
243: private void encode_partial_token() {
244: int i = line_length;
245: line[i] = map[0x3F & (buf >> 18)]; // sextet 1 (octet 1)
246: line[i + 1] = map[0x3F & (buf >> 12)]; // sextet 2 (octet 1 and 2)
247:
248: if (buf_bytes == 1)
249: line[i + 2] = '=';
250: else
251: line[i + 2] = map[0x3F & (buf >> 6)]; // sextet 3 (octet 2 and 3)
252:
253: if (buf_bytes <= 2)
254: line[i + 3] = '=';
255: else
256: line[i + 3] = map[0x3F & buf]; // sextet 4 (octet 3)
257: line_length += 4;
258: buf = 0;
259: buf_bytes = 0;
260: }
261:
262: private void flush_line() {
263: out.append(line, 0, line_length);
264: line_length = 0;
265: }
266:
267: /**
268: * Given a sequence of input bytes, produces a sequence of output bytes
269: * using the base64 encoding. If there are bytes in `out' already, the
270: * new bytes are appended, so the caller should do `out.setLength(0)'
271: * first if that's desired.
272: */
273: public final void translate(byte[] in) {
274: int in_length = in.length;
275:
276: for (int i = 0; i < in_length; i++) {
277: if (buf_bytes == 0)
278: buf = (buf & 0x00FFFF) | (in[i] << 16);
279: else if (buf_bytes == 1)
280: buf = (buf & 0xFF00FF) | ((in[i] << 8) & 0x00FFFF);
281: else
282: buf = (buf & 0xFFFF00) | (in[i] & 0x0000FF);
283:
284: if ((++buf_bytes) == 3) {
285: encode_token();
286: if (line_length >= 72) {
287: flush_line();
288: }
289: }
290:
291: if (i == (in_length - 1)) {
292: if ((buf_bytes > 0) && (buf_bytes < 3))
293: encode_partial_token();
294: if (line_length > 0)
295: flush_line();
296: }
297: }
298:
299: for (int i = 0; i < line.length; i++)
300: line[i] = 0;
301: }
302:
303: public char[] getCharArray() {
304: char[] ch;
305:
306: if (buf_bytes != 0)
307: encode_partial_token();
308: flush_line();
309: for (int i = 0; i < line.length; i++)
310: line[i] = 0;
311: ch = new char[out.length()];
312: if (out.length() > 0)
313: out.getChars(0, out.length(), ch, 0);
314: return ch;
315: }
316: }
317:
318: /*
319: *
320: * The contents of this [inner class] are subject to the Netscape Public
321: * License Version 1.1 (the "License"); you may not use this file
322: * except in compliance with the License. You may obtain a copy of
323: * the License at http://www.mozilla.org/NPL/
324: *
325: * Software distributed under the License is distributed on an "AS
326: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
327: * implied. See the License for the specific language governing
328: * rights and limitations under the License.
329: *
330: * The Original Code is mozilla.org code.
331: *
332: * The Initial Developer of the Original Code is Netscape
333: * Communications Corporation. Portions created by Netscape are
334: * Copyright (C) 1999 Netscape Communications Corporation. All
335: * Rights Reserved.
336: *
337: * Contributor(s):
338: */
339:
340: /**
341: * Base 64 text to byte decoder. To produce the binary array from
342: * base 64 encoding call {@link #translate} for each sequence of
343: * characters and {@link #getByteArray} to mark closure of the
344: * character stream and retrieve the binary contents.
345: *
346: * @author Based on code from the Mozilla Directory SDK
347: */
348:
349: private static final class Base64Decoder {
350: private ByteArrayOutputStream out = new ByteArrayOutputStream();
351:
352: private byte token[] = new byte[4]; // input buffer
353:
354: private byte bytes[] = new byte[3]; // output buffer
355:
356: private int token_length = 0; // input buffer length
357:
358: private static final byte NUL = 127; // must be out of range 0-64
359:
360: private static final byte EOF = 126; // must be out of range 0-64
361:
362: private static final byte SP = 125; // must be out of range 0-64
363:
364: private static final byte[] map = { NUL, NUL, NUL, NUL, NUL,
365: NUL, NUL, NUL, // x00 - x07
366: NUL, SP, SP, NUL, NUL, SP, NUL, NUL, // x08 - x0F
367: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // x10 - x17
368: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // x18 - x1F
369: SP, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // x20 - x2F !"#$%&'
370: NUL, NUL, NUL, 62, NUL, NUL, NUL, 63, // 050-057 ()*+,-./
371: 52, 53, 54, 55, 56, 57, 58, 59, // 060-067 01234567
372: 60, 61, NUL, NUL, NUL, EOF, NUL, NUL, // 070-077 89:;<=>?
373:
374: NUL, 0, 1, 2, 3, 4, 5, 6, // 100-107 @ABCDEFG
375: 7, 8, 9, 10, 11, 12, 13, 14, // 110-117 HIJKLMNO
376: 15, 16, 17, 18, 19, 20, 21, 22, // 120-127 PQRSTUVW
377: 23, 24, 25, NUL, NUL, NUL, NUL, NUL, // 130-137 XYZ[\]^_
378: NUL, 26, 27, 28, 29, 30, 31, 32, // 140-147 `abcdefg
379: 33, 34, 35, 36, 37, 38, 39, 40, // 150-157 hijklmno
380: 41, 42, 43, 44, 45, 46, 47, 48, // 160-167 pqrstuvw
381: 49, 50, 51, NUL, NUL, NUL, NUL, NUL, // 170-177 xyz{|}~
382:
383: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 200-207
384: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 210-217
385: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 220-227
386: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 230-237
387: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 240-247
388: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 250-257
389: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 260-267
390: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 270-277
391:
392: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 300-307
393: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 310-317
394: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 320-327
395: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 330-337
396: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 340-347
397: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 350-357
398: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 360-367
399: NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 370-377
400: };
401:
402: // Fast routine that assumes full 4-char tokens with no '=' in them.
403: //
404: private void decode_token() {
405: int num = ((token[0] << 18) | (token[1] << 12)
406: | (token[2] << 6) | (token[3]));
407:
408: bytes[0] = (byte) (0xFF & (num >> 16));
409: bytes[1] = (byte) (0xFF & (num >> 8));
410: bytes[2] = (byte) (0xFF & num);
411:
412: out.write(bytes, 0, 3);
413: }
414:
415: // Hairier routine that deals with the final token, which can have fewer
416: // than four characters, and that might be padded with '='.
417: //
418: private void decode_final_token() {
419:
420: byte b0 = token[0];
421: byte b1 = token[1];
422: byte b2 = token[2];
423: byte b3 = token[3];
424:
425: int eq_count = 0;
426:
427: if (b0 == EOF) {
428: b0 = 0;
429: eq_count++;
430: }
431: if (b1 == EOF) {
432: b1 = 0;
433: eq_count++;
434: }
435: if (b2 == EOF) {
436: b2 = 0;
437: eq_count++;
438: }
439: if (b3 == EOF) {
440: b3 = 0;
441: eq_count++;
442: }
443:
444: int num = ((b0 << 18) | (b1 << 12) | (b2 << 6) | (b3));
445:
446: // eq_count will be 0, 1, or 2.
447: // No "=" padding means 4 bytes mapped to 3, the normal case,
448: // not handled in this routine.
449: // "xxx=" means 3 bytes mapped to 2.
450: // "xx==" means 2 bytes mapped to 1.
451: // "x===" can't happen, because "x" would then be encoding
452: // only 6 bits, not 8, the minimum possible.
453:
454: out.write((byte) (num >> 16)); // byte 1, count = 0 or 1 or 2
455: if (eq_count <= 1) {
456: out.write((byte) ((num >> 8) & 0xFF)); // byte 2, count = 0 or 1
457: if (eq_count == 0) {
458: out.write((byte) (num & 0xFF)); // byte 3, count = 0
459: }
460: }
461: }
462:
463: /**
464: * Decode the base 64 string into a byte array (which can subsequently be accessed using getByteArray()
465: * @param str the base 64 string
466: * @throws IllegalArgumentException if the base64 string is incorrectly formatted
467: */
468:
469: public final void translate(CharSequence str)
470: throws IllegalArgumentException {
471: if (token == null) // already saw eof marker?
472: return;
473: int length = str.length();
474: int lengthAtEOF;
475: boolean found_eq = false;
476: for (int i = 0; i < length; i++) {
477: char c = str.charAt(i);
478: if (c > 127) {
479: throw new IllegalArgumentException(
480: "non-ASCII character in Base64 value (at offset "
481: + i + ')');
482: }
483: byte t = map[c];
484: if (t == NUL) {
485: throw new IllegalArgumentException(
486: "invalid character '" + c
487: + "' in Base64 value (at offset "
488: + i + ')');
489: }
490: if (found_eq && t != EOF && t != SP) {
491: throw new IllegalArgumentException(
492: "In Base64, an '=' character can appear only at the end");
493: }
494: if (t == EOF) {
495: if (found_eq) {
496: token_length = (token_length + 1) % 4;
497: } else {
498: found_eq = true;
499: lengthAtEOF = token_length;
500: eof();
501: token_length = (lengthAtEOF + 1) % 4;
502: }
503: } else if (t != SP) {
504: token[token_length++] = t;
505: if (token_length == 4) {
506: if (!found_eq) {
507: decode_token();
508: }
509: token_length = 0;
510: }
511: }
512: }
513: if (token_length != 0) {
514: throw new IllegalArgumentException(
515: "Base64 input must be a multiple of four characters");
516: }
517: }
518:
519: private void eof() {
520: if (token != null && token_length != 0) {
521: while (token_length < 4)
522: token[token_length++] = EOF;
523: decode_final_token();
524: }
525: token_length = 0;
526: token = new byte[4];
527: bytes = new byte[3];
528: }
529:
530: public byte[] getByteArray() {
531: eof();
532: return out.toByteArray();
533: }
534: }
535:
536: }
537:
538: //
539: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
540: // you may not use this file except in compliance with the License. You may obtain a copy of the
541: // License at http://www.mozilla.org/MPL/
542: //
543: // Software distributed under the License is distributed on an "AS IS" basis,
544: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
545: // See the License for the specific language governing rights and limitations under the License.
546: //
547: // The Original Code is: all this file, with the exception of the two subclasses which
548: // were originated by Mozilla/Netscape and reached Saxon via Castor.
549: //
550: // The Initial Developer of the Original Code is Michael H. Kay, Saxonica.
551: //
552: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
553: //
554: // Contributor(s): none.
555: //
|