ArduinoPanelSource.java

/*******************************************************************************
 * jArduino: Arduino C++ Code Generation From Java
 * Copyright 2020 Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package net.sourceforge.jarduino.gui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.Border;

import net.sourceforge.jarduino.ArduinoException;
import net.sourceforge.jarduino.message.ArduinoChar;
import net.sourceforge.jarduino.message.ArduinoLibrary;
import net.sourceforge.jarduino.message.ArduinoGenerator;
import net.sourceforge.jarduino.message.ArduinoMsgFilter;
import net.sourceforge.jarduino.message.ArduinoParser;
import net.sourceforge.jarduino.message.ArduinoSystem;

/**
 * Panel for displaying sources.
 */
public class ArduinoPanelSource {
    /**
     * Filter panel name.
     */
    private static final String FILTER = "Filter";

    /**
     * Source panel name.
     */
    private static final String SOURCE = "Source";

    /**
     * Font.
     */
    static final Font FONT = new Font("monospaced", Font.PLAIN, 12);

    /**
     * The Panel.
     */
    private final JPanel thePanel;

    /**
     * The FileSetButton.
     */
    private final ArduinoScrollButton<ArduinoFileSet> theFileSetButton;

    /**
     * The FileButton.
     */
    private final ArduinoScrollButton<ArduinoFile> theFileButton;

    /**
     * The textArea.
     */
    private final JTextArea theSourceFile;

    /**
     * The filter panel.
     */
    private final ArduinoPanelFilter theFilterPanel;

    /**
     * The current system.
     */
    private ArduinoSystem theSystem;

    /**
     * The sourceDBC.
     */
    private String theDBCFile;

    /**
     * The error.
     */
    private ArduinoException theError;

    /**
     * Constructor.
     * @param pFrame the frame
     */
    ArduinoPanelSource(final JFrame pFrame) {
        /* Create the panel */
        thePanel = new JPanel(new CardLayout());

        /* Create the filter panel */
        theFilterPanel = new ArduinoPanelFilter(this, pFrame);

        /* Create the TextPane */
        theSourceFile = new JTextArea();
        theSourceFile.setEditable(false);
        theSourceFile.setFont(FONT);

        /* Create the Filter button */
        final JButton myFilterButton = new JButton();
        myFilterButton.setText("Sources");
        myFilterButton.addActionListener(e -> showFilter());

        /* Create the file button */
        theFileButton = new ArduinoScrollButton<>(pFrame);
        theFileButton.onSelect(this::showSourceFile);
        theFileButton.setFormatter(e -> e.getFileName(theSystem));

        /* Create the fileSet button */
        theFileSetButton = new ArduinoScrollButton<>(pFrame);
        theFileSetButton.onSelect(this::populateFileSet);
        for (ArduinoFileSet myFileSet : ArduinoFileSet.values()) {
            theFileSetButton.add(myFileSet);
        }

        /* Create the file panel */
        final JPanel myFilePanel = new JPanel();
        myFilePanel.setLayout(new BoxLayout(myFilePanel, BoxLayout.X_AXIS));
        myFilePanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myFilePanel.add(theFileSetButton.getComponent());
        myFilePanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myFilePanel.add(theFileButton.getComponent());
        myFilePanel.add(Box.createHorizontalGlue());
        Border myBorder = BorderFactory.createTitledBorder("Select File To View");
        myFilePanel.setBorder(myBorder);

        /* Create the filter panel */
        final JPanel myFilterPanel = new JPanel();
        myFilterPanel.setLayout(new BoxLayout(myFilterPanel, BoxLayout.X_AXIS));
        myFilterPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myFilterPanel.add(myFilterButton);
        myFilterPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myBorder = BorderFactory.createTitledBorder("View");
        myFilterPanel.setBorder(myBorder);

        /* Create the control panel */
        final JPanel myControl = new JPanel();
        myControl.setLayout(new BoxLayout(myControl, BoxLayout.X_AXIS));
        myControl.add(myFilePanel);
        myControl.add(myFilterPanel);

        /* Create the fileView */
        final JPanel myFileView = new JPanel(new BorderLayout());
        myFileView.add(new JScrollPane(theSourceFile), BorderLayout.CENTER);
        myBorder = BorderFactory.createTitledBorder(SOURCE);
        myFileView.setBorder(myBorder);

        /* Create the main Panel */
        final JPanel mySource = new JPanel(new BorderLayout());
        mySource.add(myControl, BorderLayout.PAGE_START);
        mySource.add(myFileView, BorderLayout.CENTER);

        /* Build the panel */
        thePanel.add(mySource, SOURCE);
        thePanel.add(theFilterPanel.getComponent(), FILTER);

        /* Set Dimensions */
        thePanel.setPreferredSize(new Dimension(ArduinoPanelMain.WIDTH, ArduinoPanelMain.HEIGHT));
    }

