From c981f9811ab15c2a521ab6e40b7b5dd7b3f3d88a Mon Sep 17 00:00:00 2001 From: Charles McGarvey Date: Fri, 12 Nov 2010 16:18:38 -0700 Subject: [PATCH] small cleanups; documented the code see #1 --- src/com/dogcows/Editor.java | 198 +++++++++++++++++++++++++--------- src/com/dogcows/Util.java | 158 +++++++++++++++++++++++++++ src/com/dogcows/Utility.java | 96 ----------------- src/com/dogcows/VimCoder.java | 90 ++++++++++++---- 4 files changed, 378 insertions(+), 164 deletions(-) create mode 100644 src/com/dogcows/Util.java delete mode 100644 src/com/dogcows/Utility.java diff --git a/src/com/dogcows/Editor.java b/src/com/dogcows/Editor.java index ddb7ccf..3bc81d2 100644 --- a/src/com/dogcows/Editor.java +++ b/src/com/dogcows/Editor.java @@ -17,16 +17,37 @@ import com.topcoder.shared.problem.TestCase; /** * @author Charles McGarvey - * + * The TopCoder Arena editor plug-in providing support for Vim. + * + * Distributable under the terms and conditions of the 2-clause BSD license; + * see the file COPYING for a complete text of the license. */ public class Editor { + /** + * The problem ID number. + */ private String id; + + /** + * The name of the class. + */ private String name; + + /** + * The path of the current source file. + */ private File sourceFile; + + /** + * The path of the problem directory. + */ private File directory; + /** + * Map languages names to file extensions. + */ private static final Map languageExtension = new HashMap(); static { @@ -38,19 +59,28 @@ public class Editor } + /** + * Construct an editor with the problem objects given us by the Arena. + * @param component A container for the particulars of the problem. + * @param language The currently selected language. + * @param renderer A helper object to help format the problem statement. + * @throws Exception If the editor could not set itself up. + */ public Editor(ProblemComponentModel component, Language language, - Renderer renderer) throws IOException + Renderer renderer) throws Exception { this.id = String.valueOf(component.getProblem().getProblemID()); this.name = component.getClassName(); + // Make sure the top-level vimcoder directory exists. File topDir = new File(System.getProperty("user.home"), ".vimcoder"); if (!topDir.isDirectory()) { if (!topDir.mkdirs()) throw new IOException(topDir.getPath()); } + // Make sure the problem directory exists. this.directory = new File(topDir, id); if (!directory.isDirectory()) { @@ -60,20 +90,22 @@ public class Editor String lang = language.getName(); String ext = languageExtension.get(lang); + // Set up the terms used for the template expansion. HashMap terms = new HashMap(); - terms.put("RETURNTYPE", component.getReturnType().getDescriptor(language).replaceAll("\\s+", "")); + terms.put("RETURNTYPE",component.getReturnType().getDescriptor(language).replaceAll("\\s+", "")); terms.put("CLASSNAME", name); terms.put("METHODNAME", component.getMethodName()); terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(), component.getParamNames(), language)); - terms.put("METHODPARAMNAMES", Utility.join(component.getParamNames(), ", ")); - terms.put("METHODPARAMSTREAMIN", Utility.join(component.getParamNames(), " >> ")); - terms.put("METHODPARAMSTREAMOUT", Utility.join(component.getParamNames(), " << ")); + terms.put("METHODPARAMNAMES", Util.join(component.getParamNames(), ", ")); + terms.put("METHODPARAMSTREAMIN", Util.join(component.getParamNames(), " >> ")); + terms.put("METHODPARAMSTREAMOUT", Util.join(component.getParamNames(), " << ")); terms.put("METHODPARAMDECLARES", getMethodParamDeclarations(component.getParamTypes(), component.getParamNames(), language)); + // Write the problem statement as an HTML file in the problem directory. File problemFile = new File(directory, "Problem.html"); if (!problemFile.canRead()) { @@ -82,22 +114,37 @@ public class Editor { writer.write(renderer.toHTML(language)); } - catch (Exception exception) + finally { + writer.close(); } - writer.close(); } + // Expand the template for the main class and write it to the current + // source file. sourceFile = new File(directory, name + "." + ext); if (!sourceFile.canRead()) { - String text = Utility.expandTemplate(Utility.readResource(lang + "Template"), - terms); + String text = Util.expandTemplate(Util.readResource(lang + "Template"), + terms); FileWriter writer = new FileWriter(sourceFile); writer.write(text); writer.close(); } + + // Expand the driver template and write it to a source file. + File driverFile = new File(directory, "driver." + ext); + if (!driverFile.canRead()) + { + String text = Util.expandTemplate(Util.readResource(lang + "Driver"), + terms); + FileWriter writer = new FileWriter(driverFile); + writer.write(text); + writer.close(); + } + // Write the test cases to a text file. The driver code can read this + // file and perform the tests based on what it reads. File testcaseFile = new File(directory, "testcases.txt"); if (!testcaseFile.canRead()) { @@ -118,19 +165,10 @@ public class Editor writer.close(); } - File driverFile = new File(directory, "driver." + ext); - if (!driverFile.canRead()) - { - String text = Utility.expandTemplate(Utility.readResource(lang + "Driver"), - terms); - FileWriter writer = new FileWriter(driverFile); - writer.write(text); - writer.close(); - } - + // Finally, expand the Makefile template and write it. File makeFile = new File(directory, "Makefile"); { - String text = Utility.expandTemplate(Utility.readResource(lang + "Makefile"), + String text = Util.expandTemplate(Util.readResource(lang + "Makefile"), terms); FileWriter writer = new FileWriter(makeFile); writer.write(text); @@ -138,36 +176,75 @@ public class Editor } } + /** + * Save the source code provided by the server, and tell the Vim server to + * edit the current source file. + * @param source The source code. + * @throws Exception If the source couldn't be written or the Vim server + * had a problem. + */ public void setSource(String source) throws Exception { FileWriter writer = new FileWriter(new File(directory, name)); writer.write(source); writer.close(); - doVimCommand("--remote-tab-silent", sourceFile.getPath()); + sendVimCommand("--remote-tab-silent", sourceFile.getPath()); } + /** + * Read the source code from the current source file. + * @return The source code. + * @throws IOException If the source file could not be read. + */ public String getSource() throws IOException { - return Utility.readFile(sourceFile) + "\n// Edited by " + VimCoder.version + "\n// " + VimCoder.website + "\n\n"; + return Util.readFile(sourceFile) + "\n// Edited by " + + VimCoder.version + "\n// " + VimCoder.website + "\n\n"; } - private void doVimCommand(String command, String argument) throws Exception + /** + * Send a command to the Vim server. If the server isn't running, it will + * be started with the name VIMCODER#### where #### is the problem ID. + * @param command The command to send to the server. + * @param argument A single argument for the remote command. + * @throws Exception If the command could not be sent. + */ + private void sendVimCommand(String command, + String argument) throws Exception { String[] arguments = {argument}; - doVimCommand(command, arguments); + sendVimCommand(command, arguments); } - private void doVimCommand(String command, String[] arguments) throws Exception + /** + * Send a command to the Vim server. If the server isn't running, it will + * be started with the name VIMCODER#### where #### is the problem ID. + * @param command The command to send to the server. + * @param argument Arguments for the remote command. + * @throws Exception If the command could not be sent. + */ + private void sendVimCommand(String command, + String[] arguments) throws Exception { - String[] exec = {"gvim", "--servername", "VimCoder" + id, - command}; - exec = Utility.concat(exec, arguments); + String[] exec = {"gvim", "--servername", "VimCoder" + id, command}; + exec = Util.concat(exec, arguments); Process child = Runtime.getRuntime().exec(exec, null, directory); - long expire = System.currentTimeMillis() + 500; + /* FIXME: This is a hack with a magic number. The problem is the Vim + * process doesn't fork to the background on some systems, so we can't + * wait on the child. At the same time, calling this method before the + * previous child could finish initializing the server may result in + * multiple editor windows popping up. We'd also like to be able to + * get the return code from the child if we can. The workaround here is + * to stall the thread for a little while or until we know the child + * does exit. If the child never exits before the timeout, we will + * assume it is not backgrounding and that everything worked. This all + * works very well in practice, but perhaps there's a better way... */ + long expire = System.currentTimeMillis() + 250; while (System.currentTimeMillis() < expire) { + Thread.yield(); try { int exitCode = child.exitValue(); @@ -176,38 +253,61 @@ public class Editor } catch (IllegalThreadStateException exception) { + // The child has not exited; intentionally ignoring exception. } - Thread.yield(); } } + /** + * Convert an array of data types to an array of strings according to a + * given language. + * @param types The data types. + * @param language The language to use in the conversion. + * @return The array of string representations of the data types. + */ + private String[] getStringTypes(DataType[] types, Language language) + { + String[] strings = new String[types.length]; + for (int i = 0; i < types.length; ++i) + { + strings[i] = types[i].getDescriptor(language).replaceAll("\\s+", ""); + } + return strings; + } + + /** + * Combine the data types and parameter names into a comma-separated list of + * the method parameters. The result could be used inside the parentheses + * of a method declaration. + * @param types The data types of the parameters. + * @param names The names of the parameters. + * @param language The language used for representing the data types. + * @return The list of parameters. + */ private String getMethodParams(DataType[] types, String[] names, Language language) { - StringBuilder text = new StringBuilder(); - - text.append(types[0].getDescriptor(language).replaceAll("\\s+", "") + " " + names[0]); - for (int i = 1; i < names.length; ++i) - { - text.append(", " + types[i].getDescriptor(language).replaceAll("\\s+", "") + " " + names[i]); - } - - return text.toString(); + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, " "), ", "); } - private String getMethodParamDeclarations (DataType[] types, + /** + * Combine the data types and parameter names into a group of variable + * declarations. Each declaration is separated by a new line and terminated + * with a semicolon. + * @param types The data types of the parameters. + * @param names The names of the parameters. + * @param language The language used for representing the data types. + * @return The parameters as a block of declarations. + */ + private String getMethodParamDeclarations(DataType[] types, String[] names, Language language) { - StringBuilder text = new StringBuilder(); - - for (int i = 0; i < names.length; ++i) - { - text.append(types[i].getDescriptor(language).replaceAll("\\s+", "") + "\t" + names[i] + ";" + System.getProperty("line.separator")); - } - - return text.toString(); + final String end = ";" + System.getProperty("line.separator"); + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, "\t"), end) + end; } } diff --git a/src/com/dogcows/Util.java b/src/com/dogcows/Util.java new file mode 100644 index 0000000..bc5bdb8 --- /dev/null +++ b/src/com/dogcows/Util.java @@ -0,0 +1,158 @@ + +package com.dogcows; + +import java.io.*; +import java.util.Arrays; +import java.util.Map; + +/** + * @author Charles McGarvey + * The TopCoder Arena editor plug-in providing support for Vim. + * + * Distributable under the terms and conditions of the 2-clause BSD license; + * see the file COPYING for a complete text of the license. + */ +public abstract class Util +{ + /** + * Concatenate two arrays into a single array. + * @param a First array. + * @param b Second array. + * @return The combined array. + */ + public static T[] concat(T[] a, T[] b) + { + T[] result = Arrays.copyOf(a, a.length + b.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + + /** + * Combined string elements from two arrays into a single array, gluing + * together elements of the same index with a delimiter string. + * @param a First string array. + * @param b Second string array. + * @param glue The delimiter string. + * @return The combined array. + */ + public static String[] combine(String[] a, String[] b, String glue) + { + String[] result = new String[Math.min(a.length, b.length)]; + for (int i = 0; i < result.length; ++i) + { + result[i] = a[i] + glue + b[i]; + } + return result; + } + + /** + * Join the elements of a string array with a delimiter. + * @param a The array. + * @param glue The delimiter string. + * @return The joined string. + */ + public static String join(String[] a, String glue) + { + if (a.length == 0) return ""; + StringBuilder result = new StringBuilder(); + result.append(a[0]); + for (int i = 1; i < a.length; ++i) result.append(glue).append(a[i]); + return result.toString(); + } + + /** + * Quote a string by replacing prepending backslashes and double + * quotation characters with an extra backslash. + * @param The string to be quoted. + * @return The quoted string. + */ + public static String quote(String a) + { + a = a.replaceAll("\\\\", "\\\\\\\\"); + a = a.replaceAll("\"", "\\\\\\\""); + return a; + } + + /** + * Simply read a file's contents into a string object. + * @param file The file to read. + * @return The contents of the file. + * @throws IOException If the file is not readable. + */ + public static String readFile(File file) throws IOException + { + StringBuilder text = new StringBuilder(); + + BufferedReader reader = new BufferedReader(new FileReader(file.getPath())); + try + { + String line = null; + + while ((line = reader.readLine()) != null) + { + text.append(line + System.getProperty("line.separator")); + } + } + finally + { + reader.close(); + } + + return text.toString(); + } + + /** + * Read a resource file into a string object. The resources should be + * placed in the directory `resources' underneath the parent directory of + * this class. Reading resources packaged in a jar is allowable. + * @param path Relative path to the resource. + * @return The contents of the resource. + * @throws IOException If the resource is not readable. + */ + public static String readResource(String path) throws IOException + { + StringBuilder text = new StringBuilder(); + + InputStream stream = Util.class.getResourceAsStream("resources/" + path); + if (stream != null) + { + try + { + byte[] buffer = new byte[4096]; + int numBytes = 0; + while (0 < (numBytes = stream.read(buffer))) + { + text.append(new String(buffer, 0, numBytes)); + } + } + finally + { + stream.close(); + } + } + + return text.toString(); + } + + /** + * The poor man's template package. Provide a template and a map of terms + * to build the result with the terms expanded into the template. Terms + * in the template should appear surrounded with dollar signs. For example, + * if $MYTERM$ appears in the template, it will be replaced by the value + * into the terms map with the key MYTERM (if it exists in the map). + * @param template The template string. + * @param terms A map of key/value terms. + * @return The string expanded from the template and terms. + */ + public static String expandTemplate(String template, Map terms) + { + String text = template; + for (String key : terms.keySet()) + { + text = text.replaceAll("\\$" + key + "\\$", + Util.quote(terms.get(key))); + } + return text; + } +} + diff --git a/src/com/dogcows/Utility.java b/src/com/dogcows/Utility.java deleted file mode 100644 index 9b4c7cc..0000000 --- a/src/com/dogcows/Utility.java +++ /dev/null @@ -1,96 +0,0 @@ - -package com.dogcows; - -import java.io.*; -import java.util.Arrays; -import java.util.Map; - -/** - * @author Charles McGarvey - * - */ -public abstract class Utility -{ - - public static T[] concat(T[] a, T[] b) - { - T[] result = Arrays.copyOf(a, a.length + b.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } - - public static String join(String[] a, String glue) - { - if (a.length == 0) return ""; - StringBuilder result = new StringBuilder(); - result.append(a[0]); - for (int i = 1; i < a.length; ++i) result.append(glue).append(a[i]); - return result.toString(); - } - - public static String quote(String a) - { - a = a.replaceAll("\\\\", "\\\\\\\\"); - a = a.replaceAll("\"", "\\\\\\\""); - return a; - } - - public static String readFile(File file) throws IOException - { - StringBuilder text = new StringBuilder(); - - BufferedReader reader = new BufferedReader(new FileReader(file.getPath())); - try - { - String line = null; - - while ((line = reader.readLine()) != null) - { - text.append(line + System.getProperty("line.separator")); - } - } - finally - { - reader.close(); - } - - return text.toString(); - } - - public static String readResource(String path) throws IOException - { - StringBuilder text = new StringBuilder(); - - InputStream stream = Utility.class.getResourceAsStream("resources/" + path); - if (stream != null) - { - try - { - byte[] buffer = new byte[4096]; - int numBytes = 0; - while (0 < (numBytes = stream.read(buffer))) - { - text.append(new String(buffer, 0, numBytes)); - } - } - finally - { - stream.close(); - } - } - - return text.toString(); - } - - public static String expandTemplate(String template, Map terms) - { - String text = template; - for (String key : terms.keySet()) - { - text = text.replaceAll("\\$" + key + "\\$", - Utility.quote(terms.get(key))); - } - return text; - } -} - diff --git a/src/com/dogcows/VimCoder.java b/src/com/dogcows/VimCoder.java index 1fb9bab..01394a5 100644 --- a/src/com/dogcows/VimCoder.java +++ b/src/com/dogcows/VimCoder.java @@ -21,17 +21,36 @@ import com.topcoder.shared.problem.Renderer; public class VimCoder { /** - * + * The name and version of this plugin. */ public final static String version = "VimCoder 0.1"; + + /** + * The website of the plugin project. + */ public final static String website = "http://www.dogcows.com/vimcoder"; + + /** + * The panel given to the Arena applet when it is requested. + */ private JPanel panel; + + /** + * The text widget where log messages are appended. + */ private JTextArea logArea; + /** + * The current editor object (or null if there is none). + */ private Editor editor; + /** + * Instantiate the entry point of the editor plugin. Sets up the log widget + * and panel. + */ public VimCoder() { logArea = new JTextArea(); @@ -46,6 +65,9 @@ public class VimCoder } + /** + * Called by the Arena when the plugin is about to be used. + */ public void startUsing() { System.out.println("startUsing"); @@ -66,18 +88,32 @@ public class VimCoder } } + /** + * Called by the Arena when the plugin is no longer needed. + */ public void stopUsing() { System.out.println("stopUsing"); editor = null; } + /** + * Called by the Arena to obtain the editor panel which we will use to show + * log messages. + * @return The editor panel. + */ public JPanel getEditorPanel() { System.out.println("getEditorPanel"); return panel; } + /** + * Called by the Arena to obtain the current source. This happens when the + * user is saving, compiling, and/or submitting. + * @return The current source code. + * @throws Exception If the source file edited by Vim couldn't be read. + */ public String getSource() throws Exception { System.out.println("getSource"); @@ -89,11 +125,16 @@ public class VimCoder } catch (Exception exception) { - logError("Failed to get source code: " + exception.getLocalizedMessage()); + logError("Failed to get source code: " + + exception.getLocalizedMessage()); throw exception; } } - + + /** + * Called by the Arena to pass the source it has. + * @param source The source code. + */ public void setSource(String source) { System.out.println("setSource: " + source); @@ -104,11 +145,18 @@ public class VimCoder } catch (Exception exception) { - logError("Failed to save the source given by the server: " + exception.getLocalizedMessage()); + logError("Failed to save the source given by the server: " + + exception.getLocalizedMessage()); return; } } + /** + * Called by the Arena to pass along information about the current problem. + * @param component A container for the particulars of the problem. + * @param language The currently selected language. + * @param renderer A helper object to help format the problem statement. + */ public void setProblemComponent(ProblemComponentModel component, Language language, Renderer renderer) @@ -120,11 +168,17 @@ public class VimCoder } catch (Exception exception) { - logError("An error occured while loading the problem: " + exception.getLocalizedMessage()); + logError("An error occured while loading the problem: " + + exception.getLocalizedMessage()); } } + /** + * A generic logging function, appends text to the text area. A timestamp + * is also prepended to the next text. + * @param what The text to append. + */ private void log(final String what) { Runnable task = new Runnable() @@ -145,33 +199,31 @@ public class VimCoder } } + /** + * Output non-critical messages to the log. + * @param what The text of the message. + */ private void logInfo(String what) { log(" INFO: " + what + System.getProperty("line.separator")); } + /** + * Output potentially important messages to the log. + * @param what The text of the message. + */ private void logWarning(String what) { log(" WARN: " + what + System.getProperty("line.separator")); } + /** + * Output critical messages and errors to the log. + * @param what The text of the message. + */ private void logError(String what) { log("ERROR: " + what + System.getProperty("line.separator")); } - - - public static void main(String args[]) - { - VimCoder plugin = new VimCoder(); - - JFrame frame = new JFrame("VimCoder"); - frame.add(plugin.getEditorPanel()); - frame.setSize(640, 480); - frame.setVisible(true); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - plugin.startUsing(); - } } -- 2.44.0