Java tutorial
/* * $RCSfile$ * $Author: egonw $ * $Date: 2007-01-04 17:26:00 +0000 (Thu, 04 Jan 2007) $ * $Revision: 7634 $ * * Copyright (C) 1997-2008 Stefan Kuhn * Some portions Copyright (C) 2009 Konstantin Tokarev * * Contact: cdk-jchempaint@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.jchempaint.application; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.UIManager; import javax.vecmath.Point2d; import javax.vecmath.Vector2d; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.PosixParser; import org.apache.commons.cli.UnrecognizedOptionException; import org.openscience.cdk.ChemFile; import org.openscience.cdk.ChemModel; import org.openscience.cdk.DefaultChemObjectBuilder; import org.openscience.cdk.Molecule; import org.openscience.cdk.MoleculeSet; import org.openscience.cdk.atomtype.CDKAtomTypeMatcher; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.geometry.GeometryTools; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; import org.openscience.cdk.interfaces.IAtomType; import org.openscience.cdk.interfaces.IChemFile; import org.openscience.cdk.interfaces.IChemModel; import org.openscience.cdk.interfaces.IChemObject; import org.openscience.cdk.interfaces.IMolecule; import org.openscience.cdk.interfaces.IMoleculeSet; import org.openscience.cdk.interfaces.IPseudoAtom; import org.openscience.cdk.interfaces.IReaction; import org.openscience.cdk.interfaces.IReactionSet; import org.openscience.cdk.io.CMLReader; import org.openscience.cdk.io.ISimpleChemObjectReader; import org.openscience.cdk.io.RGroupQueryReader; import org.openscience.cdk.io.SMILESReader; import org.openscience.cdk.isomorphism.matchers.IRGroupQuery; import org.openscience.cdk.isomorphism.matchers.RGroupQuery; import org.openscience.cdk.layout.StructureDiagramGenerator; import org.openscience.cdk.layout.TemplateHandler; import org.openscience.cdk.tools.CDKHydrogenAdder; import org.openscience.cdk.tools.manipulator.ChemModelManipulator; import org.openscience.cdk.tools.manipulator.ReactionSetManipulator; import org.openscience.jchempaint.AbstractJChemPaintPanel; import org.openscience.jchempaint.GT; import org.openscience.jchempaint.JCPPropertyHandler; import org.openscience.jchempaint.JChemPaintPanel; import org.openscience.jchempaint.controller.ControllerHub; import org.openscience.jchempaint.controller.undoredo.IUndoRedoFactory; import org.openscience.jchempaint.controller.undoredo.IUndoRedoable; import org.openscience.jchempaint.controller.undoredo.UndoRedoHandler; import org.openscience.jchempaint.dialog.WaitDialog; import org.openscience.jchempaint.inchi.StdInChIReader; import org.openscience.jchempaint.io.FileHandler; import org.openscience.jchempaint.rgroups.RGroupHandler; public class JChemPaint { public static int instancecounter = 1; public static List<JFrame> frameList = new ArrayList<JFrame>(); public final static String GUI_APPLICATION = "application"; @SuppressWarnings("static-access") public static void main(String[] args) { try { String vers = System.getProperty("java.version"); String requiredJVM = "1.5.0"; Package self = Package.getPackage("org.openscience.jchempaint"); String version = GT._("Could not determine JCP version"); if (self != null) version = JCPPropertyHandler.getInstance(true).getVersion(); if (vers.compareTo(requiredJVM) < 0) { System.err.println(GT._("WARNING: JChemPaint {0} must be run with a Java VM version {1} or higher.", new String[] { version, requiredJVM })); System.err.println(GT._("Your JVM version is {0}", vers)); System.exit(1); } Options options = new Options(); options.addOption("h", "help", false, GT._("gives this help page")); options.addOption("v", "version", false, GT._("gives JChemPaints version number")); options.addOption("d", "debug", false, "switches on various debug options"); options.addOption(OptionBuilder.withArgName("property=value").hasArg().withValueSeparator() .withDescription(GT._("supported options are given below")).create("D")); CommandLine line = null; try { CommandLineParser parser = new PosixParser(); line = parser.parse(options, args); } catch (UnrecognizedOptionException exception) { System.err.println(exception.getMessage()); System.exit(-1); } catch (ParseException exception) { System.err.println("Unexpected exception: " + exception.toString()); } if (line.hasOption("v")) { System.out.println("JChemPaint v." + version + "\n"); System.exit(0); } if (line.hasOption("h")) { System.out.println("JChemPaint v." + version + "\n"); HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("JChemPaint", options); // now report on the -D options System.out.println(); System.out.println("The -D options are as follows (defaults in parathesis):"); System.out.println(" cdk.debugging [true|false] (false)"); System.out.println(" cdk.debug.stdout [true|false] (false)"); System.out.println(" user.language [ar|ca|cs|de|en|es|hu|nb|nl|pl|pt|ru|th] (en)"); System.out.println(" user.language [ar|ca|cs|de|hu|nb|nl|pl|pt_BR|ru|th] (EN)"); System.exit(0); } boolean debug = false; if (line.hasOption("d")) { debug = true; } // Set Look&Feel Properties props = JCPPropertyHandler.getInstance(true).getJCPProperties(); try { UIManager.setLookAndFeel(props.getProperty("LookAndFeelClass")); } catch (Throwable e) { String sys = UIManager.getSystemLookAndFeelClassName(); UIManager.setLookAndFeel(sys); props.setProperty("LookAndFeelClass", sys); } // Language props.setProperty("General.language", System.getProperty("user.language", "en")); // Process command line arguments String modelFilename = ""; args = line.getArgs(); if (args.length > 0) { modelFilename = args[0]; File file = new File(modelFilename); if (!file.exists()) { System.err.println(GT._("File does not exist") + ": " + modelFilename); System.exit(-1); } showInstance(file, null, null, debug); } else { showEmptyInstance(debug); } } catch (Throwable t) { System.err.println("uncaught exception: " + t); t.printStackTrace(System.err); } } public static void showEmptyInstance(boolean debug) { IChemModel chemModel = emptyModel(); showInstance(chemModel, GT._("Untitled") + " " + (instancecounter++), debug); } public static IChemModel emptyModel() { IChemModel chemModel = DefaultChemObjectBuilder.getInstance().newInstance(IChemModel.class); chemModel.setMoleculeSet(chemModel.getBuilder().newInstance(IMoleculeSet.class)); chemModel.getMoleculeSet().addAtomContainer(chemModel.getBuilder().newInstance(IMolecule.class)); return chemModel; } public static void showInstance(File inFile, String type, AbstractJChemPaintPanel jcpPanel, boolean debug) { try { IChemModel chemModel = JChemPaint.readFromFile(inFile, type, jcpPanel); String name = inFile.getName(); JChemPaintPanel p = JChemPaint.showInstance(chemModel, name, debug); p.setCurrentWorkDirectory(inFile.getParentFile()); p.setLastOpenedFile(inFile); p.setIsAlreadyAFile(inFile); } catch (CDKException ex) { JOptionPane.showMessageDialog(jcpPanel, ex.getMessage()); return; } catch (FileNotFoundException e) { JOptionPane.showMessageDialog(jcpPanel, GT._("File does not exist") + ": " + inFile.getPath()); return; } } public static JChemPaintPanel showInstance(IChemModel chemModel, String title, boolean debug) { JFrame f = new JFrame(title + " - JChemPaint"); chemModel.setID(title); f.addWindowListener(new JChemPaintPanel.AppCloser()); f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); JChemPaintPanel p = new JChemPaintPanel(chemModel, GUI_APPLICATION, debug, null); p.updateStatusBar(); f.setPreferredSize(new Dimension(800, 494)); //1.618 f.add(p); f.pack(); Point point = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint(); int w2 = (f.getWidth() / 2); int h2 = (f.getHeight() / 2); f.setLocation(point.x - w2, point.y - h2); f.setVisible(true); frameList.add(f); return p; } public static IChemModel readFromFileReader(URL fileURL, String url, String type, AbstractJChemPaintPanel panel) throws CDKException { IChemModel chemModel = null; WaitDialog.showDialog(); // InChI workaround - guessing for InChI results into an INChIReader // (this does not work, we'd need an INChIPlainTextReader..) // Instead here we use STDInChIReader, to be consistent throughout JCP // using the nestedVm based classes. try { ISimpleChemObjectReader cor = null; if (url.endsWith("txt")) { chemModel = StdInChIReader.readInChI(fileURL); } else { cor = FileHandler.createReader(fileURL, url, type); chemModel = JChemPaint.getChemModelFromReader(cor, panel); } boolean avoidOverlap = true; if (cor instanceof RGroupQueryReader) avoidOverlap = false; JChemPaint.cleanUpChemModel(chemModel, avoidOverlap); } finally { WaitDialog.hideDialog(); } return chemModel; } /** * Read an IChemModel from a given file. * @param file * @param type * @param panel * @return * @throws CDKException * @throws FileNotFoundException */ public static IChemModel readFromFile(File file, String type, AbstractJChemPaintPanel panel) throws CDKException, FileNotFoundException { String url = file.toURI().toString(); ISimpleChemObjectReader cor = null; try { cor = FileHandler.createReader(file.toURI().toURL(), url, type); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (cor instanceof CMLReader) cor.setReader(new FileInputStream(file)); // hack else cor.setReader(new FileReader(file)); // hack IChemModel chemModel = JChemPaint.getChemModelFromReader(cor, panel); boolean avoidOverlap = true; if (cor instanceof RGroupQueryReader) avoidOverlap = false; JChemPaint.cleanUpChemModel(chemModel, avoidOverlap); return chemModel; } /** * Clean up chemical model ,removing duplicates empty molecules etc * @param chemModel * @param avoidOverlap * @throws CDKException */ public static void cleanUpChemModel(IChemModel chemModel, boolean avoidOverlap) throws CDKException { JChemPaint.setReactionIDs(chemModel); JChemPaint.replaceReferencesWithClones(chemModel); // check the model is not completely empty if (ChemModelManipulator.getBondCount(chemModel) == 0 && ChemModelManipulator.getAtomCount(chemModel) == 0) { throw new CDKException("Structure does not have bonds or atoms. Cannot depict structure."); } JChemPaint.removeDuplicateMolecules(chemModel); JChemPaint.checkCoordinates(chemModel); JChemPaint.removeEmptyMolecules(chemModel); if (avoidOverlap) { ControllerHub.avoidOverlap(chemModel); } //We update implicit Hs in any case CDKAtomTypeMatcher matcher = CDKAtomTypeMatcher.getInstance(chemModel.getBuilder()); for (IAtomContainer container : ChemModelManipulator.getAllAtomContainers(chemModel)) { for (IAtom atom : container.atoms()) { if (!(atom instanceof IPseudoAtom)) { try { IAtomType type = matcher.findMatchingAtomType(container, atom); if (type != null && type.getFormalNeighbourCount() != null) { int connectedAtomCount = container.getConnectedAtomsCount(atom); atom.setHydrogenCount(type.getFormalNeighbourCount() - connectedAtomCount); } } catch (CDKException e) { e.printStackTrace(); } } } } } /** * Returns an IChemModel, using the reader provided (picked). * @param cor * @param panel * @return * @throws CDKException */ public static IChemModel getChemModelFromReader(ISimpleChemObjectReader cor, AbstractJChemPaintPanel panel) throws CDKException { panel.get2DHub().setRGroupHandler(null); String error = null; ChemModel chemModel = null; IChemFile chemFile = null; if (cor.accepts(IChemFile.class) && chemModel == null) { // try to read a ChemFile try { chemFile = (IChemFile) cor.read((IChemObject) new ChemFile()); if (chemFile == null) { error = "The object chemFile was empty unexpectedly!"; } } catch (Exception exception) { error = "Error while reading file: " + exception.getMessage(); exception.printStackTrace(); } } if (error != null) { throw new CDKException(error); } if (cor.accepts(ChemModel.class) && chemModel == null) { // try to read a ChemModel try { chemModel = (ChemModel) cor.read((IChemObject) new ChemModel()); if (chemModel == null) { error = "The object chemModel was empty unexpectedly!"; } } catch (Exception exception) { error = "Error while reading file: " + exception.getMessage(); exception.printStackTrace(); } } // Smiles reading if (cor.accepts(MoleculeSet.class) && chemModel == null) { // try to read a Molecule set try { IMoleculeSet som = (MoleculeSet) cor.read(new MoleculeSet()); chemModel = new ChemModel(); chemModel.setMoleculeSet(som); if (chemModel == null) { error = "The object chemModel was empty unexpectedly!"; } } catch (Exception exception) { error = "Error while reading file: " + exception.getMessage(); exception.printStackTrace(); } } // MDLV3000 reading if (cor.accepts(Molecule.class) && chemModel == null) { // try to read a Molecule IMolecule mol = (Molecule) cor.read(new Molecule()); if (mol != null) try { IMoleculeSet newSet = new MoleculeSet(); newSet.addMolecule(mol); chemModel = new ChemModel(); chemModel.setMoleculeSet(newSet); if (chemModel == null) { error = "The object chemModel was empty unexpectedly!"; } } catch (Exception exception) { error = "Error while reading file: " + exception.getMessage(); exception.printStackTrace(); } } // RGroupQuery reading if (cor.accepts(RGroupQuery.class) && chemModel == null) { IRGroupQuery rgroupQuery = (RGroupQuery) cor.read(new RGroupQuery()); if (rgroupQuery != null) try { chemModel = new ChemModel(); RGroupHandler rgHandler = new RGroupHandler(rgroupQuery); panel.get2DHub().setRGroupHandler(rgHandler); chemModel.setMoleculeSet(rgHandler.getMoleculeSet(chemModel)); rgHandler.layoutRgroup(); } catch (Exception exception) { error = "Error while reading file: " + exception.getMessage(); exception.printStackTrace(); } } if (error != null) { throw new CDKException(error); } if (chemModel == null && chemFile != null) { chemModel = (ChemModel) chemFile.getChemSequence(0).getChemModel(0); } //SmilesParser sets valencies, switch off by default. if (cor instanceof SMILESReader) { IAtomContainer allinone = JChemPaintPanel.getAllAtomContainersInOne(chemModel); for (int k = 0; k < allinone.getAtomCount(); k++) { allinone.getAtom(k).setValency(null); } } return chemModel; } public static void generateModel(AbstractJChemPaintPanel chemPaintPanel, IMolecule molecule, boolean generateCoordinates, boolean shiftPasted) { if (molecule == null) return; IChemModel chemModel = chemPaintPanel.getChemModel(); IMoleculeSet moleculeSet = chemModel.getMoleculeSet(); if (moleculeSet == null) { moleculeSet = new MoleculeSet(); } // On copy & paste on top of an existing drawn structure, prevent the // pasted section to be drawn exactly on top or to far away from the // original by shifting it to a fixed position next to it. if (shiftPasted) { double maxXCurr = Double.NEGATIVE_INFINITY; double minXPaste = Double.POSITIVE_INFINITY; for (IAtomContainer atc : moleculeSet.atomContainers()) { // Detect the right border of the current structure.. for (IAtom atom : atc.atoms()) { if (atom.getPoint2d().x > maxXCurr) maxXCurr = atom.getPoint2d().x; } // Detect the left border of the pasted structure.. for (IAtom atom : molecule.atoms()) { if (atom.getPoint2d().x < minXPaste) minXPaste = atom.getPoint2d().x; } } if (maxXCurr != Double.NEGATIVE_INFINITY && minXPaste != Double.POSITIVE_INFINITY) { // Shift the pasted structure to be nicely next to the existing one. final int MARGIN = 1; final double SHIFT = maxXCurr - minXPaste; for (IAtom atom : molecule.atoms()) { atom.setPoint2d(new Point2d(atom.getPoint2d().x + MARGIN + SHIFT, atom.getPoint2d().y)); } } } if (generateCoordinates) { // now generate 2D coordinates StructureDiagramGenerator sdg = new StructureDiagramGenerator(); sdg.setTemplateHandler(new TemplateHandler(moleculeSet.getBuilder())); try { sdg.setMolecule(molecule); sdg.generateCoordinates(new Vector2d(0, 1)); molecule = sdg.getMolecule(); } catch (Exception exc) { exc.printStackTrace(); } } if (moleculeSet.getAtomContainer(0).getAtomCount() == 0) moleculeSet.getAtomContainer(0).add(molecule); else moleculeSet.addAtomContainer(molecule); IUndoRedoFactory undoRedoFactory = chemPaintPanel.get2DHub().getUndoRedoFactory(); UndoRedoHandler undoRedoHandler = chemPaintPanel.get2DHub().getUndoRedoHandler(); if (undoRedoFactory != null) { IUndoRedoable undoredo = undoRedoFactory.getAddAtomsAndBondsEdit( chemPaintPanel.get2DHub().getIChemModel(), molecule, null, "Paste", chemPaintPanel.get2DHub()); undoRedoHandler.postEdit(undoredo); } chemPaintPanel.getChemModel().setMoleculeSet(moleculeSet); chemPaintPanel.get2DHub().updateView(); } private static void setReactionIDs(IChemModel chemModel) { // we give all reactions an ID, in case they have none // IDs are needed for handling in JCP IReactionSet reactionSet = chemModel.getReactionSet(); if (reactionSet != null) { int i = 0; for (IReaction reaction : reactionSet.reactions()) { if (reaction.getID() == null) reaction.setID("Reaction " + (++i)); } } } private static void replaceReferencesWithClones(IChemModel chemModel) throws CDKException { // we make references in products/reactants clones, since same compounds // in different reactions need separate layout (different positions etc) if (chemModel.getReactionSet() != null) { for (IReaction reaction : chemModel.getReactionSet().reactions()) { int i = 0; IMoleculeSet products = reaction.getProducts(); for (IAtomContainer product : products.atomContainers()) { try { products.replaceAtomContainer(i, (IAtomContainer) product.clone()); } catch (CloneNotSupportedException e) { } i++; } i = 0; IMoleculeSet reactants = reaction.getReactants(); for (IAtomContainer reactant : reactants.atomContainers()) { try { reactants.replaceAtomContainer(i, (IAtomContainer) reactant.clone()); } catch (CloneNotSupportedException e) { } i++; } } } } private static void removeDuplicateMolecules(IChemModel chemModel) { // we remove molecules which are in MoleculeSet as well as in a reaction IReactionSet reactionSet = chemModel.getReactionSet(); IMoleculeSet moleculeSet = chemModel.getMoleculeSet(); if (reactionSet != null && moleculeSet != null) { List<IAtomContainer> aclist = ReactionSetManipulator.getAllAtomContainers(reactionSet); for (int i = moleculeSet.getAtomContainerCount() - 1; i >= 0; i--) { for (int k = 0; k < aclist.size(); k++) { String label = moleculeSet.getAtomContainer(i).getID(); if (aclist.get(k).getID().equals(label)) { chemModel.getMoleculeSet().removeAtomContainer(i); break; } } } } } private static void removeEmptyMolecules(IChemModel chemModel) { IMoleculeSet moleculeSet = chemModel.getMoleculeSet(); if (moleculeSet != null && moleculeSet.getAtomContainerCount() == 0) { chemModel.setMoleculeSet(null); } } private static void checkCoordinates(IChemModel chemModel) throws CDKException { for (IAtomContainer next : ChemModelManipulator.getAllAtomContainers(chemModel)) { if (GeometryTools.has2DCoordinatesNew(next) != 2) { String error = GT._("Not all atoms have 2D coordinates." + " JCP can only show full 2D specified structures." + " Shall we lay out the structure?"); int answer = JOptionPane.showConfirmDialog(null, error, "No 2D coordinates", JOptionPane.YES_NO_OPTION); if (answer == JOptionPane.NO_OPTION) { throw new CDKException(GT._("Cannot display without 2D coordinates")); } else { // CreateCoordinatesForFileDialog frame = // new CreateCoordinatesForFileDialog(chemModel); // frame.pack(); // frame.show(); WaitDialog.showDialog(); List<IAtomContainer> acs = ChemModelManipulator.getAllAtomContainers(chemModel); generate2dCoordinates(acs); WaitDialog.hideDialog(); return; } } } /* * Add implicit hydrogens (in ControllerParameters, * autoUpdateImplicitHydrogens is true by default, so we need to do that * anyway) */ CDKHydrogenAdder hAdder = CDKHydrogenAdder.getInstance(chemModel.getBuilder()); for (IAtomContainer molecule : ChemModelManipulator.getAllAtomContainers(chemModel)) { if (molecule != null) { try { hAdder.addImplicitHydrogens(molecule); } catch (CDKException e) { // do nothing } } } } /** * Helper method to generate 2d coordinates when JChempaint loads a molecule * without 2D coordinates. Typically happens for SMILES strings. * * @param molecules * @throws Exception */ private static void generate2dCoordinates(List<IAtomContainer> molecules) { StructureDiagramGenerator sdg = new StructureDiagramGenerator(); for (int atIdx = 0; atIdx < molecules.size(); atIdx++) { IAtomContainer mol = molecules.get(atIdx); sdg.setMolecule(mol.getBuilder().newInstance(IMolecule.class, mol)); try { sdg.generateCoordinates(); } catch (Exception e) { e.printStackTrace(); } IAtomContainer ac = sdg.getMolecule(); for (int i = 0; i < ac.getAtomCount(); i++) { mol.getAtom(i).setPoint2d(ac.getAtom(i).getPoint2d()); } } } }