/* AMedia.java Purpose: Description: History: Thu May 27 15:10:46 2004, Created by tomyeh Copyright (C) 2004 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.util.media; import java.io.File; import java.io.Reader; import java.io.InputStream; import java.io.StringReader; import java.io.ByteArrayInputStream; import java.net.URL; import org.zkoss.lang.SystemException; import org.zkoss.io.Files; import org.zkoss.io.NullInputStream; import org.zkoss.io.NullReader; import org.zkoss.io.RepeatableInputStream; import org.zkoss.io.RepeatableReader; /** * A media object holding content such PDF, HTML, DOC or XLS content. * * <p>AMedia is serializable, but, if you are using InputStream or Reader, * you have to extend this class, and provide the implementation to * serialize and deserialize {@link #_isdata} or {@link #_rddata} * (they are both transient). * @author tomyeh */ public class AMedia implements Media, java.io.Serializable { /** Used if you want to implement a media whose input stream is created * dynamically each time {@link #getStreamData} is called. * @see #AMedia(String,String,String,InputStream) */ protected static final InputStream DYNAMIC_STREAM = new NullInputStream(); /** Used if you want to implement a media whose reader is created * dynamically each time {@link #getReaderData} is called. * @see #AMedia(String,String,String,Reader) */ protected static final Reader DYNAMIC_READER = new NullReader(); /** The binary data, {@link #getByteData}. */ private byte[] _bindata; /** The text data, {@link #getStringData}. */ private String _strdata; /** The input stream, {@link #getStreamData} */ protected transient InputStream _isdata; /** The input stream, {@link #getReaderData} */ protected transient Reader _rddata; /** The content type. */ private String _ctype; /** The format (e.g., pdf). */ private String _format; /** The name (usually filename). */ private String _name; /** Whether to allow Content-Disposition * or not when writing the media to response header. */ private boolean _cntDisposition = true; /** Construct with name, format, content type and binary data. * * <p>It tries to construct format and ctype from each other or name. * * @param name the name (usually filename); might be null. * @param format the format; might be null. Example: "html" and "xml" * @param ctype the content type; might be null. Example: "text/html" * and "text/xml;charset=UTF-8". * @param data the binary data; never null */ public AMedia(String name, String format, String ctype, byte[] data) { if (data == null) throw new IllegalArgumentException("data"); _bindata = data; setup(name, format, ctype); } /** Construct with name, format, content type and text data. * * <p>It tries to construct format and ctype from each other or name. * * @param name the name (usually filename); might be null. * @param format the format; might be null. * @param ctype the content type; might be null. * @param data the text data; never null */ public AMedia(String name, String format, String ctype, String data) { if (data == null) throw new IllegalArgumentException("data"); _strdata = data; setup(name, format, ctype); } /** Construct with name, format, content type and stream data (binary). * * <p>It tries to construct format and ctype from each other or name. * * @param name the name (usually filename); might be null. * @param format the format; might be null. * @param ctype the content type; might be null. * @param data the binary data; never null. * If the input stream is created dynamically each time {@link #getStreamData} * is called, you shall pass {@link #DYNAMIC_STREAM} * as the data argument. Then, override {@link #getStreamData} to return * the correct stream. * Note: the caller of {@link #getStreamData} has to close * the returned input stream. */ public AMedia(String name, String format, String ctype, InputStream data) { if (data == null) throw new IllegalArgumentException("data"); _isdata = data; setup(name, format, ctype); } /** Construct with name, format, content type and reader data (textual). * * <p>It tries to construct format and ctype from each other or name. * * @param name the name (usually filename); might be null. * @param format the format; might be null. * @param ctype the content type; might be null. * @param data the string data; never null * If the reader is created dynamically each time {@link #getReaderData} * is called, you shall pass {@link #DYNAMIC_READER} * as the data argument. Then, override {@link #getReaderData} to return * the correct reader. */ public AMedia(String name, String format, String ctype, Reader data) { if (data == null) throw new IllegalArgumentException("data"); _rddata = data; setup(name, format, ctype); } /** Construct with name, format, content type and a file. * * <p>Unlike others, it uses the so-called repeatable input * stream or reader (depending on binary or not) to represent the file, * so the input stream ({@link #getStreamData}) * or the reader ({@link #getReaderData}) will be re-opened * in the next invocation of {@link InputStream#read} * after {@link InputStream#close} is called. * See also {@link RepeatableInputStream} and {@link RepeatableReader}. * * @param name the name (usually filename); might be null. * If null, the file name is used. * @param format the format; might be null. * @param ctype the content type; might be null. * @param file the file; never null. * @param binary whether it is binary. * If not binary, "UTF-8" is assumed. */ public AMedia(String name, String format, String ctype, File file, boolean binary) throws java.io.FileNotFoundException { this(name, format, ctype, file, binary ? null: "UTF-8"); } /** Construct with name, format, content type and a file. * * <p>Unlike others, it uses the so-called repeatable input * stream or reader (depending on charset is null or not) * to represent the file, so the input stream ({@link #getStreamData}) * or the reader ({@link #getReaderData}) will be re-opened * in the next invocation of {@link InputStream#read} * after {@link InputStream#close} is called. * See also {@link RepeatableInputStream} and {@link RepeatableReader}. * * @param name the name (usually filename); might be null. * If null, the file name is used. * @param format the format; might be null. * @param ctype the content type; might be null. * @param file the file; never null. * @param charset the charset. If null, it is assumed to be binary. */ public AMedia(String name, String format, String ctype, File file, String charset) throws java.io.FileNotFoundException { if (file == null) throw new IllegalArgumentException("file"); if (charset == null) _isdata = RepeatableInputStream.getInstance(file); else _rddata = RepeatableReader.getInstance(file, charset); if (name == null) name = file.getName(); setup(name, format, ctype); } /** Construct with a file. * It is the same as AMedia(null, null, ctype, file, charset). * * @param ctype the content type; might be null. * If null, it is retrieved from the file name's extension. * @since 3.0.8 */ public AMedia(File file, String ctype, String charset) throws java.io.FileNotFoundException { this(null, null, ctype, file, charset); } /** Construct with name, format, content type and URL. * * <p>Unlike others, it uses the so-called repeatable input * stream or reader (depending on charset is null or not) to represent the * resource, so the input stream ({@link #getStreamData}) * or the reader ({@link #getReaderData}) will be re-opened * in the next invocation of {@link InputStream#read} * after {@link InputStream#close} is called. * See also {@link RepeatableInputStream} and {@link RepeatableReader}. * * @param name the name; might be null. * If null, URL's name is used. * @param format the format; might be null. * @param ctype the content type; might be null. * @param url the resource URL; never null. * @since 3.0.8 */ public AMedia(String name, String format, String ctype, URL url, String charset) throws java.io.FileNotFoundException { if (url == null) throw new IllegalArgumentException("url"); if (charset == null) _isdata = RepeatableInputStream.getInstance(url); else _rddata = RepeatableReader.getInstance(url, charset); if (name == null) { name = url.toExternalForm(); final int j = name.lastIndexOf('/'); if (j >= 0 && j < name.length() - 1) name = name.substring(j + 1); } setup(name, format, ctype); } /** Construct with a file. * It is the same as AMedia(null, null, ctype, url, charset). * * @param ctype the content type; might be null. * If null, it is retrieved from the file name's extension. * @since 3.0.8 */ public AMedia(URL url, String ctype, String charset) throws java.io.FileNotFoundException { this(null, null, ctype, url, charset); } /** Sets up the format and content type. * It assumes one of them is not null. */ private void setup(String name, String format, String ctype) { if (ctype != null) { int j = ctype.indexOf(';'); if (j >= 0) ctype = ctype.substring(0, j); } if (ctype != null && format == null) { format = ContentTypes.getFormat(ctype); } else if (ctype == null && format != null) { ctype = ContentTypes.getContentType(format); } if (name != null) { if (format == null) { final int j = name.lastIndexOf('.'); if (j >= 0) { format = name.substring(j + 1); if (ctype == null) { ctype = ContentTypes.getContentType(format); } } } } _name = name; _format = format; _ctype = ctype; } /** Set whether to allow Content-Disposition * or not when writing the media to response header. * @since 7.0.0 */ public void setContentDisposition(boolean cntDisposition) { _cntDisposition = cntDisposition; } //-- Media --// public boolean isBinary() { return _bindata != null || _isdata != null; } public boolean inMemory() { return _bindata != null || _strdata != null; } public byte[] getByteData() { if (_bindata != null) return _bindata; InputStream is = _isdata == DYNAMIC_STREAM ? getStreamData() : _isdata ; //ZK-938 if (is != null) { try { byte[] bs = Files.readAll(is); is.close(); return bs; } catch (java.io.IOException ex) { throw SystemException.Aide.wrap(ex); } } throw newIllegalStateException(); } public String getStringData() { if (_strdata != null) return _strdata; Reader reader = _rddata == DYNAMIC_READER ? getReaderData() : _rddata; //ZK-938 if (reader != null) { try { String ct = Files.readAll(reader).toString(); reader.close(); return ct; } catch (java.io.IOException ex) { throw SystemException.Aide.wrap(ex); } } throw newIllegalStateException(); } /** Returns the input stream of this media. * * <p>Note: the caller has to invoke {@link InputStream#close} * after using the input stream returned by {@link #getStreamData}. * * @exception IllegalStateException if the media is not binary * {@link #isBinary}. */ public InputStream getStreamData() { if (_isdata != null) return _isdata; if (_bindata != null) return new ByteArrayInputStream(_bindata); throw newIllegalStateException(); } /** Returns the reader of this media to retrieve the data. * * <p>Note: the caller has to invoke {@link Reader#close} * after using the input stream returned by {@link #getReaderData}. * * @exception IllegalStateException if the media is binary * {@link #isBinary}. */ public Reader getReaderData() { if (_rddata != null) return _rddata; if (_strdata != null) return new StringReader(_strdata); throw newIllegalStateException(); } private IllegalStateException newIllegalStateException() { return new IllegalStateException( "Use get" +(_bindata != null ? "Byte": _strdata != null ? "String": _isdata != null ? "Stream": "Reader") + "Data() instead"); } public String getName() { return _name; } public String getFormat() { return _format; } public String getContentType() { return _ctype; } public boolean isContentDisposition() { return _cntDisposition; } //-- Object --// public String toString() { return _name != null ? _name: "Media "+_format; } }