package nl.siegmann.epublib.domain; import java.io.Serializable; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import nl.siegmann.epublib.Constants; import nl.siegmann.epublib.service.MediatypeService; import org.apache.commons.io.Charsets; import org.rr.commons.utils.StringUtil; /** * All the resources that make up the book. * XHTML files, images and epub xml documents must be here. * * @author paul * */ public class Resources implements Serializable { private static final long serialVersionUID = 2450876953383871451L; private static final String IMAGE_PREFIX = "image_"; private static final String ITEM_PREFIX = "item_"; private int lastId = 1; private Map<String, Resource> resources = new HashMap<String, Resource>(); /** * Adds a resource to the resources. * * Fixes the resources id and href if necessary. * * @param resource * @return */ public Resource add(Resource resource) { fixResourceHref(resource); fixResourceId(resource); this.resources.put(resource.getHref(), resource); return resource; } /** * Checks the id of the given resource and changes to a unique identifier if it isn't one already. * * @param resource */ public void fixResourceId(Resource resource) { String resourceId = resource.getId(); // first try and create a unique id based on the resource's href if (StringUtil.isEmpty(resource.getId())) { resourceId = StringUtil.substringBeforeLast(resource.getHref(), '.'); resourceId = StringUtil.substringAfterLast(resourceId, '/'); } resourceId = makeValidId(resourceId, resource); // check if the id is unique. if not: create one from scratch if (StringUtil.isEmpty(resourceId) || containsId(resourceId)) { resourceId = createUniqueResourceId(resource); } resource.setId(resourceId); } /** * Check if the id is a valid identifier. if not: prepend with valid identifier * * @param resource * @return */ private String makeValidId(String resourceId, Resource resource) { if (StringUtil.isNotEmpty(resourceId) && ! Character.isJavaIdentifierStart(resourceId.charAt(0))) { resourceId = getResourceItemPrefix(resource) + resourceId; } return resourceId; } private String getResourceItemPrefix(Resource resource) { String result; if (MediatypeService.isBitmapImage(resource.getMediaType())) { result = IMAGE_PREFIX; } else { result = ITEM_PREFIX; } return result; } /** * Creates a new resource id that is guarenteed to be unique for this set of Resources * * @param resource * @return */ private String createUniqueResourceId(Resource resource) { int counter = lastId; if (counter == Integer.MAX_VALUE) { if (resources.size() == Integer.MAX_VALUE) { throw new IllegalArgumentException("Resources contains " + Integer.MAX_VALUE + " elements: no new elements can be added"); } else { counter = 1; } } String prefix = getResourceItemPrefix(resource); String result = prefix + counter; while (containsId(result)) { result = prefix + (++ counter); } lastId = counter; return result; } /** * Whether the map of resources already contains a resource with the given id. * * @param id * @return */ public boolean containsId(String id) { if (StringUtil.isEmpty(id)) { return false; } for (Resource resource: resources.values()) { if (id.equals(resource.getId())) { return true; } } return false; } /** * Gets the resource with the given id. * * @param id * @return null if not found */ public Resource getById(String id) { if (StringUtil.isEmpty(id)) { return null; } for (Resource resource: resources.values()) { if (id.equals(resource.getId())) { return resource; } } return null; } /** * Remove the resource with the given href. * * @param href * @return the removed resource, null if not found */ public Resource remove(String href) { return resources.remove(href); } private void fixResourceHref(Resource resource) { if(StringUtil.isNotEmpty(resource.getHref()) && ! resources.containsKey(resource.getHref())) { return; } if(StringUtil.isEmpty(resource.getHref())) { if(resource.getMediaType() == null) { throw new IllegalArgumentException("Resource " + resource.getId() + " must have either a MediaType or a href"); } int i = 1; String href = createHref(resource.getMediaType(), i); while(resources.containsKey(href)) { href = createHref(resource.getMediaType(), (++i)); } resource.setHref(href); } } private String createHref(MediaType mediaType, int counter) { if(MediatypeService.isBitmapImage(mediaType)) { return "image_" + counter + mediaType.getDefaultExtension(); } else { return "item_" + counter + mediaType.getDefaultExtension(); } } public boolean isEmpty() { return resources.isEmpty(); } /** * The number of resources * @return */ public int size() { return resources.size(); } /** * The resources that make up this book. * Resources can be xhtml pages, images, xml documents, etc. * * @return */ public Map<String, Resource> getResourceMap() { return resources; } public Collection<Resource> getAll() { return resources.values(); } /** * Whether there exists a resource with the given href * @param href * @return */ public boolean containsByHref(String href) { if (StringUtil.isEmpty(href)) { return false; } return resources.containsKey(StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR)); } /** * Sets the collection of Resources to the given collection of resources * * @param resources */ public void set(Collection<Resource> resources) { this.resources.clear(); addAll(resources); } /** * Adds all resources from the given Collection of resources to the existing collection. * * @param resources */ public void addAll(Collection<Resource> resources) { for(Resource resource: resources) { fixResourceHref(resource); this.resources.put(resource.getHref(), resource); } } /** * Sets the collection of Resources to the given collection of resources * * @param resources A map with as keys the resources href and as values the Resources */ public void set(Map<String, Resource> resources) { this.resources = new HashMap<String, Resource>(resources); } /** * First tries to find a resource with as id the given idOrHref, if that * fails it tries to find one with the idOrHref as href. * * @param idOrHref * @return */ public Resource getByIdOrHref(String idOrHref) { Resource resource = getById(idOrHref); if (resource == null) { resource = getByHref(idOrHref); } return resource; } /** * Gets the resource with the given href. * If the given href contains a fragmentId then that fragment id will be ignored. * * @param href * @return null if not found. */ public Resource getByHref(String href) { if (StringUtil.isEmpty(href)) { return null; } href = StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR); Resource result = resources.get(href); if(result == null && href.contains("%")) { try { //Do a try with URL decoding the href entry. result = resources.get(URLDecoder.decode(href, Charsets.UTF_8.name())); } catch (Exception e) { //just a quiet try. } } return result; } /** * Gets the first resource (random order) with the give mediatype. * * Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype. * * @param mediaType * @return */ public Resource findFirstResourceByMediaType(MediaType mediaType) { return findFirstResourceByMediaType(resources.values(), mediaType); } /** * Gets the first resource (random order) with the give mediatype. * * Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype. * * @param mediaType * @return */ public static Resource findFirstResourceByMediaType(Collection<Resource> resources, MediaType mediaType) { for (Resource resource: resources) { if (resource.getMediaType() == mediaType) { return resource; } } return null; } /** * All resources that have the given MediaType. * * @param mediaType * @return */ public List<Resource> getResourcesByMediaType(MediaType mediaType) { List<Resource> result = new ArrayList<>(); if (mediaType == null) { return result; } for (Resource resource: getAll()) { if (resource.getMediaType() == mediaType) { result.add(resource); } } return result; } /** * All Resources that match any of the given list of MediaTypes * * @param mediaTypes * @return */ public List<Resource> getResourcesByMediaTypes(MediaType[] mediaTypes) { List<Resource> result = new ArrayList<>(); if (mediaTypes == null) { return result; } // this is the fastest way of doing this according to // http://stackoverflow.com/questions/1128723/in-java-how-can-i-test-if-an-array-contains-a-certain-value List<MediaType> mediaTypesList = Arrays.asList(mediaTypes); for (Resource resource: getAll()) { if (mediaTypesList.contains(resource.getMediaType())) { result.add(resource); } } return result; } public Collection<String> getAllHrefs() { return resources.keySet(); } }