/* $Revision$ $Author$ $Date$
*
* Copyright (C) 2001-2007 Egon Willighagen <egonw@users.sf.net>
* Stefan Kuhn <shk3@users.sf.net>
* Miguel Rojas-Cherto <miguelrojasch@users.sf.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
* All we ask is that proper credit is given for our work, which includes
* - but is not limited to - adding the above copyright notice to the beginning
* of your source code files, and to any copyright notice that you may distribute
* with programs based on this work.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk.io;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Serializer;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemFile;
import org.openscience.cdk.interfaces.IChemModel;
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.interfaces.IChemSequence;
import org.openscience.cdk.interfaces.ICrystal;
import org.openscience.cdk.interfaces.IMolecule;
import org.openscience.cdk.interfaces.IMoleculeSet;
import org.openscience.cdk.interfaces.IPDBPolymer;
import org.openscience.cdk.interfaces.IReaction;
import org.openscience.cdk.interfaces.IReactionScheme;
import org.openscience.cdk.interfaces.IReactionSet;
import org.openscience.cdk.io.cml.CustomSerializer;
import org.openscience.cdk.io.formats.CMLFormat;
import org.openscience.cdk.io.formats.IResourceFormat;
import org.openscience.cdk.io.setting.BooleanIOSetting;
import org.openscience.cdk.io.setting.IOSetting;
import org.openscience.cdk.io.setting.StringIOSetting;
import org.openscience.cdk.libio.cml.Convertor;
import org.openscience.cdk.libio.cml.ICMLCustomizer;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
/**
* Serializes a {@link IMoleculeSet} or a {@link IMolecule} object to CML 2 code.
* Chemical Markup Language is an XML-based file format {@cdk.cite PMR99}.
* Output can be redirected to other Writer objects like {@link StringWriter}
* and {@link FileWriter}. An example:
*
* <pre>
* StringWriter output = new StringWriter();
* boolean makeFragment = true;
* CMLWriter cmlwriter = new CMLWriter(output, makeFragment);
* cmlwriter.write(molecule);
* cmlwriter.close();
* String cmlcode = output.toString();
* </pre>
*
* <p>Output to a file called "molecule.cml" can done with:
*
* <pre>
* FileWriter output = new FileWriter("molecule.cml");
* CMLWriter cmlwriter = new CMLWriter(output);
* cmlwriter.write(molecule);
* cmlwriter.close();
* </pre>
*
* <p>For atoms it outputs: coordinates, element type and formal charge.
* For bonds it outputs: order, atoms (2, or more) and wedges.
*
* @cdk.module libiocml
* @cdk.githash
* @cdk.builddepends xom-1.0.jar
* @cdk.depends jumbo50.jar
* @cdk.require java1.5+
* @cdk.bug 1565563
*
* @see java.io.FileWriter
* @see java.io.StringWriter
*
* @author Egon Willighagen
*
* @cdk.keyword file format, CML
*/
@TestClass("org.openscience.cdk.io.CMLWriterTest")
public class CMLWriter extends DefaultChemObjectWriter {
private OutputStream output;
private Writer writer;
private BooleanIOSetting cmlIds;
private BooleanIOSetting namespacedOutput;
private StringIOSetting namespacePrefix;
private BooleanIOSetting schemaInstanceOutput;
private StringIOSetting instanceLocation;
private BooleanIOSetting indent;
private BooleanIOSetting xmlDeclaration;
private static ILoggingTool logger =
LoggingToolFactory.createLoggingTool(CMLWriter.class);
private static List<ICMLCustomizer> customizers = null;
/**
* Constructs a new CMLWriter class. Output will be stored in the Writer
* class given as parameter. The CML code will be valid CML code with a
* XML header. Only one object can be stored.
*
* @param out Writer to redirect the output to.
*/
public CMLWriter(Writer out) {
this.writer = out;
output = new OutputStream() {
public void write(int anInt) throws IOException {
writer.write(anInt);
}
};
initIOSettings();
}
public CMLWriter(OutputStream output) {
this.output = output;
writer = null;
initIOSettings();
}
public CMLWriter() {
this(new StringWriter());
}
public void registerCustomizer(ICMLCustomizer customizer) {
if (customizers == null) customizers = new ArrayList<ICMLCustomizer>();
customizers.add(customizer);
logger.info("Loaded Customizer: ", customizer.getClass().getName());
}
@TestMethod("testGetFormat")
public IResourceFormat getFormat() {
return CMLFormat.getInstance();
}
public void setWriter(Writer writer) throws CDKException {
this.writer = writer;
}
public void setWriter(OutputStream output) throws CDKException {
//TODO dont know what the intention here is, but without this line it is not working...
this.output = output;
setWriter(new OutputStreamWriter(output));
}
/**
* Flushes the output and closes this object
*/
@TestMethod("testClose")
public void close() throws IOException {
output.close();
}
@TestMethod("testAccepts")
public boolean accepts(Class classObject) {
Class[] interfaces = classObject.getInterfaces();
for (int i=0; i<interfaces.length; i++) {
if (IAtom.class.equals(interfaces[i])) return true;
if (IBond.class.equals(interfaces[i])) return true;
if (ICrystal.class.equals(interfaces[i])) return true;
if (IChemModel.class.equals(interfaces[i])) return true;
if (IChemFile.class.equals(interfaces[i])) return true;
if (IChemSequence.class.equals(interfaces[i])) return true;
if (IMoleculeSet.class.equals(interfaces[i])) return true;
if (IReactionSet.class.equals(interfaces[i])) return true;
if (IReaction.class.equals(interfaces[i])) return true;
}
return false;
}
/**
* Serializes the IChemObject to CML and redirects it to the output Writer.
*
* @param object A Molecule of MoleculeSet object
*/
public void write(IChemObject object) throws CDKException {
if (!(object instanceof IMolecule) &&
!(object instanceof IAtomContainer) &&
!(object instanceof IReaction) &&
!(object instanceof IReactionSet) &&
!(object instanceof IMoleculeSet) &&
!(object instanceof IChemSequence) &&
!(object instanceof IChemModel) &&
!(object instanceof IChemFile) &&
!(object instanceof ICrystal) &&
!(object instanceof IAtom) &&
!(object instanceof IBond)) {
throw new CDKException("Cannot write this unsupported IChemObject: " + object.getClass().getName());
}
logger.debug("Writing object in CML of type: ", object.getClass().getName());
customizeJob();
Convertor convertor = new Convertor(
cmlIds.isSet(),
(namespacePrefix.getSetting().length() >0) ? namespacePrefix.getSetting() : null
);
// adding the customizer
if (customizers != null) {
for (ICMLCustomizer customizer : customizers) {
convertor.registerCustomizer(customizer);
}
}
// now convert the object
Element root = null;
if (object instanceof IPDBPolymer) {
root = convertor.cdkPDBPolymerToCMLMolecule((IPDBPolymer)object);
} else if (object instanceof IMolecule) {
root = convertor.cdkMoleculeToCMLMolecule((IMolecule)object);
} else if (object instanceof ICrystal) {
root = convertor.cdkCrystalToCMLMolecule((ICrystal)object);
} else if (object instanceof IAtom) {
root = convertor.cdkAtomToCMLAtom(null, (IAtom)object);
} else if (object instanceof IBond) {
root = convertor.cdkBondToCMLBond((IBond)object);
} else if (object instanceof IReaction) {
root = convertor.cdkReactionToCMLReaction((IReaction)object);
} else if (object instanceof IReactionScheme){
root = convertor.cdkReactionSchemeToCMLReactionSchemeAndMoleculeList((IReactionScheme)object);
} else if (object instanceof IReactionSet) {
root = convertor.cdkReactionSetToCMLReactionList((IReactionSet)object);
} else if (object instanceof IMoleculeSet) {
root = convertor.cdkMoleculeSetToCMLList((IMoleculeSet)object);
} else if (object instanceof IChemSequence) {
root = convertor.cdkChemSequenceToCMLList((IChemSequence)object);
} else if (object instanceof IChemModel) {
root = convertor.cdkChemModelToCMLList((IChemModel)object);
} else if (object instanceof IAtomContainer) {
root = convertor.cdkAtomContainerToCMLMolecule((IAtomContainer)object);
} else if (object instanceof IChemFile) {
root = convertor.cdkChemFileToCMLList((IChemFile)object);
}
Document doc = new Document(root);
try {
Serializer serializer = null;
if (xmlDeclaration.isSet()) {
serializer = new Serializer(output, "ISO-8859-1");
} else {
serializer = new CustomSerializer(output, "ISO-8859-1");
}
if (indent.isSet()) {
logger.info("Indenting XML output");
serializer.setIndent(2);
}
if (schemaInstanceOutput.isSet()) {
root.addNamespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");
root.addAttribute(new Attribute(
"xsi:schemaLocation=",
"http://www.w3.org/2001/XMLSchema-instance",
"http://www.xml-cml.org/schema/cml2/core " + instanceLocation.getSetting()
));
}
serializer.write(doc);
} catch (Exception exception) {
throw new CDKException("Could not write XML output: " + exception.getMessage(), exception);
}
}
private void initIOSettings() {
cmlIds = new BooleanIOSetting("CMLIDs", IOSetting.LOW,
"Should the output use CML identifiers?",
"true");
namespacedOutput = new BooleanIOSetting("NamespacedOutput", IOSetting.LOW,
"Should the output use namespaced output?",
"true");
namespacePrefix = new StringIOSetting("NamespacePrefix", IOSetting.LOW,
"What should the namespace prefix be? [empty is no prefix]",
"");
schemaInstanceOutput = new BooleanIOSetting("SchemaInstance", IOSetting.LOW,
"Should the output use the Schema-Instance attribute?",
"false");
instanceLocation = new StringIOSetting("InstanceLocation", IOSetting.LOW,
"Where is the schema found?",
"");
indent = new BooleanIOSetting("Indenting", IOSetting.LOW,
"Should the output be indented?",
"true");
xmlDeclaration = new BooleanIOSetting("XMLDeclaration", IOSetting.LOW,
"Should the output contain an XML declaration?",
"true");
}
private void customizeJob() {
fireIOSettingQuestion(cmlIds);
fireIOSettingQuestion(namespacedOutput);
if (namespacedOutput.isSet()) {
fireIOSettingQuestion(namespacePrefix);
}
fireIOSettingQuestion(schemaInstanceOutput);
if (schemaInstanceOutput.isSet()) {
fireIOSettingQuestion(instanceLocation);
}
fireIOSettingQuestion(indent);
fireIOSettingQuestion(xmlDeclaration);
}
public IOSetting[] getIOSettings() {
IOSetting[] settings = new IOSetting[7];
settings[0] = cmlIds;
settings[1] = namespacedOutput;
settings[2] = namespacePrefix;
settings[3] = schemaInstanceOutput;
settings[4] = instanceLocation;
settings[5] = indent;
settings[6] = xmlDeclaration;
return settings;
}
}