5 import java
.io
.FileWriter
;
6 import java
.io
.IOException
;
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
;
16 * @author Charles McGarvey
17 * The TopCoder Arena editor plug-in providing support for Vim.
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.
25 * The problem ID number.
30 * The name of the class.
35 * The name of the contest.
37 private String contestName
;
42 private String points
;
45 * The path of the current source file.
47 private File sourceFile
;
50 * The path of the problem directory.
52 private File directory
;
56 * Map languages names to file extensions.
58 private static final Map
<String
,String
> languageExtension
= new HashMap
<String
,String
>();
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");
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.
76 public Editor(ProblemComponentModel component
,
77 Language language
, Renderer renderer
) throws Exception
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());
84 // Make sure the top-level vimcoder directory exists.
85 File topDir
= VimCoder
.getStorageDirectory();
86 if (!topDir
.isDirectory())
88 if (!topDir
.mkdirs()) throw new IOException(topDir
.getPath());
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())
96 this.directory
= newStyleDirectory
;
98 else if (oldStyleDirectory
.isDirectory())
100 this.directory
= oldStyleDirectory
;
102 else if (VimCoder
.isContestDirNames())
104 this.directory
= newStyleDirectory
;
105 if (!directory
.mkdirs()) throw new IOException(directory
.getPath());
109 this.directory
= oldStyleDirectory
;
110 if (!directory
.mkdirs()) throw new IOException(directory
.getPath());
113 String lang
= language
.getName();
114 String ext
= languageExtension
.get(lang
);
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(),
122 component
.getParamNames(), language
));
123 terms
.put("METHODPARAMNAMES", Util
.join(component
.getParamNames(), ", "));
124 terms
.put("METHODPARAMSTREAMIN", Util
.join(component
.getParamNames(), " >> "));
125 terms
.put("METHODPARAMSTREAMOUT", Util
.join(component
.getParamNames(), " << \", \" << "));
126 terms
.put("METHODPARAMDECLARES", getMethodParamDeclarations(component
.getParamTypes(),
127 component
.getParamNames(), language
));
129 // Write the problem statement as an HTML file in the problem directory.
130 File problemFile
= new File(directory
, "Problem.html");
131 if (!problemFile
.canRead())
133 FileWriter writer
= new FileWriter(problemFile
);
136 writer
.write(renderer
.toHTML(language
));
144 // Expand the template for the main class and write it to the current
146 this.sourceFile
= new File(directory
, name
+ "." + ext
);
147 if (!sourceFile
.canRead())
149 String text
= Util
.expandTemplate(readTemplate(lang
+ "Template"), terms
);
150 FileWriter writer
= new FileWriter(sourceFile
);
155 // Expand the driver template and write it to a source file.
156 File driverFile
= new File(directory
, "driver." + ext
);
157 if (!driverFile
.canRead())
159 String text
= Util
.expandTemplate(readTemplate(lang
+ "Driver"), terms
);
160 FileWriter writer
= new FileWriter(driverFile
);
165 // Write the test cases to a text file. The driver code can read this
166 // file and perform the tests based on what it reads.
167 File testcaseFile
= new File(directory
, "testcases.txt");
168 if (!testcaseFile
.canRead())
170 StringBuilder text
= new StringBuilder();
171 if (component
.hasTestCases())
173 for (TestCase testCase
: component
.getTestCases())
175 text
.append(testCase
.getOutput() + System
.getProperty("line.separator"));
176 for (String input
: testCase
.getInput())
178 text
.append(input
+ System
.getProperty("line.separator"));
182 FileWriter writer
= new FileWriter(testcaseFile
);
183 writer
.write(text
.toString());
187 // Finally, expand the Makefile template and write it.
188 File makeFile
= new File(directory
, "Makefile");
189 if (!makeFile
.canRead())
191 String text
= Util
.expandTemplate(readTemplate(lang
+ "Makefile"), terms
);
192 FileWriter writer
= new FileWriter(makeFile
);
199 * Save the source code provided by the server, and tell the Vim server to
200 * edit the current source file.
201 * @param source The source code.
202 * @throws Exception If the source couldn't be written or the Vim server
205 public void setSource(String source
) throws Exception
207 FileWriter writer
= new FileWriter(new File(directory
, name
));
208 writer
.write(source
);
210 sendVimCommand("--remote-tab-silent", sourceFile
.getPath());
214 * Read the source code from the current source file.
215 * @return The source code.
216 * @throws IOException If the source file could not be read.
218 public String
getSource() throws IOException
220 return Util
.readFile(sourceFile
) + "\n// Edited by " +
221 VimCoder
.version
+ "\n// " + VimCoder
.website
+ "\n\n";
226 * Send a command to the Vim server.
227 * If the server isn't running, it will be started with the name
228 * VIMCODER#### where #### is the problem ID.
229 * @param command The command to send to the server.
230 * @param argument A single argument for the remote command.
231 * @throws Exception If the command could not be sent.
233 private void sendVimCommand(String command
, String argument
) throws Exception
235 String
[] arguments
= {argument
};
236 sendVimCommand(command
, arguments
);
240 * Send a command to the Vim server.
241 * If the server isn't running, it will be started with the name
242 * VIMCODER#### where #### is the problem ID.
243 * @param command The command to send to the server.
244 * @param argument Arguments for the remote command.
245 * @throws Exception If the command could not be sent.
247 private void sendVimCommand(String command
, String
[] arguments
) throws Exception
249 String
[] vimCommand
= VimCoder
.getVimCommand().split("\\s");
250 String
[] flags
= {"--servername", "VimCoder" + id
, command
};
251 vimCommand
= Util
.concat(vimCommand
, flags
);
252 vimCommand
= Util
.concat(vimCommand
, arguments
);
253 Process child
= Runtime
.getRuntime().exec(vimCommand
, null, directory
);
255 /* FIXME: This is a pretty bad hack. The problem is that the Vim
256 * process doesn't fork to the background on some systems, so we
257 * can't wait on the child. At the same time, calling this method
258 * before the previous child could finish initializing the server
259 * may result in multiple editor windows popping up. We'd also
260 * like to be able to get the return code from the child if we can.
261 * The workaround here is to stall the thread for a little while or
262 * until we see that the child exits. If the child never exits
263 * before the timeout, we will assume it is not backgrounding and
264 * that everything worked. This works as long as the Vim server is
265 * able to start within the stall period. */
266 long expire
= System
.currentTimeMillis() + 2500;
267 while (System
.currentTimeMillis() < expire
)
272 int exitCode
= child
.exitValue();
273 if (exitCode
!= 0) throw new Exception("Vim process returned exit code " + exitCode
+ ".");
276 catch (IllegalThreadStateException exception
)
278 // The child has not exited; intentionally ignoring exception.
286 * We first look in the storage directory. If we can't find one, we
287 * look among the resources.
288 * @param tName The name of the template.
289 * @return The contents of the template file, or an empty string.
291 private String
readTemplate(String tName
)
293 File templateFile
= new File(VimCoder
.getStorageDirectory(), tName
);
296 if (templateFile
.canRead()) return Util
.readFile(templateFile
);
297 return Util
.readResource(tName
);
299 catch (IOException exception
)
307 * Convert an array of data types to an array of strings according to a
309 * @param types The data types.
310 * @param language The language to use in the conversion.
311 * @return The array of string representations of the data types.
313 private String
[] getStringTypes(DataType
[] types
, Language language
)
315 String
[] strings
= new String
[types
.length
];
316 for (int i
= 0; i
< types
.length
; ++i
)
318 strings
[i
] = types
[i
].getDescriptor(language
);
324 * Combine the data types and parameter names into a comma-separated list of
325 * the method parameters.
326 * The result could be used inside the parentheses of a method
328 * @param types The data types of the parameters.
329 * @param names The names of the parameters.
330 * @param language The language used for representing the data types.
331 * @return The list of parameters.
333 private String
getMethodParams(DataType
[] types
, String
[] names
, Language language
)
335 String
[] typeStrings
= getStringTypes(types
, language
);
336 return Util
.join(Util
.combine(typeStrings
, names
, " "), ", ");
340 * Combine the data types and parameter names into a group of variable
342 * Each declaration is separated by a new line and terminated with a
344 * @param types The data types of the parameters.
345 * @param names The names of the parameters.
346 * @param language The language used for representing the data types.
347 * @return The parameters as a block of declarations.
349 private String
getMethodParamDeclarations(DataType
[] types
, String
[] names
, Language language
)
351 final String end
= ";" + System
.getProperty("line.separator");
352 String
[] typeStrings
= getStringTypes(types
, language
);
353 return Util
.join(Util
.combine(typeStrings
, names
, "\t"), end
) + end
;