package nl.siegmann.epublib.domain; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.Serializable; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; import nl.siegmann.epublib.util.IOUtil; import nl.siegmann.epublib.util.commons.io.XmlStreamReader; import org.rr.commons.utils.StringUtil; /** * Represents a resource that is part of the epub. * A resource can be a html file, image, xml, etc. * * @author paul * */ public class Resource implements Serializable { private static final long serialVersionUID = 1043946707835004037L; private String id; private String title; private String href; private byte[] rawHref; private MediaType mediaType; private String inputEncoding = Constants.ENCODING; private byte[] data; private InputStream in; private String fileName; private long cachedSize; private String packageHref; private static final Logger log = Logger.getLogger(Resource.class.getName()); /** * Creates an empty Resource with the given href. * * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * @param href The location of the resource within the epub. Example: "chapter1.html". */ public Resource(String href) { this(null, new byte[0], href, MediatypeService.determineMediaType(href)); } /** * Creates a Resource with the given data and MediaType. * The href will be automatically generated. * * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * @param data The Resource's contents * @param mediaType The MediaType of the Resource */ public Resource(byte[] data, MediaType mediaType) { this(null, data, null, mediaType); } /** * Creates a resource with the given data at the specified href. * The MediaType will be determined based on the href extension. * * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * @see nl.siegmann.epublib.service.MediatypeService.determineMediaType(String) * * @param data The Resource's contents * @param href The location of the resource within the epub. Example: "chapter1.html". */ public Resource(byte[] data, String href) { this(null, data, href, MediatypeService.determineMediaType(href), Constants.ENCODING); } /** * Creates a resource with the given data at the specified href. * The MediaType will be determined based on the href extension. * * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * @see nl.siegmann.epublib.service.MediatypeService.determineMediaType(String) * * @param data The Resource's contents * @param href The location of the resource within the epub. Example: "chapter1.html". */ public Resource(byte[] data, byte[] rawHref) { this(null, data, new String(rawHref), MediatypeService.determineMediaType(new String(rawHref)), Constants.ENCODING); this.rawHref = rawHref; } /** * Creates a resource with the data from the given Reader at the specified href. * The MediaType will be determined based on the href extension. * @see nl.siegmann.epublib.service.MediatypeService.determineMediaType(String) * * @param in The Resource's contents * @param href The location of the resource within the epub. Example: "cover.jpg". */ public Resource(Reader in, String href) throws IOException { this(null, IOUtil.toByteArray(in, Constants.ENCODING), href, MediatypeService.determineMediaType(href), Constants.ENCODING); } /** * Creates a resource with the data from the given InputStream at the specified href. * The MediaType will be determined based on the href extension. * @see nl.siegmann.epublib.service.MediatypeService.determineMediaType(String) * * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * It is recommended to us the * @see nl.siegmann.epublib.domain.Resource.Resource(Reader, String) * method for creating textual (html/css/etc) resources to prevent encoding problems. * Use this method only for binary Resources like images, fonts, etc. * * * @param in The Resource's contents * @param href The location of the resource within the epub. Example: "cover.jpg". */ public Resource(InputStream in, String href) throws IOException { this(null, (byte[]) null, href, MediatypeService.determineMediaType(href)); setInputStream(in); } /** * Creates a resource with the data from the given InputStream at the specified href. * The MediaType will be determined based on the href extension. * @see nl.siegmann.epublib.service.MediatypeService.determineMediaType(String) * * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * It is recommended to us the * @see nl.siegmann.epublib.domain.Resource.Resource(Reader, String) * method for creating textual (html/css/etc) resources to prevent encoding problems. * Use this method only for binary Resources like images, fonts, etc. * * * @param in The Resource's contents * @param href The location of the resource within the epub. Example: "cover.jpg". */ public Resource(InputStream in, byte[] rawHref) throws IOException { this(null, (byte[]) null, new String(rawHref), MediatypeService.determineMediaType(new String(rawHref))); setInputStream(in); this.rawHref = rawHref; } /** * Creates a Lazy resource, by not actually loading the data for this entry. * * The data will be loaded on the first call to getData() * * @param fileName the fileName for the epub we're created from. * @param size the size of this resource. * @param href The resource's href within the epub. */ public Resource( String fileName, long size, String href) { this( null, null, href, MediatypeService.determineMediaType(href)); this.fileName = fileName; this.cachedSize = size; } /** * Creates a resource with the given id, data, mediatype at the specified href. * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 * * @param id The id of the Resource. Internal use only. Will be auto-generated if it has a null-value. * @param data The Resource's contents * @param href The location of the resource within the epub. Example: "chapter1.html". * @param mediaType The resources MediaType */ public Resource(String id, byte[] data, String href, MediaType mediaType) { this(id, data, href, mediaType, Constants.ENCODING); } /** * Creates a resource with the given id, data, mediatype at the specified href. * If the data is of a text type (html/css/etc) then it will use the given inputEncoding. * * @param id The id of the Resource. Internal use only. Will be auto-generated if it has a null-value. * @param data The Resource's contents * @param href The location of the resource within the epub. Example: "chapter1.html". * @param mediaType The resources MediaType * @param inputEncoding If the data is of a text type (html/css/etc) then it will use the given inputEncoding. */ public Resource(String id, byte[] data, String href, MediaType mediaType, String inputEncoding) { this.id = id; this.href = href; this.mediaType = mediaType; this.inputEncoding = inputEncoding; this.data = data; } private void setInputStream(InputStream in) { if(in.markSupported()) { //mark the start of the InputStream in.mark(Integer.MAX_VALUE); } this.in = in; } private void resetInputStream() { if(in.markSupported()) { //go to the begin of the InputStream try { in.reset(); } catch (IOException e) { Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "InputStream reset for resource " + this.fileName + " has failed.", e); } } } /** * Gets the contents of the Resource as an InputStream. * * @return The contents of the Resource. * * @throws IOException */ public InputStream getInputStream() throws IOException { if(this.in != null) { resetInputStream(); return in; } return new ByteArrayInputStream(getData()); } /** * The contents of the resource as a byte[] * * If this resource was lazy-loaded and the data was not yet loaded, * it will be loaded into memory at this point. * This included opening the zip file, so expect a first load to be slow. * * @return The contents of the resource */ public byte[] getData() throws IOException { if ( data == null ) { if( in != null ) { resetInputStream(); data = IOUtil.toByteArray(in); in.close(); in = null; } else { log.info("Initializing lazy resource " + fileName + "#" + this.href ); ZipInputStream zipIn = new ZipInputStream(new FileInputStream(this.fileName)); for(ZipEntry zipEntry = zipIn.getNextEntry(); zipEntry != null; zipEntry = zipIn.getNextEntry()) { if(zipEntry.isDirectory()) { continue; } if ( zipEntry.getName().endsWith(this.href)) { this.data = IOUtil.toByteArray(zipIn); } } zipIn.close(); } } return data; } /** * Tells this resource to release its cached data. * * If this resource was not lazy-loaded, this is a no-op. */ public void close() { if ( this.fileName != null ) { this.data = null; } } /** * Sets the data of the Resource. * If the data is a of a different type then the original data then make sure to change the MediaType. * * @param data */ public void setData(byte[] data) { this.data = data; this.in = null; } /** * Returns if the data for this resource has been loaded into memory. * * @return true if data was loaded. */ public boolean isInitialized() { return data != null; } /** * Returns the size of this resource in bytes. * * @return the size. */ public long getSize() { if ( data != null ) { return data.length; } return cachedSize; } /** * If the title is found by scanning the underlying html document then it is cached here. * * @return */ public String getTitle() { return title; } /** * Sets the Resource's id: Make sure it is unique and a valid identifier. * * @param id */ public void setId(String id) { this.id = id; } /** * The resources Id. * * Must be both unique within all the resources of this book and a valid identifier. * @return */ public String getId() { return id; } /** * The location of the resource within the contents folder of the epub file. * * Example:<br/> * images/cover.jpg<br/> * content/chapter1.xhtml<br/> * * @return */ public String getHref() { return fixHref(); } /** * Sets the Resource's href. * * @param href */ public void setHref(String href) { this.href = href; } /** * The character encoding of the resource. * Is allowed to be null for non-text resources like images. * * @return */ public String getInputEncoding() { return inputEncoding; } /** * Sets the Resource's input character encoding. * * @param encoding */ public void setInputEncoding(String encoding) { this.inputEncoding = encoding; } /** * Gets the contents of the Resource as Reader. * * Does all sorts of smart things (courtesy of apache commons io XMLStreamREader) to handle encodings, byte order markers, etc. * * @param resource * @return * @throws IOException */ public Reader getReader() throws IOException { return new XmlStreamReader(new ByteArrayInputStream(getData()), inputEncoding); } /** * Gets the hashCode of the Resource's href. * */ public int hashCode() { return href.hashCode(); } /** * Checks to see of the given resourceObject is a resource and whether its href is equal to this one. * */ public boolean equals(Object resourceObject) { if (! (resourceObject instanceof Resource)) { return false; } return href.equals(((Resource) resourceObject).getHref()); } /** * This resource's mediaType. * * @return */ public MediaType getMediaType() { return mediaType; } public void setMediaType(MediaType mediaType) { this.mediaType = mediaType; } public void setTitle(String title) { this.title = title; } public String toString() { return StringUtil.toString("id", id, "title", title, "encoding", inputEncoding, "mediaType", mediaType, "href", href, "size", (data == null ? 0 : data.length)); } public String getPackageHref() { return packageHref; } public void setPackageHref(String packageHref) { this.packageHref = packageHref; } /** * Strips off the package prefixes up to the href of the packageHref. * * Example: * If the packageHref is "OEBPS/content.opf" then a resource href like "OEBPS/foo/bar.html" will be turned into "foo/bar.html" * * @param packageHref * @param resourcesByHref * @return */ private String fixHref() { int lastSlashPos = packageHref != null ? packageHref.lastIndexOf('/') : -1; if (lastSlashPos < 0) { return this.href; } String packagePath = packageHref.substring(0, lastSlashPos + 1); if (StringUtil.isNotEmpty(this.href) || this.href.length() > lastSlashPos) { if (this.href.startsWith(packagePath)) { // fix only entries within the given packageHref. The entry could be // destroyed in the case that the ref is not in the given package. return this.href.substring(lastSlashPos + 1); } } return this.href; } public byte[] getRawHref() { return this.rawHref; } }