Java tutorial
/* * Copyright 2015-2016 Matthew Aguirre * * 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 org.tros.torgo; import org.tros.torgo.interpreter.CodeBlock; import org.tros.torgo.interpreter.InterpreterListener; import org.tros.torgo.interpreter.InterpreterThread; import org.tros.torgo.interpreter.Scope; import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JSplitPane; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.event.EventListenerSupport; import org.tros.torgo.swing.AboutWindow; import org.tros.torgo.swing.Localization; import org.tros.torgo.swing.TorgoMenuBar; import org.tros.utils.swing.NamedWindow; import org.tros.utils.AutoResetEvent; /** * The main application. Controls GUI and interpreting process. * * @author matta */ public abstract class ControllerBase implements Controller { private JFrame window; protected TorgoScreen torgoCanvas; protected TorgoTextConsole torgoPanel; private InterpreterThread interp; private String filename; protected final AutoResetEvent step; protected final AtomicBoolean isStepping; private final ArrayList<JCheckBoxMenuItem> viz = new ArrayList<>(); protected final EventListenerSupport<InterpreterListener> listeners = EventListenerSupport .create(InterpreterListener.class); protected final EventListenerSupport<ControllerListener> controllerListeners = EventListenerSupport .create(ControllerListener.class); public final static String ABOUT_MENU_TORGO_ICON = "torgo-16x16.png"; /** * Add a listener * * @param listener */ @Override public void addInterpreterListener(InterpreterListener listener) { listeners.addListener(listener); } /** * Remove a listener * * @param listener */ @Override public void removeInterpreterListener(InterpreterListener listener) { listeners.removeListener(listener); } /** * Add a listener * * @param listener */ @Override public void addControllerListener(ControllerListener listener) { controllerListeners.addListener(listener); } /** * Remove a listener * * @param listener */ @Override public void removeControllerListener(ControllerListener listener) { controllerListeners.removeListener(listener); } /** * Hidden Constructor. */ protected ControllerBase() { step = new AutoResetEvent(false); isStepping = new AtomicBoolean(false); } /** * Get the console component for user I/O. * * @param app * @return */ protected abstract TorgoTextConsole createConsole(Controller app); /** * Get a canvas for drawing to the screen. This can be null. If this is * null, the canvas portion of the window will not be loaded. * * @param console * @return */ protected abstract TorgoScreen createCanvas(TorgoTextConsole console); /** * Create an interpreter thread for the desired language. * * @param source * @return */ protected abstract InterpreterThread createInterpreterThread(String source); /** * Initialize the window. This is called here from run() and not the * constructor so that the Service Provider doesn't load up all of the * necessary resources when the application loads. */ private void initSwing() { this.torgoPanel = createConsole((Controller) this); this.torgoCanvas = createCanvas(torgoPanel); //init the GUI w/ the components... Container contentPane = window.getContentPane(); JToolBar tb = createToolBar(); if (tb != null) { contentPane.add(tb, BorderLayout.NORTH); } final java.util.prefs.Preferences prefs = java.util.prefs.Preferences.userNodeForPackage(NamedWindow.class); if (torgoCanvas != null) { final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, torgoCanvas.getComponent(), torgoPanel.getComponent()); int dividerLocation = prefs.getInt(this.getClass().getName() + "divider-location", window.getWidth() - 300); splitPane.setDividerLocation(dividerLocation); splitPane.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent pce) { prefs.putInt(this.getClass().getName() + "divider-location", splitPane.getDividerLocation()); } }); contentPane.add(splitPane); } else { contentPane.add(torgoPanel.getComponent()); } JMenuBar mb = createMenuBar(); if (mb == null) { mb = new TorgoMenuBar(window, this); } window.setJMenuBar(mb); JMenu helpMenu = new JMenu("Help"); JMenuItem aboutMenu = new JMenuItem("About Torgo"); try { java.util.Enumeration<URL> resources = ClassLoader.getSystemClassLoader() .getResources(ABOUT_MENU_TORGO_ICON); ImageIcon ico = new ImageIcon(resources.nextElement()); aboutMenu.setIcon(ico); } catch (IOException ex) { Logger.getLogger(ControllerBase.class.getName()).log(Level.SEVERE, null, ex); } aboutMenu.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { AboutWindow aw = new AboutWindow(); aw.setVisible(true); } }); helpMenu.add(aboutMenu); JMenu vizMenu = new JMenu("Visualization"); for (String name : TorgoToolkit.getVisualizers()) { JCheckBoxMenuItem item = new JCheckBoxMenuItem(name); viz.add(item); vizMenu.add(item); } if (vizMenu.getItemCount() > 0) { mb.add(vizMenu); } mb.add(helpMenu); window.setJMenuBar(mb); window.addWindowListener(new WindowListener() { @Override public void windowOpened(WindowEvent e) { } /** * We only care if the window is closing so we can kill the * interpreter thread. * * @param e */ @Override public void windowClosing(WindowEvent e) { stopInterpreter(); } @Override public void windowClosed(WindowEvent e) { } @Override public void windowIconified(WindowEvent e) { } @Override public void windowDeiconified(WindowEvent e) { } @Override public void windowActivated(WindowEvent e) { } @Override public void windowDeactivated(WindowEvent e) { } }); } /** * Create a tool bar for the application. This can return null. If null is * returned, then there is no tool bar added. * * @return */ protected abstract JToolBar createToolBar(); /** * Create a menu bar for the application. This can return null. If null is * returned, then there is no menu bar added. * * @return */ protected abstract JMenuBar createMenuBar(); /** * Create a new window. */ @Override public void newFile() { if (this.window.isVisible()) { filename = null; window.setTitle("Torgo - " + Localization.getLocalizedString("UntitledLabel")); init(); } } /** * Sets up the environment. */ @Override public final void run() { this.window = new NamedWindow(this.getClass().getName()); Main.loadIcon(this.window); initSwing(); this.window.setVisible(true); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { newFile(); runHelper(); } }); } /** * Helper method called during run(). */ protected void runHelper() { } /** * Initialize the GUI back to initial state. */ private void init() { stopInterpreter(); torgoPanel.reset(); if (torgoCanvas != null) { torgoCanvas.reset(); } } /** * Open a file based on URL. * * @param file */ @Override public void openFile(URL file) { try { init(); StringWriter writer = new StringWriter(); IOUtils.copy(file.openStream(), writer, "utf-8"); this.setSource(writer.toString()); //handle windows, jar, and linux path. Not sure if necessary, but should work. String toSplit = file.getFile().replace("/", "|").replace("\\", "|");//.split("|"); String[] split = toSplit.split("\\|"); this.window.setTitle("Torgo - " + split[split.length - 1]); } catch (IOException ex) { init(); org.tros.utils.logging.Logging.getLogFactory().getLogger(ControllerBase.class).fatal(null, ex); } } /** * Open a file. */ @Override public void openFile() { JFileChooser chooser = new JFileChooser(); chooser.setMultiSelectionEnabled(false); java.util.prefs.Preferences prefs = java.util.prefs.Preferences.userNodeForPackage(ControllerBase.class); chooser.setCurrentDirectory( new File(prefs.get(ControllerBase.class.getName() + "-working-directory", "."))); if (chooser.showOpenDialog(window) == JFileChooser.APPROVE_OPTION) { filename = chooser.getSelectedFile().getPath(); prefs.put(ControllerBase.class.getName() + "-working-directory", chooser.getSelectedFile().getParent()); openFile(chooser.getSelectedFile()); } } /** * Save the script as a new file. */ @Override public void saveFileAs() { JFileChooser chooser = new JFileChooser(); chooser.setMultiSelectionEnabled(false); java.util.prefs.Preferences prefs = java.util.prefs.Preferences.userNodeForPackage(ControllerBase.class); chooser.setCurrentDirectory( new File(prefs.get(ControllerBase.class.getName() + "-working-directory", "."))); int result = chooser.showSaveDialog(window); if (result == JFileChooser.APPROVE_OPTION) { filename = chooser.getSelectedFile().getPath(); prefs.put(ControllerBase.class.getName() + "-working-directory", chooser.getSelectedFile().getParent()); saveFile(); } } /** * Save the script. */ @Override public void saveFile() { if (filename == null) { saveFileAs(); return; } try (FileOutputStream out = new FileOutputStream(filename)) { String source = torgoPanel.getSource(); byte[] sourceArray = new byte[source.length()]; for (int i = 0; i < source.length(); i++) { sourceArray[i] = (byte) source.charAt(i); } out.write(sourceArray); window.setTitle("Torgo - " + filename); out.flush(); } catch (Exception ex) { org.tros.utils.logging.Logging.getLogFactory().getLogger(ControllerBase.class).fatal(null, ex); } } /** * Print (unused) */ @Override public void printCanvas() { org.tros.utils.logging.Logging.getLogFactory().getLogger(ControllerBase.class) .debug("printCanvas() called"); /* PrinterJob printJob = PrinterJob.getPrinterJob(); PageFormat pageFormat = printJob.defaultPage(); printJob.setPrintable(torgoCanvas, pageFormat); */ } @Override public void enable(String name) { for (JCheckBoxMenuItem item : viz) { if (item.getText().equals(name)) { item.setState(true); } } } @Override public void disable(String name) { for (JCheckBoxMenuItem item : viz) { if (item.getText().equals(name)) { item.setState(false); } } } /** * Instantiate/Run the interpreter. */ @Override public void startInterpreter() { String source = torgoPanel.getSource(); interp = createInterpreterThread(source); for (JCheckBoxMenuItem item : viz) { if (item.getState()) { TorgoToolkit.getVisualization(item.getText()).create().watch(this.getLang(), this, interp); } } // viz.stream().filter((item) -> (item.getState())).map((item) -> TorgoToolkit.getVisualization(item.getText()).create()).forEach((visualization) -> { // visualization.watch(this.getLang(), this, interp); // }); for (InterpreterListener l : listeners.getListeners()) { interp.addInterpreterListener(l); } interp.addInterpreterListener(new InterpreterListener() { @Override public void started() { } @Override public void finished() { } @Override public void error(Exception e) { org.tros.utils.logging.Logging.getLogFactory().getLogger(ControllerBase.class).fatal(null, e); } @Override public void message(String msg) { torgoPanel.appendToOutputTextArea(msg); } @Override public void currStatement(CodeBlock block, Scope scope) { } }); controllerListeners.fire().onStartInterpreter(); interp.start(); } /** * Instantiate an interpreter in debug mode. */ @Override public void debugInterpreter() { String source = torgoPanel.getSource(); interp = createInterpreterThread(source); step.reset(); for (JCheckBoxMenuItem item : viz) { if (item.getState()) { TorgoToolkit.getVisualization(item.getText()).create().watch(this.getLang(), this, interp); } } // viz.stream().filter((item) -> (item.getState())).map((item) -> TorgoToolkit.getVisualization(item.getText()).create()).forEach((visualization) -> { // visualization.watch(this.getLang(), this, interp); // }); for (InterpreterListener l : listeners.getListeners()) { interp.addInterpreterListener(l); } interp.addInterpreterListener(new InterpreterListener() { @Override public void started() { } @Override public void finished() { torgoPanel.highlight(-1, 0, 0); } @Override public void error(Exception e) { org.tros.utils.logging.Logging.getLogFactory().getLogger(ControllerBase.class).fatal(null, e); } @Override public void message(String msg) { torgoPanel.appendToOutputTextArea(msg); } @Override public void currStatement(CodeBlock block, Scope scope) { try { if (isStepping.get()) { step.waitOne(); } //TODO: this needs to be configurable Thread.sleep(100); } catch (InterruptedException ex) { org.tros.utils.logging.Logging.getLogFactory().getLogger(ControllerBase.class).fatal(null, ex); } int line = block.getParserRuleContext().getStart().getLine(); int start = block.getParserRuleContext().getStart().getStartIndex(); int end = block.getParserRuleContext().getStart().getStopIndex(); torgoPanel.highlight(line, start, end); } }); controllerListeners.fire().onDebugInterpreter(); interp.start(); } /** * Allows stepping through interpreter commands one-at-a-time. */ @Override public void stepOver() { controllerListeners.fire().onStepOver(); step.set(); } /** * Pause running the interpreter. Used in debug mode. */ @Override public void pauseInterpreter() { controllerListeners.fire().onPauseInterpreter(); isStepping.set(true); } /** * Resume the interpreter. Used in debug mode. */ @Override public void resumeInterpreter() { controllerListeners.fire().onResumeInterpreter(); isStepping.set(false); step.set(); } /** * Stop the interpreter. */ @Override public void stopInterpreter() { if (interp != null) { interp.halt(); step.set(); controllerListeners.fire().onStopInterpreter(); } } /** * Close the application. */ @Override public void close() { window.dispose(); } /** * Insert a command into the input pane. * * @param command */ @Override public void insertCommand(String command) { torgoPanel.insertIntoSource(command); } /** * Set the source of the input pane. * * @param src */ @Override public void setSource(String src) { torgoPanel.setSource(src); } /** * Return the current GUI window. This is made available for setting parents * for dialog windows. * * @return */ protected final JFrame getWindow() { return this.window; } }