package elw.dp.app; import base.pattern.Result; import elw.dp.mips.Instruction; import elw.dp.mips.MipsValidator; import elw.dp.mips.TaskBean; import elw.dp.ui.DataPathForm; import elw.dp.ui.RendererFactory; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.ActionEvent; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.Arrays; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; public class Controller implements ControllerSetup, RunnableLoadTask.Callback { private static final org.slf4j.Logger log = LoggerFactory.getLogger(Controller.class); private static final String PREFIX_TEST = "Test #"; private static final Pattern PATTERN_LINE_SEPARATOR = Pattern.compile("\r|\r\n|\n"); private final DataPathForm view = new DataPathForm(); private final JLabel labelStatus = new JLabel(); // application state private AtomicLong sourceStamp = new AtomicLong(1); // NOTE: no modifications still require assembly to run before stepping private AtomicLong assembleStamp = new AtomicLong(0); private TaskBean task; private String baseUrl; private String uploadHeader; private String elwCtx; private final DefaultComboBoxModel testComboModel = new DefaultComboBoxModel(); // app data (compile/test/run cycle) private final MipsValidator validator = new MipsValidator(); private InstructionsTableModel tmInstructions = new InstructionsTableModel(validator.getDataPath().getInstructions()); private RegistersTableModel tmRegs = new RegistersTableModel(validator.getDataPath().getRegisters()); private MemoryTableModel tmMemory = new MemoryTableModel(validator.getDataPath().getMemory()); // actions private final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(3); private final AssembleAction aAssemble = new AssembleAction("Assemble>", true); private final AssembleAction aVerify = new AssembleAction("Verify", false); private final SubmitAction aSubmit = new SubmitAction("Submit"); private final UpdateTestSelectionAction aUpdateTestSelection = new UpdateTestSelectionAction(); private final TestStepAction aTestStep = new TestStepAction("Step>"); private final TestRunAction aTestRun = new TestRunAction("Run"); private final TestBatchAction aTestBatch = new TestBatchAction("Batch"); private final RunStepAction aRunStep = new RunStepAction("Step", 1); private final RunStepAction aRunRun = new RunStepAction("Run", validator.getRunSteps()); private final RunResetAction aRunReset = new RunResetAction("Reset"); public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } public void setUploadHeader(String uploadHeader) { this.uploadHeader = uploadHeader; } public void setElwCtx(String elwCtx) { this.elwCtx = elwCtx; } public String getElwCtx() { return elwCtx; } public String getBaseUrl() { return baseUrl; } public String getUploadHeader() { return uploadHeader; } public JPanel getPanelView() { return view.getRootPanel(); } public JLabel getLabelStatus() { return labelStatus; } public void setTask(TaskBean task) { this.task = task; } public void start() { view.getTestComboBox().setEnabled(false); // this does not work for unsigned applets... view.getProblemTextPane().setText("loading..."); view.getSourceTextArea().setText("loading..."); view.getSourceAssembleButton().setEnabled(false); view.getSourceVerifyButton().setEnabled(false); view.getSourceSubmitButton().setEnabled(false); view.getTestAddCustomButton().setEnabled(false); view.getTestStepButton().setEnabled(false); view.getTestRunButton().setEnabled(false); view.getTestBatchButton().setEnabled(false); view.getRunStepButton().setEnabled(false); view.getRunRunButton().setEnabled(false); view.getRunResetButton().setEnabled(false); executor.submit(new RunnableLoadTask(this, this)); } public void updateStatus(final String newStatus, Throwable fault) { if (fault == null) { log.info(newStatus); } else { log.error(newStatus, fault); } } public void onTaskLoadComplete() { testComboModel.removeAllElements(); java.util.List<String> tests = task.getTests(); for (int i = 0, testsSize = tests.size(); i < testsSize; i++) { testComboModel.addElement(PREFIX_TEST + (i + 1)); } selectTest(0); view.getProblemTextPane().setText(task.getStatement()); view.getTestComboBox().setModel(testComboModel); view.getSourceTextArea().setText(task.getSolution()); view.getSourceTextArea().getDocument().addDocumentListener(new SourceDocumentListener()); view.getSourceAssembleButton().setAction(aAssemble); view.getSourceAssembleButton().setMnemonic('a'); view.getSourceVerifyButton().setAction(aVerify); view.getSourceVerifyButton().setMnemonic('v'); view.getSourceSubmitButton().setAction(aSubmit); view.getSourceSubmitButton().setMnemonic('u'); view.getTestComboBox().setAction(aUpdateTestSelection); view.getTestAddCustomButton().setEnabled(false); // LATER #163 view.getTestStepButton().setAction(aTestStep); view.getTestStepButton().setMnemonic('s'); view.getTestRunButton().setAction(aTestRun); view.getTestRunButton().setMnemonic('r'); view.getTestBatchButton().setAction(aTestBatch); view.getTestBatchButton().setMnemonic('b'); final RendererFactory rFactory = new RendererFactory(); final JTable instrTable = view.getRunInstructionsTable(); instrTable.setModel(tmInstructions); rFactory.install(instrTable); rFactory.findColByName(instrTable, InstructionsTableModel.COL_ACC).setMaxWidth(18); rFactory.findColByName(instrTable, InstructionsTableModel.COL_ADDR).setMaxWidth(80); rFactory.findColByName(instrTable, InstructionsTableModel.COL_CODE).setMaxWidth(240); rFactory.findColByName(instrTable, InstructionsTableModel.COL_CODE).setMinWidth(160); final JTable regsTable = view.getRunRegsTable(); regsTable.setModel(tmRegs); rFactory.install(regsTable); rFactory.findColByName(regsTable, RegistersTableModel.COL_ACC).setMaxWidth(18); rFactory.findColByName(regsTable, RegistersTableModel.COL_NAME).setMinWidth(32); rFactory.findColByName(regsTable, RegistersTableModel.COL_NAME).setMaxWidth(40); rFactory.findColByName(regsTable, RegistersTableModel.COL_NUMBER).setMaxWidth(72); rFactory.findColByName(regsTable, RegistersTableModel.COL_NUMBER).setMinWidth(64); rFactory.findColByName(regsTable, RegistersTableModel.COL_HEX).setMinWidth(72); rFactory.findColByName(regsTable, RegistersTableModel.COL_HEX).setMaxWidth(96); rFactory.findColByName(regsTable, RegistersTableModel.COL_DEC).setMinWidth(72); rFactory.findColByName(regsTable, RegistersTableModel.COL_DEC).setMaxWidth(96); final JTable memTable = view.getRunMemTable(); memTable.setModel(tmMemory); rFactory.install(memTable); rFactory.findColByName(memTable, MemoryTableModel.COL_ACC).setMaxWidth(18); rFactory.findColByName(memTable, MemoryTableModel.COL_ADDR).setMinWidth(72); rFactory.findColByName(memTable, MemoryTableModel.COL_ADDR).setMaxWidth(80); view.getRunStepButton().setAction(aRunStep); view.getRunStepButton().setMnemonic('t'); view.getRunRunButton().setAction(aRunRun); view.getRunRunButton().setMnemonic('r'); view.getRunResetButton().setAction(aRunReset); view.getRunResetButton().setMnemonic('e'); log.info("startup sequence complete"); } private void fireDataPathChanged() { tmInstructions.fireTableDataChanged(); tmMemory.fireTableDataChanged(); tmRegs.fireTableDataChanged(); } private void selectTest(int i) { testComboModel.setSelectedItem(testComboModel.getElementAt(i)); final String testSource = task.getTests().get(i); final TaskBean.Test testBean = TaskBean.parseTest(testSource); if (!testBean.parseErrors.getLineToErrors().isEmpty()) { log.warn("marks broken, not loading test"); return; } view.getTestRegsTextArea().setText(testSource); view.getTestRegsTextArea().setEditable(false); setupStatus(view.getTestStatusLabel()); setupStatus(view.getRunStatusLabel()); } private void job_assemble(final JLabel statusLabel, final Result[] resRef) { setupStatus(statusLabel, "Assembling..."); final java.util.List<String> sourceLines = getSource(); final Instruction[] newInstructions = validator.assemble(resRef, sourceLines); if (newInstructions != null) { assembleStamp.set(System.currentTimeMillis()); } } private java.util.List<String> getSource() { final String source = view.getSourceTextArea().getText(); return Arrays.asList(PATTERN_LINE_SEPARATOR.split(source)); } private void job_submit(final JLabel statusLabel, final Result[] resRef, String sourceText) { if (baseUrl == null || baseUrl.length() == 0) { Result.failure(log, resRef, "Upload URL not set"); return; } if (elwCtx == null || elwCtx.length() == 0) { Result.failure(log, resRef, "elw_ctx not set"); return; } setupStatus(statusLabel, "Submitting..."); OutputStream up = null; InputStream down = null; try { final byte[] textBytes = sourceText.getBytes("UTF-8"); final String ulStr = baseUrl + "ul?elw_ctx=" + elwCtx + "&sId=code&s=s"; final HttpURLConnection connection = (HttpURLConnection) new URL(ulStr).openConnection(); connection.setFixedLengthStreamingMode(textBytes.length); connection.setRequestMethod("PUT"); connection.setDoOutput(true); connection.setRequestProperty("Content-type", "text; charset=UTF-8"); connection.setRequestProperty("Accept", "text"); if (uploadHeader != null && uploadHeader.length() > 0) { connection.setRequestProperty("Cookie", uploadHeader); } up = connection.getOutputStream(); up.write(textBytes); up.flush(); if (connection.getResponseCode() != 200) { Result.failure(log, resRef, "Failed: " + URLDecoder.decode(connection.getResponseMessage(), "UTF-8")); return; } } catch (IOException e) { Result.failure(log, resRef, e.getMessage()); return; } finally { if (up != null) { try { up.close(); } catch (IOException e) { log.info("ignoring", e); } } if (down != null) { try { down.close(); } catch (IOException e) { log.info("ignoring", e); } } } Result.success(log, resRef, "Submitted"); } private void job_loadTest(final JLabel statusLabel, Result[] resRef, final String test) { final boolean assemble = assembleStamp.get() < sourceStamp.get(); if (assemble) { job_assemble(statusLabel, resRef); } if (!assemble || resRef[0].isSuccess()) { setupStatus(statusLabel, "Loading Regs and Mem for..."); validator.loadTest(resRef, test); } else { validator.clearTest(); } } class AssembleAction extends AbstractAction { final boolean switchToTest; private AssembleAction(final String name, boolean switchToTest) { super(name); this.switchToTest = switchToTest; } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getSourceFeedbackLabel(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { job_assemble(statusLabel, resRef); } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setEnabled(true); setupStatus(statusLabel, resRef[0]); if (switchToTest && resRef[0].isSuccess()) { view.getStrTabbedPane().setSelectedIndex(1); } } }); } } }); } } class SubmitAction extends AbstractAction { private SubmitAction(final String name) { super(name); } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getSourceFeedbackLabel(); final String sourceText = view.getSourceTextArea().getText(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { job_submit(statusLabel, resRef, sourceText); } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setEnabled(true); setupStatus(statusLabel, resRef[0]); } }); } } }); } } class UpdateTestSelectionAction extends AbstractAction { private UpdateTestSelectionAction() { super("Update Test Selection"); } public void actionPerformed(ActionEvent e) { final String selectedItem = (String) testComboModel.getSelectedItem(); if (selectedItem.startsWith(PREFIX_TEST)) { selectTest(Integer.parseInt(selectedItem.substring(PREFIX_TEST.length())) - 1); } } } class TestStepAction extends AbstractAction { private TestStepAction(final String name) { super(name); } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getTestStatusLabel(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { final String testItem = (String) testComboModel.getSelectedItem(); if (testItem.startsWith(PREFIX_TEST)) { final int testIndex = Integer.parseInt(testItem.substring(PREFIX_TEST.length())) - 1; job_loadTest(statusLabel, resRef, task.getTests().get(testIndex)); } else { Result.failure(log, resRef, "Failed to parse test index: " + testItem); } if (resRef[0].isSuccess()) { setupStatus(statusLabel, "Resetting..."); validator.reset(resRef); } } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setEnabled(true); setupStatus(statusLabel, resRef[0]); if (resRef[0].isSuccess()) { view.getStrTabbedPane().setSelectedIndex(2); setupStatus(view.getRunStatusLabel()); fireDataPathChanged(); } } }); } } }); } } class RunStepAction extends AbstractAction { protected final int steps; private RunStepAction(final String name, final int steps) { super(name); this.steps = steps; } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getRunStatusLabel(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { setupStatus(statusLabel, "Stepping..."); validator.step(resRef, steps); } catch (Throwable t) { Result.failure(log, resRef, "Failed: " + t.getMessage()); log.trace("trace", t); } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setEnabled(true); setupStatus(statusLabel, resRef[0]); if (resRef[0].isSuccess()) { fireDataPathChanged(); } } }); } } }); } } class RunResetAction extends AbstractAction { private RunResetAction(final String name) { super(name); } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getRunStatusLabel(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { setupStatus(statusLabel, "Resetting..."); validator.reset(resRef); } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setupStatus(statusLabel, resRef[0]); if (resRef[0].isSuccess()) { fireDataPathChanged(); } setEnabled(true); } }); } } }); } } class TestRunAction extends AbstractAction { private TestRunAction(final String name) { super(name); } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getTestStatusLabel(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { setupStatus(statusLabel, "Running..."); final String testItem = (String) testComboModel.getSelectedItem(); if (testItem.startsWith(PREFIX_TEST)) { final int testIndex = Integer.parseInt(testItem.substring(PREFIX_TEST.length())) - 1; validator.run(resRef, task.getTests().get(testIndex), getSource()); } else { Result.failure(log, resRef, "Failed to parse test index: " + testItem); } } catch (Throwable t) { Result.failure(log, resRef, "Failed: " + t.getClass() + ": " + t.getMessage()); log.trace("trace", t); } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setEnabled(true); setupStatus(statusLabel, resRef[0]); } }); } } }); } } class TestBatchAction extends AbstractAction { private TestBatchAction(final String name) { super(name); } public void actionPerformed(ActionEvent e) { setEnabled(false); final JLabel statusLabel = view.getTestStatusLabel(); executor.submit(new Runnable() { public void run() { final Result[] resRef = new Result[]{new Result("status unknown", false)}; try { validator.batch(resRef, task, getSource(), null); } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { setEnabled(true); setupStatus(statusLabel, resRef[0]); } }); } } }); } } private class SourceDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { sourceStamp.set(System.currentTimeMillis()); setupStatus(view.getSourceFeedbackLabel()); setupStatus(view.getTestStatusLabel()); } public void removeUpdate(DocumentEvent e) { sourceStamp.set(System.currentTimeMillis()); setupStatus(view.getSourceFeedbackLabel()); setupStatus(view.getTestStatusLabel()); } public void changedUpdate(DocumentEvent e) { sourceStamp.set(System.currentTimeMillis()); setupStatus(view.getSourceFeedbackLabel()); setupStatus(view.getTestStatusLabel()); } } private static void setupStatus(final JLabel label) { setupStatus(label, "..."); } private static void setupStatus(final JLabel label, final String text) { if (SwingUtilities.isEventDispatchThread()) { label.setText(text); label.setToolTipText(text); label.setForeground(Color.DARK_GRAY); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { label.setText(text); label.setToolTipText(text); label.setForeground(Color.DARK_GRAY); } }); } } private static void setupStatus(final JLabel label, final Result result) { if (SwingUtilities.isEventDispatchThread()) { label.setToolTipText(result.getMessage()); label.setText(result.getMessage()); label.setForeground(result.isSuccess() ? Color.GREEN.darker().darker() : Color.RED.darker().darker()); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { label.setToolTipText(result.getMessage()); label.setText(result.getMessage()); label.setForeground(result.isSuccess() ? Color.GREEN.darker().darker() : Color.RED.darker().darker()); } }); } } }