GMLHandler.java

/*
 * Copyright (c) 2016 Vivid Solutions.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jts.io.gml2;

import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.gml2.GeometryStrategies.ParseStrategy;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;


/**
 * A SAX {@link DefaultHandler} which builds {@link Geometry}s 
 * from GML2-formatted geometries.
 * An XML parser can delegate SAX events to this handler
 * to parse and building Geometrys. 
 * <p>
 * This handler currently ignores both namespaces and prefixes. 
 * 
 * Hints: 
 * <ul>
 * <li>If your parent handler is a DefaultHandler register the parent handler to receive the errors and locator calls.
 * <li>Use {@link GeometryStrategies#findStrategy(String, String)} to help check for applicability
 * </ul>
 * 
 * @see DefaultHandler
 *
 * @author David Zwiers, Vivid Solutions. 
 */
public class GMLHandler extends DefaultHandler {

	/**
	 * This class is intended to log the SAX activity within a given element until its termination.
	 * At this time, a new object of value is created and passed to the parent. 
	 * An object of value is typically either java.lang.* or a JTS Geometry
	 * This class is not intended for use outside this distribution, 
	 * and may change in subsequent versions.
	 *
	 * @author David Zwiers, Vivid Solutions.
	 */
	static class Handler {
		protected Attributes attrs = null;

		protected ParseStrategy strategy;

		/**
		 * @param strategy 
		 * @param attributes Nullable
		 */
		public Handler(ParseStrategy strategy, Attributes attributes) {
			if (attributes != null)
				this.attrs = new AttributesImpl(attributes);
			this.strategy = strategy;
		}

		protected StringBuffer text = null;

		/**
		 * Caches text for the future
		 * @param str
		 */
		public void addText(String str) {
			if (text == null)
				text = new StringBuffer();
			text.append(str);
		}

		protected List children = null;

		/**
		 * Store param for the future
		 * 
		 * @param obj
		 */
		public void keep(Object obj) {
			if (children == null)
				children = new LinkedList();
			children.add(obj);

		}

		/**
		 * @param gf GeometryFactory
		 * @return Parsed Object
		 * @throws SAXException 
		 */
		public Object create(GeometryFactory gf) throws SAXException {
			return strategy.parse(this, gf);
		}
	}

	private Stack stack = new Stack();

	private ErrorHandler delegate = null;

	private GeometryFactory gf = null;

	/**
	 * Creates a new handler.
	 * Allows the user to specify a delegate object for error / warning messages. 
	 * If the delegate also implements ContentHandler then the document Locator will be passed on.
	 * 
	 * @param gf Geometry Factory
	 * @param delegate Nullable
	 * 
	 * @see ErrorHandler
	 * @see ContentHandler
	 * @see ContentHandler#setDocumentLocator(org.xml.sax.Locator)
	 * @see org.xml.sax.Locator
	 * 
	 */
	public GMLHandler(GeometryFactory gf, ErrorHandler delegate) {
		this.delegate = delegate;
		this.gf = gf;
		stack.push(new Handler(null, null));
	}

	/**
	 * Tests whether this handler has completed parsing 
	 * a geometry.
	 * If this is the case, {@link #getGeometry()} can be called
	 * to get the value of the parsed geometry.
	 * 
	 * @return if the parsing of the geometry is complete
	 */
	public boolean isGeometryComplete()
	{
		if (stack.size() > 1)
			return false;
		// top level node on stack needs to have at least one child 
		Handler h = (Handler) stack.peek();
		if (h.children.size() < 1)
			return false;
		return true;
		
	}
	
	/**
	 * Gets the geometry parsed by this handler.
	 * This method should only be called AFTER the parser has completed execution
	 * 
	 * @return the parsed Geometry, or a GeometryCollection if more than one geometry was parsed
	 * @throws IllegalStateException if called before the parse is complete
	 */
	public Geometry getGeometry() {
		if (stack.size() == 1) {
			Handler h = (Handler) stack.peek();
			if (h.children.size() == 1)
				return (Geometry) h.children.get(0);
			return gf.createGeometryCollection(
					(Geometry[]) h.children.toArray(new Geometry[stack.size()]));
		}
		throw new IllegalStateException(
				"Parse did not complete as expected, there are " + stack.size()
						+ " elements on the Stack");
	}

	//////////////////////////////////////////////
	// Parsing Methods

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
	 */
	public void characters(char[] ch, int start, int length) throws SAXException {
		if (!stack.isEmpty())
			((Handler) stack.peek()).addText(new String(ch, start, length));
	}

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#ignorableWhitespace(char[], int, int)
	 */
	public void ignorableWhitespace(char[] ch, int start, int length)
			throws SAXException {
		if (!stack.isEmpty())
			((Handler) stack.peek()).addText(" ");
	}

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
	 */
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		Handler thisAction = (Handler) stack.pop();
		((Handler) stack.peek()).keep(thisAction.create(gf));
	}

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		// create a handler
		ParseStrategy ps = GeometryStrategies.findStrategy(uri, localName);
		if (ps == null) {
			String qn = qName.substring(qName.indexOf(':') + 1, qName.length());
			ps = GeometryStrategies.findStrategy(null, qn);
		}
		Handler h = new Handler(ps, attributes);
		// and add it to the stack
		stack.push(h);
	}

	//////////////////////////////////////////////
	// Logging Methods

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
	 */
	public void setDocumentLocator(Locator locator) {
		this.locator = locator;
		if (delegate instanceof ContentHandler)
			((ContentHandler) delegate).setDocumentLocator(locator);

	}

	private Locator locator = null;

	protected Locator getDocumentLocator() {
		return locator;
	}

	//////////////////////////////////////////////
	// ERROR Methods

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
	 */
	public void fatalError(SAXParseException e) throws SAXException {
		if (delegate != null)
			delegate.fatalError(e);
		else
			super.fatalError(e);
	}

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
	 */
	public void error(SAXParseException e) throws SAXException {
		if (delegate != null)
			delegate.error(e);
		else
			super.error(e);
	}

	/**
	 * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
	 */
	public void warning(SAXParseException e) throws SAXException {
		if (delegate != null)
			delegate.warning(e);
		else
			super.warning(e);
	}

}