From: Charles McGarvey Date: Thu, 18 Nov 2010 03:05:56 +0000 (-0700) Subject: new build system; fixed white space problems X-Git-Tag: v0.3.1~3 X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fvimcoder;a=commitdiff_plain;h=28c8fae03b2294d6486233cd62f4d4d9d11603ca new build system; fixed white space problems --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e9f4f77 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ + +project := VimCoder +version := 0.3 + +mainclass = bin/com/dogcows/VimCoder.class +library = lib/ContestApplet.jar +jarfile = $(project)-$(version).jar + +JAVAC := javac +JAVACFLAGS := -d bin -sourcepath src -classpath bin:$(library) + + +.PHONY: all clean distclean dist fetch jar + +all: $(library) $(mainclass) + +clean: + rm -rf bin build + +distclean: clean + rm -rf lib + +dist: + git archive HEAD --prefix=vimcoder-$(version)/ | bzip2 >vimcoder-$(version).tar.bz2 + +fetch: $(library) + +jar: all $(jarfile) + + +$(library): + sh make.sh fetch $@ + +bin/com/dogcows/%.class: src/com/dogcows/%.java + mkdir -p bin/com/dogcows/resources &&\ +$(JAVAC) $(JAVACFLAGS) $< &&\ +cp -R src/com/dogcows/resources bin/com/dogcows/ + +$(jarfile): $(mainclass) + sh make.sh jar $@ + + +$(mainclass): src/com/dogcows/Util.java src/com/dogcows/Editor.java + diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..0bb3d82 --- /dev/null +++ b/make.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +action=$1 +shift + +case "$action" +in + + fetch) + echo "Fetching dependencies..." + mkdir -p lib + curl -o $1 http://www.topcoder.com/contest/classes/ContestApplet.jar + ;; + + jar) + echo "Packaging jar file..." + rm -rf build + mkdir -p build/META-INF + printf "Manifest-Version: 1.0\n\n" >build/META-INF/MANIFEST.MF + cp -R bin/com COPYING README build/ + cd build + files=$(find com -type f | sort) + zip $1 META-INF/MANIFEST.MF README COPYING $files + cd .. + mv build/$1 . + rm -rf build + echo "Done." + ;; +esac + diff --git a/src/com/dogcows/Editor.java b/src/com/dogcows/Editor.java index 15dad35..2f28962 100644 --- a/src/com/dogcows/Editor.java +++ b/src/com/dogcows/Editor.java @@ -24,313 +24,320 @@ import com.topcoder.shared.problem.TestCase; */ 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 - { - languageExtension.put("Java", "java"); - languageExtension.put("C++", "cc"); - languageExtension.put("C#", "cs"); - languageExtension.put("VB", "vb"); - languageExtension.put("Python", "py"); - } - - - /** - * 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 Exception - { - this.id = String.valueOf(component.getProblem().getProblemID()); - this.name = component.getClassName(); - - // Make sure the top-level vimcoder directory exists. - File topDir = VimCoder.getStorageDirectory(); - 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()) - { - if (!directory.mkdirs()) throw new IOException(directory.getPath()); - } - - 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("CLASSNAME", name); - terms.put("METHODNAME", component.getMethodName()); - terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(), - component.getParamNames(), - language)); - 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()) - { - FileWriter writer = new FileWriter(problemFile); - try - { - writer.write(renderer.toHTML(language)); - } - finally - { - 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 = Util.expandTemplate(readTemplate(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(readTemplate(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()) - { - StringBuilder text = new StringBuilder(); - if (component.hasTestCases()) - { - for (TestCase testCase : component.getTestCases()) - { - text.append(testCase.getOutput() + System.getProperty("line.separator")); - for (String input : testCase.getInput()) - { - text.append(input + System.getProperty("line.separator")); - } - } - } - FileWriter writer = new FileWriter(testcaseFile); - writer.write(text.toString()); - writer.close(); - } - - // Finally, expand the Makefile template and write it. - File makeFile = new File(directory, "Makefile"); - { - String text = Util.expandTemplate(readTemplate(lang + "Makefile"), - terms); - FileWriter writer = new FileWriter(makeFile); - writer.write(text); - writer.close(); - } - } - - /** - * 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(); - 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 Util.readFile(sourceFile) + "\n// Edited by " + - VimCoder.version + "\n// " + VimCoder.website + "\n\n"; - } - - - /** - * 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}; - sendVimCommand(command, arguments); - } - - /** - * 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[] vimCommand = VimCoder.getVimCommand().split("\\s"); - String[] flags = {"--servername", "VimCoder" + id, command}; - vimCommand = Util.concat(vimCommand, flags); - vimCommand = Util.concat(vimCommand, arguments); - Process child = Runtime.getRuntime().exec(vimCommand, null, directory); - - /* 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. */ - long expire = System.currentTimeMillis() + 250; - while (System.currentTimeMillis() < expire) - { - Thread.yield(); - try - { - int exitCode = child.exitValue(); - if (exitCode != 0) throw new Exception("Vim process returned exit code " + exitCode + "."); - break; - } - catch (IllegalThreadStateException exception) - { - // The child has not exited; intentionally ignoring exception. - } - } - } - - - /** - * Read a template. We first look in the storage directory. If we can't - * find one, we look among the resources. - * @param tName The name of the template. - * @return The contents of the template file, or an empty string. - */ - private String readTemplate(String tName) - { - File templateFile = new File(VimCoder.getStorageDirectory(), tName); - try - { - if (templateFile.canRead()) return Util.readFile(templateFile); - return Util.readResource(tName); - } - catch (IOException exception) - { - return ""; - } - } - - - /** - * 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) - { - String[] typeStrings = getStringTypes(types, language); - return Util.join(Util.combine(typeStrings, names, " "), ", "); - } - - /** - * 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) - { - final String end = ";" + System.getProperty("line.separator"); - String[] typeStrings = getStringTypes(types, language); - return Util.join(Util.combine(typeStrings, names, "\t"), end) + end; - } + /** + * 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 + { + languageExtension.put("Java", "java"); + languageExtension.put("C++", "cc"); + languageExtension.put("C#", "cs"); + languageExtension.put("VB", "vb"); + languageExtension.put("Python", "py"); + } + + + /** + * 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 Exception + { + this.id = String.valueOf(component.getProblem().getProblemID()); + this.name = component.getClassName(); + + // Make sure the top-level vimcoder directory exists. + File topDir = VimCoder.getStorageDirectory(); + 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()) + { + if (!directory.mkdirs()) throw new IOException(directory.getPath()); + } + + 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("CLASSNAME", name); + terms.put("METHODNAME", component.getMethodName()); + terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(), + component.getParamNames(), + language)); + 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()) + { + FileWriter writer = new FileWriter(problemFile); + try + { + writer.write(renderer.toHTML(language)); + } + finally + { + 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 = Util.expandTemplate(readTemplate(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(readTemplate(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()) + { + StringBuilder text = new StringBuilder(); + if (component.hasTestCases()) + { + for (TestCase testCase : component.getTestCases()) + { + text.append(testCase.getOutput() + System.getProperty("line.separator")); + for (String input : testCase.getInput()) + { + text.append(input + System.getProperty("line.separator")); + } + } + } + FileWriter writer = new FileWriter(testcaseFile); + writer.write(text.toString()); + writer.close(); + } + + // Finally, expand the Makefile template and write it. + File makeFile = new File(directory, "Makefile"); + { + String text = Util.expandTemplate(readTemplate(lang + "Makefile"), + terms); + FileWriter writer = new FileWriter(makeFile); + writer.write(text); + writer.close(); + } + } + + /** + * 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(); + 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 Util.readFile(sourceFile) + "\n// Edited by " + + VimCoder.version + "\n// " + VimCoder.website + "\n\n"; + } + + + /** + * 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}; + sendVimCommand(command, arguments); + } + + /** + * 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[] vimCommand = VimCoder.getVimCommand().split("\\s"); + String[] flags = {"--servername", "VimCoder" + id, command}; + vimCommand = Util.concat(vimCommand, flags); + vimCommand = Util.concat(vimCommand, arguments); + Process child = Runtime.getRuntime().exec(vimCommand, null, directory); + + /* FIXME: This is a pretty bad hack. The problem is that 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 see that the child exits. If the child never exits + * before the timeout, we will assume it is not backgrounding and + * that everything worked. This works as long as the Vim server is + * able to start within the stall period. */ + long expire = System.currentTimeMillis() + 250; + while (System.currentTimeMillis() < expire) + { + Thread.yield(); + try + { + int exitCode = child.exitValue(); + if (exitCode != 0) throw new Exception("Vim process returned exit code " + exitCode + "."); + break; + } + catch (IllegalThreadStateException exception) + { + // The child has not exited; intentionally ignoring exception. + } + } + } + + + /** + * Read a template. + * We first look in the storage directory. If we can't find one, we + * look among the resources. + * @param tName The name of the template. + * @return The contents of the template file, or an empty string. + */ + private String readTemplate(String tName) + { + File templateFile = new File(VimCoder.getStorageDirectory(), tName); + try + { + if (templateFile.canRead()) return Util.readFile(templateFile); + return Util.readResource(tName); + } + catch (IOException exception) + { + return ""; + } + } + + + /** + * 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) + { + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, " "), ", "); + } + + /** + * 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) + { + 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 index bc5bdb8..47fdd20 100644 --- a/src/com/dogcows/Util.java +++ b/src/com/dogcows/Util.java @@ -14,145 +14,147 @@ import java.util.Map; */ 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; - } + /** + * 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; + } - /** - * 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; - } + /** + * 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/VimCoder.java b/src/com/dogcows/VimCoder.java index a128461..658bae1 100644 --- a/src/com/dogcows/VimCoder.java +++ b/src/com/dogcows/VimCoder.java @@ -1,7 +1,6 @@ package com.dogcows; -import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -9,12 +8,13 @@ import java.beans.PropertyChangeListener; import java.io.*; import java.text.SimpleDateFormat; import java.util.*; -import com.topcoder.client.contestant.ProblemComponentModel; -import com.topcoder.shared.language.*; -import com.topcoder.shared.problem.*; -import com.topcoder.shared.problem.Renderer; +import javax.swing.*; + import com.topcoder.client.contestApplet.common.Common; import com.topcoder.client.contestApplet.common.LocalPreferences; +import com.topcoder.client.contestant.ProblemComponentModel; +import com.topcoder.shared.language.Language; +import com.topcoder.shared.problem.Renderer; /** * @author Charles McGarvey @@ -25,377 +25,379 @@ import com.topcoder.client.contestApplet.common.LocalPreferences; */ public class VimCoder { - /** - * The name and version of this plugin. - */ - public final static String version = "VimCoder 0.3"; - - /** - * The website of the plugin project. - */ - public final static String website = "http://www.dogcows.com/vimcoder"; - - - /** - * The first part of the command used to invoke the Vim server. - */ - private static String vimCommand = "gvim"; - - /** - * The path to the main VimCoder directory. - */ - private static File rootDir; - static - { - if (System.getProperty("os.name").toLowerCase().equals("win")) - { - vimCommand = "C:\\WINDOWS\\gvim.bat"; - } - rootDir = new File(System.getProperty("user.home") + - System.getProperty("file.separator") + ".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; - - /** - * The configuration panel. - */ - private JDialog configDialog; - - - /** - * The key for the vim command preference. - */ - private final static String VIMCOMMAND = "com.dogcows.VimCoder.config.vimcommand"; - - /** - * The key for the root directory preference. - */ - private final static String ROOTDIR = "com.dogcows.VimCoder.config.rootdir"; - - /** - * The preferences object for storing plugin settings. - */ - private static LocalPreferences prefs = LocalPreferences.getInstance(); - - - /** - * Get the command for invoking vim. - * @return The command. - */ - public static String getVimCommand() - { - return vimCommand; - } - - /** - * Get the storage directory. - * @return The directory. - */ - public static File getStorageDirectory() - { - return rootDir; - } - - - /** - * Instantiate the entry point of the editor plugin. Sets up the log widget - * and panel. - */ - public VimCoder() - { - logArea = new JTextArea(); - logArea.setForeground(Color.GREEN); - logArea.setBackground(Color.BLACK); - logArea.setEditable(false); - Font font = new Font("Courier", Font.PLAIN, 12); - if (font != null) logArea.setFont(font); - - panel = new JPanel(new BorderLayout()); - panel.add(new JScrollPane(logArea), BorderLayout.CENTER); - } - - - /** - * Called by the Arena when the plugin is about to be used. - */ - public void startUsing() - { - Runnable task = new Runnable() - { - public void run() - { - logArea.setText(""); - } - }; - if (SwingUtilities.isEventDispatchThread()) - { - task.run(); - } - else - { - SwingUtilities.invokeLater(task); - } - loadConfiguration(); - } - - /** - * Called by the Arena when the plugin is no longer needed. - */ - public void 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() - { - 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 - { - try - { - String source = editor.getSource(); - logInfo("Source code uploaded to server."); - return source; - } - catch (Exception exception) - { - 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) - { - try - { - editor.setSource(source); - logInfo("Source code downloaded from server."); - } - catch (Exception exception) - { - 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) - { - try - { - editor = new Editor(component, language, renderer); - } - catch (Exception exception) - { - logError("An error occured while loading the problem: " + - exception.getLocalizedMessage()); - } - } - - /** - * Called by the Arena when it's time to show our configuration panel. - */ - public void configure() - { - loadConfiguration(); - - configDialog = new JDialog(); - Container pane = configDialog.getContentPane(); - - pane.setPreferredSize(new Dimension(550, 135)); - pane.setLayout(new GridBagLayout()); - pane.setForeground(Common.FG_COLOR); - pane.setBackground(Common.WPB_COLOR); - GridBagConstraints c = new GridBagConstraints(); - - JLabel rootDirLabel = new JLabel("Storage Directory:", SwingConstants.RIGHT); - rootDirLabel.setForeground(Common.FG_COLOR); - c.fill = GridBagConstraints.HORIZONTAL; - c.insets = new Insets(5, 5, 5, 5); - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 1; - pane.add(rootDirLabel, c); - - final JTextField rootDirField = new JTextField(rootDir.getPath(), 25); - c.gridx = 1; - c.gridy = 0; - pane.add(rootDirField, c); - - JButton browseButton = new JButton("Browse"); - c.gridx = 2; - c.gridy = 0; - c.anchor = GridBagConstraints.BASELINE_LEADING; - pane.add(browseButton, c); - - JLabel vimCommandLabel = new JLabel("Vim Command:", SwingConstants.RIGHT); - vimCommandLabel.setForeground(Common.FG_COLOR); - c.fill = GridBagConstraints.HORIZONTAL; - c.gridx = 0; - c.gridy = 1; - pane.add(vimCommandLabel, c); - - final JTextField vimCommandField = new JTextField(vimCommand, 25); - c.gridx = 1; - c.gridy = 1; - c.gridwidth = 2; - pane.add(vimCommandField, c); - - JButton closeButton = new JButton("Cancel"); - c.fill = GridBagConstraints.NONE; - c.gridx = 1; - c.gridy = 2; - c.gridwidth = 1; - c.anchor = GridBagConstraints.EAST; - pane.add(closeButton, c); - - JButton saveButton = new JButton("Save"); - c.fill = GridBagConstraints.HORIZONTAL; - c.gridx = 2; - c.gridy = 2; - c.anchor = GridBagConstraints.EAST; - pane.add(saveButton, c); - configDialog.getRootPane().setDefaultButton(saveButton); - - browseButton.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent actionEvent) - { - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory(new File(".")); - chooser.setDialogTitle("Choose Storage Directory"); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.setAcceptAllFileFilterUsed(false); - - if (chooser.showOpenDialog(configDialog) == JFileChooser.APPROVE_OPTION) - { - rootDirField.setText(chooser.getSelectedFile().getPath()); - } - } - }); - - closeButton.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent actionEvent) - { - configDialog.dispose(); - } - }); - - saveButton.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent actionEvent) - { - prefs.setProperty(VIMCOMMAND, vimCommandField.getText()); - prefs.setProperty(ROOTDIR, rootDirField.getText()); - configDialog.dispose(); - } - }); - - configDialog.setTitle("VimCoder Preferences"); - configDialog.pack(); - configDialog.setLocationByPlatform(true); - configDialog.setModalityType(Dialog.DEFAULT_MODALITY_TYPE); - configDialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - configDialog.setVisible(true); - } - - - /** - * Load the local preferences related to this plugin. - */ - private void loadConfiguration() - { - String vc = prefs.getProperty(VIMCOMMAND); - if (vc != null) vimCommand = vc; - - String dir = prefs.getProperty(ROOTDIR); - if (dir != null) rootDir = new File(dir); - } - - - /** - * 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() - { - public void run() - { - SimpleDateFormat format = new SimpleDateFormat("kk:mm:ss"); - logArea.append(format.format(new Date()) + ", " + what); - } - }; - if (SwingUtilities.isEventDispatchThread()) - { - task.run(); - } - else - { - SwingUtilities.invokeLater(task); - } - } - - /** - * 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 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")); - } + /** + * The name and version of this plugin. + */ + public final static String version = "VimCoder 0.3"; + + /** + * The website of the plugin project. + */ + public final static String website = "http://www.dogcows.com/vimcoder"; + + + /** + * The first part of the command used to invoke the Vim server. + */ + private static String vimCommand = "gvim"; + + /** + * The path to the main VimCoder directory. + */ + private static File rootDir; + static + { + if (System.getProperty("os.name").toLowerCase().equals("win")) + { + vimCommand = "C:\\WINDOWS\\gvim.bat"; + } + rootDir = new File(System.getProperty("user.home") + + System.getProperty("file.separator") + ".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; + + /** + * The configuration panel. + */ + private JDialog configDialog; + + + /** + * The key for the vim command preference. + */ + private final static String VIMCOMMAND = "com.dogcows.VimCoder.config.vimcommand"; + + /** + * The key for the root directory preference. + */ + private final static String ROOTDIR = "com.dogcows.VimCoder.config.rootdir"; + + /** + * The preferences object for storing plugin settings. + */ + private static LocalPreferences prefs = LocalPreferences.getInstance(); + + + /** + * Get the command for invoking vim. + * @return The command. + */ + public static String getVimCommand() + { + return vimCommand; + } + + /** + * Get the storage directory. + * @return The directory. + */ + public static File getStorageDirectory() + { + return rootDir; + } + + + /** + * Instantiate the entry point of the editor plugin. + * Sets up the log widget and panel. + */ + public VimCoder() + { + logArea = new JTextArea(); + logArea.setForeground(Color.GREEN); + logArea.setBackground(Color.BLACK); + logArea.setEditable(false); + Font font = new Font("Courier", Font.PLAIN, 12); + if (font != null) logArea.setFont(font); + + panel = new JPanel(new BorderLayout()); + panel.add(new JScrollPane(logArea), BorderLayout.CENTER); + } + + + /** + * Called by the Arena when the plugin is about to be used. + */ + public void startUsing() + { + Runnable task = new Runnable() + { + public void run() + { + logArea.setText(""); + } + }; + if (SwingUtilities.isEventDispatchThread()) + { + task.run(); + } + else + { + SwingUtilities.invokeLater(task); + } + loadConfiguration(); + } + + /** + * Called by the Arena when the plugin is no longer needed. + */ + public void 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() + { + 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 + { + try + { + String source = editor.getSource(); + logInfo("Source code uploaded to server."); + return source; + } + catch (Exception exception) + { + 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) + { + try + { + editor.setSource(source); + logInfo("Source code downloaded from server."); + } + catch (Exception exception) + { + 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) + { + try + { + editor = new Editor(component, language, renderer); + } + catch (Exception exception) + { + logError("An error occured while loading the problem: " + + exception.getLocalizedMessage()); + } + } + + /** + * Called by the Arena when it's time to show our configuration panel. + */ + public void configure() + { + loadConfiguration(); + + configDialog = new JDialog(); + Container pane = configDialog.getContentPane(); + + pane.setPreferredSize(new Dimension(550, 135)); + pane.setLayout(new GridBagLayout()); + pane.setForeground(Common.FG_COLOR); + pane.setBackground(Common.WPB_COLOR); + GridBagConstraints c = new GridBagConstraints(); + + JLabel rootDirLabel = new JLabel("Storage Directory:", SwingConstants.RIGHT); + rootDirLabel.setForeground(Common.FG_COLOR); + c.fill = GridBagConstraints.HORIZONTAL; + c.insets = new Insets(5, 5, 5, 5); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + pane.add(rootDirLabel, c); + + final JTextField rootDirField = new JTextField(rootDir.getPath(), 25); + c.gridx = 1; + c.gridy = 0; + pane.add(rootDirField, c); + + JButton browseButton = new JButton("Browse"); + c.gridx = 2; + c.gridy = 0; + c.anchor = GridBagConstraints.BASELINE_LEADING; + pane.add(browseButton, c); + + JLabel vimCommandLabel = new JLabel("Vim Command:", SwingConstants.RIGHT); + vimCommandLabel.setForeground(Common.FG_COLOR); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 1; + pane.add(vimCommandLabel, c); + + final JTextField vimCommandField = new JTextField(vimCommand, 25); + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 2; + pane.add(vimCommandField, c); + + JButton closeButton = new JButton("Cancel"); + c.fill = GridBagConstraints.NONE; + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.EAST; + pane.add(closeButton, c); + + JButton saveButton = new JButton("Save"); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 2; + c.gridy = 2; + c.anchor = GridBagConstraints.EAST; + pane.add(saveButton, c); + configDialog.getRootPane().setDefaultButton(saveButton); + + browseButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new File(".")); + chooser.setDialogTitle("Choose Storage Directory"); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setAcceptAllFileFilterUsed(false); + + if (chooser.showOpenDialog(configDialog) == JFileChooser.APPROVE_OPTION) + { + rootDirField.setText(chooser.getSelectedFile().getPath()); + } + } + }); + + closeButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + configDialog.dispose(); + } + }); + + saveButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + prefs.setProperty(VIMCOMMAND, vimCommandField.getText()); + prefs.setProperty(ROOTDIR, rootDirField.getText()); + configDialog.dispose(); + } + }); + + configDialog.setTitle("VimCoder Preferences"); + configDialog.pack(); + configDialog.setLocationByPlatform(true); + configDialog.setModalityType(Dialog.DEFAULT_MODALITY_TYPE); + configDialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + configDialog.setVisible(true); + } + + + /** + * Load the local preferences related to this plugin. + */ + private void loadConfiguration() + { + String vc = prefs.getProperty(VIMCOMMAND); + if (vc != null) vimCommand = vc; + + String dir = prefs.getProperty(ROOTDIR); + if (dir != null) rootDir = new File(dir); + } + + + /** + * 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() + { + public void run() + { + SimpleDateFormat format = new SimpleDateFormat("kk:mm:ss"); + logArea.append(format.format(new Date()) + ", " + what); + } + }; + if (SwingUtilities.isEventDispatchThread()) + { + task.run(); + } + else + { + SwingUtilities.invokeLater(task); + } + } + + /** + * 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 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")); + } }