    /**
     * Obtain the component.
     * @return the component
     */
    JComponent getComponent() {
        return thePanel;
    }

    /**
     * Obtain the system.
     * @return the system
     */
    ArduinoSystem getSystem() {
        return theSystem;
    }

    /**
     * Obtain the error.
     * @return the error
     */
    ArduinoException getError() {
        return theError;
    }

    /**
     * Show Filter Panel.
     */
    private void showFilter() {
        ((CardLayout) thePanel.getLayout()).show(thePanel, FILTER);
    }

    /**
     * Show Source Panel.
     */
    void showSource() {
        ((CardLayout) thePanel.getLayout()).show(thePanel, SOURCE);
    }

    /**
     * Update the filter.
     */
    void updateFilter() {
        showSourceFile(theFileButton.getSelectedItem());
    }

    /**
     * Set selected.
     * @param pSource the selected file
     * @param pCharSet the character set to use
     * @return success true/false
     */
    boolean setSelected(final File pSource,
                        final Charset pCharSet) {
        /* Protect against exceptions */
        try {
            /* Clear the error */
            theError = null;

            /* Load the file */
            final ArduinoSystem mySystem = ArduinoParser.parseFile(pSource, pCharSet);

            /* Load the source file */
            theDBCFile = loadSource(pSource, pCharSet);

            /* Record the successful system and update the filter */
            theSystem = mySystem;
            theFilterPanel.setSystem(mySystem);
            theFileSetButton.setSelectedItem(ArduinoFileSet.SRCDBC);
            return true;

            /* Handle exceptions */
        } catch (ArduinoException e) {
            /* Record the error and return */
            theError = e;
            return false;
        }
    }

    /**
     * Set System.
     * @param pFile the source file
     */
    private void showSourceFile(final ArduinoFile pFile) {
        /* Set Base Headers */
        theSourceFile.setText(getSourceText(pFile));
        theSourceFile.setCaretPosition(0);
    }

    /**
     * populate FileButton for fileSet.
     * @param pFileSet the fileSet
     */
    private void populateFileSet(final ArduinoFileSet pFileSet) {
        /* Set Base Headers */
        theFileButton.removeAll();
        for (ArduinoFile myFile : ArduinoFile.values()) {
            if (myFile.isInFileSet(pFileSet)) {
                theFileButton.add(myFile);
            }
        }
    }

    /**
     * Obtain source text.
     * @param pFile the source file
     * @return the source text
     */
    private String getSourceText(final ArduinoFile pFile) {
        /* Handle initialisation case */
        if (theSystem == null) {
            return null;
        }

        /* Switch on file */
        switch (pFile) {
            case BASEHEADER:
                return ArduinoLibrary.loadLibraryHeader();
            case BASEBODY:
                return ArduinoLibrary.loadLibraryCode();
            case SRCHEADER:
                return ArduinoGenerator.formatHeader(theSystem, theFilterPanel.getFilter());
            case SRCBODY:
                return ArduinoGenerator.formatBody(theSystem, theFilterPanel.getFilter());
            case DBCFILE:
            default:
                return theDBCFile;
        }
    }

    /**
     * Load Source.
     * @param pSource the source.
     * @param pCharSet the character set to use
     * @return the source
     * @throws ArduinoException on error
     */
    private static String loadSource(final File pSource,
                                     final Charset pCharSet) throws ArduinoException {
        /* Protect against exceptions */
        try (FileInputStream myStream = new FileInputStream(pSource);
             InputStreamReader myReader = new InputStreamReader(myStream, pCharSet);
             BufferedReader myInput = new BufferedReader(myReader)) {
            /* Reset the builder */
            final StringBuilder myBuilder = new StringBuilder();

            /* Read the header entry */
            for (;;) {
                /* Read next line */
                final String myLine = myInput.readLine();
                if (myLine == null) {
                    break;
                }

                /* Add to the string buffer */
                myBuilder.append(myLine);
                myBuilder.append(ArduinoChar.LF);
            }

            /* Build the string */
            return myBuilder.toString();

            /* Catch exceptions */
        } catch (IOException e) {
            throw new ArduinoException("Failed to load File");
        }
    }

