Added
Link Here
|
1 |
/******************************************************************************* |
2 |
* Copyright (c) 2004 Ben Konrath <ben@bagu.org> |
3 |
* All rights reserved. This program and the accompanying materials |
4 |
* are made available under the terms of the Eclipse Public License v1.0 |
5 |
* which accompanies this distribution, and is available at |
6 |
* http://www.eclipse.org/legal/epl-v10.html |
7 |
* |
8 |
* Contributors: |
9 |
* Ben Konrath <ben@bagu.org> - initial implementation |
10 |
*******************************************************************************/ |
11 |
|
12 |
package org.eclipse.jdt.core.formatter; |
13 |
|
14 |
import java.io.BufferedReader; |
15 |
import java.io.BufferedWriter; |
16 |
import java.io.File; |
17 |
import java.io.FileInputStream; |
18 |
import java.io.FileReader; |
19 |
import java.io.FileWriter; |
20 |
import java.io.IOException; |
21 |
import java.io.Writer; |
22 |
import java.text.MessageFormat; |
23 |
import java.util.ArrayList; |
24 |
import java.util.HashMap; |
25 |
import java.util.Map; |
26 |
import java.util.MissingResourceException; |
27 |
import java.util.ResourceBundle; |
28 |
|
29 |
import javax.xml.parsers.ParserConfigurationException; |
30 |
import javax.xml.parsers.SAXParser; |
31 |
import javax.xml.parsers.SAXParserFactory; |
32 |
|
33 |
import org.eclipse.core.runtime.IPlatformRunnable; |
34 |
import org.eclipse.core.runtime.Platform; |
35 |
import org.eclipse.jdt.core.ToolFactory; |
36 |
import org.eclipse.jdt.internal.core.util.Util; |
37 |
import org.eclipse.jface.text.BadLocationException; |
38 |
import org.eclipse.jface.text.Document; |
39 |
import org.eclipse.jface.text.IDocument; |
40 |
import org.eclipse.text.edits.TextEdit; |
41 |
import org.xml.sax.Attributes; |
42 |
import org.xml.sax.InputSource; |
43 |
import org.xml.sax.SAXException; |
44 |
import org.xml.sax.helpers.DefaultHandler; |
45 |
|
46 |
/** |
47 |
* Class that handles the org.eclipse.jdt.core.JavaCodeFormatter the |
48 |
* application. The map file reading code is based on code in ProfileStore.java |
49 |
* (org.eclipse.jdf.ui). |
50 |
* |
51 |
* There are a couple improvments that could be made: |
52 |
* 1. Add an import clean up option (requires stuff from org.eclipse.jdt.ui). |
53 |
* 2. Make a list of all the files first and then format. You could then |
54 |
* remove duplicate files. |
55 |
* |
56 |
* @author Ben Konrath <ben@bagu.org> |
57 |
* @since 3.1 |
58 |
*/ |
59 |
public class CodeFormatterApplication implements IPlatformRunnable { |
60 |
|
61 |
/** |
62 |
* A SAX event handler to parse the config xml. |
63 |
*/ |
64 |
private final static class ConfigHandler extends DefaultHandler { |
65 |
|
66 |
/** |
67 |
* Identifiers for the XML file. |
68 |
*/ |
69 |
private final String XML_NODE_ROOT = "profiles"; //$NON-NLS-1$ |
70 |
|
71 |
private final String XML_NODE_PROFILE = "profile"; //$NON-NLS-1$ |
72 |
|
73 |
private final String XML_NODE_SETTING = "setting"; //$NON-NLS-1$ |
74 |
|
75 |
private final String XML_ATTRIBUTE_VERSION = "version"; //$NON-NLS-1$ |
76 |
|
77 |
private final String XML_ATTRIBUTE_ID = "id"; //$NON-NLS-1$ |
78 |
|
79 |
private final String XML_ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ |
80 |
|
81 |
private final String XML_ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$ |
82 |
|
83 |
private int fVersion; |
84 |
|
85 |
private String fName; |
86 |
|
87 |
private Map fSettings; |
88 |
|
89 |
public void startElement(String uri, String localName, String qName, |
90 |
Attributes attributes) throws SAXException { |
91 |
|
92 |
if (qName.equals(XML_NODE_SETTING)) { |
93 |
|
94 |
final String key = attributes.getValue(XML_ATTRIBUTE_ID); |
95 |
final String value = attributes.getValue(XML_ATTRIBUTE_VALUE); |
96 |
fSettings.put(key, value); |
97 |
|
98 |
} else if (qName.equals(XML_NODE_PROFILE)) { |
99 |
|
100 |
fName = attributes.getValue(XML_ATTRIBUTE_NAME); |
101 |
fSettings = new HashMap(200); |
102 |
|
103 |
} else if (qName.equals(XML_NODE_ROOT)) { |
104 |
|
105 |
try { |
106 |
fVersion = Integer.parseInt(attributes |
107 |
.getValue(XML_ATTRIBUTE_VERSION)); |
108 |
} catch (NumberFormatException ex) { |
109 |
throw new SAXException(ex); |
110 |
} |
111 |
|
112 |
} |
113 |
} |
114 |
|
115 |
public Map getSettings() { |
116 |
return fSettings; |
117 |
} |
118 |
|
119 |
public int getVersion() { |
120 |
return fVersion; |
121 |
} |
122 |
|
123 |
public String getName() { |
124 |
return fName; |
125 |
} |
126 |
|
127 |
} |
128 |
|
129 |
/** |
130 |
* Deals with the messages in the properties file (cut n' pasted from a |
131 |
* generated class). |
132 |
*/ |
133 |
private final static class FormatterAppMessages { |
134 |
private static final String BUNDLE_NAME = "org.eclipse.jdt.core.formatter.FormatterAppMessages";//$NON-NLS-1$ |
135 |
|
136 |
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle |
137 |
.getBundle(BUNDLE_NAME); |
138 |
|
139 |
public static String getString(String key) { |
140 |
try { |
141 |
return RESOURCE_BUNDLE.getString(key); |
142 |
} catch (MissingResourceException e) { |
143 |
return '!' + key + '!'; |
144 |
} |
145 |
} |
146 |
|
147 |
public static String getFormattedString(String key, String arg) { |
148 |
return getFormattedString(key, new String[] { arg }); |
149 |
} |
150 |
|
151 |
public static String getFormattedString(String key, String[] args) { |
152 |
return MessageFormat.format(getString(key), args); |
153 |
} |
154 |
} |
155 |
|
156 |
/* |
157 |
* FIXME This value should come from ProfileVersioner.CURRENT_VERSION, however |
158 |
* this class cannot be included here because it is internal to |
159 |
* org.eclipse.jdt.ui and becuase this plugin (org.eclipse.jdt.core) does |
160 |
* not require org.eclipse.jdt.ui (nor should it). Refactoring this to make |
161 |
* it an external class of org.eclipse.jdt.core would solve these problems. |
162 |
*/ |
163 |
int CURRENT_VERSION = 6; |
164 |
|
165 |
/** |
166 |
* Read the xml config file and return a Map representing the options that |
167 |
* are in the specified config file. |
168 |
*/ |
169 |
public Map readConfig(String filename) { |
170 |
|
171 |
try { |
172 |
final FileInputStream reader = new FileInputStream(new File( |
173 |
filename)); |
174 |
final ConfigHandler handler = new ConfigHandler(); |
175 |
|
176 |
try { |
177 |
InputSource inputSource = new InputSource(reader); |
178 |
final SAXParserFactory factory = SAXParserFactory.newInstance(); |
179 |
final SAXParser parser = factory.newSAXParser(); |
180 |
parser.parse(inputSource, handler); |
181 |
if (handler.getVersion() != CURRENT_VERSION) |
182 |
return null; |
183 |
configName = handler.getName(); |
184 |
return handler.getSettings(); |
185 |
|
186 |
} finally { |
187 |
try { reader.close(); } catch (IOException e) { /* ignore */ } |
188 |
} |
189 |
|
190 |
} catch (IOException e) { |
191 |
Util.log(e, FormatterAppMessages |
192 |
.getString("ConfigFile.reading.error")); //$NON-NLS-1$ |
193 |
} catch (SAXException e) { |
194 |
Util.log(e, FormatterAppMessages |
195 |
.getString("ConfigFile.reading.error")); //$NON-NLS-1$ |
196 |
} catch (ParserConfigurationException e) { |
197 |
Util.log(e, FormatterAppMessages |
198 |
.getString("ConfigFile.reading.error")); //$NON-NLS-1$ |
199 |
} |
200 |
return null; |
201 |
} |
202 |
|
203 |
/* |
204 |
* Allow the formatter to run in a stand-alone mode. |
205 |
*/ |
206 |
public static void main(String[] args) throws Exception { |
207 |
new CodeFormatterApplication().run(args); |
208 |
} |
209 |
|
210 |
/** |
211 |
* Runs the Java code formatter application |
212 |
*/ |
213 |
public Object run(Object args) throws Exception { |
214 |
|
215 |
ArrayList fileList = processCommandLine((String[]) args); |
216 |
|
217 |
// nothing to do |
218 |
if (fileList == null || fileList.isEmpty()) |
219 |
return EXIT_OK; |
220 |
|
221 |
if (!quiet) { |
222 |
if (configName != null) |
223 |
System.out.println(FormatterAppMessages.getFormattedString("CommandLine.config.file", configName)); //$NON-NLS-1$ |
224 |
System.out.println(FormatterAppMessages.getString("CommandLine.start")); //$NON-NLS-1$ |
225 |
} |
226 |
|
227 |
// format the list of files and/or directories |
228 |
while (!fileList.isEmpty()) { |
229 |
File file = (File) fileList.remove(0); |
230 |
|
231 |
if (file.isDirectory()) |
232 |
formatDirTree(file); |
233 |
else |
234 |
formatFile(file); |
235 |
} |
236 |
|
237 |
if (!quiet) { |
238 |
System.out.println(FormatterAppMessages.getString("CommandLine.done")); //$NON-NLS-1$ |
239 |
} |
240 |
|
241 |
return EXIT_OK; |
242 |
} |
243 |
|
244 |
private void displayHelp(String message) { |
245 |
System.err.println(message); |
246 |
System.out.println(""); //$NON-NLS-1$ |
247 |
displayHelp(); |
248 |
} |
249 |
|
250 |
private String configName; |
251 |
|
252 |
/* |
253 |
* The output will look like this: |
254 |
* |
255 |
* "Usage: eclipse -application org.eclipse.jdt.core.JavaCodeFormatter [ OPTIONS ] <files> |
256 |
* <files> Java source files and/or directories to format. |
257 |
* Only files ending with .java will be formatted in the given directory. |
258 |
* OPTIONS: |
259 |
* -config <file> Use the formatting style from the specified config file. |
260 |
* This file must be an xml file that has been exported by Eclipse 3.0. |
261 |
* -help Display this message. |
262 |
* -quiet Only print error messages. |
263 |
* -verbose Be verbose about the formatting job. |
264 |
*/ |
265 |
private void displayHelp() { |
266 |
String binaryName = "eclipse"; //$NON-NLS-1$ |
267 |
|
268 |
// this is UG-LY. is there a way to make this look nicer? |
269 |
System.out.println(FormatterAppMessages.getFormattedString("CommandLine.usage", //$NON-NLS-1$ |
270 |
binaryName + " -application org.eclipse.jdt.core.JavaCodeFormatter")); //$NON-NLS-1$ |
271 |
System.out.println(""); //$NON-NLS-1$ |
272 |
|
273 |
System.out.println(" " + FormatterAppMessages.getString("CommandLine.files") //$NON-NLS-1$ //$NON-NLS-2$ |
274 |
+ "\t" + FormatterAppMessages.getString("CommandLine.files.msg1")); //$NON-NLS-1$ //$NON-NLS-2$ |
275 |
System.out.println("\t\t" //$NON-NLS-1$ |
276 |
+ FormatterAppMessages.getFormattedString("CommandLine.files.msg2", ".java")); //$NON-NLS-1$ //$NON-NLS-2$ |
277 |
|
278 |
System.out.println(FormatterAppMessages.getString("CommandLine.options")); //$NON-NLS-1$ |
279 |
System.out.println(" " + FormatterAppMessages.getFormattedString("CommandLine.config", ARG_CONFIG) //$NON-NLS-1$ //$NON-NLS-2$ |
280 |
+ "\t" + FormatterAppMessages.getString("CommandLine.config.msg1")); //$NON-NLS-1$ //$NON-NLS-2$ |
281 |
System.out.println("\t\t\t" + FormatterAppMessages.getString("CommandLine.config.msg2")); //$NON-NLS-1$ //$NON-NLS-2$ |
282 |
System.out.println(" " + ARG_HELP + "\t\t" + FormatterAppMessages.getString("CommandLine.help")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
283 |
System.out.println(" " + ARG_QUIET + "\t\t" + FormatterAppMessages.getString("CommandLine.quiet")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
284 |
System.out.println(" " + ARG_VERBOSE +"\t\t" + FormatterAppMessages.getString("CommandLine.verbose")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
285 |
} |
286 |
|
287 |
private final String ARG_HELP = "-help"; //$NON-NLS-1$ |
288 |
private final String ARG_CONFIG = "-config"; //$NON-NLS-1$ |
289 |
private final String ARG_VERBOSE = "-verbose"; //$NON-NLS-1$ |
290 |
private final String ARG_QUIET = "-quiet"; //$NON-NLS-1$ |
291 |
private boolean verbose = false; |
292 |
private boolean quiet = false; |
293 |
|
294 |
private ArrayList processCommandLine(String[] argsArray) { |
295 |
|
296 |
ArrayList args = new ArrayList(); |
297 |
for (int i = 0; i < argsArray.length; i++) { |
298 |
args.add(argsArray[i]); |
299 |
} |
300 |
|
301 |
// look for flag-like args |
302 |
if (args.remove(ARG_HELP)) { |
303 |
displayHelp(); |
304 |
return null; |
305 |
} |
306 |
if (args.remove(ARG_VERBOSE)) |
307 |
verbose = true; |
308 |
if (args.remove(ARG_QUIET)) |
309 |
quiet = true; |
310 |
|
311 |
if (quiet && verbose) { |
312 |
displayHelp(FormatterAppMessages.getFormattedString |
313 |
("CommandLineError.quiet.verbose", new String[] {ARG_QUIET, ARG_VERBOSE})); //$NON-NLS-1$ |
314 |
return null; |
315 |
} |
316 |
args.remove("-pdelaunch"); //$NON-NLS-1$ |
317 |
|
318 |
// look for flag/param args |
319 |
int index = args.indexOf(ARG_CONFIG); |
320 |
if (index >= 0) { |
321 |
args.remove(index); |
322 |
String configFile = (String) args.remove(index); |
323 |
options = readConfig(configFile); |
324 |
if (options == null) { |
325 |
displayHelp(FormatterAppMessages |
326 |
.getFormattedString("CommandLineError.config", configFile)); //$NON-NLS-1$ |
327 |
return null; |
328 |
} |
329 |
} |
330 |
|
331 |
// only the files and directories should remain |
332 |
ArrayList fileList = new ArrayList(); |
333 |
while (!args.isEmpty()) { |
334 |
String fileName = (String) args.remove(0); |
335 |
File file = new File(fileName); |
336 |
if (file.exists()) { |
337 |
fileList.add(file); |
338 |
} else { |
339 |
displayHelp(FormatterAppMessages |
340 |
.getFormattedString("CommandLineError.file", fileName)); //$NON-NLS-1$ |
341 |
return null; |
342 |
} |
343 |
} |
344 |
|
345 |
if (fileList.isEmpty()) |
346 |
displayHelp(FormatterAppMessages.getString("CommandLineError.file.dir")); //$NON-NLS-1$ |
347 |
|
348 |
return fileList; |
349 |
} |
350 |
|
351 |
/** |
352 |
* Recursively format the Java source code that is contained in the |
353 |
* directory rooted at dir. |
354 |
*/ |
355 |
private void formatDirTree(File dir) { |
356 |
|
357 |
File[] files = dir.listFiles(); |
358 |
if (files == null) |
359 |
return; |
360 |
|
361 |
for (int i = 0; i < files.length; i++) { |
362 |
File file = files[i]; |
363 |
if (file.isDirectory()) { |
364 |
formatDirTree(file); |
365 |
} else if (file.getPath().endsWith(".java")) { //$NON-NLS-1$ |
366 |
formatFile(file); |
367 |
} |
368 |
} |
369 |
} |
370 |
|
371 |
// internal representation of configuration options in the xml file |
372 |
private Map options = null; |
373 |
|
374 |
/** |
375 |
* Format the given Java source file. |
376 |
*/ |
377 |
private void formatFile(File file) { |
378 |
|
379 |
IDocument doc = new Document(); |
380 |
try { |
381 |
// read the file |
382 |
final BufferedReader in = new BufferedReader(new FileReader(file)); |
383 |
if (verbose) { |
384 |
System.out.println(FormatterAppMessages.getFormattedString |
385 |
("CommandLine.formatting", file.getName())); //$NON-NLS-1$ |
386 |
} |
387 |
String line; |
388 |
String contents = ""; //$NON-NLS-1$ |
389 |
try { |
390 |
while ((line = in.readLine()) != null) |
391 |
contents = contents |
392 |
+ System.getProperty("line.separator") + line; //$NON-NLS-1$ |
393 |
} finally { |
394 |
try { in.close(); } catch (IOException e) { /* ignore */ } |
395 |
} |
396 |
|
397 |
// format the file (the meat and potatoes) |
398 |
doc.set(contents); |
399 |
TextEdit edit = ToolFactory.createCodeFormatter(options).format( |
400 |
CodeFormatter.K_COMPILATION_UNIT, doc.get(), 0, |
401 |
doc.getLength(), 0, null); |
402 |
if (edit != null) { |
403 |
edit.apply(doc); |
404 |
} else { |
405 |
System.err.println |
406 |
(FormatterAppMessages.getFormattedString("Edit.problem", file.getName())); //$NON-NLS-1$ |
407 |
return; |
408 |
} |
409 |
|
410 |
// write the file |
411 |
final Writer out = new BufferedWriter(new FileWriter(file, false)); |
412 |
try { |
413 |
out.write(doc.get()); |
414 |
out.flush(); |
415 |
} finally { |
416 |
try { out.close(); } catch (IOException e) { /* ignore */ } |
417 |
} |
418 |
|
419 |
} catch (IOException e) { |
420 |
String errorMessage = FormatterAppMessages.getString("Exception.io") + " " //$NON-NLS-1$ //$NON-NLS-2$ |
421 |
+ e.getLocalizedMessage(); |
422 |
Util.log(e, errorMessage); |
423 |
System.err.println(errorMessage); |
424 |
System.err.println(FormatterAppMessages.getString("Exception.skip")); //$NON-NLS-1$ |
425 |
|
426 |
} catch (BadLocationException e) { |
427 |
String errorMessage = FormatterAppMessages.getString("Exception.bad.location") + " " //$NON-NLS-1$ //$NON-NLS-2$ |
428 |
+ e.getLocalizedMessage(); |
429 |
Util.log(e, errorMessage); |
430 |
System.err.println(errorMessage); |
431 |
System.err.println(FormatterAppMessages.getString("Exception.skip")); //$NON-NLS-1$ |
432 |
} |
433 |
} |
434 |
|
435 |
} |