4 import java
.io
.BufferedReader
;
6 import java
.io
.FileReader
;
7 import java
.io
.FileWriter
;
8 import java
.io
.IOException
;
9 import java
.io
.InputStream
;
12 import com
.topcoder
.client
.contestant
.ProblemComponentModel
;
13 import com
.topcoder
.shared
.language
.Language
;
14 import com
.topcoder
.shared
.problem
.DataType
;
15 import com
.topcoder
.shared
.problem
.Renderer
;
16 import com
.topcoder
.shared
.problem
.TestCase
;
19 * @author Charles McGarvey
20 * The TopCoder Arena editor plug-in providing support for Vim.
22 * Distributable under the terms and conditions of the 2-clause BSD license;
23 * see the file COPYING for a complete text of the license.
28 * The problem ID number.
33 * The name of the class.
38 * The path of the current source file.
40 private File sourceFile
;
43 * The path of the problem directory.
45 private File directory
;
49 * Map languages names to file extensions.
51 private static final Map
<String
,String
> languageExtension
= new HashMap
<String
,String
>();
54 languageExtension
.put("Java", "java");
55 languageExtension
.put("C++", "cc");
56 languageExtension
.put("C#", "cs");
57 languageExtension
.put("VB", "vb");
58 languageExtension
.put("Python", "py");
63 * Construct an editor with the problem objects given us by the Arena.
64 * @param component A container for the particulars of the problem.
65 * @param language The currently selected language.
66 * @param renderer A helper object to help format the problem statement.
67 * @throws Exception If the editor could not set itself up.
69 public Editor(ProblemComponentModel component
,
71 Renderer renderer
) throws Exception
73 this.id
= String
.valueOf(component
.getProblem().getProblemID());
74 this.name
= component
.getClassName();
76 // Make sure the top-level vimcoder directory exists.
77 File topDir
= VimCoder
.getStorageDirectory();
78 if (!topDir
.isDirectory())
80 if (!topDir
.mkdirs()) throw new IOException(topDir
.getPath());
83 // Make sure the problem directory exists.
84 this.directory
= new File(topDir
, id
);
85 if (!directory
.isDirectory())
87 if (!directory
.mkdirs()) throw new IOException(directory
.getPath());
90 String lang
= language
.getName();
91 String ext
= languageExtension
.get(lang
);
93 // Set up the terms used for the template expansion.
94 HashMap
<String
,String
> terms
= new HashMap
<String
,String
>();
95 terms
.put("RETURNTYPE",component
.getReturnType().getDescriptor(language
).replaceAll("\\s+", ""));
96 terms
.put("CLASSNAME", name
);
97 terms
.put("METHODNAME", component
.getMethodName());
98 terms
.put("METHODPARAMS", getMethodParams(component
.getParamTypes(),
99 component
.getParamNames(),
101 terms
.put("METHODPARAMNAMES", Util
.join(component
.getParamNames(), ", "));
102 terms
.put("METHODPARAMSTREAMIN", Util
.join(component
.getParamNames(), " >> "));
103 terms
.put("METHODPARAMSTREAMOUT", Util
.join(component
.getParamNames(), " << "));
104 terms
.put("METHODPARAMDECLARES", getMethodParamDeclarations(component
.getParamTypes(),
105 component
.getParamNames(),
108 // Write the problem statement as an HTML file in the problem directory.
109 File problemFile
= new File(directory
, "Problem.html");
110 if (!problemFile
.canRead())
112 FileWriter writer
= new FileWriter(problemFile
);
115 writer
.write(renderer
.toHTML(language
));
123 // Expand the template for the main class and write it to the current
125 sourceFile
= new File(directory
, name
+ "." + ext
);
126 if (!sourceFile
.canRead())
128 String text
= Util
.expandTemplate(readTemplate(lang
+ "Template"),
130 FileWriter writer
= new FileWriter(sourceFile
);
135 // Expand the driver template and write it to a source file.
136 File driverFile
= new File(directory
, "driver." + ext
);
137 if (!driverFile
.canRead())
139 String text
= Util
.expandTemplate(readTemplate(lang
+ "Driver"),
141 FileWriter writer
= new FileWriter(driverFile
);
146 // Write the test cases to a text file. The driver code can read this
147 // file and perform the tests based on what it reads.
148 File testcaseFile
= new File(directory
, "testcases.txt");
149 if (!testcaseFile
.canRead())
151 StringBuilder text
= new StringBuilder();
152 if (component
.hasTestCases())
154 for (TestCase testCase
: component
.getTestCases())
156 text
.append(testCase
.getOutput() + System
.getProperty("line.separator"));
157 for (String input
: testCase
.getInput())
159 text
.append(input
+ System
.getProperty("line.separator"));
163 FileWriter writer
= new FileWriter(testcaseFile
);
164 writer
.write(text
.toString());
168 // Finally, expand the Makefile template and write it.
169 File makeFile
= new File(directory
, "Makefile");
171 String text
= Util
.expandTemplate(readTemplate(lang
+ "Makefile"),
173 FileWriter writer
= new FileWriter(makeFile
);
180 * Save the source code provided by the server, and tell the Vim server to
181 * edit the current source file.
182 * @param source The source code.
183 * @throws Exception If the source couldn't be written or the Vim server
186 public void setSource(String source
) throws Exception
188 FileWriter writer
= new FileWriter(new File(directory
, name
));
189 writer
.write(source
);
191 sendVimCommand("--remote-tab-silent", sourceFile
.getPath());
195 * Read the source code from the current source file.
196 * @return The source code.
197 * @throws IOException If the source file could not be read.
199 public String
getSource() throws IOException
201 return Util
.readFile(sourceFile
) + "\n// Edited by " +
202 VimCoder
.version
+ "\n// " + VimCoder
.website
+ "\n\n";
207 * Send a command to the Vim server. If the server isn't running, it will
208 * be started with the name VIMCODER#### where #### is the problem ID.
209 * @param command The command to send to the server.
210 * @param argument A single argument for the remote command.
211 * @throws Exception If the command could not be sent.
213 private void sendVimCommand(String command
,
214 String argument
) throws Exception
216 String
[] arguments
= {argument
};
217 sendVimCommand(command
, arguments
);
221 * Send a command to the Vim server. If the server isn't running, it will
222 * be started with the name VIMCODER#### where #### is the problem ID.
223 * @param command The command to send to the server.
224 * @param argument Arguments for the remote command.
225 * @throws Exception If the command could not be sent.
227 private void sendVimCommand(String command
,
228 String
[] arguments
) throws Exception
230 String
[] vimCommand
= VimCoder
.getVimCommand().split("\\s");
231 String
[] flags
= {"--servername", "VimCoder" + id
, command
};
232 vimCommand
= Util
.concat(vimCommand
, flags
);
233 vimCommand
= Util
.concat(vimCommand
, arguments
);
234 Process child
= Runtime
.getRuntime().exec(vimCommand
, null, directory
);
236 /* FIXME: This is a hack with a magic number. The problem is the Vim
237 * process doesn't fork to the background on some systems, so we can't
238 * wait on the child. At the same time, calling this method before the
239 * previous child could finish initializing the server may result in
240 * multiple editor windows popping up. We'd also like to be able to
241 * get the return code from the child if we can. The workaround here is
242 * to stall the thread for a little while or until we know the child
243 * does exit. If the child never exits before the timeout, we will
244 * assume it is not backgrounding and that everything worked. */
245 long expire
= System
.currentTimeMillis() + 250;
246 while (System
.currentTimeMillis() < expire
)
251 int exitCode
= child
.exitValue();
252 if (exitCode
!= 0) throw new Exception("Vim process returned exit code " + exitCode
+ ".");
255 catch (IllegalThreadStateException exception
)
257 // The child has not exited; intentionally ignoring exception.
264 * Read a template. We first look in the storage directory. If we can't
265 * find one, we look among the resources.
266 * @param tName The name of the template.
267 * @return The contents of the template file, or an empty string.
269 private String
readTemplate(String tName
)
271 File templateFile
= new File(VimCoder
.getStorageDirectory(), tName
);
274 if (templateFile
.canRead()) return Util
.readFile(templateFile
);
275 return Util
.readResource(tName
);
277 catch (IOException exception
)
285 * Convert an array of data types to an array of strings according to a
287 * @param types The data types.
288 * @param language The language to use in the conversion.
289 * @return The array of string representations of the data types.
291 private String
[] getStringTypes(DataType
[] types
, Language language
)
293 String
[] strings
= new String
[types
.length
];
294 for (int i
= 0; i
< types
.length
; ++i
)
296 strings
[i
] = types
[i
].getDescriptor(language
).replaceAll("\\s+", "");
302 * Combine the data types and parameter names into a comma-separated list of
303 * the method parameters. The result could be used inside the parentheses
304 * of a method declaration.
305 * @param types The data types of the parameters.
306 * @param names The names of the parameters.
307 * @param language The language used for representing the data types.
308 * @return The list of parameters.
310 private String
getMethodParams(DataType
[] types
,
314 String
[] typeStrings
= getStringTypes(types
, language
);
315 return Util
.join(Util
.combine(typeStrings
, names
, " "), ", ");
319 * Combine the data types and parameter names into a group of variable
320 * declarations. Each declaration is separated by a new line and terminated
322 * @param types The data types of the parameters.
323 * @param names The names of the parameters.
324 * @param language The language used for representing the data types.
325 * @return The parameters as a block of declarations.
327 private String
getMethodParamDeclarations(DataType
[] types
,
331 final String end
= ";" + System
.getProperty("line.separator");
332 String
[] typeStrings
= getStringTypes(types
, language
);
333 return Util
.join(Util
.combine(typeStrings
, names
, "\t"), end
) + end
;