/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package org.fcrepo.server.utilities; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Iterator; import java.util.NoSuchElementException; /** * Iterable implementation over a stream containing PIDs. * * Allows a stream containing a list of pids to be iterated in a for (String pid : wrapper) statement. * * Stream must contain either: * <ul> * <li>Raw list of PIDs, separated by newlines (blank lines are skipped)</li> * <li>XML containing <pid> elements. Structure of XML is not interpreted, * only single lines containing this element are read. Only one PID per line. * Empty pid elements (<pid/>) will be skipped. * This is compatible with basic search output</li> * </ul> * @author Stephen Bayliss * @version $Id$ */ public final class PIDStreamIterableWrapper implements Iterable<String> { private final PIDStreamIterator m_iterator; public PIDStreamIterableWrapper(InputStream stream) throws IOException { // construct iterator here, as iterator() can't through (checked) exception m_iterator = new PIDStreamIterator(stream); } @Override public Iterator<String> iterator() { return m_iterator; } /** * Iterator over a stream containing PIDs * * Note: does not implement remove(); should not be used as a general-purpose * iterator and only used in context of the enclosing Iterable. * * @author Stephen Bayliss * @version $Id$ */ private class PIDStreamIterator implements Iterator<String> { boolean m_isXML = false; boolean m_started = false; BufferedReader m_reader; String m_nextPID = null; String m_nextLine = null; // text delimiters for PID element in XML file private static final String XML_START = "<pid>"; private static final String XML_END = "</pid>"; @SuppressWarnings("unused") private PIDStreamIterator() { } public PIDStreamIterator(InputStream stream) throws IOException { m_reader = new BufferedReader(new InputStreamReader(stream)); // get first element ready (if there is one) getNext(); } @Override public boolean hasNext() { return (m_nextPID != null); } @Override public String next(){ // return the next element, or flag end of list if (m_nextPID != null) { String next = m_nextPID; // get ready for next time try { getNext(); } catch (IOException e) { // we've already started reading from the stream by now, so this shouldn't really happen throw new RuntimeException("IO Error reading PIDs from stream", e); } return next; } else { throw new NoSuchElementException("End of PIDs"); } } @Override public void remove() { // not applicable, "collection" is not modifiable throw new RuntimeException("method remove() called on" + PIDStreamIterator.class.getCanonicalName()); } /** * read from file until next pid found. Sets m_nextPID to null if there are no more * @throws IOException */ private void getNext() throws IOException { m_nextPID = null; while ((m_nextLine = m_reader.readLine()) != null) { // skip blank lines if (!m_nextLine.trim().isEmpty()) { // first time read, see what kind of file it is if (!m_started) { if (m_nextLine.contains("<")) { m_isXML = true; } else { m_isXML = false; } m_started = true; } // do we have a pid in this line? String nextPID = getPID(m_nextLine); if (nextPID != null) { m_nextPID = nextPID; break; // found one, stop reading } } // continue until pid found or end of input } // either PID has been found, or end of file reached if (m_nextPID == null) m_reader.close(); } /** * Get PID from line of text. Null if not found. * @param line * @return */ private String getPID(String line) { if (m_isXML) { // xml element contents, based on textual delimiters int start = line.indexOf(XML_START); int end = line.indexOf(XML_END); if (start == -1 || end == -1) { return null; } else { return line.substring(start + XML_START.length(), end); } } else { // raw contents of line, ignore leading/trailing whitespace, ignore empty lines if (line.trim().isEmpty()) { return null; } else { return line.trim(); } } } @Override protected void finalize() throws Throwable { // just in case... m_reader.close(); } } }