Commit 50deac6c authored by Jason Louis Calalang's avatar Jason Louis Calalang
Browse files

Merge branch 'master' into '8-implement-subgraph-search'

# Conflicts:
#   src/Main.java
#   src/MoleculeDatabase.java
#   src/edu/bu/ec504/project/Molecule.java
parents 1f77ad11 923c5518
Loading
Loading
Loading
Loading

src/GUI.java

0 → 100644
+291 −0
Original line number Diff line number Diff line
import edu.bu.ec504.project.Molecule;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;

public class GUI extends JFrame {
    private JTextArea outputTextArea;
    private JButton chooseFileButton;
    private JButton addMoleculeButton;
    private JButton findMoleculeButton;
    private JButton statisticsButton;
    private JButton displayMoleculeButton;
    private JButton findSubgraphButton;
    private JButton downloadPubChemButton;
    private JTextField filePathField;
    private static MDB moleculeDb;
    private Socket clientSocket;
    private PrintWriter writer;
    private BufferedReader reader;

    /**
     * GUI constructor
     */
    public GUI() {
        // Set up the JFrame
        setTitle("Molecule Database");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        getContentPane().setBackground(Color.BLACK);

        // Create components
        outputTextArea = new JTextArea(20, 50); // the text area for all outputs
        outputTextArea.setBackground(Color.BLACK); // Set the background color of the text area
        outputTextArea.setForeground(Color.WHITE); // Set the text color
        JScrollPane scrollPane = new JScrollPane(outputTextArea);
        chooseFileButton = new JButton("Choose File");
        downloadPubChemButton = new JButton("Download PubChem");
        addMoleculeButton = new JButton("Add Molecule");
        findMoleculeButton = new JButton("Find Molecule");
        findSubgraphButton = new JButton("Find Subgraph");
        statisticsButton = new JButton("Database Statistics");
        displayMoleculeButton = new JButton("Display Molecule");
        filePathField = new JTextField(20); // to show the file path
        JLabel filePathLabel = new JLabel("File Path:");
        filePathLabel.setForeground(Color.WHITE); // Set the text color
        filePathField.setBackground(Color.WHITE); // Set the background color of the text field
        filePathField.setForeground(Color.BLACK); // Set the text color

        // Add components to the JFrame
        JPanel controlPanel = new JPanel();
        controlPanel.setBackground(Color.BLACK);
        controlPanel.add(chooseFileButton);
        controlPanel.add(downloadPubChemButton);
        controlPanel.add(addMoleculeButton);
        controlPanel.add(findMoleculeButton);
        controlPanel.add(findSubgraphButton);
        controlPanel.add(displayMoleculeButton);
        controlPanel.add(statisticsButton);
        controlPanel.add(filePathLabel);
        controlPanel.add(filePathField);
        add(controlPanel, BorderLayout.NORTH); // to show the control panel (e.g., buttons)
        add(scrollPane, BorderLayout.CENTER); // to show the printed output text area

        // Initialize molecule database
        moleculeDb = new MDB(outputTextArea);

        // Action listener for Choose File button
        chooseFileButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Create a file chooser
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setDialogTitle("Choose a file");
                int result = fileChooser.showOpenDialog(GUI.this);
                // If a file is selected, set its path in the molecule path field
                if (result == JFileChooser.APPROVE_OPTION) {
                    File selectedFile = fileChooser.getSelectedFile();
                    filePathField.setText(selectedFile.getAbsolutePath());
                }
            }
        });

        // Action listener for Download PubChem button
        downloadPubChemButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Get the molecule path from the text field
                // Input format for the file path is "start,end" where 'start' and 'end' are the starting and ending CID indices of molecules in PubChem
                // For example, enter 12,24 to download molecules 12-24
                String twoIndices = filePathField.getText();

                // Repurposed moleculePath should be in format "start,end"
                String[] indexes = twoIndices.split(",");

                if (indexes.length == 2) {
                    String start = indexes[0];
                    String end = indexes[1];
                    moleculeDb.downloadPubChem(start, end);
                } else {
                    outputTextArea.append("Invalid Input" + "\n\n");
                }
            }
        });

        // Action listener for Add Molecule button
        addMoleculeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Get the molecule path from the text field
                String moleculePath = filePathField.getText();
                // Execute the addMolecule command
                moleculeDb.addMolecule(new Molecule(moleculePath));
                // Display output
                outputTextArea.append("Molecule added: " + moleculePath + "\n\n");
            }
        });

        // Action listener for Find Molecule button
        findMoleculeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Get the molecule path from the text field
                String moleculePath = filePathField.getText();
                // Execute the findMolecule command
                Molecule molecule = moleculeDb.findMolecule(new Molecule(moleculePath));
                if (molecule == null) {
                    outputTextArea.append("NO EXACT MATCH FOUND" + "\n\n");
                    molecule= moleculeDb.similarMolecule(new Molecule(moleculePath));
                    if(molecule!=null)
                    {
                        // Perform most similar function
                        outputTextArea.append(molecule.moleculeName + " is the most similar" + "\n\n");
                    }
                } else {
                    outputTextArea.append("FOUND\n\n");
                }
            }
        });

        // Action listener for Find Subgraph button
        findSubgraphButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String moleculePath = filePathField.getText();
                ArrayList<Molecule> mList = moleculeDb.findSubgraph(new Molecule(moleculePath));
                if (mList.isEmpty())
                    outputTextArea.append("No subraphs found" + "\n\n");
                else
                    for (Molecule m : mList)
                        outputTextArea.append("Subgraph found: " + m.moleculeName + "\n\n");
            }
        });

        // Action listener for Display Molecule button
        displayMoleculeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                // Get the API for the molecule
                String moleculePath = filePathField.getText();
                String moleculeName = null;

                try (BufferedReader reader = new BufferedReader(new FileReader(moleculePath))) {
                    moleculeName = reader.readLine(); // Read the first line to get the molecule name

                } catch (IOException ex) {
                    System.err.println("Error reading the file: " + ex.getMessage());
                    outputTextArea.append("Error reading the file:" + ex.getMessage() + "\n\n");
                    return;
                }

                // https://cactus.nci.nih.gov/chemical/structure/isopropanol/image
                String imageURL = "https://cactus.nci.nih.gov/chemical/structure/" + moleculeName + "/image";

                try {
                    // Download the image from the URL
                    URL url = new URL(imageURL);
                    BufferedImage image = ImageIO.read(url);

                    // Create a JLabel to display the image
                    JLabel imageLabel = new JLabel(new ImageIcon(image));

                    // Create a new JFrame to display the image
                    JFrame imageFrame = new JFrame("Molecule Display");
                    imageFrame.getContentPane().add(imageLabel, BorderLayout.CENTER);
                    imageFrame.setSize(400, 400);
                    imageFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                    imageFrame.setLocationRelativeTo(null); // Center the frame
                    imageFrame.setVisible(true); // Make the frame visible

                } catch (IOException ex) {
                    ex.printStackTrace();
                    outputTextArea.append("Molecule display failed. The provided molecule name may be incorrect or does not match any records in the PubChem database.\n\n");
                }
            }
        });

        // Action listener for Statistics button
        statisticsButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                moleculeDb.printDb(); // print the content of database
            }
        });

        // Window listener to save the database before closing the GUI
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                try {
                    moleculeDb.save("molecule.db"); // save the database
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });

        // Connect to client or server
        connectToServerOrClient();

        // Display the JFrame
        pack();
        setLocationRelativeTo(null);
        setVisible(true);

        // Load database on startup
        try {
            initDb("molecule.db");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * Run as client or server
     */
    private void connectToServerOrClient() {
        try {
            // Run the client side
            clientSocket = new Socket("localhost", 5000);
            writer = new PrintWriter(clientSocket.getOutputStream(), true);
            reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        } catch (ConnectException e) {
            // Run the server side
            try {
                ServerSocket serverSocket = new ServerSocket(5000);
                serverSocket.setSoTimeout(60 * 1000);

                // Close the server socket after accepting the connection
                serverSocket.close();

            } catch (IOException ex) {
                ex.printStackTrace();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Initialize a database
     */
    public void initDb(String dbName) throws IOException {
        // Load the database
        moleculeDb = new MDB(outputTextArea);
        File dbFile = new File(dbName);
        if (dbFile.exists()) {
            moleculeDb.load(dbName);
        }
    }

    /**
     * Main function
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                GUI gui = new GUI();
            }
        });
    }
}

src/MDB.java

0 → 100644
+231 −0
Original line number Diff line number Diff line
import edu.bu.ec504.project.Atom;
import edu.bu.ec504.project.Molecule;

import javax.swing.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.List;

/**
 * A class represents the molecule database that works with the GUI.
 */
public class MDB {

    public HashMap<Integer, ArrayList<Molecule>> db;   // Molecule database
    public JTextArea outputTextArea; // Reference to the text area in the GUI

    /**
     * Constructs a database
     */
    public MDB(JTextArea outputTextArea) {
        this.db = new HashMap<>();
        this.outputTextArea = outputTextArea;
    }

    /**
     * Print database statistics to GUI
     */
    public void printDb() {
        // Print number of molecules
        int size = 0;
        for (ArrayList<Molecule> molecules : db.values()) {
            size += molecules.size();
        }
        outputTextArea.append("# of molecules: " + size + "\n\n");

        if (size == 0)
            return; // if database is empty, exit early

        // Print the list of molecules
        outputTextArea.append("List of molecules: " + "\n\n");
        for (Integer atomCount : this.db.keySet()) {
            ArrayList<Molecule> moleculesWithSameNumAtoms = this.db.get(atomCount);
            for (Molecule molecule : moleculesWithSameNumAtoms) {
                outputTextArea.append("Molecule name: " + molecule.moleculeName + "\n");
                outputTextArea.append("# of atoms: " + atomCount.toString() + "\n\n");
            }
        }

        // Print the largest and biggest molecules
        int maxAtoms = Integer.MIN_VALUE;
        int minAtoms = Integer.MAX_VALUE;
        Molecule largestMolecule = null;
        Molecule smallestMolecule = null;
        for (Map.Entry<Integer, ArrayList<Molecule>> entry : db.entrySet()) {
            int numAtoms = entry.getKey();
            if (numAtoms > maxAtoms) {
                maxAtoms = numAtoms;
                largestMolecule = entry.getValue().get(0); // only print 1 representative molecule
            }
            if (numAtoms < minAtoms) {
                minAtoms = numAtoms;
                smallestMolecule = entry.getValue().get(0); // only print 1 representative molecule
            }
        }
        outputTextArea.append("Smallest molecule: " + smallestMolecule.moleculeName + "\n");
        outputTextArea.append("Largest molecule: " + largestMolecule.moleculeName + "\n\n");

    }

    /**
     * Add a new molecule into the database
     */
    public void addMolecule(Molecule molecule) {
        if (molecule == null) {
            outputTextArea.append("molecule == null" + "\n\n");
            return;
        }
        int numAtoms = molecule.getNumAtoms();
        //test if molecule has an unconnected atom
        for(Atom a: molecule.getAtomArrayList())
            if(a.connected.isEmpty()) {
                outputTextArea.append("Error: molecule file is incorrect (contains unconnected atom)" + "\n\n");
                return;
            }

        if (this.db.containsKey(numAtoms)) {
            this.db.get(numAtoms).add(molecule);
        } else {
            ArrayList<Molecule> moleculesWithSameNumAtoms = new ArrayList<>();
            moleculesWithSameNumAtoms.add(molecule);
            this.db.put(numAtoms, moleculesWithSameNumAtoms);
        }
    }

    /**
     * Find isomorphic molecule from the database
     */
    public Molecule findMolecule(Molecule molecule) {
        // Retrieve the partitioned array list based on the number of atoms
        int numAtoms = molecule.getNumAtoms();
        if (!db.containsKey(numAtoms)) {
            outputTextArea.append("No ArrayList with correct # of atoms" + "\n\n");
            return null;
        }
        ArrayList<Molecule> moleculesWithSameNumAtoms = db.get(numAtoms);

        // Iterate through the array list of molecules with the same number of atoms
        for (Molecule dbMolecule : moleculesWithSameNumAtoms) {
            outputTextArea.append(dbMolecule.moleculeName + " vs " + molecule.moleculeName + "\n\n");
            Molecule result = dbMolecule.areMoleculesEqual(molecule);
            if (result != null) {
                return result; // Return the isomorphic molecule
            }
        }
        return null; // Return null if molecule not found
    }

    /**
     * Find the most similar Molecule from the database
     */
    public Molecule similarMolecule(Molecule molecule) {

        // If an exact match is not found then find the most similar
        int maxResult=0;
        Molecule similar=null;
        for (Map.Entry<Integer, ArrayList<Molecule>> entry : db.entrySet()) {
            // Access the key and value of each entry
            Integer numberAtoms = entry.getKey();

            //only check for similarity if they have similar number of atoms within tolerance of 100
            if( (molecule.getNumAtoms()-100)<numberAtoms && numberAtoms<(molecule.getNumAtoms()+100) )
            {
                for (Molecule dbMolecule : db.get(numberAtoms)) {
                    int res = dbMolecule.mostSimilar(molecule);
                    if (res > maxResult) {
                        similar = dbMolecule; // save the similar molecule
                        maxResult = res;
                    }
                }
            }
        }

        return similar;
    }

    /**
     * Find subgraph
     */
    public ArrayList<Molecule> findSubgraph(Molecule molecule) {
        ArrayList<Molecule> returnList = new ArrayList<Molecule>();
        int startingNumber = molecule.getNumAtoms();
        for(int ii : db.keySet()) {
            if (ii >= startingNumber) {
                for(Molecule m: db.get(ii)) {
                    if(m.isSubGraphPresent(molecule) != null) {
                        returnList.add(m);
                        outputTextArea.append(m.moleculeName + "\n\n");
                    }
                }
            }
        }

        return returnList;
    }

    /**
     * Download Molecules from Pubchem in range [start, end]
     */
    public void downloadPubChem(String start, String end) {
        String scriptPath = "testcases/downloadPubChem.py";
        List<String> filenames = new ArrayList<>();

        try {
            // build Python script call
            ProcessBuilder builder = new ProcessBuilder("python", scriptPath, start, end);
            Process process = builder.start();

            // get Python filename output
            InputStream stdout = process.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));

            String file;
            while ((file = reader.readLine()) != null) {
                outputTextArea.append("file created: " + file + "\n\n");
                filenames.add(file);
            }

            int exitCode = process.waitFor();
            outputTextArea.append("Exited with code: " + exitCode + "\n\n");

            // add created files to the database
            for (String filename : filenames) {
                this.addMolecule(new Molecule(filename));
            }

        } catch (Exception e) {
            outputTextArea.append("Error downloading from PubChem" + "\n\n");
            e.printStackTrace();
        }
    }

    /**
     * Save database to file system
     */
    public void save(String filename) throws IOException {
        FileOutputStream fileOutStream = new FileOutputStream(filename);
        ObjectOutputStream objOutStream = new ObjectOutputStream(fileOutStream);
        objOutStream.writeObject(this.db);
        objOutStream.close();
        fileOutStream.close();
    }

    /**
     * Load database from file system
     */
    public void load(String filename) throws IOException {
        FileInputStream fileInStream = new FileInputStream(filename);
        ObjectInputStream objInStream = new ObjectInputStream(fileInStream);
        try {
            this.db = (HashMap<Integer, ArrayList<Molecule>>) objInStream.readObject();
            outputTextArea.append("Database loaded successfully." + "\n\n");
        } catch (IOException | ClassNotFoundException e) {
            outputTextArea.append("Error loading database: " + e.getMessage() + "\n\n");
        }
        objInStream.close();
        fileInStream.close();
    }

}
+19 −1
Original line number Diff line number Diff line
@@ -54,7 +54,12 @@ public class Main {
            case "--findMolecule":
                Molecule molecule = moleculeDb.findMolecule(new Molecule(moleculePath));
                if (molecule == null) {
                    System.out.println("NOT FOUND");
                    System.out.println("NO EXACT MATCH FOUND");
                    molecule= moleculeDb.similarMolecule(new Molecule(moleculePath));
                    if(molecule!=null)
                    {
                        System.out.println(molecule.moleculeName + " is the most similar");
                    }
                } else {
                    printVerbose("FOUND");
                }
@@ -67,6 +72,19 @@ public class Main {
                    for(Molecule m: mList)
                        System.out.println(m.moleculeName);
                break;
            case "--downloadPubChem":
                // Repurposed moleculePath should be in format "start,end"
                // Input format for the file path is "start,end" where 'start' and 'end' are the starting and ending CID indices of molecules in PubChem
                // For example, enter 12,24 to download molecules 12-24
                String[] indexes = moleculePath.split(",");
                if (indexes.length == 2) {
                    String start = indexes[0];
                    String end = indexes[1];
                    moleculeDb.downloadPubChem(start, end);
                } else {
                    printVerbose("invalid Input");
                }
                break;
            default:
                printVerbose("unrecognized command: " + cmd);
                break;
+141 −6

File changed.

Preview size limit exceeded, changes collapsed.

+6 −2
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ public class Atom implements Serializable {
        atomName = name;
        degree = 0;
        connected = new HashMap<>();
        connectedMarked= new ArrayList<>();
        elementType = elem;
        marked = false;
    }
@@ -18,8 +19,10 @@ public class Atom implements Serializable {
        if(connected.containsKey(i.getName())) {
            connected.put(i.getName(),new ElemOrderPair(i.elementType,connected.get(i.getName()).bondOrder+1));
        }
        else
        else {
            connected.put(i.getName(), new ElemOrderPair(i.elementType, 1));
            connectedMarked.add(false);
        }
    }
    public String getName() {
        return this.atomName;
@@ -28,6 +31,7 @@ public class Atom implements Serializable {
    public int elementType;
    public int degree;
    public Map<String,ElemOrderPair> connected;
    public ArrayList<Boolean> connectedMarked;
    public boolean marked;


Loading