]> Dogcows Code - chaz/vimcoder/blobdiff - src/com/dogcows/Editor.java
now loads template files from storage directory
[chaz/vimcoder] / src / com / dogcows / Editor.java
index dda5481fe2971f2173044d0758b8fdf887900adc..15dad35a8f5712c70c21017377e30e73dbea181f 100644 (file)
@@ -7,8 +7,7 @@ import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import com.topcoder.client.contestant.ProblemComponentModel;
 import com.topcoder.shared.language.Language;
@@ -18,17 +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<String,String> languageExtension = new HashMap<String,String>();
     static
     {
@@ -40,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();
+        this.id = String.valueOf(component.getProblem().getProblemID());
+        this.name = component.getClassName();
         
-        File topDir = new File(System.getProperty("user.home"), ".vimcoder");
+        // 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())
         {
@@ -62,15 +90,22 @@ public class Editor
         String lang = language.getName();
         String ext  = languageExtension.get(lang);
         
+        // Set up the terms used for the template expansion.
         HashMap<String,String> terms = new HashMap<String,String>();
-        terms.put("RETURNTYPE", component.getReturnType().getDescriptor(language));
-        terms.put("CLASSNAME",  component.getClassName());
+        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", Utilities.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())
         {
@@ -79,104 +114,223 @@ public class Editor
             {
                 writer.write(renderer.toHTML(language));
             }
-            catch (Exception exception)
+            finally
             {
+                writer.close();
             }
-            writer.close();
         }
         
-        sourceFile = new File(directory, terms.get("CLASSNAME") + "." + ext);
+        // 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 = Utilities.expandTemplate(Utilities.readResource(lang + "Template"),
-                                         terms);
+            String text = Util.expandTemplate(readTemplate(lang + "Template"),
+                                              terms);
             FileWriter writer = new FileWriter(sourceFile);
             writer.write(text);
             writer.close();
         }
-
-        File driverFile = new File(directory, "driver" + "." + ext);
+        
+        // Expand the driver template and write it to a source file.
+        File driverFile = new File(directory, "driver." + ext);
         if (!driverFile.canRead())
         {
-            StringBuilder testCases = new StringBuilder();
+            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())
             {
-                HashMap<String,String> testTerms = new HashMap<String,String>();
-                testTerms.putAll(terms);
-                String template = Utilities.readResource(lang + "Test");
                 for (TestCase testCase : component.getTestCases())
                 {
-                    testTerms.put("TESTOUTPUT", "\"" + Utilities.quote(testCase.getOutput()) + "\"");
-                    testTerms.put("TESTINPUTS", Utilities.join(testCase.getInput(), ", "));
-                    testCases.append(Utilities.expandTemplate(template, testTerms));
+                    text.append(testCase.getOutput() + System.getProperty("line.separator"));
+                    for (String input : testCase.getInput())
+                    {
+                        text.append(input + System.getProperty("line.separator"));
+                    }
                 }
             }
-            terms.put("TESTCASES", testCases.toString());
-            
-            String text = Utilities.expandTemplate(Utilities.readResource(lang + "Driver"),
-                                         terms);
-            FileWriter writer = new FileWriter(driverFile);
-            writer.write(text);
+            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 = Utilities.expandTemplate(Utilities.readResource(lang + "Makefile"),
-                                         terms);
+            String text = Util.expandTemplate(readTemplate(lang + "Makefile"),
+                                              terms);
             FileWriter writer = new FileWriter(makeFile);
             writer.write(text);
             writer.close();
         }
     }
-    
-    public void setSource(String source) throws IOException
+
+    /**
+     * 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 Utilities.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 boolean doVimCommand(String command, String argument)
+    /**
+     * 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};
-        return doVimCommand(command, arguments);
+        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.
+            }
+        }
     }
     
-    private boolean doVimCommand(String command, String[] arguments)
+    
+    /**
+     * 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
         {
-            String[] exec = {"gvim", "--servername", "VimCoder" + id,
-                             command};
-            exec = Utilities.concat(exec, arguments);
-            Runtime.getRuntime().exec(exec, null, directory);
+            if (templateFile.canRead()) return Util.readFile(templateFile);
+            return Util.readResource(tName);
         }
         catch (IOException exception)
         {
-            System.out.println("Failed to launch external vim process.  :-(");
+            return "";
         }
-        return false;
     }
     
-    private String getMethodParams(DataType[] types, String[] names, Language language)
+    
+    /**
+     * 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)
     {
-        StringBuilder text = new StringBuilder();
-        
-        text.append(types[0].getDescriptor(language) + " " + names[0]);
-        for (int i = 1; i < names.length; ++i)
+        String[] strings = new String[types.length];
+        for (int i = 0; i < types.length; ++i)
         {
-            text.append(", " + types[i].getDescriptor(language) + " " + names[i]);
+            strings[i] = types[i].getDescriptor(language).replaceAll("\\s+", "");
         }
-        
-        return text.toString();
+        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;
     }
 }
 
This page took 0.034975 seconds and 4 git commands to generate.