001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
027:
028: import java.io.ByteArrayInputStream;
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.io.OutputStream;
032:
033: import javax.activation.DataHandler;
034: import javax.activation.DataSource;
035:
036: import com.sun.xml.internal.bind.DatatypeConverterImpl;
037: import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
038: import com.sun.xml.internal.bind.v2.runtime.output.Pcdata;
039: import com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput;
040: import com.sun.xml.internal.bind.v2.util.ByteArrayOutputStreamEx;
041: import com.sun.istack.internal.Nullable;
042:
043: /**
044: * Fed to unmarshaller when the 'text' data is actually
045: * a virtual image of base64 encoding of the binary data
046: * transferred on the wire.
047: *
048: * Used for the MTOM support.
049: *
050: * This object is mutable and the owner of this object can
051: * reuse it with new data.
052: *
053: * Also used by the marshaller to write out the binary data
054: * that could be possibly attached.
055: *
056: * @see XmlVisitor#text(CharSequence)
057: * @see XMLSerializer#text(Pcdata,String)
058: *
059: * @author Kohsuke Kawaguchi
060: */
061: public final class Base64Data extends Pcdata {
062:
063: // either dataHandler or (data,dataLen,mimeType?) must be present
064:
065: private DataHandler dataHandler;
066:
067: private byte[] data;
068: /**
069: * Length of the valid data in {@link #data}.
070: */
071: private int dataLen;
072: /**
073: * Optional MIME type of {@link #data}.
074: *
075: * Unused when {@link #dataHandler} is set.
076: * Use {@link DataHandler#getContentType()} in that case.
077: */
078: private @Nullable
079: String mimeType;
080:
081: /**
082: * Fills in the data object by a portion of the byte[].
083: *
084: * @param len
085: * data[0] to data[len-1] are treated as the data.
086: */
087: public void set(byte[] data, int len, @Nullable
088: String mimeType) {
089: this .data = data;
090: this .dataLen = len;
091: this .dataHandler = null;
092: this .mimeType = mimeType;
093: }
094:
095: /**
096: * Fills in the data object by the byte[] of the exact length.
097: *
098: * @param data
099: * this buffer may be owned directly by the unmarshaleld JAXB object.
100: */
101: public void set(byte[] data, @Nullable
102: String mimeType) {
103: set(data, data.length, mimeType);
104: }
105:
106: /**
107: * Fills in the data object by a {@link DataHandler}.
108: */
109: public void set(DataHandler data) {
110: assert data != null;
111: this .dataHandler = data;
112: this .data = null;
113: }
114:
115: /**
116: * Gets the raw data.
117: */
118: public DataHandler getDataHandler() {
119: if (dataHandler == null) {
120: dataHandler = new DataHandler(new DataSource() {
121: public String getContentType() {
122: return getMimeType();
123: }
124:
125: public InputStream getInputStream() {
126: return new ByteArrayInputStream(data, 0, dataLen);
127: }
128:
129: public String getName() {
130: return null;
131: }
132:
133: public OutputStream getOutputStream() {
134: throw new UnsupportedOperationException();
135: }
136: });
137: }
138:
139: return dataHandler;
140: }
141:
142: /**
143: * Gets the byte[] of the exact length.
144: */
145: public byte[] getExact() {
146: get();
147: if (dataLen != data.length) {
148: byte[] buf = new byte[dataLen];
149: System.arraycopy(data, 0, buf, 0, dataLen);
150: data = buf;
151: }
152: return data;
153: }
154:
155: /**
156: * Gets the data as an {@link InputStream}.
157: */
158: public InputStream getInputStream() throws IOException {
159: if (dataHandler != null)
160: return dataHandler.getInputStream();
161: else
162: return new ByteArrayInputStream(data, 0, dataLen);
163: }
164:
165: /**
166: * Returns false if this object only has {@link DataHandler} and therefore
167: * {@link #get()} operation is likely going to be expensive.
168: */
169: public boolean hasData() {
170: return data != null;
171: }
172:
173: /**
174: * Gets the raw data. The size of the byte array maybe larger than the actual length.
175: */
176: public byte[] get() {
177: if (data == null) {
178: try {
179: ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(
180: 1024);
181: InputStream is = dataHandler.getDataSource()
182: .getInputStream();
183: baos.readFrom(is);
184: is.close();
185: data = baos.getBuffer();
186: dataLen = baos.size();
187: } catch (IOException e) {
188: // TODO: report the error to the unmarshaller
189: dataLen = 0; // recover by assuming length-0 data
190: }
191: }
192: return data;
193: }
194:
195: public int getDataLen() {
196: return dataLen;
197: }
198:
199: public String getMimeType() {
200: if (mimeType == null)
201: return "application/octet-stream";
202: return mimeType;
203: }
204:
205: /**
206: * Gets the number of characters needed to represent
207: * this binary data in the base64 encoding.
208: */
209: public int length() {
210: // for each 3 bytes you use 4 chars
211: // if the remainder is 1 or 2 there will be 4 more
212: get(); // fill in the buffer if necessary
213: return ((dataLen + 2) / 3) * 4;
214: }
215:
216: /**
217: * Encode this binary data in the base64 encoding
218: * and returns the character at the specified position.
219: */
220: public char charAt(int index) {
221: // we assume that the length() method is called before this method
222: // (otherwise how would the caller know that the index is valid?)
223: // so we assume that the byte[] is already populated
224:
225: int offset = index % 4;
226: int base = (index / 4) * 3;
227:
228: byte b1, b2;
229:
230: switch (offset) {
231: case 0:
232: return DatatypeConverterImpl.encode(data[base] >> 2);
233: case 1:
234: if (base + 1 < dataLen)
235: b1 = data[base + 1];
236: else
237: b1 = 0;
238: return DatatypeConverterImpl
239: .encode(((data[base] & 0x3) << 4)
240: | ((b1 >> 4) & 0xF));
241: case 2:
242: if (base + 1 < dataLen) {
243: b1 = data[base + 1];
244: if (base + 2 < dataLen)
245: b2 = data[base + 2];
246: else
247: b2 = 0;
248:
249: return DatatypeConverterImpl.encode(((b1 & 0xF) << 2)
250: | ((b2 >> 6) & 0x3));
251: } else
252: return '=';
253: case 3:
254: if (base + 2 < dataLen)
255: return DatatypeConverterImpl
256: .encode(data[base + 2] & 0x3F);
257: else
258: return '=';
259: }
260:
261: throw new IllegalStateException();
262: }
263:
264: /**
265: * Internally this is only used to split a text to a list,
266: * which doesn't happen that much for base64.
267: * So this method should be smaller than faster.
268: */
269: public CharSequence subSequence(int start, int end) {
270: StringBuilder buf = new StringBuilder();
271: get(); // fill in the buffer if we haven't done so
272: for (int i = start; i < end; i++)
273: buf.append(charAt(i));
274: return buf;
275: }
276:
277: /**
278: * Returns the base64 encoded string of this data.
279: */
280: public String toString() {
281: get(); // fill in the buffer
282: return DatatypeConverterImpl._printBase64Binary(data, 0,
283: dataLen);
284: }
285:
286: public void writeTo(char[] buf, int start) {
287: get();
288: DatatypeConverterImpl._printBase64Binary(data, 0, dataLen, buf,
289: start);
290: }
291:
292: public void writeTo(UTF8XmlOutput output) throws IOException {
293: // TODO: this is inefficient if the data source is note byte[] but DataHandler
294: get();
295: output.text(data, dataLen);
296: }
297: }
|