]> Dogcows Code - chaz/vimcoder/blob - src/com/dogcows/Editor.java
do not overwrite makefiles while opening a problem
[chaz/vimcoder] / src / com / dogcows / Editor.java
1
2 package com.dogcows;
3
4 import java.io.File;
5 import java.io.FileWriter;
6 import java.io.IOException;
7 import java.util.*;
8
9 import com.topcoder.client.contestant.ProblemComponentModel;
10 import com.topcoder.shared.language.Language;
11 import com.topcoder.shared.problem.DataType;
12 import com.topcoder.shared.problem.Renderer;
13 import com.topcoder.shared.problem.TestCase;
14
15 /**
16 * @author Charles McGarvey
17 * The TopCoder Arena editor plug-in providing support for Vim.
18 *
19 * Distributable under the terms and conditions of the 2-clause BSD license;
20 * see the file COPYING for a complete text of the license.
21 */
22 public class Editor
23 {
24 /**
25 * The problem ID number.
26 */
27 private String id;
28
29 /**
30 * The name of the class.
31 */
32 private String name;
33
34 /**
35 * The path of the current source file.
36 */
37 private File sourceFile;
38
39 /**
40 * The path of the problem directory.
41 */
42 private File directory;
43
44
45 /**
46 * Map languages names to file extensions.
47 */
48 private static final Map<String,String> languageExtension = new HashMap<String,String>();
49 static
50 {
51 languageExtension.put("Java", "java");
52 languageExtension.put("C++", "cc");
53 languageExtension.put("C#", "cs");
54 languageExtension.put("VB", "vb");
55 languageExtension.put("Python", "py");
56 }
57
58
59 /**
60 * Construct an editor with the problem objects given us by the Arena.
61 * @param component A container for the particulars of the problem.
62 * @param language The currently selected language.
63 * @param renderer A helper object to help format the problem statement.
64 * @throws Exception If the editor could not set itself up.
65 */
66 public Editor(ProblemComponentModel component,
67 Language language,
68 Renderer renderer) throws Exception
69 {
70 this.id = String.valueOf(component.getProblem().getProblemID());
71 this.name = component.getClassName();
72
73 // Make sure the top-level vimcoder directory exists.
74 File topDir = VimCoder.getStorageDirectory();
75 if (!topDir.isDirectory())
76 {
77 if (!topDir.mkdirs()) throw new IOException(topDir.getPath());
78 }
79
80 // Make sure the problem directory exists.
81 this.directory = new File(topDir, id);
82 if (!directory.isDirectory())
83 {
84 if (!directory.mkdirs()) throw new IOException(directory.getPath());
85 }
86
87 String lang = language.getName();
88 String ext = languageExtension.get(lang);
89
90 // Set up the terms used for the template expansion.
91 HashMap<String,String> terms = new HashMap<String,String>();
92 terms.put("RETURNTYPE", component.getReturnType().getDescriptor(language));
93 terms.put("CLASSNAME", name);
94 terms.put("METHODNAME", component.getMethodName());
95 terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(),
96 component.getParamNames(),
97 language));
98 terms.put("METHODPARAMNAMES", Util.join(component.getParamNames(), ", "));
99 terms.put("METHODPARAMSTREAMIN", Util.join(component.getParamNames(), " >> "));
100 terms.put("METHODPARAMSTREAMOUT", Util.join(component.getParamNames(), " << \", \" << "));
101 terms.put("METHODPARAMDECLARES", getMethodParamDeclarations(component.getParamTypes(),
102 component.getParamNames(),
103 language));
104
105 // Write the problem statement as an HTML file in the problem directory.
106 File problemFile = new File(directory, "Problem.html");
107 if (!problemFile.canRead())
108 {
109 FileWriter writer = new FileWriter(problemFile);
110 try
111 {
112 writer.write(renderer.toHTML(language));
113 }
114 finally
115 {
116 writer.close();
117 }
118 }
119
120 // Expand the template for the main class and write it to the current
121 // source file.
122 sourceFile = new File(directory, name + "." + ext);
123 if (!sourceFile.canRead())
124 {
125 String text = Util.expandTemplate(readTemplate(lang + "Template"),
126 terms);
127 FileWriter writer = new FileWriter(sourceFile);
128 writer.write(text);
129 writer.close();
130 }
131
132 // Expand the driver template and write it to a source file.
133 File driverFile = new File(directory, "driver." + ext);
134 if (!driverFile.canRead())
135 {
136 String text = Util.expandTemplate(readTemplate(lang + "Driver"),
137 terms);
138 FileWriter writer = new FileWriter(driverFile);
139 writer.write(text);
140 writer.close();
141 }
142
143 // Write the test cases to a text file. The driver code can read this
144 // file and perform the tests based on what it reads.
145 File testcaseFile = new File(directory, "testcases.txt");
146 if (!testcaseFile.canRead())
147 {
148 StringBuilder text = new StringBuilder();
149 if (component.hasTestCases())
150 {
151 for (TestCase testCase : component.getTestCases())
152 {
153 text.append(testCase.getOutput() + System.getProperty("line.separator"));
154 for (String input : testCase.getInput())
155 {
156 text.append(input + System.getProperty("line.separator"));
157 }
158 }
159 }
160 FileWriter writer = new FileWriter(testcaseFile);
161 writer.write(text.toString());
162 writer.close();
163 }
164
165 // Finally, expand the Makefile template and write it.
166 File makeFile = new File(directory, "Makefile");
167 if (!makeFile.canRead())
168 {
169 String text = Util.expandTemplate(readTemplate(lang + "Makefile"),
170 terms);
171 FileWriter writer = new FileWriter(makeFile);
172 writer.write(text);
173 writer.close();
174 }
175 }
176
177 /**
178 * Save the source code provided by the server, and tell the Vim server to
179 * edit the current source file.
180 * @param source The source code.
181 * @throws Exception If the source couldn't be written or the Vim server
182 * had a problem.
183 */
184 public void setSource(String source) throws Exception
185 {
186 FileWriter writer = new FileWriter(new File(directory, name));
187 writer.write(source);
188 writer.close();
189 sendVimCommand("--remote-tab-silent", sourceFile.getPath());
190 }
191
192 /**
193 * Read the source code from the current source file.
194 * @return The source code.
195 * @throws IOException If the source file could not be read.
196 */
197 public String getSource() throws IOException
198 {
199 return Util.readFile(sourceFile) + "\n// Edited by " +
200 VimCoder.version + "\n// " + VimCoder.website + "\n\n";
201 }
202
203
204 /**
205 * Send a command to the Vim server.
206 * If the server isn't running, it will be started with the name
207 * VIMCODER#### where #### is the problem ID.
208 * @param command The command to send to the server.
209 * @param argument A single argument for the remote command.
210 * @throws Exception If the command could not be sent.
211 */
212 private void sendVimCommand(String command,
213 String argument) throws Exception
214 {
215 String[] arguments = {argument};
216 sendVimCommand(command, arguments);
217 }
218
219 /**
220 * Send a command to the Vim server.
221 * If the server isn't running, it will be started with the name
222 * 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.
226 */
227 private void sendVimCommand(String command,
228 String[] arguments) throws Exception
229 {
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);
235
236 /* FIXME: This is a pretty bad hack. The problem is that the Vim
237 * process doesn't fork to the background on some systems, so we
238 * can't wait on the child. At the same time, calling this method
239 * before the previous child could finish initializing the server
240 * may result in multiple editor windows popping up. We'd also
241 * like to be able to get the return code from the child if we can.
242 * The workaround here is to stall the thread for a little while or
243 * until we see that the child exits. If the child never exits
244 * before the timeout, we will assume it is not backgrounding and
245 * that everything worked. This works as long as the Vim server is
246 * able to start within the stall period. */
247 long expire = System.currentTimeMillis() + 1000;
248 while (System.currentTimeMillis() < expire)
249 {
250 Thread.yield();
251 try
252 {
253 int exitCode = child.exitValue();
254 if (exitCode != 0) throw new Exception("Vim process returned exit code " + exitCode + ".");
255 break;
256 }
257 catch (IllegalThreadStateException exception)
258 {
259 // The child has not exited; intentionally ignoring exception.
260 }
261 }
262 }
263
264
265 /**
266 * Read a template.
267 * We first look in the storage directory. If we can't find one, we
268 * look among the resources.
269 * @param tName The name of the template.
270 * @return The contents of the template file, or an empty string.
271 */
272 private String readTemplate(String tName)
273 {
274 File templateFile = new File(VimCoder.getStorageDirectory(), tName);
275 try
276 {
277 if (templateFile.canRead()) return Util.readFile(templateFile);
278 return Util.readResource(tName);
279 }
280 catch (IOException exception)
281 {
282 return "";
283 }
284 }
285
286
287 /**
288 * Convert an array of data types to an array of strings according to a
289 * given language.
290 * @param types The data types.
291 * @param language The language to use in the conversion.
292 * @return The array of string representations of the data types.
293 */
294 private String[] getStringTypes(DataType[] types, Language language)
295 {
296 String[] strings = new String[types.length];
297 for (int i = 0; i < types.length; ++i)
298 {
299 strings[i] = types[i].getDescriptor(language);
300 }
301 return strings;
302 }
303
304 /**
305 * Combine the data types and parameter names into a comma-separated list of
306 * the method parameters.
307 * The result could be used inside the parentheses of a method
308 * declaration.
309 * @param types The data types of the parameters.
310 * @param names The names of the parameters.
311 * @param language The language used for representing the data types.
312 * @return The list of parameters.
313 */
314 private String getMethodParams(DataType[] types,
315 String[] names,
316 Language language)
317 {
318 String[] typeStrings = getStringTypes(types, language);
319 return Util.join(Util.combine(typeStrings, names, " "), ", ");
320 }
321
322 /**
323 * Combine the data types and parameter names into a group of variable
324 * declarations.
325 * Each declaration is separated by a new line and terminated with a
326 * semicolon.
327 * @param types The data types of the parameters.
328 * @param names The names of the parameters.
329 * @param language The language used for representing the data types.
330 * @return The parameters as a block of declarations.
331 */
332 private String getMethodParamDeclarations(DataType[] types,
333 String[] names,
334 Language language)
335 {
336 final String end = ";" + System.getProperty("line.separator");
337 String[] typeStrings = getStringTypes(types, language);
338 return Util.join(Util.combine(typeStrings, names, "\t"), end) + end;
339 }
340 }
341
This page took 0.046813 seconds and 4 git commands to generate.