/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.command;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
/**
* Class used to parse custom associations XML files.
* <p>
* Association file parsing is done through the {@link #read(InputStream,AssociationBuilder) read} method, which is
* the only way to interact with this class.
* </p>
* <p>
* Note that while this class knows how to read the content of an association XML file, its role is not to interpret it. This
* is done by instances of {@link AssociationBuilder}.
* </p>
* @see AssociationsXmlConstants
* @see AssociationBuilder
* @see AssociationWriter
* @author Nicolas Rinaudo
*/
public class AssociationReader extends DefaultHandler implements AssociationsXmlConstants {
// - Instance variables --------------------------------------------------
// -----------------------------------------------------------------------
/** Where to send building messages. */
private AssociationBuilder builder;
private boolean isInAssociation;
// - Initialisation ------------------------------------------------------
// -----------------------------------------------------------------------
/**
* Creates a new command reader.
* @param b where to send custom command events.
*/
private AssociationReader(AssociationBuilder b) {builder = b;}
// - XML interaction -----------------------------------------------------
// -----------------------------------------------------------------------
/**
* Parses the content of the specified input stream.
* <p>
* This method will go through the specified input stream and notify the builder of any new association declaration it
* encounters. Note that parsing is done in a very lenient fashion, and perfectly invalid XML files might not raise
* an exception. This is not a flaw in the parser, and both allows muCommander to be error resilient and the associations
* file format to be extended without having to rewrite most of this code.
* </p>
* <p>
* Note that even if an error occurs, both of the builder's {@link AssociationBuilder#startBuilding()} and
* {@link AssociationBuilder#endBuilding()} methods will still be called. Parsing will stop at the first error
* however, so while the builder is guaranteed to receive correct messages, it might not receive all declared
* associations.
* </p>
* @param in where to read association data from.
* @param b where to send building events to.
* @throws IOException if any IO error occurs.
* @see #read(InputStream,AssociationBuilder)
*/
public static void read(InputStream in, AssociationBuilder b) throws IOException, CommandException {
b.startBuilding();
try {SAXParserFactory.newInstance().newSAXParser().parse(in, new AssociationReader(b));}
catch(ParserConfigurationException e) {throw new CommandException(e);}
catch(SAXException e) {throw new CommandException(e);}
finally {b.endBuilding();}
}
// - XML methods ---------------------------------------------------------
// -----------------------------------------------------------------------
/**
* This method is public as an implementation side effect and should not be called directly.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
String buffer;
try {
if(!isInAssociation) {
if(qName.equals(ELEMENT_ASSOCIATION)) {
// Makes sure the required attributes are present.
if((buffer = attributes.getValue(ATTRIBUTE_COMMAND)) == null)
return;
isInAssociation = true;
builder.startAssociation(buffer);
}
}
else {
if(qName.equals(ELEMENT_MASK)) {
String caseSensitive;
if((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null)
return;
if((caseSensitive = attributes.getValue(ATTRIBUTE_CASE_SENSITIVE)) != null)
builder.setMask(buffer, caseSensitive.equals(VALUE_TRUE));
else
builder.setMask(buffer, true);
}
else if(qName.equals(ELEMENT_IS_HIDDEN)) {
if((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null)
return;
builder.setIsHidden(buffer.equals(VALUE_TRUE));
}
else if(qName.equals(ELEMENT_IS_SYMLINK)) {
if((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null)
return;
builder.setIsSymlink(buffer.equals(VALUE_TRUE));
}
else if(qName.equals(ELEMENT_IS_READABLE)) {
if((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null)
return;
builder.setIsReadable(buffer.equals(VALUE_TRUE));
}
else if(qName.equals(ELEMENT_IS_WRITABLE)) {
if((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null)
return;
builder.setIsWritable(buffer.equals(VALUE_TRUE));
}
else if(qName.equals(ELEMENT_IS_EXECUTABLE)) {
if((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null)
return;
builder.setIsExecutable(buffer.equals(VALUE_TRUE));
}
}
}
catch(CommandException e) {throw new SAXException(e);}
}
/**
* This method is public as an implementation side effect, but should not be called directly.
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(qName.equals(ELEMENT_ASSOCIATION) && isInAssociation) {
try {builder.endAssociation();}
catch(CommandException e) {throw new SAXException(e);}
isInAssociation = false;
}
}
}