From 7b2a61045662e9b03eb2fa5292fd0c4397332f66 Mon Sep 17 00:00:00 2001 From: Alex Kuiper Date: Wed, 27 Mar 2013 07:58:24 +0100 Subject: [PATCH 01/57] Added OGG audio to recognized file types. --- .../java/nl/siegmann/epublib/service/MediatypeService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java b/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java index d9074082..835567d9 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java @@ -37,13 +37,14 @@ public class MediatypeService { // audio public static final MediaType MP3 = new MediaType("audio/mpeg", ".mp3"); public static final MediaType MP4 = new MediaType("audio/mp4", ".mp4"); + public static final MediaType OGG = new MediaType("audio/ogg", ".ogg"); public static final MediaType SMIL = new MediaType("application/smil+xml", ".smil"); public static final MediaType XPGT = new MediaType("application/adobe-page-template+xml", ".xpgt"); public static final MediaType PLS = new MediaType("application/pls+xml", ".pls"); public static MediaType[] mediatypes = new MediaType[] { - XHTML, EPUB, JPG, PNG, GIF, CSS, SVG, TTF, NCX, XPGT, OPENTYPE, WOFF, SMIL, PLS, JAVASCRIPT, MP3, MP4 + XHTML, EPUB, JPG, PNG, GIF, CSS, SVG, TTF, NCX, XPGT, OPENTYPE, WOFF, SMIL, PLS, JAVASCRIPT, MP3, MP4, OGG }; public static Map mediaTypesByName = new HashMap(); From 6ef20621ab63a3d15f90f4a1a4664b2d91ffeaf0 Mon Sep 17 00:00:00 2001 From: Mayleen Lacouture Date: Thu, 4 Apr 2013 16:06:45 +0200 Subject: [PATCH 02/57] Added support for reading epub files directly from a java.zip.ZipFile instead of a ZipInputStream: the ZipInputStream approach stops reading from the input when a null entry is found, which in some cases leads to a toc-not-found-error. --- .../nl/siegmann/epublib/domain/Resource.java | 6 ++- .../nl/siegmann/epublib/epub/EpubReader.java | 53 +++++++++++++++---- .../siegmann/epublib/util/ResourceUtil.java | 12 ++--- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 7b46e8f1..598501d5 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -12,6 +12,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.commons.io.IOUtils; + import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; import nl.siegmann.epublib.util.IOUtil; @@ -111,7 +113,7 @@ public Resource(Reader in, String href) throws IOException { * @param href The location of the resource within the epub. Example: "cover.jpg". */ public Resource(InputStream in, String href) throws IOException { - this(null, IOUtil.toByteArray(in), href, MediatypeService.determineMediaType(href)); + this(null, IOUtils.toByteArray(in), href, MediatypeService.determineMediaType(href)); } /** @@ -194,7 +196,7 @@ public byte[] getData() throws IOException { } if ( zipEntry.getName().endsWith(this.href)) { - this.data = IOUtil.toByteArray(in); + this.data = IOUtils.toByteArray(in); } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java index 1fcb7cea..96aff62a 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java @@ -4,8 +4,10 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import nl.siegmann.epublib.Constants; @@ -40,7 +42,11 @@ public Book readEpub(InputStream in) throws IOException { public Book readEpub(ZipInputStream in) throws IOException { return readEpub(in, Constants.CHARACTER_ENCODING); } - + + public Book readEpub(ZipFile zipfile) throws IOException { + return readEpub(zipfile, Constants.CHARACTER_ENCODING); + } + /** * Read epub from inputstream * @@ -90,18 +96,25 @@ public Book readEpubLazy( String fileName, String encoding ) throws IOException } public Book readEpub(ZipInputStream in, String encoding) throws IOException { - Book result = new Book(); - Resources resources = readResources(in, encoding); - handleMimeType(result, resources); - String packageResourceHref = getPackageResourceHref(resources); - Resource packageResource = processPackageResource(packageResourceHref, result, resources); - result.setOpfResource(packageResource); - Resource ncxResource = processNcxResource(packageResource, result); - result.setNcxResource(ncxResource); - result = postProcessBook(result); - return result; + return readEpubResources(readResources(in, encoding)); } + public Book readEpub(ZipFile in, String encoding) throws IOException { + return readEpubResources(readResources(in, encoding)); + } + + public Book readEpubResources(Resources resources) throws IOException{ + Book result = new Book(); + handleMimeType(result, resources); + String packageResourceHref = getPackageResourceHref(resources); + Resource packageResource = processPackageResource(packageResourceHref, result, resources); + result.setOpfResource(packageResource); + Resource ncxResource = processNcxResource(packageResource, result); + result.setNcxResource(ncxResource); + result = postProcessBook(result); + return result; + } + private Book postProcessBook(Book book) { if (bookProcessor != null) { book = bookProcessor.processBook(book); @@ -193,4 +206,22 @@ private Resources readResources(ZipInputStream in, String defaultHtmlEncoding) t } return result; } + + private Resources readResources(ZipFile zipFile, String defaultHtmlEncoding) throws IOException { + Resources result = new Resources(); + Enumeration entries = zipFile.entries(); + + while(entries.hasMoreElements()){ + ZipEntry zipEntry = entries.nextElement(); + if(zipEntry != null && !zipEntry.isDirectory()){ + Resource resource = ResourceUtil.createResource(zipEntry, zipFile.getInputStream(zipEntry)); + if(resource.getMediaType() == MediatypeService.XHTML) { + resource.setInputEncoding(defaultHtmlEncoding); + } + result.add(resource); + } + } + + return result; + } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java index 36ea1a04..1db60226 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java @@ -1,10 +1,6 @@ package nl.siegmann.epublib.util; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Reader; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -64,7 +60,11 @@ public static Resource createResource(ZipEntry zipEntry, ZipInputStream zipInput return new Resource(zipInputStream, zipEntry.getName()); } - + + public static Resource createResource(ZipEntry zipEntry, InputStream zipInputStream) throws IOException { + return new Resource(zipInputStream, zipEntry.getName()); + + } /** * Converts a given string from given input character encoding to the requested output character encoding. From ba2a9bd37f4839fe9b101dabe55cec16f15662c3 Mon Sep 17 00:00:00 2001 From: Alex Kuiper Date: Wed, 1 May 2013 21:13:09 +0200 Subject: [PATCH 03/57] Small memory optimization. --- .../nl/siegmann/epublib/domain/Resource.java | 37 ++++++++++----- .../nl/siegmann/epublib/epub/EpubReader.java | 2 +- .../java/nl/siegmann/epublib/util/IOUtil.java | 45 +++++++++++++++---- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 7b46e8f1..7204ebe1 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -1,11 +1,6 @@ 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.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -111,9 +106,30 @@ public Resource(Reader in, String href) throws IOException { * @param href The location of the resource within the epub. Example: "cover.jpg". */ public Resource(InputStream in, String href) throws IOException { - this(null, IOUtil.toByteArray(in), href, MediatypeService.determineMediaType(href)); + this(null, IOUtil.toByteArray(in), href, MediatypeService.determineMediaType(href)); } - + + /** + * Creates a Resource that tries to load the data, but falls back to lazy loading. + * + * If the size of the resource is known ahead of time we can use that to allocate + * a matching byte[]. If this succeeds we can safely load the data. + * + * If it fails we leave the data null for now and it will be lazy-loaded when + * it is accessed. + * + * @param in + * @param fileName + * @param length + * @param href + * @throws IOException + */ + public Resource(InputStream in, String fileName, int length, String href) throws IOException { + this( null, IOUtil.toByteArray(in, length), href, MediatypeService.determineMediaType(href)); + this.fileName = fileName; + this.cachedSize = length; + } + /** * Creates a Lazy resource, by not actually loading the data for this entry. * @@ -141,7 +157,8 @@ public Resource( String fileName, long size, String href) { public Resource(String id, byte[] data, String href, MediaType mediaType) { this(id, data, href, mediaType, Constants.CHARACTER_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. @@ -194,7 +211,7 @@ public byte[] getData() throws IOException { } if ( zipEntry.getName().endsWith(this.href)) { - this.data = IOUtil.toByteArray(in); + this.data = IOUtil.toByteArray(in, (int) this.cachedSize); } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java index 1fcb7cea..6cf03616 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java @@ -167,7 +167,7 @@ private Resources readLazyResources( String fileName, String defaultHtmlEncoding if ( lazyLoadedTypes.contains(mediaType) ) { resource = new Resource(fileName, zipEntry.getSize(), href); } else { - resource = new Resource( in, href ); + resource = new Resource( in, fileName, (int) zipEntry.getSize(), href ); } if(resource.getMediaType() == MediatypeService.XHTML) { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java index 163b720c..89d669af 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java @@ -1,12 +1,6 @@ package nl.siegmann.epublib.util; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; +import java.io.*; /** * Most of the functions herein are re-implementations of the ones in apache io IOUtils. @@ -45,7 +39,40 @@ public static byte[] toByteArray(InputStream in) throws IOException { return result.toByteArray(); } - /** + /** + * Reads data from the InputStream, using the specified buffer size. + * + * This is meant for situations where memory is tight, since + * it prevents buffer expansion. + * + * @param stream the stream to read data from + * @param size the size of the array to create + * @return the array, or null + * @throws IOException + */ + public static byte[] toByteArray( InputStream in, int size ) throws IOException { + + try { + ByteArrayOutputStream result; + + if ( size > 0 ) { + result = new ByteArrayOutputStream(size); + } else { + result = new ByteArrayOutputStream(); + } + + copy(in, result); + result.flush(); + return result.toByteArray(); + } catch ( OutOfMemoryError error ) { + //Return null so it gets loaded lazily. + return null; + } + + } + + + /** * if totalNrRead < 0 then totalNrRead is returned, if (nrRead + totalNrRead) < Integer.MAX_VALUE then nrRead + totalNrRead is returned, -1 otherwise. * @param nrRead * @param totalNrNread @@ -62,7 +89,7 @@ protected static int calcNewNrReadSize(int nrRead, int totalNrNread) { } } - /** + /** * Copies the contents of the InputStream to the OutputStream. * * @param in From 805ddea206f09c4805a10fb0ea34e7c5407daff3 Mon Sep 17 00:00:00 2001 From: Alex Kuiper Date: Fri, 7 Jun 2013 18:06:09 +0200 Subject: [PATCH 04/57] Extra check --- .../main/java/nl/siegmann/epublib/domain/Resource.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 7204ebe1..9b334694 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -211,13 +211,19 @@ public byte[] getData() throws IOException { } if ( zipEntry.getName().endsWith(this.href)) { - this.data = IOUtil.toByteArray(in, (int) this.cachedSize); + byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); + + if ( readData == null ) { + throw new IOException("Could not lazy-load data."); + } else { + this.data = readData; + } } } in.close(); } - + return data; } From 47046806afaf657180e4ca7bd34a786b2ed746fe Mon Sep 17 00:00:00 2001 From: Veselin Nikolov Date: Tue, 15 May 2012 14:47:29 +0300 Subject: [PATCH 05/57] Adds support for ePubs with large resources that don't fit into memory. Conflicts: epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java --- .../nl/siegmann/epublib/domain/Resource.java | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 9b334694..867be905 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -1,18 +1,24 @@ package nl.siegmann.epublib.domain; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.Serializable; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; import nl.siegmann.epublib.util.IOUtil; import nl.siegmann.epublib.util.StringUtil; import nl.siegmann.epublib.util.commons.io.XmlStreamReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Represents a resource that is part of the epub. * A resource can be a html file, image, xml, etc. @@ -185,7 +191,20 @@ public Resource(String id, byte[] data, String href, MediaType mediaType, String * @throws IOException */ public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(getData()); + if (isInitialized()) { + return new ByteArrayInputStream(getData()); + } else { + return getResourceStream(); + } + } + + /** + * Initializes the resource by loading its data into memory. + * + * @throws IOException + */ + public void initialize() throws IOException { + getData(); } /** @@ -203,22 +222,12 @@ public byte[] getData() throws IOException { LOG.info("Initializing lazy resource " + fileName + "#" + this.href ); - ZipInputStream in = new ZipInputStream(new FileInputStream(this.fileName)); - - for(ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { - if(zipEntry.isDirectory()) { - continue; - } - - if ( zipEntry.getName().endsWith(this.href)) { - byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); - - if ( readData == null ) { - throw new IOException("Could not lazy-load data."); - } else { - this.data = readData; - } - } + ZipInputStream in = getResourceStream(); + byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); + if ( readData == null ) { + throw new IOException("Could not lazy-load data."); + } else { + this.data = readData; } in.close(); @@ -226,6 +235,22 @@ public byte[] getData() throws IOException { return data; } + + private ZipInputStream getResourceStream() throws FileNotFoundException, + IOException { + ZipInputStream in = new ZipInputStream(new FileInputStream(this.fileName)); + for(ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { + if(zipEntry.isDirectory()) { + continue; + } + + if ( zipEntry.getName().endsWith(this.href)) { + return in; + } + } + + throw new IllegalStateException("Cannot find resources href in the epub file"); + } /** * Tells this resource to release its cached data. From f681c401d39249160ec4ad452ca68ea5d83abd17 Mon Sep 17 00:00:00 2001 From: ttopalov Date: Thu, 14 Mar 2013 14:49:45 +0200 Subject: [PATCH 06/57] Otpimized the way the zip entry of a lazy loaded Resource is found in the epub file. Conflicts: epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java --- .../nl/siegmann/epublib/domain/Resource.java | 26 ++++++------- .../epublib/domain/ResourceInputStream.java | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 867be905..b1baa323 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -1,14 +1,13 @@ package nl.siegmann.epublib.domain; import java.io.ByteArrayInputStream; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.Serializable; import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.zip.ZipFile; import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; @@ -35,6 +34,7 @@ public class Resource implements Serializable { private String id; private String title; private String href; + private String originalHref; private MediaType mediaType; private String inputEncoding = Constants.CHARACTER_ENCODING; private byte[] data; @@ -178,6 +178,7 @@ public Resource(String id, byte[] data, String href, MediaType mediaType) { public Resource(String id, byte[] data, String href, MediaType mediaType, String inputEncoding) { this.id = id; this.href = href; + this.originalHref = href; this.mediaType = mediaType; this.inputEncoding = inputEncoding; this.data = data; @@ -222,7 +223,7 @@ public byte[] getData() throws IOException { LOG.info("Initializing lazy resource " + fileName + "#" + this.href ); - ZipInputStream in = getResourceStream(); + InputStream in = getResourceStream(); byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); if ( readData == null ) { throw new IOException("Could not lazy-load data."); @@ -236,20 +237,15 @@ public byte[] getData() throws IOException { return data; } - private ZipInputStream getResourceStream() throws FileNotFoundException, + private InputStream getResourceStream() throws FileNotFoundException, IOException { - ZipInputStream in = new ZipInputStream(new FileInputStream(this.fileName)); - for(ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { - if(zipEntry.isDirectory()) { - continue; - } - - if ( zipEntry.getName().endsWith(this.href)) { - return in; - } + ZipFile zipResource = new ZipFile(fileName); + ZipEntry zipEntry = zipResource.getEntry(originalHref); + if (zipEntry == null) { + zipResource.close(); + throw new IllegalStateException("Cannot find resources href in the epub file"); } - - throw new IllegalStateException("Cannot find resources href in the epub file"); + return new ResourceInputStream(zipResource.getInputStream(zipEntry), zipResource); } /** diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java new file mode 100644 index 00000000..1a636f81 --- /dev/null +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java @@ -0,0 +1,37 @@ +package nl.siegmann.epublib.domain; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipFile; + + +/** + * A wrapper class for closing a ZipFile object when the InputStream derived + * from it is closed. + * + * @author ttopalov + * + */ +public class ResourceInputStream extends FilterInputStream { + + private ZipFile zipResource; + + /** + * Constructor. + * + * @param in + * The InputStream object. + * @param f + * The ZipFile object. + */ + public ResourceInputStream(InputStream in, ZipFile f) { + super(in); + zipResource = f; + } + + @Override + public void close() throws IOException { + super.close(); + zipResource.close(); + } +} From 365554a632fa97d02358f8121502ad59fdf7ecae Mon Sep 17 00:00:00 2001 From: Mayleen Lacouture Date: Thu, 4 Apr 2013 16:06:45 +0200 Subject: [PATCH 07/57] Added support for reading epub files directly from a java.zip.ZipFile instead of a ZipInputStream: the ZipInputStream approach stops reading from the input when a null entry is found, which in some cases leads to a toc-not-found-error. Conflicts: epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java --- .../nl/siegmann/epublib/epub/EpubReader.java | 53 +++++++++++++++---- .../siegmann/epublib/util/ResourceUtil.java | 12 ++--- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java index 6cf03616..18003810 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java @@ -4,8 +4,10 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import nl.siegmann.epublib.Constants; @@ -40,7 +42,11 @@ public Book readEpub(InputStream in) throws IOException { public Book readEpub(ZipInputStream in) throws IOException { return readEpub(in, Constants.CHARACTER_ENCODING); } - + + public Book readEpub(ZipFile zipfile) throws IOException { + return readEpub(zipfile, Constants.CHARACTER_ENCODING); + } + /** * Read epub from inputstream * @@ -90,18 +96,25 @@ public Book readEpubLazy( String fileName, String encoding ) throws IOException } public Book readEpub(ZipInputStream in, String encoding) throws IOException { - Book result = new Book(); - Resources resources = readResources(in, encoding); - handleMimeType(result, resources); - String packageResourceHref = getPackageResourceHref(resources); - Resource packageResource = processPackageResource(packageResourceHref, result, resources); - result.setOpfResource(packageResource); - Resource ncxResource = processNcxResource(packageResource, result); - result.setNcxResource(ncxResource); - result = postProcessBook(result); - return result; + return readEpubResources(readResources(in, encoding)); } + public Book readEpub(ZipFile in, String encoding) throws IOException { + return readEpubResources(readResources(in, encoding)); + } + + public Book readEpubResources(Resources resources) throws IOException{ + Book result = new Book(); + handleMimeType(result, resources); + String packageResourceHref = getPackageResourceHref(resources); + Resource packageResource = processPackageResource(packageResourceHref, result, resources); + result.setOpfResource(packageResource); + Resource ncxResource = processNcxResource(packageResource, result); + result.setNcxResource(ncxResource); + result = postProcessBook(result); + return result; + } + private Book postProcessBook(Book book) { if (bookProcessor != null) { book = bookProcessor.processBook(book); @@ -193,4 +206,22 @@ private Resources readResources(ZipInputStream in, String defaultHtmlEncoding) t } return result; } + + private Resources readResources(ZipFile zipFile, String defaultHtmlEncoding) throws IOException { + Resources result = new Resources(); + Enumeration entries = zipFile.entries(); + + while(entries.hasMoreElements()){ + ZipEntry zipEntry = entries.nextElement(); + if(zipEntry != null && !zipEntry.isDirectory()){ + Resource resource = ResourceUtil.createResource(zipEntry, zipFile.getInputStream(zipEntry)); + if(resource.getMediaType() == MediatypeService.XHTML) { + resource.setInputEncoding(defaultHtmlEncoding); + } + result.add(resource); + } + } + + return result; + } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java index 36ea1a04..1db60226 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java @@ -1,10 +1,6 @@ package nl.siegmann.epublib.util; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Reader; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -64,7 +60,11 @@ public static Resource createResource(ZipEntry zipEntry, ZipInputStream zipInput return new Resource(zipInputStream, zipEntry.getName()); } - + + public static Resource createResource(ZipEntry zipEntry, InputStream zipInputStream) throws IOException { + return new Resource(zipInputStream, zipEntry.getName()); + + } /** * Converts a given string from given input character encoding to the requested output character encoding. From ae41c312e43050f91d3f3b2fae7c11cba9825287 Mon Sep 17 00:00:00 2001 From: Mayleen Lacouture Date: Thu, 11 Jul 2013 16:24:32 +0200 Subject: [PATCH 08/57] Add sbt build file to project --- epublib-core/build.sbt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 epublib-core/build.sbt diff --git a/epublib-core/build.sbt b/epublib-core/build.sbt new file mode 100644 index 00000000..07c74f76 --- /dev/null +++ b/epublib-core/build.sbt @@ -0,0 +1,14 @@ +scalaVersion := "2.10.1" + +libraryDependencies += "net.sf.kxml" % "kxml2" % "2.3.0" + +libraryDependencies += "xmlpull" % "xmlpull" % "1.1.3.4d_b4_min" + +libraryDependencies += "org.slf4j" % "slf4j-api" % "1.6.1" + +libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.6.1" + +libraryDependencies += "junit" % "junit" % "4.10" + +libraryDependencies += "commons-io" % "commons-io" % "2.1" + From 0d25a18153c4751a6a7e51055c740287d9c1186e Mon Sep 17 00:00:00 2001 From: Mayleen Lacouture Date: Tue, 16 Jul 2013 16:22:44 +0200 Subject: [PATCH 09/57] Complete sbt project file --- epublib-core/build.sbt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/epublib-core/build.sbt b/epublib-core/build.sbt index 07c74f76..c40a3905 100644 --- a/epublib-core/build.sbt +++ b/epublib-core/build.sbt @@ -1,5 +1,13 @@ scalaVersion := "2.10.1" +name := "epublib-core" + +organization := "nl.siegmann.epublib" + +version := "3.1" + +publishMavenStyle := true + libraryDependencies += "net.sf.kxml" % "kxml2" % "2.3.0" libraryDependencies += "xmlpull" % "xmlpull" % "1.1.3.4d_b4_min" @@ -12,3 +20,4 @@ libraryDependencies += "junit" % "junit" % "4.10" libraryDependencies += "commons-io" % "commons-io" % "2.1" + From e56a8fb808adae8148106229dd8fb2ed8d728f9c Mon Sep 17 00:00:00 2001 From: Dave Jarvis Date: Sat, 7 Sep 2013 00:51:47 -0700 Subject: [PATCH 10/57] Metadata no longer has coverImage. Note that the Metadata class still contains an unused reference to coverImage. --- README.markdown | 111 ++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/README.markdown b/README.markdown index 4c226235..bcac2189 100644 --- a/README.markdown +++ b/README.markdown @@ -22,54 +22,73 @@ Set the cover image of an existing epub package nl.siegmann.epublib.examples; + import java.io.InputStream; import java.io.FileOutputStream; - + import nl.siegmann.epublib.domain.Author; import nl.siegmann.epublib.domain.Book; - import nl.siegmann.epublib.domain.InputStreamResource; - import nl.siegmann.epublib.domain.Section; + import nl.siegmann.epublib.domain.Metadata; + import nl.siegmann.epublib.domain.Resource; + import nl.siegmann.epublib.domain.TOCReference; + import nl.siegmann.epublib.epub.EpubWriter; - - public class Simple1 { - public static void main(String[] args) { - try { - // Create new Book - Book book = new Book(); - - // Set the title - book.getMetadata().addTitle("Epublib test book 1"); - - // Add an Author - book.getMetadata().addAuthor(new Author("Joe", "Tester")); - - // Set cover image - book.getMetadata().setCoverImage(new InputStreamResource(Simple1.class.getResourceAsStream("/book1/test_cover.png"), "cover.png")); - - // Add Chapter 1 - book.addResourceAsSection("Introduction", new InputStreamResource(Simple1.class.getResourceAsStream("/book1/chapter1.html"), "chapter1.html")); - - // Add css file - book.getResources().add(new InputStreamResource(Simple1.class.getResourceAsStream("/book1/book1.css"), "book1.css")); - - // Add Chapter 2 - Section chapter2 = book.addResourceAsSection("Second Chapter", new InputStreamResource(Simple1.class.getResourceAsStream("/book1/chapter2.html"), "chapter2.html")); - - // Add image used by Chapter 2 - book.getResources().add(new InputStreamResource(Simple1.class.getResourceAsStream("/book1/flowers_320x240.jpg"), "flowers.jpg")); - - // Add Chapter2, Section 1 - book.addResourceAsSubSection(chapter2, "Chapter 2, section 1", new InputStreamResource(Simple1.class.getResourceAsStream("/book1/chapter2_1.html"), "chapter2_1.html")); - - // Add Chapter 3 - book.addResourceAsSection("Conclusion", new InputStreamResource(Simple1.class.getResourceAsStream("/book1/chapter3.html"), "chapter3.html")); - - // Create EpubWriter - EpubWriter epubWriter = new EpubWriter(); - - // Write the Book as Epub - epubWriter.write(book, new FileOutputStream("test1_book1.epub")); - } catch(Exception e) { - e.printStackTrace(); - } - } + + public class Translator { + private static InputStream getResource( String path ) { + return Translator.class.getResourceAsStream( path ); + } + + private static Resource getResource( String path, String href ) { + return new Resource( getResource( path ), href ); + } + + public static void main(String[] args) { + try { + // Create new Book + Book book = new Book(); + Metadata metadata = book.getMetadata(); + + // Set the title + metadata.addTitle("Epublib test book 1"); + + // Add an Author + metadata.addAuthor(new Author("Joe", "Tester")); + + // Set cover image + book.setCoverImage( + getResource("/book1/test_cover.png", "cover.png") ); + + // Add Chapter 1 + book.addSection("Introduction", + getResource("/book1/chapter1.html", "chapter1.html") ); + + // Add css file + book.getResources().add( + getResource("/book1/book1.css", "book1.css") ); + + // Add Chapter 2 + TOCReference chapter2 = book.addSection( "Second Chapter", + getResource("/book1/chapter2.html", "chapter2.html") ); + + // Add image used by Chapter 2 + book.getResources().add( + getResource("/book1/flowers_320x240.jpg", "flowers.jpg")); + + // Add Chapter2, Section 1 + book.addSection(chapter2, "Chapter 2, section 1", + getResource("/book1/chapter2_1.html", "chapter2_1.html")); + + // Add Chapter 3 + book.addSection("Conclusion", + getResource("/book1/chapter3.html", "chapter3.html")); + + // Create EpubWriter + EpubWriter epubWriter = new EpubWriter(); + + // Write the Book as Epub + epubWriter.write(book, new FileOutputStream("test1_book1.epub")); + } catch (Exception e) { + e.printStackTrace(); + } + } } From 202e9c4177dd010971f6a69189856cdc59f936d0 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 20:27:54 +0200 Subject: [PATCH 11/57] remove unused class --- .../src/main/java/nl/siegmann/epublib/util/DomUtil.java | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 epublib-core/src/main/java/nl/siegmann/epublib/util/DomUtil.java diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/DomUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/DomUtil.java deleted file mode 100644 index 4ca8dc37..00000000 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/DomUtil.java +++ /dev/null @@ -1,9 +0,0 @@ -package nl.siegmann.epublib.util; - -import org.w3c.dom.Node; - -public class DomUtil { - - public static void getFirstChildWithTagname(String tagname, Node parentNode) { - } -} From 69ff55d2ba62b4ac347ae0c4b035192fba736dd4 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 20:29:04 +0200 Subject: [PATCH 12/57] refactoring and more testing --- .../nl/siegmann/epublib/domain/Metadata.java | 12 +++- .../epub/PackageDocumentMetadataReader.java | 24 +++++++- .../epublib/epub/PackageDocumentReader.java | 2 +- .../PackageDocumentMetadataReaderTest.java | 55 +++++++++++++++++-- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java index 680ca886..1be02862 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java @@ -41,8 +41,8 @@ public class Metadata implements Serializable { private List types = new ArrayList(); private List descriptions = new ArrayList(); private List publishers = new ArrayList(); - private Resource coverImage; - + private Map metaAttributes = new HashMap(); + public Metadata() { identifiers.add(new Identifier()); autoGeneratedId = true; @@ -207,4 +207,12 @@ public List getTypes() { public void setTypes(List types) { this.types = types; } + + public String getMetaAttribute(String name) { + return metaAttributes.get(name); + } + + public void setMetaAttributes(Map metaAttributes) { + this.metaAttributes = metaAttributes; + } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java index 33dd8d7f..707de36c 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java @@ -11,7 +11,6 @@ import nl.siegmann.epublib.domain.Date; import nl.siegmann.epublib.domain.Identifier; import nl.siegmann.epublib.domain.Metadata; -import nl.siegmann.epublib.domain.Resources; import nl.siegmann.epublib.util.StringUtil; import org.slf4j.Logger; @@ -34,7 +33,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase { private static final Logger log = LoggerFactory.getLogger(PackageDocumentMetadataReader.class); - public static Metadata readMetadata(Document packageDocument, Resources resources) { + public static Metadata readMetadata(Document packageDocument) { Metadata result = new Metadata(); Element metadataElement = DOMUtil.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), NAMESPACE_OPF, OPFTags.metadata); if(metadataElement == null) { @@ -52,7 +51,7 @@ public static Metadata readMetadata(Document packageDocument, Resources resource result.setContributors(readContributors(metadataElement)); result.setDates(readDates(metadataElement)); result.setOtherProperties(readOtherProperties(metadataElement)); - + result.setMetaAttributes(readMetaProperties(metadataElement)); Element languageTag = DOMUtil.getFirstElementByTagNameNS(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.language); if (languageTag != null) { result.setLanguage(DOMUtil.getTextChildrenContent(languageTag)); @@ -85,6 +84,25 @@ private static Map readOtherProperties(Element metadataElement) { return result; } + /** + * consumes meta tags that have a property attribute as defined in the standard. For example: + * <meta property="rendition:layout">pre-paginated</meta> + * @param metadataElement + * @return + */ + private static Map readMetaProperties(Element metadataElement) { + Map result = new HashMap(); + + NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta); + for (int i = 0; i < metaTags.getLength(); i++) { + Element metaElement = (Element) metaTags.item(i); + String name = metaElement.getAttribute(OPFAttributes.name); + String value = DOMUtil.getTextChildrenContent(metaElement); + result.put(name, value); + } + + return result; + } private static String getBookIdId(Document document) { Element packageElement = DOMUtil.getFirstElementByTagNameNS(document.getDocumentElement(), NAMESPACE_OPF, OPFTags.packageTag); diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java index 9c3d64d3..a57ddb09 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java @@ -58,7 +58,7 @@ public static void read(Resource packageResource, EpubReader epubReader, Book bo resources = readManifest(packageDocument, packageHref, epubReader, resources, idMapping); book.setResources(resources); readCover(packageDocument, book); - book.setMetadata(PackageDocumentMetadataReader.readMetadata(packageDocument, book.getResources())); + book.setMetadata(PackageDocumentMetadataReader.readMetadata(packageDocument)); book.setSpine(readSpine(packageDocument, epubReader, book.getResources(), idMapping)); // if we did not find a cover page then we make the first page of the book the cover page diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java index a2e7d872..b0851f28 100644 --- a/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java +++ b/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java @@ -1,18 +1,23 @@ package nl.siegmann.epublib.epub; +import java.io.IOException; +import java.io.StringReader; + import junit.framework.TestCase; +import nl.siegmann.epublib.domain.Identifier; import nl.siegmann.epublib.domain.Metadata; -import nl.siegmann.epublib.domain.Resources; +import org.junit.Assert; import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; public class PackageDocumentMetadataReaderTest extends TestCase { public void test1() { try { Document document = EpubProcessorSupport.createDocumentBuilder().parse(PackageDocumentMetadataReader.class.getResourceAsStream("/opf/test2.opf")); - Resources resources = new Resources(); - Metadata metadata = PackageDocumentMetadataReader.readMetadata(document, resources); + Metadata metadata = PackageDocumentMetadataReader.readMetadata(document); assertEquals(1, metadata.getAuthors().size()); } catch (Exception e) { e.printStackTrace(); @@ -33,9 +38,8 @@ public void testDefaultsToEnglish() { private Metadata getMetadata(String file) { try { Document document = EpubProcessorSupport.createDocumentBuilder().parse(PackageDocumentMetadataReader.class.getResourceAsStream(file)); - Resources resources = new Resources(); - return PackageDocumentMetadataReader.readMetadata(document, resources); + return PackageDocumentMetadataReader.readMetadata(document); } catch (Exception e) { e.printStackTrace(); assertTrue(false); @@ -43,4 +47,45 @@ private Metadata getMetadata(String file) { return null; } } + + public void test2() throws SAXException, IOException { + // given + String input = "" + + "" + + "Three Men in a Boat" + + "Jerome K. Jerome" + + "A. Frederics" + + "en" + + "1889" + + "2009-05-17" + + "zelda@mobileread.com:2010040720" + + "zelda pinwheel" + + "zelda pinwheel" + + "Public Domain" + + "Text" + + "Image" + + "Travel" + + "Humour" + + "Three Men in a Boat (To Say Nothing of the Dog), published in 1889, is a humorous account by Jerome K. Jerome of a boating holiday on the Thames between Kingston and Oxford. The book was initially intended to be a serious travel guide, with accounts of local history along the route, but the humorous elements took over to the point where the serious and somewhat sentimental passages seem a distraction to the comic novel. One of the most praised things about Three Men in a Boat is how undated it appears to modern readers, the jokes seem fresh and witty even today." + + "" + + "" + + "" + + ""; + + // when + Document metadataDocument = EpubProcessorSupport.createDocumentBuilder().parse(new InputSource(new StringReader(input))); + Metadata metadata = PackageDocumentMetadataReader.readMetadata(metadataDocument); + + // then + Assert.assertEquals("Three Men in a Boat", metadata.getFirstTitle()); + + // test identifier + Assert.assertNotNull(metadata.getIdentifiers()); + Assert.assertEquals(1, metadata.getIdentifiers().size()); + Identifier identifier = metadata.getIdentifiers().get(0); + Assert.assertEquals("URI", identifier.getScheme()); + Assert.assertEquals("zelda@mobileread.com:2010040720", identifier.getValue()); + + Assert.assertEquals("8", metadata.getMetaAttribute("calibre:rating")); + } } From e5ccfcbbccc2167cb9dcf8a81f4805ff23375c92 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 20:29:35 +0200 Subject: [PATCH 13/57] attempt at making the github repo work again --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 886d3b2e..6e2000d5 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ github.repo - file:///D:/private/project/git-maven-repo/mvn-repo + file:///D:/private/project/git-maven-repo/mvn-repo/releases From b938462c0049d8ba83913a403fef60b6bc2cdc30 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 20:58:40 +0200 Subject: [PATCH 14/57] fix getting of meta attribute values --- .../nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java index 707de36c..5e6a3d6c 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReader.java @@ -97,7 +97,7 @@ private static Map readMetaProperties(Element metadataElement) { for (int i = 0; i < metaTags.getLength(); i++) { Element metaElement = (Element) metaTags.item(i); String name = metaElement.getAttribute(OPFAttributes.name); - String value = DOMUtil.getTextChildrenContent(metaElement); + String value = metaElement.getAttribute(OPFAttributes.content); result.put(name, value); } From 1d5f89a013b26b730422780927fe62fa724a4ca2 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 21:02:32 +0200 Subject: [PATCH 15/57] add another test --- .../siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java index b0851f28..c4623206 100644 --- a/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java +++ b/epublib-core/src/test/java/nl/siegmann/epublib/epub/PackageDocumentMetadataReaderTest.java @@ -87,5 +87,6 @@ public void test2() throws SAXException, IOException { Assert.assertEquals("zelda@mobileread.com:2010040720", identifier.getValue()); Assert.assertEquals("8", metadata.getMetaAttribute("calibre:rating")); + Assert.assertEquals("cover_pic", metadata.getMetaAttribute("cover")); } } From ecc3b528b90b77d761aa7b08bd9eb361e6897479 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 21:45:38 +0200 Subject: [PATCH 16/57] Make images work in the viewer on Windows --- .../java/nl/siegmann/epublib/viewer/ImageLoaderCache.java | 3 +++ .../src/main/java/nl/siegmann/epublib/viewer/Viewer.java | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ImageLoaderCache.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ImageLoaderCache.java index dc921466..2ca5a250 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ImageLoaderCache.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ImageLoaderCache.java @@ -85,6 +85,9 @@ private String getResourceHref(String requestUrl) { String resourceHref = requestUrl.toString().substring(IMAGE_URL_PREFIX.length()); resourceHref = currentFolder + resourceHref; resourceHref = FilenameUtils.normalize(resourceHref); + // normalize uses the SYSTEM_SEPARATOR, which on windows is a '\' + // replace with '/' to make it href '/' + resourceHref = resourceHref.replaceAll("\\\\", "/"); return resourceHref; } diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/Viewer.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/Viewer.java index 6506e933..eea823f8 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/Viewer.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/Viewer.java @@ -40,8 +40,6 @@ public class Viewer { - private static final long serialVersionUID = 1610691708767665447L; - static final Logger log = LoggerFactory.getLogger(Viewer.class); private final JFrame mainWindow; private BrowseBar browseBar; @@ -49,7 +47,7 @@ public class Viewer { private JSplitPane leftSplitPane; private JSplitPane rightSplitPane; private Navigator navigator = new Navigator(); - NavigationHistory browserHistory; + private NavigationHistory browserHistory; private BookProcessorPipeline epubCleaner = new BookProcessorPipeline(Collections.emptyList()); public Viewer(InputStream bookStream) { From 24736e45c9ef8704e31ea8cc43faa3e10a3347d3 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 22:41:11 +0200 Subject: [PATCH 17/57] removed commons-io dependency from epublib-core --- epublib-core/build.sbt | 2 - epublib-core/pom.xml | 6 - .../nl/siegmann/epublib/domain/Resource.java | 5 +- .../nl/siegmann/epublib/epub/NCXDocument.java | 3 +- .../nl/siegmann/epublib/util/StringUtil.java | 82 +++++-- .../epublib/epub/NCXDocumentTest.java | 24 +- .../siegmann/epublib/util/StringUtilTest.java | 223 ++++++++---------- 7 files changed, 177 insertions(+), 168 deletions(-) diff --git a/epublib-core/build.sbt b/epublib-core/build.sbt index c40a3905..4ae23e47 100644 --- a/epublib-core/build.sbt +++ b/epublib-core/build.sbt @@ -18,6 +18,4 @@ libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.6.1" libraryDependencies += "junit" % "junit" % "4.10" -libraryDependencies += "commons-io" % "commons-io" % "2.1" - diff --git a/epublib-core/pom.xml b/epublib-core/pom.xml index ec676b6a..c8d0f0a8 100644 --- a/epublib-core/pom.xml +++ b/epublib-core/pom.xml @@ -46,12 +46,6 @@ 4.10 test - - commons-io - commons-io - 2.1 - jar - diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 3f367c61..1f563ab1 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -9,7 +9,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.apache.commons.io.IOUtils; import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; @@ -114,7 +113,7 @@ public Resource(Reader in, String href) throws IOException { * @param href The location of the resource within the epub. Example: "cover.jpg". */ public Resource(InputStream in, String href) throws IOException { - this(null, IOUtils.toByteArray(in), href, MediatypeService.determineMediaType(href)); + this(null, IOUtil.toByteArray(in), href, MediatypeService.determineMediaType(href)); } /** @@ -233,7 +232,7 @@ public byte[] getData() throws IOException { throw new IOException("Could not lazy-load data."); } else { this.data = readData; - this.data = IOUtils.toByteArray(in); + this.data = IOUtil.toByteArray(in); } in.close(); diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java index af089132..b7e6c579 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java @@ -21,7 +21,6 @@ import nl.siegmann.epublib.service.MediatypeService; import nl.siegmann.epublib.util.ResourceUtil; import nl.siegmann.epublib.util.StringUtil; -import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -119,7 +118,7 @@ private static List readTOCReferences(NodeList navpoints, Book boo private static TOCReference readTOCReference(Element navpointElement, Book book) { String label = readNavLabel(navpointElement); - String reference = FilenameUtils.getPath(book.getSpine().getTocResource().getHref())+readNavReference(navpointElement); + String reference = StringUtil.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/') + '/' + readNavReference(navpointElement); String href = StringUtil.substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR); String fragmentId = StringUtil.substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR); Resource resource = book.getResources().getByHref(href); diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java index eadae95a..3735c652 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java @@ -7,24 +7,62 @@ /** * Various String utility functions. * - * Most of the functions herein are re-implementations of the ones in apache commons StringUtils. - * The reason for re-implementing this is that the functions are fairly simple and using my own implementation saves the inclusion of a 200Kb jar file. + * Most of the functions herein are re-implementations of the ones in apache + * commons StringUtils. The reason for re-implementing this is that the + * functions are fairly simple and using my own implementation saves the + * inclusion of a 200Kb jar file. * * @author paul.siegmann - * + * */ public class StringUtil { /** - * Whether the String is not null, not zero-length and does not contain of only whitespace. + * Changes a path containing '..', '.' and empty dirs into a path that + * doesn't. X/foo/../Y is changed into 'X/Y', etc. Does not handle invalid + * paths like "../". + * + * @param path + * @return + */ + public static String collapsePathDots(String path) { + String[] stringParts = path.split("/"); + List parts = new ArrayList(Arrays.asList(stringParts)); + for (int i = 0; i < parts.size() - 1; i++) { + String currentDir = parts.get(i); + if (currentDir.length() == 0 || currentDir.equals(".")) { + parts.remove(i); + i--; + } else if (currentDir.equals("..")) { + parts.remove(i - 1); + parts.remove(i - 1); + i -= 2; + } + } + StringBuilder result = new StringBuilder(); + if (path.startsWith("/")) { + result.append('/'); + } + for (int i = 0; i < parts.size(); i++) { + result.append(parts.get(i)); + if (i < (parts.size() - 1)) { + result.append('/'); + } + } + return result.toString(); + } + + /** + * Whether the String is not null, not zero-length and does not contain of + * only whitespace. * * @param text * @return */ public static boolean isNotBlank(String text) { - return ! isBlank(text); + return !isBlank(text); } - + /** * Whether the String is null, zero-length and does contain only whitespace. */ @@ -33,13 +71,13 @@ public static boolean isBlank(String text) { return true; } for (int i = 0; i < text.length(); i++) { - if (! Character.isWhitespace(text.charAt(i))) { + if (!Character.isWhitespace(text.charAt(i))) { return false; } } return true; } - + /** * Whether the given string is null or zero-length. * @@ -49,9 +87,10 @@ public static boolean isBlank(String text) { public static boolean isEmpty(String text) { return (text == null) || (text.length() == 0); } - + /** - * Whether the given source string ends with the given suffix, ignoring case. + * Whether the given source string ends with the given suffix, ignoring + * case. * * @param source * @param suffix @@ -67,10 +106,11 @@ public static boolean endsWithIgnoreCase(String source, String suffix) { if (suffix.length() > source.length()) { return false; } - return source.substring(source.length() - suffix.length()).toLowerCase().endsWith(suffix.toLowerCase()); + return source.substring(source.length() - suffix.length()) + .toLowerCase().endsWith(suffix.toLowerCase()); } - - /** + + /** * If the given text is null return "", the original text otherwise. * * @param text @@ -114,7 +154,7 @@ public static boolean equals(String text1, String text2) { * @param keyValues * @return */ - public static String toString(Object ... keyValues) { + public static String toString(Object... keyValues) { StringBuilder result = new StringBuilder(); result.append('['); for (int i = 0; i < keyValues.length; i += 2) { @@ -139,7 +179,7 @@ public static String toString(Object ... keyValues) { return result.toString(); } - public static int hashCode(String ... values) { + public static int hashCode(String... values) { int result = 31; for (int i = 0; i < values.length; i++) { result ^= String.valueOf(values[i]).hashCode(); @@ -150,7 +190,8 @@ public static int hashCode(String ... values) { /** * Gives the substring of the given text before the given separator. * - * If the text does not contain the given separator then the given text is returned. + * If the text does not contain the given separator then the given text is + * returned. * * @param text * @param separator @@ -168,9 +209,11 @@ public static String substringBefore(String text, char separator) { } /** - * Gives the substring of the given text before the last occurrence of the given separator. + * Gives the substring of the given text before the last occurrence of the + * given separator. * - * If the text does not contain the given separator then the given text is returned. + * If the text does not contain the given separator then the given text is + * returned. * * @param text * @param separator @@ -188,7 +231,8 @@ public static String substringBeforeLast(String text, char separator) { } /** - * Gives the substring of the given text after the last occurrence of the given separator. + * Gives the substring of the given text after the last occurrence of the + * given separator. * * If the text does not contain the given separator then "" is returned. * diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/epub/NCXDocumentTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/epub/NCXDocumentTest.java index e8905ff8..a64f9c2c 100644 --- a/epublib-core/src/test/java/nl/siegmann/epublib/epub/NCXDocumentTest.java +++ b/epublib-core/src/test/java/nl/siegmann/epublib/epub/NCXDocumentTest.java @@ -1,20 +1,21 @@ package nl.siegmann.epublib.epub; +import static org.junit.Assert.assertEquals; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; + import nl.siegmann.epublib.domain.Book; -import nl.siegmann.epublib.domain.GuideReference; import nl.siegmann.epublib.domain.Resource; -import nl.siegmann.epublib.domain.TOCReference; import nl.siegmann.epublib.service.MediatypeService; -import org.apache.commons.io.FileUtils; +import nl.siegmann.epublib.util.IOUtil; + import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.*; public class NCXDocumentTest { @@ -33,13 +34,19 @@ public static void tearDownClass() { @Before public void setUp() throws IOException { - ncxData = FileUtils.readFileToByteArray(new File("src/test/resources/toc.xml")); + ncxData = IOUtil.toByteArray(new FileInputStream(new File("src/test/resources/toc.xml"))); } @After public void tearDown() { } + private void addResource(Book book, String filename) { + Resource chapterResource = new Resource("id1", "Hello, world !".getBytes(), filename, MediatypeService.XHTML); + book.addResource(chapterResource); + book.getSpine().addResource(chapterResource); + } + /** * Test of read method, of class NCXDocument. */ @@ -49,9 +56,10 @@ public void testReadWithNonRootLevelTOC() { // If the tox.ncx file is not in the root, the hrefs it refers to need to preserve its path. Book book = new Book(); Resource ncxResource = new Resource(ncxData, "xhtml/toc.ncx"); - Resource chapterResource = new Resource("id1", "Hello, world !".getBytes(), "xhtml/chapter1.html", MediatypeService.XHTML); - book.addResource(chapterResource); - book.getSpine().addResource(chapterResource); + addResource(book, "xhtml/chapter1.html"); + addResource(book, "xhtml/chapter2.html"); + addResource(book, "xhtml/chapter2_1.html"); + addResource(book, "xhtml/chapter3.html"); book.setNcxResource(ncxResource); book.getSpine().setTocResource(ncxResource); diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/util/StringUtilTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/util/StringUtilTest.java index 062bd0f8..fb990421 100644 --- a/epublib-core/src/test/java/nl/siegmann/epublib/util/StringUtilTest.java +++ b/epublib-core/src/test/java/nl/siegmann/epublib/util/StringUtilTest.java @@ -1,65 +1,50 @@ package nl.siegmann.epublib.util; import java.io.IOException; + import junit.framework.TestCase; -import org.apache.commons.io.FilenameUtils; public class StringUtilTest extends TestCase { public void testDefaultIfNull() { - Object[] testData = new Object[] { - null, "", - "", "", - " ", " ", - "foo", "foo" - }; + Object[] testData = new Object[] { null, "", "", "", " ", " ", "foo", + "foo" }; for (int i = 0; i < testData.length; i += 2) { - String actualResult = StringUtil.defaultIfNull((String) testData[i]); + String actualResult = StringUtil + .defaultIfNull((String) testData[i]); String expectedResult = (String) testData[i + 1]; - assertEquals((i / 2) + " : " + testData[i], expectedResult, actualResult); + assertEquals((i / 2) + " : " + testData[i], expectedResult, + actualResult); } } public void testDefaultIfNull_with_default() { - Object[] testData = new Object[] { - null, null, null, - "", null, "", - null, "", "", - "foo", "", "foo", - "", "foo", "", - " ", " ", " ", - null, "foo", "foo", - }; + Object[] testData = new Object[] { null, null, null, "", null, "", + null, "", "", "foo", "", "foo", "", "foo", "", " ", " ", " ", + null, "foo", "foo", }; for (int i = 0; i < testData.length; i += 3) { - String actualResult = StringUtil.defaultIfNull((String) testData[i], (String) testData[i + 1]); + String actualResult = StringUtil.defaultIfNull( + (String) testData[i], (String) testData[i + 1]); String expectedResult = (String) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } - + public void testIsEmpty() { - Object[] testData = new Object[] { - null, true, - "", true, - " ", false, - "asdfasfd", false - }; + Object[] testData = new Object[] { null, true, "", true, " ", false, + "asdfasfd", false }; for (int i = 0; i < testData.length; i += 2) { boolean actualResult = StringUtil.isEmpty((String) testData[i]); boolean expectedResult = (Boolean) testData[i + 1]; assertEquals(expectedResult, actualResult); } } - public void testIsBlank() { - Object[] testData = new Object[] { - null, true, - "", true, - " ", true, - "\t\t \n\n", true, - "asdfasfd", false - }; + Object[] testData = new Object[] { null, true, "", true, " ", true, + "\t\t \n\n", true, "asdfasfd", false }; for (int i = 0; i < testData.length; i += 2) { boolean actualResult = StringUtil.isBlank((String) testData[i]); boolean expectedResult = (Boolean) testData[i + 1]; @@ -68,150 +53,132 @@ public void testIsBlank() { } public void testIsNotBlank() { - Object[] testData = new Object[] { - null, ! true, - "", ! true, - " ", ! true, - "\t\t \n\n", ! true, - "asdfasfd", ! false - }; + Object[] testData = new Object[] { null, !true, "", !true, " ", !true, + "\t\t \n\n", !true, "asdfasfd", !false }; for (int i = 0; i < testData.length; i += 2) { boolean actualResult = StringUtil.isNotBlank((String) testData[i]); boolean expectedResult = (Boolean) testData[i + 1]; - assertEquals((i / 2) + " : " + testData[i], expectedResult, actualResult); + assertEquals((i / 2) + " : " + testData[i], expectedResult, + actualResult); } } - public void testEquals() { - Object[] testData = new Object[] { - null, null, true, - "", "", true, - null, "", false, - "", null, false, - null, "foo", false, - "foo", null, false, - "", "foo", false, - "foo", "", false, - "foo", "bar", false, - "foo", "foo", true - }; + Object[] testData = new Object[] { null, null, true, "", "", true, + null, "", false, "", null, false, null, "foo", false, "foo", + null, false, "", "foo", false, "foo", "", false, "foo", "bar", + false, "foo", "foo", true }; for (int i = 0; i < testData.length; i += 3) { - boolean actualResult = StringUtil.equals( (String) testData[i], (String) testData[i + 1]); + boolean actualResult = StringUtil.equals((String) testData[i], + (String) testData[i + 1]); boolean expectedResult = (Boolean) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } - + public void testEndWithIgnoreCase() { - Object[] testData = new Object[] { - null, null, true, - "", "", true, - "", "foo", false, - "foo", "foo", true, - "foo.bar", "bar", true, - "foo.bar", "barX", false, - "foo.barX", "bar", false, - "foo", "bar", false, - "foo.BAR", "bar", true, - "foo.bar", "BaR", true - }; + Object[] testData = new Object[] { null, null, true, "", "", true, "", + "foo", false, "foo", "foo", true, "foo.bar", "bar", true, + "foo.bar", "barX", false, "foo.barX", "bar", false, "foo", + "bar", false, "foo.BAR", "bar", true, "foo.bar", "BaR", true }; for (int i = 0; i < testData.length; i += 3) { - boolean actualResult = StringUtil.endsWithIgnoreCase( (String) testData[i], (String) testData[i + 1]); + boolean actualResult = StringUtil.endsWithIgnoreCase( + (String) testData[i], (String) testData[i + 1]); boolean expectedResult = (Boolean) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } public void testSubstringBefore() { - Object[] testData = new Object[] { - "", ' ', "", - "", 'X', "", - "fox", 'x', "fo", - "foo.bar", 'b', "foo.", - "aXbXc", 'X', "a", - }; + Object[] testData = new Object[] { "", ' ', "", "", 'X', "", "fox", + 'x', "fo", "foo.bar", 'b', "foo.", "aXbXc", 'X', "a", }; for (int i = 0; i < testData.length; i += 3) { - String actualResult = StringUtil.substringBefore((String) testData[i], (Character) testData[i + 1]); + String actualResult = StringUtil.substringBefore( + (String) testData[i], (Character) testData[i + 1]); String expectedResult = (String) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } public void testSubstringBeforeLast() { - Object[] testData = new Object[] { - "", ' ', "", - "", 'X', "", - "fox", 'x', "fo", - "foo.bar", 'b', "foo.", - "aXbXc", 'X', "aXb", - }; + Object[] testData = new Object[] { "", ' ', "", "", 'X', "", "fox", + 'x', "fo", "foo.bar", 'b', "foo.", "aXbXc", 'X', "aXb", }; for (int i = 0; i < testData.length; i += 3) { - String actualResult = StringUtil.substringBeforeLast((String) testData[i], (Character) testData[i + 1]); + String actualResult = StringUtil.substringBeforeLast( + (String) testData[i], (Character) testData[i + 1]); String expectedResult = (String) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } public void testSubstringAfter() { - Object[] testData = new Object[] { - "", ' ', "", - "", 'X', "", - "fox", 'f', "ox", - "foo.bar", 'b', "ar", - "aXbXc", 'X', "bXc", - }; + Object[] testData = new Object[] { "", ' ', "", "", 'X', "", "fox", + 'f', "ox", "foo.bar", 'b', "ar", "aXbXc", 'X', "bXc", }; for (int i = 0; i < testData.length; i += 3) { - String actualResult = StringUtil.substringAfter((String) testData[i], (Character) testData[i + 1]); + String actualResult = StringUtil.substringAfter( + (String) testData[i], (Character) testData[i + 1]); String expectedResult = (String) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } public void testSubstringAfterLast() { - Object[] testData = new Object[] { - "", ' ', "", - "", 'X', "", - "fox", 'f', "ox", - "foo.bar", 'b', "ar", - "aXbXc", 'X', "c", - }; + Object[] testData = new Object[] { "", ' ', "", "", 'X', "", "fox", + 'f', "ox", "foo.bar", 'b', "ar", "aXbXc", 'X', "c", }; for (int i = 0; i < testData.length; i += 3) { - String actualResult = StringUtil.substringAfterLast((String) testData[i], (Character) testData[i + 1]); + String actualResult = StringUtil.substringAfterLast( + (String) testData[i], (Character) testData[i + 1]); String expectedResult = (String) testData[i + 2]; - assertEquals((i / 3) + " : " + testData[i] + ", " + testData[i + 1], expectedResult, actualResult); + assertEquals( + (i / 3) + " : " + testData[i] + ", " + testData[i + 1], + expectedResult, actualResult); } } public void testToString() { assertEquals("[name: 'paul']", StringUtil.toString("name", "paul")); - assertEquals("[name: 'paul', address: 'a street']", StringUtil.toString("name", "paul", "address", "a street")); + assertEquals("[name: 'paul', address: 'a street']", + StringUtil.toString("name", "paul", "address", "a street")); assertEquals("[name: ]", StringUtil.toString("name", null)); - assertEquals("[name: 'paul', address: ]", StringUtil.toString("name", "paul", "address")); + assertEquals("[name: 'paul', address: ]", + StringUtil.toString("name", "paul", "address")); } - + public void testHashCode() { assertEquals(2522795, StringUtil.hashCode("isbn", "1234")); assertEquals(3499691, StringUtil.hashCode("ISBN", "1234")); } - + public void testReplacementForCollapsePathDots() throws IOException { - // This used to test StringUtil.collapsePathDots(String path). - // I have left it to confirm that the Apache commons FilenameUtils.normalize - // is a suitable replacement, but works where for "/a/b/../../c", which - // the old method did not. - String[] testData = new String[] { - "/foo/bar.html", "/foo/bar.html", - "/foo/../bar.html", "/bar.html", - "/foo/moo/../../bar.html", "/bar.html", - "/foo//bar.html", "/foo/bar.html", - "/foo/./bar.html", "/foo/bar.html", - "/foo/../sub/bar.html", "/sub/bar.html" + // This used to test StringUtil.collapsePathDots(String path). + // I have left it to confirm that the Apache commons + // FilenameUtils.normalize + // is a suitable replacement, but works where for "/a/b/../../c", which + // the old method did not. + String[] testData = new String[] { // + "/foo/bar.html", "/foo/bar.html", + "/foo/../bar.html", "/bar.html", // + "/foo/moo/../../bar.html", // + "/bar.html", "/foo//bar.html", // + "/foo/bar.html", "/foo/./bar.html", // + "/foo/bar.html", // + "/a/b/../../c", "/c", // + "/foo/../sub/bar.html", "/sub/bar.html" // }; - for (int i = 0; i < testData.length; i += 2) { - String actualResult = FilenameUtils.normalize(testData[i], true); - assertEquals(testData[i + 1], actualResult); - } + for (int i = 0; i < testData.length; i += 2) { + String actualResult = StringUtil.collapsePathDots(testData[i]); + assertEquals(testData[i], testData[i + 1], actualResult); + } } } From 912be6c00e420e055aec72fd02cbb7e2ca489412 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 23:18:42 +0200 Subject: [PATCH 18/57] fix the getting of TOC references --- .../main/java/nl/siegmann/epublib/epub/NCXDocument.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java index b7e6c579..d9857b41 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java @@ -118,7 +118,13 @@ private static List readTOCReferences(NodeList navpoints, Book boo private static TOCReference readTOCReference(Element navpointElement, Book book) { String label = readNavLabel(navpointElement); - String reference = StringUtil.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/') + '/' + readNavReference(navpointElement); + String tocResourceRoot = StringUtil.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/'); + if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref().length()) { + tocResourceRoot = ""; + } else { + tocResourceRoot = tocResourceRoot + "/"; + } + String reference = tocResourceRoot + readNavReference(navpointElement); String href = StringUtil.substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR); String fragmentId = StringUtil.substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR); Resource resource = book.getResources().getByHref(href); @@ -130,7 +136,6 @@ private static TOCReference readTOCReference(Element navpointElement, Book book) result.setChildren(readTOCReferences(navpointElement.getChildNodes(), book)); return result; } - private static String readNavReference(Element navpointElement) { Element contentElement = DOMUtil.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, NCXTags.content); From d637e5e0565b146fe4fa3a6accf841ba3ef3ee77 Mon Sep 17 00:00:00 2001 From: psiegman Date: Mon, 9 Sep 2013 23:20:52 +0200 Subject: [PATCH 19/57] remove unused import --- .../src/main/java/nl/siegmann/epublib/chm/ChmParser.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java b/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java index f9ca6d65..97001a7d 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java @@ -22,8 +22,6 @@ import org.apache.commons.vfs.FileSystemException; import org.apache.commons.vfs.FileType; -import com.sun.org.apache.bcel.internal.Constants; - /** * Reads the files that are extracted from a windows help ('.chm') file and creates a epublib Book out of it. * From 88d577bb6da9b794a14a88ff415a5708507aa4ff Mon Sep 17 00:00:00 2001 From: "P. Siegmann" Date: Tue, 10 Sep 2013 20:04:40 +0200 Subject: [PATCH 20/57] maven pom.xml reorganisation/cleanup --- epublib-core/pom.xml | 2 +- pom.xml => epublib-parent/pom.xml | 5 +++-- epublib-tools/pom.xml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) rename pom.xml => epublib-parent/pom.xml (96%) diff --git a/epublib-core/pom.xml b/epublib-core/pom.xml index c8d0f0a8..afa15b0b 100644 --- a/epublib-core/pom.xml +++ b/epublib-core/pom.xml @@ -8,7 +8,6 @@ nl.siegmann.epublib epublib-core epublib-core - 3.1 A java library for reading/writing/manipulating epub files http://www.siegmann.nl/epublib 2009 @@ -17,6 +16,7 @@ nl.siegmann.epublib epublib-parent 3.1 + ../epublib-parent/pom.xml diff --git a/pom.xml b/epublib-parent/pom.xml similarity index 96% rename from pom.xml rename to epublib-parent/pom.xml index 6e2000d5..394047b6 100644 --- a/pom.xml +++ b/epublib-parent/pom.xml @@ -20,8 +20,8 @@ - epublib-core - epublib-tools + ../epublib-core + ../epublib-tools @@ -87,6 +87,7 @@ org.apache.maven.plugins maven-javadoc-plugin + 2.9.1 attach-javadocs diff --git a/epublib-tools/pom.xml b/epublib-tools/pom.xml index 6b2e5c1f..8f96ae2f 100644 --- a/epublib-tools/pom.xml +++ b/epublib-tools/pom.xml @@ -8,7 +8,6 @@ nl.siegmann.epublib epublib-tools epublib-tools - 3.1 A java library for reading/writing/manipulating epub files http://www.siegmann.nl/epublib 2009 @@ -17,6 +16,7 @@ nl.siegmann.epublib epublib-parent 3.1 + ../epublib-parent/pom.xml From ff14999c509e6e3b0289dd4c0cd6561cf8e50dee Mon Sep 17 00:00:00 2001 From: "P. Siegmann" Date: Tue, 10 Sep 2013 20:05:45 +0200 Subject: [PATCH 21/57] javadoc fixes --- .../browsersupport/NavigationEvent.java | 4 +-- .../browsersupport/NavigationHistory.java | 3 +- .../epublib/browsersupport/Navigator.java | 3 +- .../java/nl/siegmann/epublib/domain/Book.java | 27 ++++++++-------- .../nl/siegmann/epublib/domain/Guide.java | 6 ++-- .../siegmann/epublib/domain/Identifier.java | 4 +-- .../nl/siegmann/epublib/domain/Metadata.java | 4 +-- .../nl/siegmann/epublib/domain/Relator.java | 2 +- .../nl/siegmann/epublib/domain/Resource.java | 27 ++++++++-------- .../epublib/domain/ResourceReference.java | 2 +- .../nl/siegmann/epublib/domain/Resources.java | 29 ++++++++++------- .../nl/siegmann/epublib/domain/Spine.java | 17 ++++++---- .../epublib/domain/SpineReference.java | 4 +-- .../epublib/domain/TableOfContents.java | 15 ++++++--- .../domain/TitledResourceReference.java | 2 +- .../epublib/epub/EpubProcessorSupport.java | 2 +- .../nl/siegmann/epublib/epub/EpubReader.java | 6 ++-- .../nl/siegmann/epublib/epub/NCXDocument.java | 7 ++-- .../epub/PackageDocumentMetadataWriter.java | 1 - .../epublib/epub/PackageDocumentReader.java | 13 ++++---- .../epublib/service/MediatypeService.java | 2 +- .../siegmann/epublib/util/CollectionUtil.java | 4 +-- .../java/nl/siegmann/epublib/util/IOUtil.java | 12 +++---- .../siegmann/epublib/util/ResourceUtil.java | 10 +++--- .../nl/siegmann/epublib/util/StringUtil.java | 32 ++++++++++--------- .../nl/siegmann/epublib/chm/ChmParser.java | 4 +-- .../epublib/fileset/FilesetBookCreator.java | 2 +- .../siegmann/epublib/search/SearchIndex.java | 4 +-- .../nl/siegmann/epublib/util/DesktopUtil.java | 2 +- .../epublib/util/ToolsResourceUtil.java | 4 +-- .../nl/siegmann/epublib/util/VFSUtil.java | 4 +-- .../nl/siegmann/epublib/viewer/ButtonBar.java | 3 +- .../siegmann/epublib/viewer/ContentPane.java | 7 ++-- .../epublib/viewer/HTMLDocumentFactory.java | 6 ++-- .../epublib/viewer/TableOfContentsPane.java | 1 - .../siegmann/epublib/viewer/ViewerUtil.java | 2 +- 36 files changed, 142 insertions(+), 135 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationEvent.java b/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationEvent.java index 590433f1..e3a96b04 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationEvent.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationEvent.java @@ -40,7 +40,7 @@ public NavigationEvent(Object source, Navigator navigator) { /** * The previous position within the section. * - * @return + * @return The previous position within the section. */ public int getOldSectionPos() { return oldSectionPos; @@ -151,4 +151,4 @@ public String toString() { public boolean isSectionPosChanged() { return oldSectionPos != getCurrentSectionPos(); } -} \ No newline at end of file +} diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationHistory.java b/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationHistory.java index 61849900..1d7aed5c 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationHistory.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/NavigationHistory.java @@ -76,7 +76,7 @@ public void initBook(Book book) { * If the time between a navigation event is less than the historyWaitTime then the new location is not added to the history. * When a user is rapidly viewing many pages using the slider we do not want all of them to be added to the history. * - * @return + * @return the time we wait before adding the page to the history */ public long getHistoryWaitTime() { return historyWaitTime; @@ -101,7 +101,6 @@ public void addLocation(Resource resource) { * If this nr of locations becomes larger then the historySize then the first item(s) will be removed. * * @param location - * @return */ public void addLocation(Location location) { // do nothing if the new location matches the current location diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/Navigator.java b/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/Navigator.java index dfa352c5..7f1e6643 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/Navigator.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/browsersupport/Navigator.java @@ -137,7 +137,7 @@ public int gotoSpineSection(int newSpinePos, Object source) { * * @param newSpinePos * @param source - * @return + * @return The current position within the spine */ public int gotoSpineSection(int newSpinePos, int newPagePos, Object source) { if (newSpinePos == currentSpinePos) { @@ -203,7 +203,6 @@ public Book getBook() { * * If you want the eventListeners called use gotoSection(index); * - * @param currentSpinePos */ public int setCurrentResource(Resource currentResource) { this.currentSpinePos = book.getSpine().getResourceIndex(currentResource); diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Book.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Book.java index 277f144b..152d5b69 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Book.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Book.java @@ -312,7 +312,7 @@ public class Book implements Serializable { * @param parentSection * @param sectionTitle * @param resource - * @return + * @return The table of contents */ public TOCReference addSection(TOCReference parentSection, String sectionTitle, Resource resource) { @@ -337,7 +337,7 @@ public void generateSpineFromTableOfContents() { * * @param title * @param resource - * @return + * @return The table of contents */ public TOCReference addSection(String title, Resource resource) { getResources().add(resource); @@ -352,7 +352,7 @@ public TOCReference addSection(String title, Resource resource) { /** * The Book's metadata (titles, authors, etc) * - * @return + * @return The Book's metadata (titles, authors, etc) */ public Metadata getMetadata() { return metadata; @@ -374,7 +374,7 @@ public Resource addResource(Resource resource) { /** * The collection of all images, chapters, sections, xhtml files, stylesheets, etc that make up the book. * - * @return + * @return The collection of all images, chapters, sections, xhtml files, stylesheets, etc that make up the book. */ public Resources getResources() { return resources; @@ -384,7 +384,7 @@ public Resources getResources() { /** * The sections of the book that should be shown if a user reads the book from start to finish. * - * @return + * @return The Spine */ public Spine getSpine() { return spine; @@ -399,7 +399,7 @@ public void setSpine(Spine spine) { /** * The Table of Contents of the book. * - * @return + * @return The Table of Contents of the book. */ public TableOfContents getTableOfContents() { return tableOfContents; @@ -411,10 +411,10 @@ public void setTableOfContents(TableOfContents tableOfContents) { } /** - * The book's cover page. + * The book's cover page as a Resource. * An XHTML document containing a link to the cover image. * - * @return + * @return The book's cover page as a Resource */ public Resource getCoverPage() { Resource coverPage = guide.getCoverPage(); @@ -438,7 +438,7 @@ public void setCoverPage(Resource coverPage) { /** * Gets the first non-blank title from the book's metadata. * - * @return + * @return the first non-blank title from the book's metadata. */ public String getTitle() { return getMetadata().getFirstTitle(); @@ -448,7 +448,7 @@ public String getTitle() { /** * The book's cover image. * - * @return + * @return The book's cover image. */ public Resource getCoverImage() { return coverImage; @@ -467,7 +467,7 @@ public void setCoverImage(Resource coverImage) { /** * The guide; contains references to special sections of the book like colophon, glossary, etc. * - * @return + * @return The guide; contains references to special sections of the book like colophon, glossary, etc. */ public Guide getGuide() { return guide; @@ -483,9 +483,8 @@ public Guide getGuide() { *
  • The resources of the Table of Contents that are not already in the result
  • *
  • The resources of the Guide that are not already in the result
  • * - * To get all html files that make up the epub file use - * @see getResources().getAll() - * @return + * To get all html files that make up the epub file use {@link #getResources()} + * @return All Resources of the Book that can be reached via the Spine, the TableOfContents or the Guide. */ public List getContents() { Map result = new LinkedHashMap(); diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Guide.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Guide.java index 58e1e018..e18d7167 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Guide.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Guide.java @@ -83,7 +83,7 @@ private void initCoverPage() { /** * The coverpage of the book. * - * @return + * @return The coverpage of the book. */ public Resource getCoverPage() { GuideReference guideReference = getCoverReference(); @@ -109,7 +109,7 @@ public ResourceReference addReference(GuideReference reference) { * A list of all GuideReferences that have the given referenceTypeName (ignoring case). * * @param referenceTypeName - * @return + * @return A list of all GuideReferences that have the given referenceTypeName (ignoring case). */ public List getGuideReferencesByType(String referenceTypeName) { List result = new ArrayList(); @@ -120,4 +120,4 @@ public List getGuideReferencesByType(String referenceTypeName) { } return result; } -} \ No newline at end of file +} diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Identifier.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Identifier.java index 90e7944f..a6e30acd 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Identifier.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Identifier.java @@ -50,7 +50,7 @@ public Identifier(String scheme, String value) { * If no identifier has bookId == true then the first bookId identifier is written as the primary. * * @param identifiers - * @return + * @return The first identifier for which the bookId is true is made the bookId identifier. */ public static Identifier getBookIdIdentifier(List identifiers) { if(identifiers == null || identifiers.isEmpty()) { @@ -97,7 +97,7 @@ public void setBookId(boolean bookId) { * The Dublin Core metadata spec allows multiple identifiers for a Book. * The epub spec requires exactly one identifier to be marked as the book id. * - * @return + * @return whether this is the unique book id. */ public boolean isBookId() { return bookId; diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java index 1be02862..4fbbc312 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Metadata.java @@ -55,7 +55,7 @@ public boolean isAutoGeneratedId() { /** * Metadata properties not hard-coded like the author, title, etc. * - * @return + * @return Metadata properties not hard-coded like the author, title, etc. */ public Map getOtherProperties() { return otherProperties; @@ -124,7 +124,7 @@ public List getRights() { * Gets the first non-blank title of the book. * Will return "" if no title found. * - * @return + * @return the first non-blank title of the book. */ public String getFirstTitle() { if (titles == null || titles.isEmpty()) { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Relator.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Relator.java index 3dc5bc88..4ce05796 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Relator.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Relator.java @@ -8,7 +8,7 @@ * * This is contains the complete Library of Concress relator list. * - * @see http://www.loc.gov/marc/relators/relaterm.html + * @see MARC Code List for Relators * * @author paul * diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index 1f563ab1..a5f66cc6 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -75,7 +75,7 @@ public Resource(byte[] data, MediaType mediaType) { * * 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) + * @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". @@ -87,7 +87,8 @@ public Resource(byte[] data, String href) { /** * 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) + * + * @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". @@ -99,13 +100,13 @@ public Resource(Reader in, String href) throws IOException { /** * 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) + * + * @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. + * It is recommended to us the {@link #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. * * @@ -298,7 +299,7 @@ public long getSize() { /** * If the title is found by scanning the underlying html document then it is cached here. * - * @return + * @return the title */ public String getTitle() { return title; @@ -317,7 +318,7 @@ public void setId(String id) { * The resources Id. * * Must be both unique within all the resources of this book and a valid identifier. - * @return + * @return The resources Id. */ public String getId() { return id; @@ -330,7 +331,7 @@ public String getId() { * images/cover.jpg
    * content/chapter1.xhtml
    * - * @return + * @return The location of the resource within the contents folder of the epub file. */ public String getHref() { return href; @@ -349,7 +350,7 @@ public void setHref(String href) { * The character encoding of the resource. * Is allowed to be null for non-text resources like images. * - * @return + * @return The character encoding of the resource. */ public String getInputEncoding() { return inputEncoding; @@ -369,8 +370,7 @@ public void setInputEncoding(String encoding) { * * Does all sorts of smart things (courtesy of apache commons io XMLStreamREader) to handle encodings, byte order markers, etc. * - * @param resource - * @return + * @return the contents of the Resource as Reader. * @throws IOException */ public Reader getReader() throws IOException { @@ -388,6 +388,7 @@ public int hashCode() { /** * Checks to see of the given resourceObject is a resource and whether its href is equal to this one. * + * @return whether the given resourceObject is a resource and whether its href is equal to this one. */ public boolean equals(Object resourceObject) { if (! (resourceObject instanceof Resource)) { @@ -399,7 +400,7 @@ public boolean equals(Object resourceObject) { /** * This resource's mediaType. * - * @return + * @return This resource's mediaType. */ public MediaType getMediaType() { return mediaType; diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceReference.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceReference.java index 096aa466..9ba8cea0 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceReference.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceReference.java @@ -34,7 +34,7 @@ public void setResource(Resource resource) { * * null of the reference is null or has a null id itself. * - * @return + * @return The id of the reference referred to. */ public String getResourceId() { if (resource != null) { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java index 85ed1233..2067afd5 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java @@ -37,7 +37,7 @@ public class Resources implements Serializable { * Fixes the resources id and href if necessary. * * @param resource - * @return + * @return the newly added resource */ public Resource add(Resource resource) { fixResourceHref(resource); @@ -73,7 +73,7 @@ public void fixResourceId(Resource resource) { * Check if the id is a valid identifier. if not: prepend with valid identifier * * @param resource - * @return + * @return a valid id */ private String makeValidId(String resourceId, Resource resource) { if (StringUtil.isNotBlank(resourceId) && ! Character.isJavaIdentifierStart(resourceId.charAt(0))) { @@ -96,7 +96,7 @@ private String getResourceItemPrefix(Resource resource) { * Creates a new resource id that is guarenteed to be unique for this set of Resources * * @param resource - * @return + * @return a new resource id that is guarenteed to be unique for this set of Resources */ private String createUniqueResourceId(Resource resource) { int counter = lastId; @@ -120,7 +120,7 @@ private String createUniqueResourceId(Resource resource) { * Whether the map of resources already contains a resource with the given id. * * @param id - * @return + * @return Whether the map of resources already contains a resource with the given id. */ public boolean containsId(String id) { if (StringUtil.isBlank(id)) { @@ -195,7 +195,7 @@ public boolean isEmpty() { /** * The number of resources - * @return + * @return The number of resources */ public int size() { return resources.size(); @@ -205,7 +205,7 @@ public int size() { * The resources that make up this book. * Resources can be xhtml pages, images, xml documents, etc. * - * @return + * @return The resources that make up this book. */ public Map getResourceMap() { return resources; @@ -219,7 +219,7 @@ public Collection getAll() { /** * Whether there exists a resource with the given href * @param href - * @return + * @return Whether there exists a resource with the given href */ public boolean containsByHref(String href) { if (StringUtil.isBlank(href)) { @@ -265,7 +265,7 @@ public void set(Map resources) { * fails it tries to find one with the idOrHref as href. * * @param idOrHref - * @return + * @return the found Resource */ public Resource getByIdOrHref(String idOrHref) { Resource resource = getById(idOrHref); @@ -298,7 +298,7 @@ public Resource getByHref(String href) { * Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype. * * @param mediaType - * @return + * @return the first resource (random order) with the give mediatype. */ public Resource findFirstResourceByMediaType(MediaType mediaType) { return findFirstResourceByMediaType(resources.values(), mediaType); @@ -310,7 +310,7 @@ public Resource findFirstResourceByMediaType(MediaType mediaType) { * Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype. * * @param mediaType - * @return + * @return the first resource (random order) with the give mediatype. */ public static Resource findFirstResourceByMediaType(Collection resources, MediaType mediaType) { for (Resource resource: resources) { @@ -325,7 +325,7 @@ public static Resource findFirstResourceByMediaType(Collection resourc * All resources that have the given MediaType. * * @param mediaType - * @return + * @return All resources that have the given MediaType. */ public List getResourcesByMediaType(MediaType mediaType) { List result = new ArrayList(); @@ -344,7 +344,7 @@ public List getResourcesByMediaType(MediaType mediaType) { * All Resources that match any of the given list of MediaTypes * * @param mediaTypes - * @return + * @return All Resources that match any of the given list of MediaTypes */ public List getResourcesByMediaTypes(MediaType[] mediaTypes) { List result = new ArrayList(); @@ -364,6 +364,11 @@ public List getResourcesByMediaTypes(MediaType[] mediaTypes) { } + /** + * All resource hrefs + * + * @return all resource hrefs + */ public Collection getAllHrefs() { return resources.keySet(); } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Spine.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Spine.java index d0cda7ea..069ac5f7 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Spine.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Spine.java @@ -63,7 +63,7 @@ public void setSpineReferences(List spineReferences) { * Null if not found. * * @param index - * @return + * @return the resource at the given index. */ public Resource getResource(int index) { if (index < 0 || index >= spineReferences.size()) { @@ -78,7 +78,7 @@ public Resource getResource(int index) { * Null if not found. * * @param resourceId - * @return + * @return the first resource that has the given resourceId. */ public int findFirstResourceById(String resourceId) { if (StringUtil.isBlank(resourceId)) { @@ -98,7 +98,7 @@ public int findFirstResourceById(String resourceId) { * Adds the given spineReference to the spine references and returns it. * * @param spineReference - * @return + * @return the given spineReference */ public SpineReference addSpineReference(SpineReference spineReference) { if (spineReferences == null) { @@ -111,8 +111,7 @@ public SpineReference addSpineReference(SpineReference spineReference) { /** * Adds the given resource to the spine references and returns it. * - * @param spineReference - * @return + * @return the given spineReference */ public SpineReference addResource(Resource resource) { return addSpineReference(new SpineReference(resource)); @@ -121,7 +120,7 @@ public SpineReference addResource(Resource resource) { /** * The number of elements in the spine. * - * @return + * @return The number of elements in the spine. */ public int size() { return spineReferences.size(); @@ -142,7 +141,7 @@ public void setTocResource(Resource tocResource) { * The resource containing the XML for the tableOfContents. * When saving an epub file this resource needs to be in this place. * - * @return + * @return The resource containing the XML for the tableOfContents. */ public Resource getTocResource() { return tocResource; @@ -182,6 +181,10 @@ public int getResourceIndex(String resourceHref) { return result; } + /** + * Whether the spine has any references + * @return Whether the spine has any references + */ public boolean isEmpty() { return spineReferences.isEmpty(); } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/SpineReference.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/SpineReference.java index db2804b2..6b545f43 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/SpineReference.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/SpineReference.java @@ -41,9 +41,9 @@ public SpineReference(Resource resource, boolean linear) { * a popup window apart from the main window which presents the primary * content. (For an example of the types of content that may be considered * auxiliary, refer to the example below and the subsequent discussion.) - * @see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html#TOC2.4 + * @see OPF Spine specification * - * @return + * @return whether the section is Primary or Auxiliary. */ public boolean isLinear() { return linear; diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/TableOfContents.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/TableOfContents.java index 94d65d15..56cf6d0e 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/TableOfContents.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/TableOfContents.java @@ -50,6 +50,7 @@ public void setTocReferences(List tocReferences) { /** * Calls addTOCReferenceAtLocation after splitting the path using the DEFAULT_PATH_SEPARATOR. + * @return the new TOCReference */ public TOCReference addSection(Resource resource, String path) { return addSection(resource, path, DEFAULT_PATH_SEPARATOR); @@ -61,7 +62,7 @@ public TOCReference addSection(Resource resource, String path) { * @param resource * @param path * @param pathSeparator - * @return + * @return the new TOCReference */ public TOCReference addSection(Resource resource, String path, String pathSeparator) { String[] pathElements = path.split(pathSeparator); @@ -98,7 +99,7 @@ private static TOCReference findTocReferenceByTitle(String title, List getAllUniqueResources() { Set uniqueHrefs = new HashSet(); @@ -218,7 +219,7 @@ private static void getAllUniqueResources(Set uniqueHrefs, List tocReferences) { return result; } + /** + * The maximum depth of the reference tree + * @return The maximum depth of the reference tree + */ public int calculateDepth() { return calculateDepth(tocReferences, 0); } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/TitledResourceReference.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/TitledResourceReference.java index 173943e9..81cee4b3 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/TitledResourceReference.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/TitledResourceReference.java @@ -48,7 +48,7 @@ public void setTitle(String title) { /** * If the fragmentId is blank it returns the resource href, otherwise it returns the resource href + '#' + the fragmentId. * - * @return + * @return If the fragmentId is blank it returns the resource href, otherwise it returns the resource href + '#' + the fragmentId. */ public String getCompleteHref() { if (StringUtil.isBlank(fragmentId)) { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubProcessorSupport.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubProcessorSupport.java index 0a4cfc64..70faba6a 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubProcessorSupport.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubProcessorSupport.java @@ -106,7 +106,7 @@ public DocumentBuilderFactory getDocumentBuilderFactory() { /** * Creates a DocumentBuilder that looks up dtd's and schema's from epublib's classpath. * - * @return + * @return a DocumentBuilder that looks up dtd's and schema's from epublib's classpath. */ public static DocumentBuilder createDocumentBuilder() { DocumentBuilder result = null; diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java index 18003810..929bf22d 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java @@ -52,7 +52,7 @@ public Book readEpub(ZipFile zipfile) throws IOException { * * @param in the inputstream from which to read the epub * @param encoding the encoding to use for the html files within the epub - * @return + * @return the Book as read from the inputstream * @throws IOException */ public Book readEpub(InputStream in, String encoding) throws IOException { @@ -65,7 +65,7 @@ public Book readEpub(InputStream in, String encoding) throws IOException { * @param fileName the file to load * @param encoding the encoding for XHTML files * @param lazyLoadedTypes a list of the MediaType to load lazily - * @return + * @return this Book without loading all resources into memory. * @throws IOException */ public Book readEpubLazy( String fileName, String encoding, List lazyLoadedTypes ) throws IOException { @@ -88,7 +88,7 @@ public Book readEpubLazy( String fileName, String encoding, List lazy * @param fileName the file to load * @param encoding the encoding for XHTML files * - * @return + * @return this Book without loading all resources into memory. * @throws IOException */ public Book readEpubLazy( String fileName, String encoding ) throws IOException { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java index d9857b41..7e18e249 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/NCXDocument.java @@ -165,10 +165,9 @@ public static void write(EpubWriter epubWriter, Book book, ZipOutputStream resul /** * Generates a resource containing an xml document containing the table of contents of the book in ncx format. * - * @param epubWriter - * @param book - * @return - * @ + * @param xmlSerializer the serializer used + * @param book the book to serialize + * * @throws FactoryConfigurationError * @throws IOException * @throws IllegalStateException diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataWriter.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataWriter.java index 858eff99..af667816 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataWriter.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentMetadataWriter.java @@ -26,7 +26,6 @@ public class PackageDocumentMetadataWriter extends PackageDocumentBase { * @throws IOException * @throws IllegalStateException * @throws IllegalArgumentException - * @ */ public static void writeMetaData(Book book, XmlSerializer serializer) throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(NAMESPACE_OPF, OPFTags.metadata); diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java index a57ddb09..cfd15e99 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/PackageDocumentReader.java @@ -177,7 +177,7 @@ private static void readGuide(Document packageDocument, * * @param packageHref * @param resourcesByHref - * @return + * @return The stipped package href */ private static Resources fixHrefs(String packageHref, Resources resourcesByHref) { @@ -203,7 +203,7 @@ private static Resources fixHrefs(String packageHref, * @param epubReader * @param book * @param resourcesById - * @return + * @return the document's spine, containing all sections in reading order. */ private static Spine readSpine(Document packageDocument, EpubReader epubReader, Resources resources, Map idMapping) { @@ -248,7 +248,7 @@ private static Spine readSpine(Document packageDocument, EpubReader epubReader, * The generated spine consists of all XHTML pages in order of their href. * * @param resources - * @return + * @return a spine created out of all resources in the resources. */ private static Spine generateSpineFromResources(Resources resources) { Spine result = new Spine(); @@ -275,7 +275,7 @@ private static Spine generateSpineFromResources(Resources resources) { * * @param spineElement * @param resourcesById - * @return + * @return the Resource containing the table of contents */ private static Resource findTableOfContentsResource(Element spineElement, Resources resources) { String tocResourceId = DOMUtil.getAttribute(spineElement, NAMESPACE_OPF, OPFAttributes.toc); @@ -314,7 +314,7 @@ private static Resource findTableOfContentsResource(Element spineElement, Resour * Search the meta tags and the guide references * * @param packageDocument - * @return + * @return all resources that have something to do with the coverpage and the cover image. */ // package static Set findCoverHrefs(Document packageDocument) { @@ -352,7 +352,6 @@ static Set findCoverHrefs(Document packageDocument) { * @param packageDocument * @param book * @param resources - * @return */ private static void readCover(Document packageDocument, Book book) { @@ -372,4 +371,4 @@ private static void readCover(Document packageDocument, Book book) { } -} \ No newline at end of file +} diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java b/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java index 835567d9..045962bf 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/service/MediatypeService.java @@ -63,7 +63,7 @@ public static boolean isBitmapImage(MediaType mediaType) { * Null of no matching extension found. * * @param filename - * @return + * @return the MediaType based on the file extension. */ public static MediaType determineMediaType(String filename) { for(int i = 0; i < mediatypes.length; i++) { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java index 7c33206e..5b563f15 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java @@ -34,7 +34,7 @@ public T nextElement() { * Creates an Enumeration out of the given Iterator. * @param * @param it - * @return + * @return an Enumeration created out of the given Iterator. */ public static Enumeration createEnumerationFromIterator(Iterator it) { return new IteratorEnumerationAdapter(it); @@ -46,7 +46,7 @@ public static Enumeration createEnumerationFromIterator(Iterator it) { * * @param * @param list - * @return + * @return the first element of the list, null if the list is null or empty. */ public static T first(List list) { if(list == null || list.isEmpty()) { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java index 89d669af..4d2dd804 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/IOUtil.java @@ -15,7 +15,7 @@ public class IOUtil { * * @param in * @param encoding - * @return + * @return the contents of the Reader as a byte[], with the given character encoding. * @throws IOException */ public static byte[] toByteArray(Reader in, String encoding) throws IOException { @@ -29,7 +29,7 @@ public static byte[] toByteArray(Reader in, String encoding) throws IOException * Returns the contents of the InputStream as a byte[] * * @param in - * @return + * @return the contents of the InputStream as a byte[] * @throws IOException */ public static byte[] toByteArray(InputStream in) throws IOException { @@ -45,7 +45,7 @@ public static byte[] toByteArray(InputStream in) throws IOException { * This is meant for situations where memory is tight, since * it prevents buffer expansion. * - * @param stream the stream to read data from + * @param in the stream to read data from * @param size the size of the array to create * @return the array, or null * @throws IOException @@ -76,7 +76,7 @@ public static byte[] toByteArray( InputStream in, int size ) throws IOException * if totalNrRead < 0 then totalNrRead is returned, if (nrRead + totalNrRead) < Integer.MAX_VALUE then nrRead + totalNrRead is returned, -1 otherwise. * @param nrRead * @param totalNrNread - * @return + * @return if totalNrRead < 0 then totalNrRead is returned, if (nrRead + totalNrRead) < Integer.MAX_VALUE then nrRead + totalNrRead is returned, -1 otherwise. */ protected static int calcNewNrReadSize(int nrRead, int totalNrNread) { if (totalNrNread < 0) { @@ -94,7 +94,7 @@ protected static int calcNewNrReadSize(int nrRead, int totalNrNread) { * * @param in * @param out - * @return the nr of bytes read, or -1 if the amount > Integer.MAX_VALUE + * @return the nr of bytes read, or -1 if the amount > Integer.MAX_VALUE * @throws IOException */ public static int copy(InputStream in, OutputStream out) @@ -115,7 +115,7 @@ public static int copy(InputStream in, OutputStream out) * * @param in * @param out - * @return the nr of characters read, or -1 if the amount > Integer.MAX_VALUE + * @return the nr of characters read, or -1 if the amount > Integer.MAX_VALUE * @throws IOException */ public static int copy(Reader in, Writer out) throws IOException { diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java index 1db60226..a1e23f6e 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/ResourceUtil.java @@ -41,7 +41,7 @@ public static Resource createResource(File file) throws IOException { * * @param title * @param href - * @return + * @return a resource with as contents a html page with the given title. */ public static Resource createResource(String title, String href) { String content = "" + title + "

    " + title + "

    "; @@ -53,7 +53,7 @@ public static Resource createResource(String title, String href) { * * @param zipEntry * @param zipInputStream - * @return + * @return a resource created out of the given zipEntry and zipInputStream. * @throws IOException */ public static Resource createResource(ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException { @@ -72,7 +72,7 @@ public static Resource createResource(ZipEntry zipEntry, InputStream zipInputStr * @param inputEncoding * @param outputEncoding * @param input - * @return + * @return the string from given input character encoding converted to the requested output character encoding. * @throws UnsupportedEncodingException */ public static byte[] recode(String inputEncoding, String outputEncoding, byte[] input) throws UnsupportedEncodingException { @@ -108,8 +108,8 @@ public static Document getAsDocument(Resource resource) throws UnsupportedEncodi * Reads the given resources inputstream, parses the xml therein and returns the result as a Document * * @param resource - * @param documentBuilderFactory - * @return + * @param documentBuilder + * @return the document created from the given resource * @throws UnsupportedEncodingException * @throws SAXException * @throws IOException diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java index 3735c652..0f60b923 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java @@ -23,7 +23,7 @@ public class StringUtil { * paths like "../". * * @param path - * @return + * @return the normalized path */ public static String collapsePathDots(String path) { String[] stringParts = path.split("/"); @@ -57,7 +57,7 @@ public static String collapsePathDots(String path) { * only whitespace. * * @param text - * @return + * @return Whether the String is not null, not zero-length and does not contain of */ public static boolean isNotBlank(String text) { return !isBlank(text); @@ -65,6 +65,8 @@ public static boolean isNotBlank(String text) { /** * Whether the String is null, zero-length and does contain only whitespace. + * + * @return Whether the String is null, zero-length and does contain only whitespace. */ public static boolean isBlank(String text) { if (isEmpty(text)) { @@ -81,8 +83,8 @@ public static boolean isBlank(String text) { /** * Whether the given string is null or zero-length. * - * @param text - * @return + * @param text the input for this method + * @return Whether the given string is null or zero-length. */ public static boolean isEmpty(String text) { return (text == null) || (text.length() == 0); @@ -94,7 +96,7 @@ public static boolean isEmpty(String text) { * * @param source * @param suffix - * @return + * @return Whether the given source string ends with the given suffix, ignoring case. */ public static boolean endsWithIgnoreCase(String source, String suffix) { if (isEmpty(suffix)) { @@ -114,7 +116,7 @@ public static boolean endsWithIgnoreCase(String source, String suffix) { * If the given text is null return "", the original text otherwise. * * @param text - * @return + * @return If the given text is null "", the original text otherwise. */ public static String defaultIfNull(String text) { return defaultIfNull(text, ""); @@ -125,7 +127,7 @@ public static String defaultIfNull(String text) { * * @param text * @param defaultValue - * @return + * @return If the given text is null "", the given defaultValue otherwise. */ public static String defaultIfNull(String text, String defaultValue) { if (text == null) { @@ -139,7 +141,7 @@ public static String defaultIfNull(String text, String defaultValue) { * * @param text1 * @param text2 - * @return + * @return whether the two strings are equal */ public static boolean equals(String text1, String text2) { if (text1 == null) { @@ -152,7 +154,7 @@ public static boolean equals(String text1, String text2) { * Pretty toString printer. * * @param keyValues - * @return + * @return a string representation of the input values */ public static String toString(Object... keyValues) { StringBuilder result = new StringBuilder(); @@ -195,7 +197,7 @@ public static int hashCode(String... values) { * * @param text * @param separator - * @return + * @return the substring of the given text before the given separator. */ public static String substringBefore(String text, char separator) { if (isEmpty(text)) { @@ -217,7 +219,7 @@ public static String substringBefore(String text, char separator) { * * @param text * @param separator - * @return + * @return the substring of the given text before the last occurrence of the given separator. */ public static String substringBeforeLast(String text, char separator) { if (isEmpty(text)) { @@ -238,7 +240,7 @@ public static String substringBeforeLast(String text, char separator) { * * @param text * @param separator - * @return + * @return the substring of the given text after the last occurrence of the given separator. */ public static String substringAfterLast(String text, char separator) { if (isEmpty(text)) { @@ -256,9 +258,9 @@ public static String substringAfterLast(String text, char separator) { * * If the text does not contain the given separator then "" is returned. * - * @param text - * @param separator - * @return + * @param text the input text + * @param c the separator char + * @return the substring of the given text after the given separator. */ public static String substringAfter(String text, char c) { if (isEmpty(text)) { diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java b/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java index 97001a7d..501a9f5b 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/chm/ChmParser.java @@ -59,11 +59,11 @@ public static Book parseChm(FileObject chmRootDir, String inputHtmlEncoding) /** - * Finds in the '#SYSTEM' file the 3rd set of characters that have ascii value >= 32 and <= 126 and is more than 3 characters long. + * Finds in the '#SYSTEM' file the 3rd set of characters that have ascii value >= 32 and >= 126 and is more than 3 characters long. * Assumes that that is then the title of the book. * * @param chmRootDir - * @return + * @return Finds in the '#SYSTEM' file the 3rd set of characters that have ascii value >= 32 and >= 126 and is more than 3 characters long. * @throws IOException */ protected static String findTitle(FileObject chmRootDir) throws IOException { diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/fileset/FilesetBookCreator.java b/epublib-tools/src/main/java/nl/siegmann/epublib/fileset/FilesetBookCreator.java index c38f1cdd..66f41280 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/fileset/FilesetBookCreator.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/fileset/FilesetBookCreator.java @@ -61,7 +61,7 @@ public static Book createBookFromDirectory(FileObject rootDirectory) throws IOEx * * @see nl.siegmann.epublib.domain.MediaTypeService * @param rootDirectory - * @return + * @return the newly created Book * @throws IOException */ public static Book createBookFromDirectory(FileObject rootDirectory, String encoding) throws IOException { diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/search/SearchIndex.java b/epublib-tools/src/main/java/nl/siegmann/epublib/search/SearchIndex.java index e3ce2e84..1c1c5d11 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/search/SearchIndex.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/search/SearchIndex.java @@ -142,7 +142,7 @@ public static String getSearchContent(Reader content) { * Checks whether the given character is a java whitespace or a non-breaking-space (&nbsp;). * * @param c - * @return + * @return whether the given character is a java whitespace or a non-breaking-space (&nbsp;). */ private static boolean isHtmlWhitespace(int c) { return c == NBSP || Character.isWhitespace(c); @@ -177,7 +177,7 @@ public static String unicodeTrim(String text) { * Replaces multiple whitespaces with a single space.
    * * @param text - * @return + * @return html encoded text turned into plain text. */ public static String cleanText(String text) { text = unicodeTrim(text); diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/util/DesktopUtil.java b/epublib-tools/src/main/java/nl/siegmann/epublib/util/DesktopUtil.java index b052e386..aa18159f 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/util/DesktopUtil.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/util/DesktopUtil.java @@ -13,7 +13,7 @@ public class DesktopUtil { /** * Open a URL in the default web browser. * - * @param a URL to open in a web browser. + * @param url a URL to open in a web browser. * @return true if a browser has been launched. */ public static boolean launchBrowser(URL url) throws BrowserLaunchException { diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/util/ToolsResourceUtil.java b/epublib-tools/src/main/java/nl/siegmann/epublib/util/ToolsResourceUtil.java index 7e23646c..3f84e175 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/util/ToolsResourceUtil.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/util/ToolsResourceUtil.java @@ -56,11 +56,11 @@ public static String getTitle(Resource resource) { /** - * Retrieves whatever it finds between ... or .... + * Retrieves whatever it finds between <title>...</title> or <h1-7>...</h1-7>. * The first match is returned, even if it is a blank string. * If it finds nothing null is returned. * @param resource - * @return + * @return whatever it finds in the resource between <title>...</title> or <h1-7>...</h1-7>. */ public static String findTitleFromXhtml(Resource resource) { if (resource == null) { diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/util/VFSUtil.java b/epublib-tools/src/main/java/nl/siegmann/epublib/util/VFSUtil.java index 248e1059..4124ca42 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/util/VFSUtil.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/util/VFSUtil.java @@ -46,7 +46,7 @@ public static String calculateHref(FileObject rootDir, FileObject currentFile) t /** * First tries to load the inputLocation via VFS; if that doesn't work it tries to load it as a local File * @param inputLocation - * @return + * @return the FileObject referred to by the inputLocation * @throws FileSystemException */ public static FileObject resolveFileObject(String inputLocation) throws FileSystemException { @@ -69,7 +69,7 @@ public static FileObject resolveFileObject(String inputLocation) throws FileSyst * First tries to load the inputLocation via VFS; if that doesn't work it tries to load it as a local File * * @param inputLocation - * @return + * @return the InputStream referred to by the inputLocation * @throws FileSystemException */ public static InputStream resolveInputStream(String inputLocation) throws FileSystemException { diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ButtonBar.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ButtonBar.java index b1cda782..553f74b3 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ButtonBar.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ButtonBar.java @@ -12,7 +12,6 @@ /** * Creates a panel with the first,previous,next and last buttons. * - * @return */ class ButtonBar extends JPanel { private static final long serialVersionUID = 6431437924245035812L; @@ -95,4 +94,4 @@ public void actionPerformed(ActionEvent e) { } }); } -} \ No newline at end of file +} diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ContentPane.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ContentPane.java index 6cb59583..ae76fac6 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ContentPane.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ContentPane.java @@ -38,7 +38,6 @@ /** * Displays a page * - * @return */ public class ContentPane extends JPanel implements NavigationEventListener, HyperlinkListener { @@ -140,7 +139,7 @@ private void initBook(Book book) { * * @param searchString * @param possibleValues - * @return + * @return Whether the given searchString matches any of the possibleValues. */ private static boolean matchesAny(String searchString, String... possibleValues) { for (int i = 0; i < possibleValues.length; i++) { @@ -337,7 +336,7 @@ public void gotoNextPage() { * Property handles http encoded spaces and such. * * @param clickUrl - * @return + * @return a link generated by a click on a link transformed into a document to a resource href. */ private String calculateTargetHref(URL clickUrl) { String resourceHref = clickUrl.toString(); @@ -383,4 +382,4 @@ public void navigationPerformed(NavigationEvent navigationEvent) { } -} \ No newline at end of file +} diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/HTMLDocumentFactory.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/HTMLDocumentFactory.java index edd5edc6..3af997b0 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/HTMLDocumentFactory.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/HTMLDocumentFactory.java @@ -84,7 +84,7 @@ private void putDocument(Resource resource, HTMLDocument document) { * the resource and adds it to the cache. * * @param resource - * @return + * @return the HTMLDocument representation of the resource. */ public HTMLDocument getDocument(Resource resource) { HTMLDocument document = null; @@ -122,7 +122,7 @@ private String stripHtml(String input) { * these confuse the html viewer. * * @param input - * @return + * @return the input stripped of control characters */ private static String removeControlTags(String input) { StringBuilder result = new StringBuilder(); @@ -150,7 +150,7 @@ private static String removeControlTags(String input) { * If the resources is not of type XHTML then null is returned. * * @param resource - * @return + * @return a swing HTMLDocument created from the given resource. */ private HTMLDocument createDocument(Resource resource) { HTMLDocument result = null; diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/TableOfContentsPane.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/TableOfContentsPane.java index 8c73a8f8..5bd6631a 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/TableOfContentsPane.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/TableOfContentsPane.java @@ -46,7 +46,6 @@ public class TableOfContentsPane extends JPanel implements NavigationEventListen * Also sets up a selectionListener that updates the SectionWalker when an item in the tree is selected. * * @param navigator - * @return */ public TableOfContentsPane(Navigator navigator) { super(new GridLayout(1, 0)); diff --git a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ViewerUtil.java b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ViewerUtil.java index 57739988..7f9e3b0e 100644 --- a/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ViewerUtil.java +++ b/epublib-tools/src/main/java/nl/siegmann/epublib/viewer/ViewerUtil.java @@ -19,7 +19,7 @@ public class ViewerUtil { * * @param iconName * @param backupLabel - * @return + * @return a button with the given icon. */ // package static JButton createButton(String iconName, String backupLabel) { From fd1e80fb5c8b9e975ab1c7148f91d285e9a167c6 Mon Sep 17 00:00:00 2001 From: Alex Kuiper Date: Thu, 25 Jul 2013 21:06:52 +0200 Subject: [PATCH 22/57] Also used ZipFile for lazy loading now. --- .../java/nl/siegmann/epublib/epub/EpubReader.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java index 929bf22d..bfba08a8 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java @@ -164,10 +164,14 @@ private void handleMimeType(Book result, Resources resources) { private Resources readLazyResources( String fileName, String defaultHtmlEncoding, List lazyLoadedTypes) throws IOException { - ZipInputStream in = new ZipInputStream(new FileInputStream(fileName)); - + ZipFile zipFile = new ZipFile(fileName); + Resources result = new Resources(); - for(ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { + Enumeration entries = zipFile.entries(); + + while( entries.hasMoreElements() ) { + ZipEntry zipEntry = entries.nextElement(); + if(zipEntry.isDirectory()) { continue; } @@ -180,7 +184,7 @@ private Resources readLazyResources( String fileName, String defaultHtmlEncoding if ( lazyLoadedTypes.contains(mediaType) ) { resource = new Resource(fileName, zipEntry.getSize(), href); } else { - resource = new Resource( in, fileName, (int) zipEntry.getSize(), href ); + resource = new Resource( zipFile.getInputStream(zipEntry), fileName, (int) zipEntry.getSize(), href ); } if(resource.getMediaType() == MediatypeService.XHTML) { From eb6bd3724f41641bf815b928b23e80359ddc55d6 Mon Sep 17 00:00:00 2001 From: "P. Siegmann" Date: Mon, 17 Mar 2014 05:22:43 +0100 Subject: [PATCH 23/57] add function to CollectionUtil --- .../java/nl/siegmann/epublib/util/CollectionUtil.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java index 5b563f15..f780cb68 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/CollectionUtil.java @@ -1,5 +1,6 @@ package nl.siegmann.epublib.util; +import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.List; @@ -54,4 +55,14 @@ public static T first(List list) { } return list.get(0); } + + /** + * Whether the given collection is null or has no elements. + * + * @param collection + * @return Whether the given collection is null or has no elements. + */ + public static boolean isEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } } From 8ea49df246efb5250c7c01358e9dc7f471d71e76 Mon Sep 17 00:00:00 2001 From: "P. Siegmann" Date: Mon, 17 Mar 2014 05:27:01 +0100 Subject: [PATCH 24/57] modernize unit test style --- .../siegmann/epublib/epub/EpubReaderTest.java | 118 +++++++++--------- .../siegmann/epublib/epub/EpubWriterTest.java | 95 +++++++------- 2 files changed, 103 insertions(+), 110 deletions(-) diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubReaderTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubReaderTest.java index 16df2fa9..54a8ff2f 100644 --- a/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubReaderTest.java +++ b/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubReaderTest.java @@ -2,77 +2,73 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; -import junit.framework.TestCase; import nl.siegmann.epublib.domain.Book; import nl.siegmann.epublib.domain.Resource; import nl.siegmann.epublib.service.MediatypeService; -public class EpubReaderTest extends TestCase { - - public void testCover_only_cover() { - try { - Book book = new Book(); - - book.setCoverImage(new Resource(this.getClass().getResourceAsStream("/book1/cover.png"), "cover.png")); +import org.junit.Assert; +import org.junit.Test; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - (new EpubWriter()).write(book, out); - byte[] epubData = out.toByteArray(); - Book readBook = new EpubReader().readEpub(new ByteArrayInputStream(epubData)); - assertNotNull(readBook.getCoverImage()); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - assertTrue(false); - } +public class EpubReaderTest { + @Test + public void testCover_only_cover() throws IOException { + Book book = new Book(); + + book.setCoverImage(new Resource(this.getClass().getResourceAsStream( + "/book1/cover.png"), "cover.png")); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + (new EpubWriter()).write(book, out); + byte[] epubData = out.toByteArray(); + Book readBook = new EpubReader().readEpub(new ByteArrayInputStream( + epubData)); + Assert.assertNotNull(readBook.getCoverImage()); } - public void testCover_cover_one_section() { - try { - Book book = new Book(); - - book.setCoverImage(new Resource(this.getClass().getResourceAsStream("/book1/cover.png"), "cover.png")); - book.addSection("Introduction", new Resource(this.getClass().getResourceAsStream("/book1/chapter1.html"), "chapter1.html")); - book.generateSpineFromTableOfContents(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - (new EpubWriter()).write(book, out); - byte[] epubData = out.toByteArray(); - Book readBook = new EpubReader().readEpub(new ByteArrayInputStream(epubData)); - assertNotNull(readBook.getCoverPage()); - assertEquals(1, readBook.getSpine().size()); - assertEquals(1, readBook.getTableOfContents().size()); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - assertTrue(false); - } + @Test + public void testCover_cover_one_section() throws IOException { + Book book = new Book(); + + book.setCoverImage(new Resource(this.getClass().getResourceAsStream( + "/book1/cover.png"), "cover.png")); + book.addSection("Introduction", new Resource(this.getClass() + .getResourceAsStream("/book1/chapter1.html"), "chapter1.html")); + book.generateSpineFromTableOfContents(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + (new EpubWriter()).write(book, out); + byte[] epubData = out.toByteArray(); + Book readBook = new EpubReader().readEpub(new ByteArrayInputStream( + epubData)); + Assert.assertNotNull(readBook.getCoverPage()); + Assert.assertEquals(1, readBook.getSpine().size()); + Assert.assertEquals(1, readBook.getTableOfContents().size()); } - public void testReadEpub_opf_ncx_docs() { - try { - Book book = new Book(); - - book.setCoverImage(new Resource(this.getClass().getResourceAsStream("/book1/cover.png"), "cover.png")); - book.addSection("Introduction", new Resource(this.getClass().getResourceAsStream("/book1/chapter1.html"), "chapter1.html")); - book.generateSpineFromTableOfContents(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - (new EpubWriter()).write(book, out); - byte[] epubData = out.toByteArray(); - Book readBook = new EpubReader().readEpub(new ByteArrayInputStream(epubData)); - assertNotNull(readBook.getCoverPage()); - assertEquals(1, readBook.getSpine().size()); - assertEquals(1, readBook.getTableOfContents().size()); - assertNotNull(readBook.getOpfResource()); - assertNotNull(readBook.getNcxResource()); - assertEquals(MediatypeService.NCX, readBook.getNcxResource().getMediaType()); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - assertTrue(false); - } + @Test + public void testReadEpub_opf_ncx_docs() throws IOException { + Book book = new Book(); + + book.setCoverImage(new Resource(this.getClass().getResourceAsStream( + "/book1/cover.png"), "cover.png")); + book.addSection("Introduction", new Resource(this.getClass() + .getResourceAsStream("/book1/chapter1.html"), "chapter1.html")); + book.generateSpineFromTableOfContents(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + (new EpubWriter()).write(book, out); + byte[] epubData = out.toByteArray(); + Book readBook = new EpubReader().readEpub(new ByteArrayInputStream( + epubData)); + Assert.assertNotNull(readBook.getCoverPage()); + Assert.assertEquals(1, readBook.getSpine().size()); + Assert.assertEquals(1, readBook.getTableOfContents().size()); + Assert.assertNotNull(readBook.getOpfResource()); + Assert.assertNotNull(readBook.getNcxResource()); + Assert.assertEquals(MediatypeService.NCX, readBook.getNcxResource() + .getMediaType()); } } diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubWriterTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubWriterTest.java index fb3c0289..644b1a82 100644 --- a/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubWriterTest.java +++ b/epublib-core/src/test/java/nl/siegmann/epublib/epub/EpubWriterTest.java @@ -2,11 +2,11 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import junit.framework.TestCase; import nl.siegmann.epublib.domain.Author; import nl.siegmann.epublib.domain.Book; import nl.siegmann.epublib.domain.GuideReference; @@ -15,62 +15,59 @@ import nl.siegmann.epublib.domain.TOCReference; import nl.siegmann.epublib.util.CollectionUtil; -public class EpubWriterTest extends TestCase { +import org.junit.Assert; +import org.junit.Test; - public void testBook1() { - try { - // create test book - Book book = createTestBook(); - - // write book to byte[] - byte[] bookData = writeBookToByteArray(book); -// FileOutputStream fileOutputStream = new FileOutputStream("foo.zip"); -// fileOutputStream.write(bookData); -// fileOutputStream.flush(); -// fileOutputStream.close(); - assertNotNull(bookData); - assertTrue(bookData.length > 0); - - // read book from byte[] - Book readBook = new EpubReader().readEpub(new ByteArrayInputStream(bookData)); - - // assert book values are correct - assertEquals(book.getMetadata().getTitles(), readBook.getMetadata().getTitles()); - assertEquals(Identifier.Scheme.ISBN, CollectionUtil.first(readBook.getMetadata().getIdentifiers()).getScheme()); - assertEquals(CollectionUtil.first(book.getMetadata().getIdentifiers()).getValue(), CollectionUtil.first(readBook.getMetadata().getIdentifiers()).getValue()); - assertEquals(CollectionUtil.first(book.getMetadata().getAuthors()), CollectionUtil.first(readBook.getMetadata().getAuthors())); - assertEquals(1, readBook.getGuide().getGuideReferencesByType(GuideReference.COVER).size()); - assertEquals(5, readBook.getSpine().size()); - assertNotNull(book.getCoverPage()); - assertNotNull(book.getCoverImage()); - assertEquals(4, readBook.getTableOfContents().size()); +public class EpubWriterTest { + + @Test + public void testBook1() throws IOException { + // create test book + Book book = createTestBook(); + + // write book to byte[] + byte[] bookData = writeBookToByteArray(book); + FileOutputStream fileOutputStream = new FileOutputStream("foo.zip"); + fileOutputStream.write(bookData); + fileOutputStream.flush(); + fileOutputStream.close(); + Assert.assertNotNull(bookData); + Assert.assertTrue(bookData.length > 0); + + // read book from byte[] + Book readBook = new EpubReader().readEpub(new ByteArrayInputStream(bookData)); + + // assert book values are correct + Assert.assertEquals(book.getMetadata().getTitles(), readBook.getMetadata().getTitles()); + Assert.assertEquals(Identifier.Scheme.ISBN, CollectionUtil.first(readBook.getMetadata().getIdentifiers()).getScheme()); + Assert.assertEquals(CollectionUtil.first(book.getMetadata().getIdentifiers()).getValue(), CollectionUtil.first(readBook.getMetadata().getIdentifiers()).getValue()); + Assert.assertEquals(CollectionUtil.first(book.getMetadata().getAuthors()), CollectionUtil.first(readBook.getMetadata().getAuthors())); + Assert.assertEquals(1, readBook.getGuide().getGuideReferencesByType(GuideReference.COVER).size()); + Assert.assertEquals(5, readBook.getSpine().size()); + Assert.assertNotNull(book.getCoverPage()); + Assert.assertNotNull(book.getCoverImage()); + Assert.assertEquals(4, readBook.getTableOfContents().size()); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } } /** * Test for a very old bug where epublib would throw a NullPointerException when writing a book with a cover that has no id. + * @throws IOException + * @throws FileNotFoundException * */ - public void testWritingBookWithCoverWithNullId() { - try { - Book book = new Book(); - book.getMetadata().addTitle("Epub test book 1"); - book.getMetadata().addAuthor(new Author("Joe", "Tester")); - InputStream is = this.getClass().getResourceAsStream("/book1/cover.png"); - book.setCoverImage(new Resource(is, "cover.png")); - // Add Chapter 1 - InputStream is1 = this.getClass().getResourceAsStream("/book1/chapter1.html"); - book.addSection("Introduction", new Resource(is1, "chapter1.html")); - - EpubWriter epubWriter = new EpubWriter(); - epubWriter.write(book, new FileOutputStream("test1_book1.epub")); - } catch (IOException e) { - fail(e.getMessage()); - } + public void testWritingBookWithCoverWithNullId() throws FileNotFoundException, IOException { + Book book = new Book(); + book.getMetadata().addTitle("Epub test book 1"); + book.getMetadata().addAuthor(new Author("Joe", "Tester")); + InputStream is = this.getClass().getResourceAsStream("/book1/cover.png"); + book.setCoverImage(new Resource(is, "cover.png")); + // Add Chapter 1 + InputStream is1 = this.getClass().getResourceAsStream("/book1/chapter1.html"); + book.addSection("Introduction", new Resource(is1, "chapter1.html")); + + EpubWriter epubWriter = new EpubWriter(); + epubWriter.write(book, new FileOutputStream("test1_book1.epub")); } private Book createTestBook() throws IOException { From 1b0aa98b7a9f67e1b795c0177dc9674b2cb064b8 Mon Sep 17 00:00:00 2001 From: "P. Siegmann" Date: Mon, 17 Mar 2014 05:28:54 +0100 Subject: [PATCH 25/57] refactor the loading of resources --- .../siegmann/epublib/domain/LazyResource.java | 166 ++++++++++++++++ .../nl/siegmann/epublib/domain/Resource.java | 119 +----------- .../epublib/domain/ResourceInputStream.java | 10 +- .../nl/siegmann/epublib/domain/Resources.java | 4 +- .../nl/siegmann/epublib/epub/EpubReader.java | 118 +++--------- .../epublib/epub/ResourcesLoader.java | 135 +++++++++++++ .../epublib/epub/ResourcesLoaderTest.java | 178 ++++++++++++++++++ .../epublib/util/CollectionUtilTest.java | 25 +++ .../src/test/resources/testbook1.epub | Bin 0 -> 337350 bytes 9 files changed, 536 insertions(+), 219 deletions(-) create mode 100644 epublib-core/src/main/java/nl/siegmann/epublib/domain/LazyResource.java create mode 100644 epublib-core/src/main/java/nl/siegmann/epublib/epub/ResourcesLoader.java create mode 100644 epublib-core/src/test/java/nl/siegmann/epublib/epub/ResourcesLoaderTest.java create mode 100644 epublib-core/src/test/java/nl/siegmann/epublib/util/CollectionUtilTest.java create mode 100644 epublib-core/src/test/resources/testbook1.epub diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/LazyResource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/LazyResource.java new file mode 100644 index 00000000..7f49dc62 --- /dev/null +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/LazyResource.java @@ -0,0 +1,166 @@ +package nl.siegmann.epublib.domain; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import nl.siegmann.epublib.service.MediatypeService; +import nl.siegmann.epublib.util.IOUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Resource that loads its data only on-demand. + * This way larger books can fit into memory and can be opened faster. + * + */ +public class LazyResource extends Resource { + + + /** + * + */ + private static final long serialVersionUID = 5089400472352002866L; + private String filename; + private long cachedSize; + + private static final Logger LOG = LoggerFactory.getLogger(LazyResource.class); + + /** + * 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 LazyResource(String filename, long size, String href) { + super( null, null, href, MediatypeService.determineMediaType(href)); + this.filename = filename; + this.cachedSize = size; + } + + /** + * Creates a Resource that tries to load the data, but falls back to lazy loading. + * + * If the size of the resource is known ahead of time we can use that to allocate + * a matching byte[]. If this succeeds we can safely load the data. + * + * If it fails we leave the data null for now and it will be lazy-loaded when + * it is accessed. + * + * @param in + * @param fileName + * @param length + * @param href + * @throws IOException + */ + public LazyResource(InputStream in, String filename, int length, String href) throws IOException { + super(null, IOUtil.toByteArray(in, length), href, MediatypeService.determineMediaType(href)); + this.filename = filename; + this.cachedSize = length; + } + + /** + * Gets the contents of the Resource as an InputStream. + * + * @return The contents of the Resource. + * + * @throws IOException + */ + public InputStream getInputStream() throws IOException { + if (isInitialized()) { + return new ByteArrayInputStream(getData()); + } else { + return getResourceStream(); + } + } + + /** + * Initializes the resource by loading its data into memory. + * + * @throws IOException + */ + public void initialize() throws IOException { + 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 ) { + + LOG.debug("Initializing lazy resource " + filename + "#" + this.getHref() ); + + InputStream in = getResourceStream(); + byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); + if ( readData == null ) { + throw new IOException("Could not load the contents of entry " + this.getHref() + " from epub file " + filename); + } else { + this.data = readData; + } + + in.close(); + } + + return data; + } + + + private InputStream getResourceStream() throws FileNotFoundException, + IOException { + ZipFile zipFile = new ZipFile(filename); + ZipEntry zipEntry = zipFile.getEntry(originalHref); + if (zipEntry == null) { + zipFile.close(); + throw new IllegalStateException("Cannot find entry " + originalHref + " in epub file " + filename); + } + return new ResourceInputStream(zipFile.getInputStream(zipEntry), zipFile); + } + + /** + * 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; + } + } + + /** + * 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; + } +} diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java index a5f66cc6..e8fefc18 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resource.java @@ -1,14 +1,10 @@ package nl.siegmann.epublib.domain; import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.Serializable; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; @@ -16,9 +12,6 @@ import nl.siegmann.epublib.util.StringUtil; import nl.siegmann.epublib.util.commons.io.XmlStreamReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Represents a resource that is part of the epub. * A resource can be a html file, image, xml, etc. @@ -35,15 +28,10 @@ public class Resource implements Serializable { private String id; private String title; private String href; - private String originalHref; + protected String originalHref; private MediaType mediaType; private String inputEncoding = Constants.CHARACTER_ENCODING; - private byte[] data; - - private String fileName; - private long cachedSize; - - private static final Logger LOG = LoggerFactory.getLogger(Resource.class); + protected byte[] data; /** * Creates an empty Resource with the given href. @@ -116,44 +104,6 @@ public Resource(Reader in, String href) throws IOException { public Resource(InputStream in, String href) throws IOException { this(null, IOUtil.toByteArray(in), href, MediatypeService.determineMediaType(href)); } - - /** - * Creates a Resource that tries to load the data, but falls back to lazy loading. - * - * If the size of the resource is known ahead of time we can use that to allocate - * a matching byte[]. If this succeeds we can safely load the data. - * - * If it fails we leave the data null for now and it will be lazy-loaded when - * it is accessed. - * - * @param in - * @param fileName - * @param length - * @param href - * @throws IOException - */ - public Resource(InputStream in, String fileName, int length, String href) throws IOException { - this( null, IOUtil.toByteArray(in, length), href, MediatypeService.determineMediaType(href)); - this.fileName = fileName; - this.cachedSize = length; - } - - /** - - /** - * 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. @@ -196,72 +146,24 @@ public Resource(String id, byte[] data, String href, MediaType mediaType, String * @throws IOException */ public InputStream getInputStream() throws IOException { - if (isInitialized()) { - return new ByteArrayInputStream(getData()); - } else { - return getResourceStream(); - } + return new ByteArrayInputStream(getData()); } - /** - * Initializes the resource by loading its data into memory. - * - * @throws IOException - */ - public void initialize() throws IOException { - 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 ) { - - LOG.info("Initializing lazy resource " + fileName + "#" + this.href ); - - InputStream in = getResourceStream(); - byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); - if ( readData == null ) { - throw new IOException("Could not lazy-load data."); - } else { - this.data = readData; - this.data = IOUtil.toByteArray(in); - } - - in.close(); - } - return data; } - private InputStream getResourceStream() throws FileNotFoundException, - IOException { - ZipFile zipResource = new ZipFile(fileName); - ZipEntry zipEntry = zipResource.getEntry(originalHref); - if (zipEntry == null) { - zipResource.close(); - throw new IllegalStateException("Cannot find resources href in the epub file"); - } - return new ResourceInputStream(zipResource.getInputStream(zipEntry), zipResource); - } - /** * 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; - } } /** @@ -274,26 +176,13 @@ public void setData(byte[] data) { this.data = data; } - /** - * 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; + return data.length; } /** diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java index 1a636f81..92b305ff 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/ResourceInputStream.java @@ -14,24 +14,24 @@ */ public class ResourceInputStream extends FilterInputStream { - private ZipFile zipResource; + private final ZipFile zipFile; /** * Constructor. * * @param in * The InputStream object. - * @param f + * @param zipFile * The ZipFile object. */ - public ResourceInputStream(InputStream in, ZipFile f) { + public ResourceInputStream(InputStream in, ZipFile zipFile) { super(in); - zipResource = f; + this.zipFile = zipFile; } @Override public void close() throws IOException { super.close(); - zipResource.close(); + zipFile.close(); } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java index 2067afd5..d7b88a9c 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/Resources.java @@ -93,10 +93,10 @@ private String getResourceItemPrefix(Resource resource) { } /** - * Creates a new resource id that is guarenteed to be unique for this set of Resources + * Creates a new resource id that is guaranteed to be unique for this set of Resources * * @param resource - * @return a new resource id that is guarenteed to be unique for this set of Resources + * @return a new resource id that is guaranteed to be unique for this set of Resources */ private String createUniqueResourceId(Resource resource) { int counter = lastId; diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java index bfba08a8..f239f559 100644 --- a/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/EpubReader.java @@ -1,12 +1,9 @@ package nl.siegmann.epublib.epub; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; -import java.util.Enumeration; import java.util.List; -import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; @@ -59,27 +56,6 @@ public Book readEpub(InputStream in, String encoding) throws IOException { return readEpub(new ZipInputStream(in), encoding); } - /** - * Reads this EPUB without loading all resources into memory. - * - * @param fileName the file to load - * @param encoding the encoding for XHTML files - * @param lazyLoadedTypes a list of the MediaType to load lazily - * @return this Book without loading all resources into memory. - * @throws IOException - */ - public Book readEpubLazy( String fileName, String encoding, List lazyLoadedTypes ) throws IOException { - Book result = new Book(); - Resources resources = readLazyResources(fileName, encoding, lazyLoadedTypes); - handleMimeType(result, resources); - String packageResourceHref = getPackageResourceHref(resources); - Resource packageResource = processPackageResource(packageResourceHref, result, resources); - result.setOpfResource(packageResource); - Resource ncxResource = processNcxResource(packageResource, result); - result.setNcxResource(ncxResource); - result = postProcessBook(result); - return result; - } /** @@ -91,19 +67,35 @@ public Book readEpubLazy( String fileName, String encoding, List lazy * @return this Book without loading all resources into memory. * @throws IOException */ - public Book readEpubLazy( String fileName, String encoding ) throws IOException { - return readEpubLazy(fileName, encoding, Arrays.asList(MediatypeService.mediatypes) ); + public Book readEpubLazy(ZipFile zipFile, String encoding ) throws IOException { + return readEpubLazy(zipFile, encoding, Arrays.asList(MediatypeService.mediatypes) ); } public Book readEpub(ZipInputStream in, String encoding) throws IOException { - return readEpubResources(readResources(in, encoding)); + return readEpub(ResourcesLoader.loadResources(in, encoding)); } public Book readEpub(ZipFile in, String encoding) throws IOException { - return readEpubResources(readResources(in, encoding)); + return readEpub(ResourcesLoader.loadResources(in, encoding)); } - public Book readEpubResources(Resources resources) throws IOException{ + /** + * Reads this EPUB without loading all resources into memory. + * + * @param fileName the file to load + * @param encoding the encoding for XHTML files + * @param lazyLoadedTypes a list of the MediaType to load lazily + * @return this Book without loading all resources into memory. + * @throws IOException + */ + public Book readEpubLazy(ZipFile zipFile, String encoding, List lazyLoadedTypes ) throws IOException { + Book result = new Book(); + Resources resources = ResourcesLoader.loadResources(zipFile, encoding, lazyLoadedTypes); + readEpub(resources); + return result; + } + + public Book readEpub(Resources resources) throws IOException{ Book result = new Book(); handleMimeType(result, resources); String packageResourceHref = getPackageResourceHref(resources); @@ -160,72 +152,4 @@ private String getPackageResourceHref(Resources resources) { private void handleMimeType(Book result, Resources resources) { resources.remove("mimetype"); } - - private Resources readLazyResources( String fileName, String defaultHtmlEncoding, - List lazyLoadedTypes) throws IOException { - - ZipFile zipFile = new ZipFile(fileName); - - Resources result = new Resources(); - Enumeration entries = zipFile.entries(); - - while( entries.hasMoreElements() ) { - ZipEntry zipEntry = entries.nextElement(); - - if(zipEntry.isDirectory()) { - continue; - } - - String href = zipEntry.getName(); - MediaType mediaType = MediatypeService.determineMediaType(href); - - Resource resource; - - if ( lazyLoadedTypes.contains(mediaType) ) { - resource = new Resource(fileName, zipEntry.getSize(), href); - } else { - resource = new Resource( zipFile.getInputStream(zipEntry), fileName, (int) zipEntry.getSize(), href ); - } - - if(resource.getMediaType() == MediatypeService.XHTML) { - resource.setInputEncoding(defaultHtmlEncoding); - } - result.add(resource); - } - - return result; - } - - private Resources readResources(ZipInputStream in, String defaultHtmlEncoding) throws IOException { - Resources result = new Resources(); - for(ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { - if(zipEntry.isDirectory()) { - continue; - } - Resource resource = ResourceUtil.createResource(zipEntry, in); - if(resource.getMediaType() == MediatypeService.XHTML) { - resource.setInputEncoding(defaultHtmlEncoding); - } - result.add(resource); - } - return result; - } - - private Resources readResources(ZipFile zipFile, String defaultHtmlEncoding) throws IOException { - Resources result = new Resources(); - Enumeration entries = zipFile.entries(); - - while(entries.hasMoreElements()){ - ZipEntry zipEntry = entries.nextElement(); - if(zipEntry != null && !zipEntry.isDirectory()){ - Resource resource = ResourceUtil.createResource(zipEntry, zipFile.getInputStream(zipEntry)); - if(resource.getMediaType() == MediatypeService.XHTML) { - resource.setInputEncoding(defaultHtmlEncoding); - } - result.add(resource); - } - } - - return result; - } } diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/epub/ResourcesLoader.java b/epublib-core/src/main/java/nl/siegmann/epublib/epub/ResourcesLoader.java new file mode 100644 index 00000000..9284ff8d --- /dev/null +++ b/epublib-core/src/main/java/nl/siegmann/epublib/epub/ResourcesLoader.java @@ -0,0 +1,135 @@ +package nl.siegmann.epublib.epub; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +import nl.siegmann.epublib.domain.LazyResource; +import nl.siegmann.epublib.domain.MediaType; +import nl.siegmann.epublib.domain.Resource; +import nl.siegmann.epublib.domain.Resources; +import nl.siegmann.epublib.service.MediatypeService; +import nl.siegmann.epublib.util.CollectionUtil; +import nl.siegmann.epublib.util.ResourceUtil; + +/** + * Loads Resources from inputStreams, ZipFiles, etc + * + * @author paul + * + */ +public class ResourcesLoader { + /** + * Loads the entries of the zipFile as resources. + * + * The MediaTypes that are in the lazyLoadedTypes will not get their contents loaded, but are stored as references to + * entries into the ZipFile and are loaded on demand by the Resource system. + * + * @param zipFile + * @param defaultHtmlEncoding + * @param lazyLoadedTypes + * @return + * @throws IOException + */ + static Resources loadResources(ZipFile zipFile, String defaultHtmlEncoding, + List lazyLoadedTypes) throws IOException { + + Resources result = new Resources(); + Enumeration entries = zipFile.entries(); + + while( entries.hasMoreElements() ) { + ZipEntry zipEntry = entries.nextElement(); + + if(zipEntry == null || zipEntry.isDirectory()) { + continue; + } + + String href = zipEntry.getName(); + + Resource resource; + + if (shouldLoadLazy(href, lazyLoadedTypes)) { + resource = new LazyResource(zipFile.getName(), zipEntry.getSize(), href); + } else { + resource = ResourceUtil.createResource(zipEntry, zipFile.getInputStream(zipEntry)); + } + + if(resource.getMediaType() == MediatypeService.XHTML) { + resource.setInputEncoding(defaultHtmlEncoding); + } + result.add(resource); + } + + return result; + } + + /** + * Whether the given href will load a mediaType that is in the collection of lazilyLoadedMediaTypes. + * + * @param href + * @param lazilyLoadedMediaTypes + * @return Whether the given href will load a mediaType that is in the collection of lazilyLoadedMediaTypes. + */ + private static boolean shouldLoadLazy(String href, Collection lazilyLoadedMediaTypes) { + if (CollectionUtil.isEmpty(lazilyLoadedMediaTypes)) { + return false; + } + MediaType mediaType = MediatypeService.determineMediaType(href); + return lazilyLoadedMediaTypes.contains(mediaType); + } + + + static Resources loadResources(InputStream in, String defaultHtmlEncoding) throws IOException { + return loadResources(new ZipInputStream(in), defaultHtmlEncoding); + } + + + /** + * Loads all entries from the ZipInputStream as Resources. + * + * Loads the contents of all ZipEntries into memory. + * Is fast, but may lead to memory problems when reading large books on devices with small amounts of memory. + * + * @param in + * @param defaultHtmlEncoding + * @return + * @throws IOException + */ + static Resources loadResources(ZipInputStream in, String defaultHtmlEncoding) throws IOException { + Resources result = new Resources(); + for(ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { + if(zipEntry == null || zipEntry.isDirectory()) { + continue; + } + Resource resource = ResourceUtil.createResource(zipEntry, in); + if(resource.getMediaType() == MediatypeService.XHTML) { + resource.setInputEncoding(defaultHtmlEncoding); + } + result.add(resource); + } + return result; + } + + + /** + * Loads all entries from the ZipInputStream as Resources. + * + * Loads the contents of all ZipEntries into memory. + * Is fast, but may lead to memory problems when reading large books on devices with small amounts of memory. + * + * @param in + * @param defaultHtmlEncoding + * @return + * @throws IOException + */ + static Resources loadResources(ZipFile zipFile, String defaultHtmlEncoding) throws IOException { + return loadResources(zipFile, defaultHtmlEncoding, Collections.emptyList()); + } + +} diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/epub/ResourcesLoaderTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/epub/ResourcesLoaderTest.java new file mode 100644 index 00000000..4d621b2a --- /dev/null +++ b/epublib-core/src/test/java/nl/siegmann/epublib/epub/ResourcesLoaderTest.java @@ -0,0 +1,178 @@ +package nl.siegmann.epublib.epub; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +import nl.siegmann.epublib.domain.LazyResource; +import nl.siegmann.epublib.domain.Resource; +import nl.siegmann.epublib.domain.Resources; +import nl.siegmann.epublib.service.MediatypeService; +import nl.siegmann.epublib.util.IOUtil; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ResourcesLoaderTest { + + private static final String encoding = "UTF-8"; + private static String testBookFilename; + + @BeforeClass + public static void setUpClass() throws IOException { + File testbook = File.createTempFile("testbook", ".epub"); + OutputStream out = new FileOutputStream(testbook); + IOUtil.copy(ResourcesLoaderTest.class.getResourceAsStream("/testbook1.epub"), out); + out.close(); + + ResourcesLoaderTest.testBookFilename = testbook.getAbsolutePath(); + } + + @AfterClass + public static void tearDownClass() { + new File(testBookFilename).delete(); + } + + /** + * Loads the Resource from an InputStream + * + * @throws FileNotFoundException + * @throws IOException + */ + @Test + public void testLoadResources_InputStream() throws FileNotFoundException, IOException { + // given + InputStream inputStream = new FileInputStream(new File(testBookFilename)); + + // when + Resources resources = ResourcesLoader.loadResources(inputStream, encoding); + + // then + verifyResources(resources); + } + + /** + * Loads the Resources from a ZipInputStream + * + * @throws FileNotFoundException + * @throws IOException + */ + @Test + public void testLoadResources_ZipInputStream() throws FileNotFoundException, IOException { + // given + ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(new File(testBookFilename))); + + // when + Resources resources = ResourcesLoader.loadResources(zipInputStream, encoding); + + // then + verifyResources(resources); + } + + /** + * Loads the Resources from a ZipFile + * + * @throws FileNotFoundException + * @throws IOException + */ + @Test + public void testLoadResources_ZipFile() throws FileNotFoundException, IOException { + // given + ZipFile zipFile = new ZipFile(testBookFilename); + + // when + Resources resources = ResourcesLoader.loadResources(zipFile, encoding); + + // then + verifyResources(resources); + } + + /** + * Loads all Resources lazily from a ZipFile + * + * @throws FileNotFoundException + * @throws IOException + */ + @Test + public void testLoadResources_ZipFile_lazy_all() throws FileNotFoundException, IOException { + // given + ZipFile zipFile = new ZipFile(testBookFilename); + + // when + Resources resources = ResourcesLoader.loadResources(zipFile, encoding, Arrays.asList(MediatypeService.mediatypes)); + + // then + verifyResources(resources); + Assert.assertEquals(Resource.class, resources.getById("container").getClass()); + Assert.assertEquals(LazyResource.class, resources.getById("book1").getClass()); + } + + /** + * Loads the Resources from a ZipFile, some of them lazily. + * + * @throws FileNotFoundException + * @throws IOException + */ + @Test + public void testLoadResources_ZipFile_partial_lazy() throws FileNotFoundException, IOException { + // given + ZipFile zipFile = new ZipFile(testBookFilename); + + // when + Resources resources = ResourcesLoader.loadResources(zipFile, encoding, Arrays.asList(MediatypeService.CSS)); + + // then + verifyResources(resources); + Assert.assertEquals(Resource.class, resources.getById("container").getClass()); + Assert.assertEquals(LazyResource.class, resources.getById("book1").getClass()); + Assert.assertEquals(Resource.class, resources.getById("chapter1").getClass()); + } + + private void verifyResources(Resources resources) throws IOException { + Assert.assertNotNull(resources); + Assert.assertEquals(12, resources.getAll().size()); + List allHrefs = new ArrayList(resources.getAllHrefs()); + Collections.sort(allHrefs); + + Resource resource; + byte[] expectedData; + + // container + resource = resources.getByHref(allHrefs.get(0)); + Assert.assertEquals("container", resource.getId()); + Assert.assertEquals("META-INF/container.xml", resource.getHref()); + Assert.assertNull(resource.getMediaType()); + Assert.assertEquals(230, resource.getData().length); + + // book1.css + resource = resources.getByHref(allHrefs.get(1)); + Assert.assertEquals("book1", resource.getId()); + Assert.assertEquals("OEBPS/book1.css", resource.getHref()); + Assert.assertEquals(MediatypeService.CSS, resource.getMediaType()); + Assert.assertEquals(65, resource.getData().length); + expectedData = IOUtil.toByteArray(this.getClass().getResourceAsStream("/book1/book1.css")); + Assert.assertTrue(Arrays.equals(expectedData, resource.getData())); + + + // chapter1 + resource = resources.getByHref(allHrefs.get(2)); + Assert.assertEquals("chapter1", resource.getId()); + Assert.assertEquals("OEBPS/chapter1.html", resource.getHref()); + Assert.assertEquals(MediatypeService.XHTML, resource.getMediaType()); + Assert.assertEquals(247, resource.getData().length); + expectedData = IOUtil.toByteArray(this.getClass().getResourceAsStream("/book1/chapter1.html")); + Assert.assertTrue(Arrays.equals(expectedData, resource.getData())); + } +} diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/util/CollectionUtilTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/util/CollectionUtilTest.java new file mode 100644 index 00000000..ef9e7c26 --- /dev/null +++ b/epublib-core/src/test/java/nl/siegmann/epublib/util/CollectionUtilTest.java @@ -0,0 +1,25 @@ +package nl.siegmann.epublib.util; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; + +public class CollectionUtilTest { + + @Test + public void testIsEmpty_null() { + Assert.assertTrue(CollectionUtil.isEmpty(null)); + } + + @Test + public void testIsEmpty_empty() { + Assert.assertTrue(CollectionUtil.isEmpty(new ArrayList())); + } + + @Test + public void testIsEmpty_elements() { + Assert.assertFalse(CollectionUtil.isEmpty(Arrays.asList("foo"))); + } +} diff --git a/epublib-core/src/test/resources/testbook1.epub b/epublib-core/src/test/resources/testbook1.epub new file mode 100644 index 0000000000000000000000000000000000000000..25992d8dc35958e8a4d1baa2205180f6344cd7bd GIT binary patch literal 337350 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

    MW)Waj2WDyRQfi> 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
    a!O=nWMdk%@^#=I5uW6w1Vd9y;Oy;t_$t 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

    <^T9Y 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