
org.openscience.cdk.layout.TemplateHandler Maven / Gradle / Ivy
/* Copyright (C) 2003-2005 Christoph Steinbeck
* 2003-2008 Egon Willighagen
* Stefan Kuhn
* Rajarshi Guha
* 2015 John May
*
* Contact: [email protected]
*
* 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.layout;
import com.google.common.collect.FluentIterable;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.geometry.GeometryUtil;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IAtomContainerSet;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemFile;
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.interfaces.IChemObjectBuilder;
import org.openscience.cdk.io.CMLReader;
import org.openscience.cdk.isomorphism.AtomMatcher;
import org.openscience.cdk.isomorphism.BondMatcher;
import org.openscience.cdk.isomorphism.Mappings;
import org.openscience.cdk.isomorphism.Pattern;
import org.openscience.cdk.isomorphism.VentoFoggia;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
import org.openscience.cdk.tools.manipulator.ChemFileManipulator;
import javax.vecmath.Point2d;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Helper class for Structure Diagram Generation. Handles templates. This is
* our layout solution for ring systems which are notoriously difficult to
* layout, like cubane, adamantane, porphyrin, etc.
*
* @author steinbeck
* @cdk.created 2003-09-04
* @cdk.keyword layout
* @cdk.keyword 2D-coordinates
* @cdk.keyword structure diagram generation
* @cdk.require java1.4+
* @cdk.module sdg
* @cdk.githash
*/
public final class TemplateHandler {
private final static ILoggingTool LOGGER = LoggingToolFactory.createLoggingTool(TemplateHandler.class);
private final List templates = new ArrayList<>();
private final List anonPatterns = new ArrayList<>();
private final List elemPatterns = new ArrayList<>();
private final AtomMatcher elemAtomMatcher = new AtomMatcher() {
@Override
public boolean matches(IAtom a, IAtom b) {
return a.getAtomicNumber().equals(b.getAtomicNumber());
}
};
private final AtomMatcher anonAtomMatcher = new AtomMatcher() {
@Override
public boolean matches(IAtom a, IAtom b) {
return true;
}
};
private final BondMatcher anonBondMatcher = new BondMatcher() {
@Override
public boolean matches(IBond a, IBond b) {
return true;
}
};
/**
* Creates a new TemplateHandler with default templates loaded.
*/
public TemplateHandler(IChemObjectBuilder builder) {
loadTemplates(builder);
}
/**
* Creates a new TemplateHandler without any default templates.
*/
public TemplateHandler() {
}
/**
* Loads all existing templates into memory. To add templates to be used in
* SDG, place a drawing with the new template in org/openscience/cdk/layout/templates and add the
* template filename to org/openscience/cdk/layout/templates/template.list
*/
public void loadTemplates(IChemObjectBuilder builder) {
String line = null;
try {
InputStream ins = this.getClass().getClassLoader()
.getResourceAsStream("org/openscience/cdk/layout/templates/templates.list");
BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
while (reader.ready()) {
line = reader.readLine();
line = "org/openscience/cdk/layout/templates/" + line;
LOGGER.debug("Attempting to read template ", line);
try {
CMLReader structureReader = new CMLReader(this.getClass().getClassLoader()
.getResourceAsStream(line));
IChemFile file = (IChemFile) structureReader.read(builder.newInstance(IChemFile.class));
List files = ChemFileManipulator.getAllAtomContainers(file);
for (int i = 0; i < files.size(); i++)
addMolecule(files.get(i));
LOGGER.debug("Successfully read template ", line);
} catch (CDKException | IllegalArgumentException e) {
LOGGER.warn("Could not read template ", line, ", reason: ", e.getMessage());
LOGGER.debug(e);
}
}
} catch (IOException e) {
LOGGER.warn("Could not read (all of the) templates, reason: ", e.getMessage());
LOGGER.debug(e);
}
}
/**
* Adds a Molecule to the list of templates use by this TemplateHandler.
*
* @param molecule The molecule to be added to the TemplateHandler
*/
public void addMolecule(IAtomContainer molecule) {
if (!GeometryUtil.has2DCoordinates(molecule))
throw new IllegalArgumentException("Template did not have 2D coordinates");
// we want a consistent scale!
GeometryUtil.scaleMolecule(molecule, GeometryUtil.getScaleFactor(molecule,
StructureDiagramGenerator.DEFAULT_BOND_LENGTH));
templates.add(molecule);
anonPatterns.add(VentoFoggia.findSubstructure(molecule,
anonAtomMatcher,
anonBondMatcher));
elemPatterns.add(VentoFoggia.findSubstructure(molecule,
elemAtomMatcher,
anonBondMatcher));
}
public IAtomContainer removeMolecule(IAtomContainer molecule) throws CDKException {
for (int i = 0; i < templates.size(); i++) {
if (VentoFoggia.findIdentical(templates.get(i), anonAtomMatcher, anonBondMatcher)
.matches(molecule)) {
elemPatterns.remove(i);
anonPatterns.remove(i);
return templates.remove(i);
}
}
return null;
}
/**
* Checks if one of the loaded templates is isomorph to the given
* Molecule. If so, it assigns the coordinates from the template to the
* respective atoms in the Molecule, and marks the atoms as ISPLACED.
*
* @param molecule The molecule to be check for potential templates
* @return True if there was a possible mapping
*/
public boolean mapTemplateExact(IAtomContainer molecule) throws CDKException {
for (IAtomContainer template : templates) {
Mappings mappings = VentoFoggia.findIdentical(template, anonAtomMatcher, anonBondMatcher)
.matchAll(molecule);
for (Map atoms : mappings.toAtomMap()) {
for (Map.Entry e : atoms.entrySet()) {
e.getValue().setPoint2d(new Point2d(e.getKey().getPoint2d()));
e.getValue().setFlag(CDKConstants.ISPLACED, true);
}
if (!atoms.isEmpty())
return true;
}
}
return false;
}
/**
* Checks if one of the loaded templates is a substructure in the given
* Molecule. If so, it assigns the coordinates from the template to the
* respective atoms in the Molecule, and marks the atoms as ISPLACED.
*
* @param molecule The molecule to be check for potential templates
* @return True if there was a possible mapping
*/
public boolean mapTemplates(IAtomContainer molecule) throws CDKException {
// match element patterns first so hetero atoms are oriented correctly
for (Pattern anonPattern : elemPatterns) {
for (Map atoms : anonPattern.matchAll(molecule).toAtomMap()) {
for (Map.Entry e : atoms.entrySet()) {
e.getValue().setPoint2d(new Point2d(e.getKey().getPoint2d()));
e.getValue().setFlag(CDKConstants.ISPLACED, true);
}
if (!atoms.isEmpty())
return true;
}
}
// no element pattern matched, try anonymous patterns
for (Pattern anonPattern : anonPatterns) {
for (Map atoms : anonPattern.matchAll(molecule).toAtomMap()) {
for (Map.Entry e : atoms.entrySet()) {
e.getValue().setPoint2d(new Point2d(e.getKey().getPoint2d()));
e.getValue().setFlag(CDKConstants.ISPLACED, true);
}
if (!atoms.isEmpty())
return true;
}
}
return false;
}
/**
* Gets the templateCount attribute of the TemplateHandler object
*
* @return The templateCount value
*/
public int getTemplateCount() {
return templates.size();
}
/**
* Gets the templateAt attribute of the TemplateHandler object
*
* @param position Description of the Parameter
* @return The templateAt value
*/
public IAtomContainer getTemplateAt(int position) {
return templates.get(position);
}
/**
* Checks if one of the loaded templates is a substructure in the given
* Molecule and returns all matched substructures in a IAtomContainerSet.
* This method does not assign any coordinates.
*
* @param molecule The molecule to be check for potential templates
* @return an IAtomContainerSet of all matched substructures of
* the molecule
* @throws CDKException if an error occurs
*/
public IAtomContainerSet getMappedSubstructures(IAtomContainer molecule) throws CDKException {
final IAtomContainerSet matchedSubstructures = molecule.getBuilder().newInstance(IAtomContainerSet.class);
final Set matchedChemObjs = new HashSet<>();
for (Pattern anonPattern : anonPatterns) {
for (Map map : anonPattern.matchAll(molecule)
.uniqueAtoms()
.toAtomBondMap()) {
boolean overlaps = false;
IAtomContainer matched = molecule.getBuilder()
.newInstance(IAtomContainer.class);
for (Map.Entry e : map.entrySet()) {
if (matchedChemObjs.contains(e.getValue()))
overlaps = true;
if (e.getValue() instanceof IAtom)
matched.addAtom((IAtom) e.getValue());
else if (e.getValue() instanceof IBond)
matched.addBond((IBond) e.getValue());
}
// only add if the atoms/bonds of this match don't overlap existing
if (!overlaps) {
matchedChemObjs.addAll(FluentIterable.from(matched.atoms()).toList());
matchedChemObjs.addAll(FluentIterable.from(matched.bonds()).toList());
matchedSubstructures.addAtomContainer(matched);
}
}
}
return matchedSubstructures;
}
/**
* Singleton template instance, mainly useful for aligning molecules. If the template
* does not have coordinates an error is thrown.
*
* For safety we clone the molecule.
*
* @param template the molecule
* @return new template handler
*/
public static TemplateHandler createSingleton(IAtomContainer template) {
try {
TemplateHandler handler = new TemplateHandler();
IAtomContainer copy = template.clone();
handler.addMolecule(copy);
return handler;
} catch (CloneNotSupportedException e) {
throw new IllegalArgumentException("Could not clone molecule.");
}
}
/**
* Create a template from a substructure pattern. Using this template handler in the diagram
* generator then allows us to align to common reference.
*
* @param ptrn the structure pattern to match
* @param mols list of molecules
* @return new template handler
*/
public static TemplateHandler createFromSubstructure(Pattern ptrn, Iterable mols) {
for (IAtomContainer mol : mols) {
for (IAtomContainer template : ptrn.matchAll(mol).toSubstructures())
return createSingleton(template);
}
throw new IllegalArgumentException("Pattern does not match any provided molecules");
}
/**
* Create a template from a substructure pattern. Using this template handler in the diagram
* generator then allows us to align to common reference.
*
* @param ptrn the structure pattern to match
* @param mol molecule
* @return new template handler
*/
public static TemplateHandler createFromSubstructure(Pattern ptrn, IAtomContainer mol) {
for (IAtomContainer template : ptrn.matchAll(mol).toSubstructures())
return createSingleton(template);
throw new IllegalArgumentException("Pattern does not match any provided molecules");
}
/**
* Convert to an identity template library.
*
* @return identity template library
*/
IdentityTemplateLibrary toIdentityTemplateLibrary() {
IdentityTemplateLibrary lib = IdentityTemplateLibrary.empty();
for (IAtomContainer mol : templates) {
lib.add(mol);
lib.add(AtomContainerManipulator.anonymise(mol));
}
return lib;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy