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