2 * Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
4 * The Sun Project JXTA(TM) Software License
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
16 * 3. The end-user documentation included with the redistribution, if any, must
17 * include the following acknowledgment: "This product includes software
18 * developed by Sun Microsystems, Inc. for JXTA(TM) technology."
19 * Alternately, this acknowledgment may appear in the software itself, if
20 * and wherever such third-party acknowledgments normally appear.
22 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
23 * not be used to endorse or promote products derived from this software
24 * without prior written permission. For written permission, please contact
25 * Project JXTA at http://www.jxta.org.
27 * 5. Products derived from this software may not be called "JXTA", nor may
28 * "JXTA" appear in their name, without prior written permission of Sun.
30 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
33 * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 * JXTA is a registered trademark of Sun Microsystems, Inc. in the United
42 * States and other countries.
44 * Please see the license information page at :
45 * <http://www.jxta.org/project/www/license.html> for instructions on use of
46 * the license in source files.
48 * ====================================================================
50 * This software consists of voluntary contributions made by many individuals
51 * on behalf of Project JXTA. For more information on Project JXTA, please see
52 * http://www.jxta.org.
54 * This license is based on the BSD license adopted by the Apache Foundation.
56 package net.jxta.endpoint;
59 import net.jxta.document.MimeMediaType;
60 import net.jxta.logging.Logging;
62 import java.io.ByteArrayInputStream;
63 import java.io.IOException;
64 import java.io.InputStream;
65 import java.io.InputStreamReader;
66 import java.io.OutputStream;
67 import java.io.Reader;
68 import java.io.StringReader;
69 import java.io.UnsupportedEncodingException;
70 import java.io.Writer;
71 import java.lang.ref.SoftReference;
72 import java.nio.ByteBuffer;
73 import java.nio.CharBuffer;
74 import java.nio.charset.Charset;
75 import java.nio.charset.CharsetEncoder;
76 import java.nio.charset.CoderResult;
77 import java.nio.charset.CodingErrorAction;
78 import java.util.logging.Level;
79 import java.util.logging.Logger;
83 * A Message Element using character strings for the element data.
85 public class StringMessageElement extends TextMessageElement {
90 private final static transient Logger LOG = Logger.getLogger(StringMessageElement.class.getName());
93 * The MIME media type we will be use for encoding {@code String}s when no
94 * encoding is specified.
96 private static final MimeMediaType DEFAULT_TEXT_ENCODING = new MimeMediaType(MimeMediaType.TEXT_DEFAULTENCODING, "charset=\"" + Charset.defaultCharset().name() + "\"", true).intern();
99 * The data for this Message Element.
101 protected String data;
104 * Returns an appropriate mime type for the given encoding name. The
105 * mimetype will contain the canonical name of the encoding.
107 * @param encoding name of the desired encoding.
108 * @return the mime type.
109 * @throws java.io.UnsupportedEncodingException
110 * if the mime is unsupported
112 private static MimeMediaType makeMimeType(String encoding) throws UnsupportedEncodingException {
113 InputStreamReader getEncoding = new InputStreamReader(new ByteArrayInputStream(new byte[0]), encoding);
115 String canonicalName = getEncoding.getEncoding();
117 return new MimeMediaType(MimeMediaType.TEXT_DEFAULTENCODING, "charset=\"" + canonicalName + "\"", true).intern();
121 * Create a new Message Element from the provided String. The String will
122 * be encoded for transmission using UTF-8.
124 * @param name Name of the Element. May be the empty string ("") or null if
125 * the Element is not named.
126 * @param value A String containing the contents of this element.
127 * @param sig Message digest/digital signature element. If no signature is
128 * to be specified, pass <code>null</code>.
129 * @throws IllegalArgumentException if <code>value</code> is
132 public StringMessageElement(String name, String value, MessageElement sig) {
133 super(name, MimeMediaType.TEXTUTF8, sig);
136 throw new IllegalArgumentException("value must be non-null");
143 * Create a new Message Element from the provided String. The string will
144 * be encoded for transmission using specified character encoding.
146 * @param name Name of the MessageElement. May be the empty string ("") or
147 * <code>null</code> if the MessageElement is not named.
148 * @param value A String containing the contents of this element.
149 * @param encoding Name of the character encoding to use. If
150 * <code>null</code> then the system default character encoding will be
151 * used. (Using the system default character encoding should be used with
153 * @param sig Message digest/digital signature element. If no signature is
154 * to be specified, pass <code>null</code>.
155 * @throws IllegalArgumentException if <code>value</code> is
157 * @throws UnsupportedEncodingException if the requested encoding is not
160 public StringMessageElement(String name, String value, String encoding, MessageElement sig) throws UnsupportedEncodingException {
161 super(name, (null == encoding) ? DEFAULT_TEXT_ENCODING : makeMimeType(encoding), sig);
164 throw new IllegalArgumentException("value must be non-null");
174 public boolean equals(Object target) {
175 if (this == target) {
179 if (target instanceof MessageElement) {
180 if (!super.equals(target)) {
184 if (target instanceof StringMessageElement) {
185 StringMessageElement likeMe = (StringMessageElement) target;
187 return data.equals(likeMe.data); // same chars?
188 } else if (target instanceof TextMessageElement) {
189 // have to do a slow char by char comparison. Still better than the stream since it saves encoding.
190 // XXX 20020615 bondolo@jxta.org the performance of this could be much improved.
192 TextMessageElement likeMe = (TextMessageElement) target;
195 Reader myReader = getReader();
196 Reader itsReader = likeMe.getReader();
202 mine = myReader.read();
203 its = itsReader.read();
207 } // content didn't match
209 } while ((-1 != mine) && (-1 != its));
211 return ((-1 == mine) && (-1 == its)); // end at the same time?
212 } catch (IOException fatal) {
213 IllegalStateException failure = new IllegalStateException("MessageElements could not be compared.");
215 failure.initCause(fatal);
219 // have to do a slow stream comparison.
220 // XXX 20020615 bondolo@jxta.org the performance of this could be much improved.
222 MessageElement likeMe = (MessageElement) target;
225 InputStream myStream = getStream();
226 InputStream itsStream = likeMe.getStream();
232 mine = myStream.read();
233 its = itsStream.read();
237 } // content didn't match
239 } while ((-1 != mine) && (-1 != its));
241 return ((-1 == mine) && (-1 == its)); // end at the same time?
242 } catch (IOException fatal) {
243 IllegalStateException failure = new IllegalStateException("MessageElements could not be compared.");
245 failure.initCause(fatal);
251 return false; // not a new message element
258 public int hashCode() {
259 int result = super.hashCode() * 6037 + // a prime
269 public String toString() {
277 public synchronized byte[] getBytes(boolean copy) {
278 byte[] cachedBytes = null;
280 if (null != cachedGetBytes) {
281 cachedBytes = cachedGetBytes.get();
284 if (null == cachedBytes) {
285 if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
287 "Creating getBytes of " + getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(this)));
290 String charset = type.getParameter("charset");
293 cachedBytes = data.getBytes(charset);
294 } catch (UnsupportedEncodingException caught) {
295 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
296 LOG.log(Level.WARNING, "MessageElement Data could not be generated", caught);
298 IllegalStateException failure = new IllegalStateException("MessageElement Data could not be generated");
300 failure.initCause(caught);
304 cachedGetBytes = new SoftReference<byte[]>(cachedBytes);
311 byte[] bytesCopy = cachedBytes.clone();
320 public long getCharLength() {
321 return data.length();
328 public synchronized char[] getChars(boolean copy) {
329 char[] cachedChars = null;
331 if (null != cachedGetChars) {
332 cachedChars = cachedGetChars.get();
335 if (null == cachedChars) {
336 if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
337 LOG.finer("creating cachedGetChars of " + getClass().getName() + '@' + Integer.toHexString(hashCode()));
340 cachedChars = new char[data.length()];
342 data.getChars(0, data.length(), cachedChars, 0);
344 // if this is supposed to be a shared buffer then we can cache it.
346 cachedGetChars = new SoftReference<char[]>(cachedChars);
353 char[] copyChars = cachedChars.clone();
361 public InputStream getStream() throws IOException {
362 byte cachedBytes[] = null;
364 synchronized (this) {
365 if (null != cachedGetBytes) {
366 cachedBytes = cachedGetBytes.get();
370 if (null != cachedBytes) {
371 return new ByteArrayInputStream(cachedBytes);
373 String charset = type.getParameter("charset");
374 return new CharSequenceInputStream(data, charset);
381 * @return InputStream of the stream containing element data.
382 * @throws IOException when there is a problem getting a reader.
384 public Reader getReader() throws IOException {
386 return new StringReader(data);
393 public void sendToStream(OutputStream sendTo) throws IOException {
395 sendTo.write(getBytes(false));
402 public void sendToWriter(Writer sendTo) throws IOException {
409 private static class CharSequenceInputStream extends InputStream {
411 private final CharBuffer charData;
413 private final CharsetEncoder conversion;
415 private boolean marked = false;
416 private byte mark_multiByteChar[];
417 private int mark_position;
419 private byte multiByteChar[];
420 private int position;
423 * @param s the char sequence
424 * @param encoding the charset encoding
426 CharSequenceInputStream(CharSequence s, String encoding) {
427 charData = CharBuffer.wrap(s);
429 Charset encodingCharset = Charset.forName(encoding);
431 conversion = encodingCharset.newEncoder();
432 conversion.onMalformedInput(CodingErrorAction.REPLACE);
433 conversion.onUnmappableCharacter(CodingErrorAction.REPLACE);
435 int maxBytes = new Float(conversion.maxBytesPerChar()).intValue();
437 multiByteChar = new byte[maxBytes];
438 position = multiByteChar.length;
445 public void mark(int ignored) {
447 mark_multiByteChar = multiByteChar.clone();
448 mark_position = position;
456 public boolean markSupported() {
464 public void reset() throws IOException {
467 throw new IOException("reset() called before mark()");
471 multiByteChar = mark_multiByteChar.clone();
472 position = mark_position;
479 public int read() throws IOException {
480 // prefill the buffer
481 while (multiByteChar.length == position) {
482 int readsome = read(multiByteChar, 0, multiByteChar.length);
484 if (-1 == readsome) {
488 position = multiByteChar.length - readsome;
490 if ((0 != position) && (0 != readsome)) {
491 System.arraycopy(multiByteChar, 0, multiByteChar, position, readsome);
495 return (multiByteChar[position++] & 0xFF);
502 public int read(byte[] buffer) throws IOException {
503 return read(buffer, 0, buffer.length);
510 public int read(byte[] buffer, int offset, int length) throws IOException {
511 // handle partial characters;
512 if (multiByteChar.length != position) {
513 int copying = Math.min(length, multiByteChar.length - position);
515 System.arraycopy(multiByteChar, position, buffer, offset, copying);
520 ByteBuffer bb = ByteBuffer.wrap(buffer, offset, length);
522 int before = bb.remaining();
524 CoderResult result = conversion.encode(charData, bb, true);
526 int readin = before - bb.remaining();
528 if (CoderResult.UNDERFLOW == result) {
536 if (CoderResult.OVERFLOW == result) {
540 result.throwException();