    /**
     * Write Sketch Files to directory.
     * @param pDirectory the directory to write to
     * @param pCharSet the character set to use
     * @return the exception (or null if successful)
     */
    ArduinoException writeSketchToDirectory(final File pDirectory,
                                            final Charset pCharSet) {
        /* Protect against exceptions */
        try {
            /* Obtain the message filter */
            final ArduinoMsgFilter myFilter = theFilterPanel.getFilter();

            /* Write system header */
            File myFile = new File(pDirectory, ArduinoFile.SRCHEADER.getFileName(theSystem));
            writeStringToFile(ArduinoGenerator.formatHeader(theSystem, myFilter), pCharSet, myFile);

            /* Write system code */
            myFile = new File(pDirectory, ArduinoFile.SRCBODY.getFileName(theSystem));
            writeStringToFile(ArduinoGenerator.formatBody(theSystem, myFilter), pCharSet, myFile);

            /* Signal OK */
            return null;

            /* Catch exceptions */
        } catch (ArduinoException e) {
            return e;
        }
    }

    /**
     * Write Files to directory.
     * @param pDirectory the directory to write to
     * @param pCharSet the character set to use
     * @return the exception (or null if successful)
     */
    ArduinoException writeLibraryToDirectory(final File pDirectory,
                                             final Charset pCharSet) {
        /* Protect against exceptions */
        try {
            /* Write base header */
            File myFile = new File(pDirectory, ArduinoFile.BASEHEADER.getFileName(theSystem));
            writeStringToFile(ArduinoLibrary.loadLibraryHeader(), pCharSet, myFile);

            /* Write base code */
            myFile = new File(pDirectory, ArduinoFile.BASEBODY.getFileName(theSystem));
            writeStringToFile(ArduinoLibrary.loadLibraryCode(), pCharSet, myFile);

            /* Signal OK */
            return null;

            /* Catch exceptions */
        } catch (ArduinoException e) {
            return e;
        }
    }

    /**
     * Write String to file.
     * @param pData the String to write
     * @param pCharSet the characterSet to use
     * @param pFile the file to write to
     * @throws ArduinoException on error
     */
    private static void writeStringToFile(final String pData,
                                          final Charset pCharSet,
                                          final File pFile) throws ArduinoException {
        /* Protect the write */
        try (PrintWriter myWriter = new PrintWriter(pFile, pCharSet.name())) {
            /* Write the data to the file */
            myWriter.print(pData);

        } catch (IOException e) {
            throw new ArduinoException("Failed to output to file", pFile.getAbsolutePath());
        }
    }

    /**
     * The source files.
     */
    private enum ArduinoFile {
        /**
         * Source Header.
         */
        SRCHEADER,

        /**
         * Source Body.
         */
        SRCBODY,

        /**
         * Base Header.
         */
        BASEHEADER,

        /**
         * Base Body.
         */
        BASEBODY,

        /**
         * DBCFile.
         */
        DBCFILE;

        /**
         * Obtain name.
         * @param pSystem the system
         * @return the name
         */
        String getFileName(final ArduinoSystem pSystem) {
            /* Handle initialisation */
            if (pSystem == null) {
                return "Unknown";
            }

            /* Switch on file */
            switch (this) {
                case SRCHEADER:
                    return pSystem.getCName() + ".h";
                case SRCBODY:
                    return pSystem.getCName() + ".cpp";
                case BASEHEADER:
                    return ArduinoLibrary.HEADER;
                case BASEBODY:
                    return ArduinoLibrary.CODE;
                case DBCFILE:
                default:
                    return pSystem.getName() + ".dbc";
            }
        }

        /**
         * is this file in the fileSet?
         * @param pFileSet the fileSet
         * @return true/false
         */
        boolean isInFileSet(final ArduinoFileSet pFileSet) {
            /* Switch on file */
            switch (this) {
                case SRCBODY:
                case SRCHEADER:
                    return ArduinoFileSet.SKETCH.equals(pFileSet);
                case BASEHEADER:
                case BASEBODY:
                    return ArduinoFileSet.LIBRARY.equals(pFileSet);
                case DBCFILE:
                default:
                    return ArduinoFileSet.SRCDBC.equals(pFileSet);
            }
        }
    }

    /**
     * The fileSet files.
     */
    private enum ArduinoFileSet {
        /**
         * Source DBC.
         */
        SRCDBC,

        /**
         * Sketch.
         */
        SKETCH,

        /**
         * Library.
         */
        LIBRARY;

        @Override
        public String toString() {
            switch (this) {
                case SRCDBC:
                    return "Source DBC";
                case SKETCH:
                    return "Sketch";
                case LIBRARY:
                default:
                    return "Library";
            }
        }
    }
}