Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id: DSCParser.java 1345683 2012-06-03 14:50:33Z gadams $ */ package org.apache.xmlgraphics.ps.dsc; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.ps.DSCConstants; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.xmlgraphics.ps.dsc.events.DSCAtend; import org.apache.xmlgraphics.ps.dsc.events.DSCComment; import org.apache.xmlgraphics.ps.dsc.events.DSCEvent; import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment; import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment; import org.apache.xmlgraphics.ps.dsc.events.PostScriptLine; import org.apache.xmlgraphics.ps.dsc.events.UnparsedDSCComment; import org.apache.xmlgraphics.ps.dsc.tools.DSCTools; /** * Parser for DSC-compliant PostScript files (DSC = Document Structuring Conventions). The parser * is implemented as a pull parser but has the ability to act as a push parser through the * DSCHandler interface. */ public class DSCParser implements DSCParserConstants { private static final Log LOG = LogFactory.getLog(DSCParser.class); private InputStream in; private BufferedReader reader; private boolean eofFound = false; private boolean checkEOF = true; private DSCEvent currentEvent; private DSCEvent nextEvent; private DSCListener nestedDocumentHandler; private DSCListener filterListener; private List listeners; private boolean listenersDisabled = false; /** * Creates a new DSC parser. * @param in InputStream to read the PostScript file from * (the stream is not closed by this class, the caller is responsible for that) * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ public DSCParser(InputStream in) throws IOException, DSCException { if (in.markSupported()) { this.in = in; } else { //Decorate for better performance this.in = new java.io.BufferedInputStream(this.in); } String encoding = "US-ASCII"; try { this.reader = new java.io.BufferedReader(new java.io.InputStreamReader(this.in, encoding)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Incompatible VM! " + e.getMessage()); } parseNext(); } /** * Returns the InputStream the PostScript code is read from. * @return the InputStream the PostScript code is read from */ public InputStream getInputStream() { return this.in; } /** * This method is used to write out warning messages for the parsing process. Subclass to * override this method. The default implementation writes to logger. * @param msg the warning message */ protected void warn(String msg) { LOG.warn(msg); } /** * Reads one line from the input file * @return the line or null if there are no more lines * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ protected String readLine() throws IOException, DSCException { String line; line = this.reader.readLine(); checkLine(line); return line; } private void checkLine(String line) throws DSCException { if (line == null) { if (!eofFound) { throw new DSCException("%%EOF not found. File is not well-formed."); } } else if (line.length() > 255) { warn("Line longer than 255 characters. This file is not fully PostScript conforming."); } } private boolean isWhitespace(char c) { return c == ' ' || c == '\t'; } private DSCComment parseDSCLine(String line) throws IOException, DSCException { int colon = line.indexOf(':'); String name; String value = ""; if (colon > 0) { name = line.substring(2, colon); int startOfValue = colon + 1; if (startOfValue < line.length()) { if (isWhitespace(line.charAt(startOfValue))) { startOfValue++; } value = line.substring(startOfValue).trim(); if (value.equals(DSCConstants.ATEND.toString())) { return new DSCAtend(name); } } String nextLine; while (true) { this.reader.mark(512); nextLine = readLine(); if (nextLine == null) { break; } else if (!nextLine.startsWith("%%+")) { break; } value = value + nextLine.substring(3); } this.reader.reset(); } else { name = line.substring(2); value = null; } return parseDSCComment(name, value); } private DSCComment parseDSCComment(String name, String value) { DSCComment parsed = DSCCommentFactory.createDSCCommentFor(name); if (parsed != null) { try { parsed.parseValue(value); return parsed; } catch (Exception e) { //ignore and fall back to unparsed DSC comment } } UnparsedDSCComment unparsed = new UnparsedDSCComment(name); unparsed.parseValue(value); return unparsed; } /** * Starts the parser in push parsing mode sending events to the DSCHandler instance. * @param handler the DSCHandler instance to send the events to * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ public void parse(DSCHandler handler) throws IOException, DSCException { DSCHeaderComment header = DSCTools.checkAndSkipDSC30Header(this); handler.startDocument("%!" + header.getComment()); DSCEvent event; while (hasNext()) { event = nextEvent(); switch (event.getEventType()) { case HEADER_COMMENT: handler.startDocument("%!" + ((DSCHeaderComment) event).getComment()); break; case DSC_COMMENT: handler.handleDSCComment(event.asDSCComment()); break; case COMMENT: handler.comment(((PostScriptComment) event).getComment()); break; case LINE: handler.line(getLine()); break; case EOF: if (isCheckEOF()) { this.eofFound = true; } handler.endDocument(); break; default: throw new IllegalStateException("Illegal event type: " + event.getEventType()); } } } /** * Indicates whether there are additional items. * @return true if there are additonal items, false if the end of the file has been reached */ public boolean hasNext() { return (this.nextEvent != null); } /** * Steps to the next item indicating the type of event. * @return the type of event (See {@link DSCParserConstants}) * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec * @throws NoSuchElementException If an attempt was made to advance beyond the end of the file */ public int next() throws IOException, DSCException { if (hasNext()) { this.currentEvent = nextEvent; parseNext(); processListeners(); return this.currentEvent.getEventType(); } else { throw new NoSuchElementException("There are no more events"); } } private void processListeners() throws IOException, DSCException { if (isListenersDisabled()) { return; } if (this.filterListener != null) { //Filter always comes first this.filterListener.processEvent(this.currentEvent, this); } if (this.listeners != null) { Iterator iter = this.listeners.iterator(); while (iter.hasNext()) { ((DSCListener) iter.next()).processEvent(this.currentEvent, this); } } } /** * Steps to the next item returning the new event. * @return the new event * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ public DSCEvent nextEvent() throws IOException, DSCException { next(); return getCurrentEvent(); } /** * Returns the current event. * @return the current event */ public DSCEvent getCurrentEvent() { return this.currentEvent; } /** * Returns the next event without moving the cursor to the next event. * @return the next event */ public DSCEvent peek() { return this.nextEvent; } /** * Parses the next event. * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ protected void parseNext() throws IOException, DSCException { String line = readLine(); if (line != null) { if (eofFound && (line.length() > 0)) { throw new DSCException("Content found after EOF"); } if (line.startsWith("%%")) { DSCComment comment = parseDSCLine(line); if (comment.getEventType() == EOF && isCheckEOF()) { this.eofFound = true; } this.nextEvent = comment; } else if (line.startsWith("%!")) { this.nextEvent = new DSCHeaderComment(line.substring(2)); } else if (line.startsWith("%")) { this.nextEvent = new PostScriptComment(line.substring(1)); } else { this.nextEvent = new PostScriptLine(line); } } else { this.nextEvent = null; } } /** * Returns the current PostScript line. * @return the current PostScript line * @throws IllegalStateException if the current event is not a normal PostScript line */ public String getLine() { if (this.currentEvent.getEventType() == LINE) { return ((PostScriptLine) this.currentEvent).getLine(); } else { throw new IllegalStateException("Current event is not a PostScript line"); } } /** * Advances to the next DSC comment with the given name. * @param name the name of the DSC comment * @return the requested DSC comment or null if the end of the file is reached * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ public DSCComment nextDSCComment(String name) throws IOException, DSCException { return nextDSCComment(name, null); } /** * Advances to the next DSC comment with the given name. * @param name the name of the DSC comment * @param gen PSGenerator to pass the skipped events though to * @return the requested DSC comment or null if the end of the file is reached * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ public DSCComment nextDSCComment(String name, PSGenerator gen) throws IOException, DSCException { while (hasNext()) { DSCEvent event = nextEvent(); if (event.isDSCComment()) { DSCComment comment = event.asDSCComment(); if (name.equals(comment.getName())) { return comment; } } if (gen != null) { event.generate(gen); //Pipe through to PSGenerator } } return null; } /** * Advances to the next PostScript comment with the given prefix. This is used to find * comments following the DSC extension mechanism. * <p> * Example: To find FOP's custom comments, pass in "FOP" as a prefix. This will find comments * like "%FOPFontSetup". * @param prefix the prefix of the extension comment * @param gen PSGenerator to pass the skipped events though to * @return the requested PostScript comment or null if the end of the file is reached * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */ public PostScriptComment nextPSComment(String prefix, PSGenerator gen) throws IOException, DSCException { while (hasNext()) { DSCEvent event = nextEvent(); if (event.isComment()) { PostScriptComment comment = (PostScriptComment) event; if (comment.getComment().startsWith(prefix)) { return comment; } } if (gen != null) { event.generate(gen); //Pipe through to PSGenerator } } return null; } /** * Sets a filter for DSC events. * @param filter the filter to use or null to disable filtering */ public void setFilter(DSCFilter filter) { if (filter != null) { this.filterListener = new FilteringEventListener(filter); } else { this.filterListener = null; } } /** * Adds a DSC event listener. * @param listener the listener */ public void addListener(DSCListener listener) { if (listener == null) { throw new NullPointerException("listener must not be null"); } if (this.listeners == null) { this.listeners = new java.util.ArrayList(); } this.listeners.add(listener); } /** * Removes a DSC event listener. * @param listener the listener to remove */ public void removeListener(DSCListener listener) { if (this.listeners != null) { this.listeners.remove(listener); } } /** * Allows to disable all listeners. This can be used to disable any filtering, for example in * nested documents. * @param value true to disable all listeners, false to re-enable them */ public void setListenersDisabled(boolean value) { this.listenersDisabled = value; } /** * Indicates whether the listeners are currently disabled. * @return true if they are disabled */ public boolean isListenersDisabled() { return this.listenersDisabled; } /** * Sets a NestedDocumentHandler which is used to skip nested documents like embedded EPS files. * You can also process those parts in a special way. * <p> * It is suggested to use the more generally usable {@link #addListener(DSCListener)} and * {@link #removeListener(DSCListener)} instead. NestedDocumentHandler is internally * mapped onto a {@link DSCListener}. * @param handler the NestedDocumentHandler instance or null to disable the feature */ public void setNestedDocumentHandler(final NestedDocumentHandler handler) { if (handler == null) { removeListener(this.nestedDocumentHandler); } else { addListener(new DSCListener() { public void processEvent(DSCEvent event, DSCParser parser) throws IOException, DSCException { handler.handle(event, parser); } }); } } /** * Tells the parser whether to check for content after the EOF comment. * This can be disabled to skip nested documents. * @param value true if the check is enabled */ public void setCheckEOF(boolean value) { this.checkEOF = value; } /** * Indicates whether the parser is configured to check for content after the EOF comment. * @return true if the check is enabled. */ public boolean isCheckEOF() { return this.checkEOF; } }