You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Main.java 28KB

21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
14 years ago
14 years ago
21 years ago
14 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. /* *******************************************************************
  2. * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
  3. * All rights reserved.
  4. * This program and the accompanying materials are made available
  5. * under the terms of the Eclipse Public License v 2.0
  6. * which accompanies this distribution and is available at
  7. * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
  8. *
  9. * Contributors:
  10. * PARC initial implementation
  11. * ******************************************************************/
  12. package org.aspectj.tools.ajc;
  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.io.PrintStream;
  17. import java.util.Arrays;
  18. import java.util.Date;
  19. import java.util.List;
  20. import org.aspectj.ajdt.internal.core.builder.AjBuildManager;
  21. import org.aspectj.bridge.AbortException;
  22. import org.aspectj.bridge.ICommand;
  23. import org.aspectj.bridge.IMessage;
  24. import org.aspectj.bridge.IMessage.Kind;
  25. import org.aspectj.bridge.IMessageHandler;
  26. import org.aspectj.bridge.IMessageHolder;
  27. import org.aspectj.bridge.ISourceLocation;
  28. import org.aspectj.bridge.Message;
  29. import org.aspectj.bridge.MessageHandler;
  30. import org.aspectj.bridge.MessageUtil;
  31. import org.aspectj.bridge.ReflectionFactory;
  32. import org.aspectj.bridge.Version;
  33. import org.aspectj.bridge.context.CompilationAndWeavingContext;
  34. import org.aspectj.util.FileUtil;
  35. import org.aspectj.util.LangUtil;
  36. import org.aspectj.weaver.Dump;
  37. import javax.lang.model.SourceVersion;
  38. /**
  39. * Programmatic and command-line interface to AspectJ compiler. The compiler is an ICommand obtained by reflection. Not thread-safe.
  40. * By default, messages are printed as they are emitted; info messages go to the output stream, and warnings and errors go to the
  41. * error stream.
  42. * <p>
  43. * Clients can handle all messages by registering a holder:
  44. *
  45. * <pre>
  46. * Main main = new Main();
  47. * IMessageHolder holder = new MessageHandler();
  48. * main.setHolder(holder);
  49. * </pre>
  50. *
  51. * Clients can get control after each command completes by installing a Runnable:
  52. *
  53. * <pre>
  54. * main.setCompletionRunner(new Runnable() {..});
  55. * </pre>
  56. *
  57. */
  58. public class Main {
  59. /** Header used when rendering exceptions for users */
  60. public static final String THROWN_PREFIX = "Exception thrown from AspectJ " + Version.getText() + LangUtil.EOL + "" + LangUtil.EOL
  61. + "This might be logged as a bug already -- find current bugs at" + LangUtil.EOL
  62. + " https://bugs.eclipse.org/bugs/buglist.cgi?product=AspectJ&component=Compiler" + LangUtil.EOL + "" + LangUtil.EOL
  63. + "Bugs for exceptions thrown have titles File:line from the top stack, " + LangUtil.EOL
  64. + "e.g., \"SomeFile.java:243\"" + LangUtil.EOL + "" + LangUtil.EOL
  65. + "If you don't find the exception below in a bug, please add a new bug" + LangUtil.EOL
  66. + "at https://bugs.eclipse.org/bugs/enter_bug.cgi?product=AspectJ" + LangUtil.EOL
  67. + "To make the bug a priority, please include a test program" + LangUtil.EOL + "that can reproduce this exception."
  68. + LangUtil.EOL;
  69. private static final String OUT_OF_MEMORY_MSG = "AspectJ " + Version.getText() + " ran out of memory during compilation:"
  70. + LangUtil.EOL + LangUtil.EOL + "Please increase the memory available to ajc by editing the ajc script " + LangUtil.EOL
  71. + "found in your AspectJ installation directory. The -Xmx parameter value" + LangUtil.EOL
  72. + "should be increased from 64M (default) to 128M or even 256M." + LangUtil.EOL + LangUtil.EOL
  73. + "See the AspectJ FAQ available from the documentation link" + LangUtil.EOL
  74. + "on the AspectJ home page at https://www.eclipse.org/aspectj";
  75. private static final String MESSAGE_HOLDER_OPTION = "-messageHolder";
  76. // Minimal Java runtime version necessary to run AJC
  77. // TODO: Update value, if Eclipse JDT Core raises compilation target level.
  78. private static final int MINIMAL_JRE_VERSION = 17;
  79. private static final String MINIMAL_JRE_VERSION_ERROR =
  80. "The AspectJ compiler needs at least Java runtime " + MINIMAL_JRE_VERSION;
  81. /** @param args the String[] of command-line arguments */
  82. public static void main(String[] args) throws IOException {
  83. new Main().runMain(args, true);
  84. }
  85. /**
  86. * Convenience method to run ajc and collect String lists of messages. This can be reflectively invoked with the List collecting
  87. * parameters supplied by a parent class loader. The String messages take the same form as command-line messages.
  88. * This method does not catch unchecked exceptions thrown by the compiler.
  89. *
  90. * @param args the String[] args to pass to the compiler
  91. * @param useSystemExit if true and errors, return System.exit(errs)
  92. * @param fails the List sink, if any, for String failure (or worse) messages
  93. * @param errors the List sink, if any, for String error messages
  94. * @param warnings the List sink, if any, for String warning messages
  95. * @param infos the List sink, if any, for String info messages
  96. * @param usages the List sink, if any, for String usage messages
  97. * @return number of messages reported with level ERROR or above
  98. */
  99. public static int bareMain(
  100. String[] args, boolean useSystemExit,
  101. List<String> fails, List<String> errors, List<String> warnings, List<String> infos, List<String> usages
  102. ) {
  103. Main main = new Main();
  104. MessageHandler holder = new MessageHandler();
  105. main.setHolder(holder);
  106. try {
  107. main.runMain(args, useSystemExit);
  108. } finally {
  109. readMessages(holder, IMessage.FAIL, true, fails);
  110. readMessages(holder, IMessage.ERROR, false, errors);
  111. readMessages(holder, IMessage.WARNING, false, warnings);
  112. readMessages(holder, IMessage.INFO, false, infos);
  113. readMessages(holder, IMessage.USAGE, false, usages);
  114. }
  115. return holder.numMessages(IMessage.ERROR, true);
  116. }
  117. /** Read messages of a given kind into a List as String */
  118. private static void readMessages(IMessageHolder holder, IMessage.Kind kind, boolean orGreater, List<String> sink) {
  119. if ((null == sink) || (null == holder)) {
  120. return;
  121. }
  122. IMessage[] messages = holder.getMessages(kind, orGreater);
  123. if (!LangUtil.isEmpty(messages)) {
  124. for (IMessage message : messages) {
  125. sink.add(MessagePrinter.render(message));
  126. }
  127. }
  128. }
  129. /**
  130. * @return String rendering throwable as compiler error for user/console, including information on how to report as a bug.
  131. * @throws NullPointerException if thrown is null
  132. */
  133. public static String renderExceptionForUser(Throwable thrown) {
  134. String m = thrown.getMessage();
  135. return THROWN_PREFIX + (null != m ? m + "\n" : "") + "\n" + CompilationAndWeavingContext.getCurrentContext()
  136. + LangUtil.renderException(thrown, true);
  137. }
  138. private static String parmInArgs(String flag, String[] args) {
  139. int loc = 1 + (null == args ? -1 : Arrays.asList(args).indexOf(flag));
  140. return ((0 == loc) || (args.length <= loc) ? null : args[loc]);
  141. }
  142. private static boolean flagInArgs(String flag, String[] args) {
  143. return ((null != args) && (Arrays.asList(args).contains(flag)));
  144. }
  145. /**
  146. * append nothing if numItems is 0, numItems + label + (numItems > 1? "s" : "") otherwise, prefixing with " " if sink has
  147. * content
  148. */
  149. private static void appendNLabel(StringBuilder sink, String label, int numItems) {
  150. if (0 == numItems) {
  151. return;
  152. }
  153. if (0 < sink.length()) {
  154. sink.append(", ");
  155. }
  156. sink.append(numItems).append(" ");
  157. if (!LangUtil.isEmpty(label)) {
  158. sink.append(label);
  159. }
  160. if (1 < numItems) {
  161. sink.append("s");
  162. }
  163. }
  164. /** control iteration/continuation for command (compiler) */
  165. protected CommandController controller;
  166. /** ReflectionFactory identifier for command (compiler) */
  167. protected String commandName;
  168. protected ICommand command;
  169. /** client-set message sink */
  170. private IMessageHolder clientHolder;
  171. /** internally-set message sink */
  172. protected final MessageHandler ourHandler;
  173. private int lastFails;
  174. private int lastErrors;
  175. /** if not null, run this synchronously after each compile completes */
  176. private Runnable completionRunner;
  177. public Main() {
  178. controller = new CommandController();
  179. commandName = ReflectionFactory.ECLIPSE;
  180. CompilationAndWeavingContext.setMultiThreaded(false);
  181. try {
  182. String value = System.getProperty("aspectj.multithreaded");
  183. if (value != null && value.equalsIgnoreCase("true")) {
  184. CompilationAndWeavingContext.setMultiThreaded(true);
  185. }
  186. } catch (Exception e) {
  187. // silent
  188. }
  189. ourHandler = new MessageHandler(true);
  190. }
  191. public MessageHandler getMessageHandler() {
  192. return ourHandler;
  193. }
  194. // for unit testing...
  195. void setController(CommandController controller) {
  196. this.controller = controller;
  197. }
  198. public void setCommand(ICommand command) {
  199. this.command = command;
  200. }
  201. /**
  202. * Run without throwing exceptions, but optionally using {@link System#exit(int)}. This sets up a message handler
  203. * which emits messages immediately, so {@link #report(boolean, IMessageHolder)} only reports the total number of
  204. * errors or warnings.
  205. *
  206. * @param args the compiler command line
  207. * @param useSystemExit if true, use {@link System#exit(int)} to complete unless one of the args is {@code -noExit},
  208. * and signal result (0 = no exceptions/error, &lt;0 = exceptions, &gt;0 = compiler errors).
  209. * Note: While some shells like Windows <i>cmd.exe</i> can correctly print negative exit codes
  210. * via {@code echo %errorlevel%"}, UNIX shells like Bash interpret them as positive byte values
  211. * modulo 256. E.g., exit code -1 will be printed as 127 using {@code echo $?}.
  212. */
  213. public void runMain(String[] args, boolean useSystemExit) {
  214. final boolean doExit = useSystemExit && !flagInArgs("-noExit", args);
  215. // This needs to be checked, before any classes using JDT Core classes are used for the first time. Otherwise, users
  216. // will see ugly UnsupportedClassVersionError stack traces, which they might or might not interpret correctly.
  217. // Therefore, interrupt AJC usage right here, even if it means that not even a usage page can be printed. It is
  218. // better to save users from subsequent problems later.
  219. if (SourceVersion.latest().ordinal() < MINIMAL_JRE_VERSION)
  220. throw new AbortException(MINIMAL_JRE_VERSION_ERROR);
  221. // Urk - default no check for AJDT, enabled here for Ant, command-line
  222. AjBuildManager.enableRuntimeVersionCheck(this);
  223. final boolean verbose = flagInArgs("-verbose", args);
  224. final boolean timers = flagInArgs("-timers", args);
  225. if (null == this.clientHolder) {
  226. this.clientHolder = checkForCustomMessageHolder(args);
  227. }
  228. IMessageHolder holder = clientHolder;
  229. if (null == holder) {
  230. holder = ourHandler;
  231. if (verbose) {
  232. ourHandler.setInterceptor(MessagePrinter.VERBOSE);
  233. } else {
  234. ourHandler.ignore(IMessage.INFO);
  235. ourHandler.setInterceptor(MessagePrinter.TERSE);
  236. }
  237. }
  238. // make sure we handle out of memory gracefully...
  239. try {
  240. // byte[] b = new byte[100000000]; for testing OoME only!
  241. long stime = System.currentTimeMillis();
  242. // uncomment next line to pause before startup (attach jconsole)
  243. // try {Thread.sleep(5000); }catch(Exception e) {}
  244. run(args, holder);
  245. long etime = System.currentTimeMillis();
  246. if (timers) {
  247. System.out.println("Compiler took " + (etime - stime) + "ms");
  248. }
  249. holder.handleMessage(MessageUtil.info("Compiler took " + (etime - stime) + "ms"));
  250. // uncomment next line to pause at end (keeps jconsole alive!)
  251. // try { System.in.read(); } catch (Exception e) {}
  252. } catch (OutOfMemoryError outOfMemory) {
  253. IMessage outOfMemoryMessage = new Message(OUT_OF_MEMORY_MSG, null, true);
  254. holder.handleMessage(outOfMemoryMessage);
  255. System.exit(-1); // we can't reasonably continue from this point.
  256. } finally {
  257. CompilationAndWeavingContext.reset();
  258. Dump.reset();
  259. }
  260. if (doExit)
  261. systemExit();
  262. }
  263. // put calls around run() call above to allowing connecting jconsole
  264. // private void pause(int ms) {
  265. // try {
  266. // System.err.println("Pausing for "+ms+"ms");
  267. // System.gc();
  268. // Thread.sleep(ms);
  269. // System.gc();
  270. // System.err.println("Continuing");
  271. // } catch (Exception e) {}
  272. // }
  273. /**
  274. * @param args
  275. */
  276. private IMessageHolder checkForCustomMessageHolder(String[] args) {
  277. IMessageHolder holder = null;
  278. final String customMessageHolder = parmInArgs(MESSAGE_HOLDER_OPTION, args);
  279. if (customMessageHolder != null) {
  280. try {
  281. holder = (IMessageHolder) Class.forName(customMessageHolder).getDeclaredConstructor().newInstance();
  282. } catch (Exception ex) {
  283. throw new AbortException("Failed to create custom message holder of class '" + customMessageHolder + "' : " + ex);
  284. }
  285. }
  286. return holder;
  287. }
  288. /**
  289. * Run without using System.exit(..), putting all messages in holder:
  290. * <ul>
  291. * <li>ERROR: compiler error</li>
  292. * <li>WARNING: compiler warning</li>
  293. * <li>FAIL: command error (bad arguments, exception thrown)</li>
  294. * </ul>
  295. * This handles incremental behavior:
  296. * <ul>
  297. * <li>If args include "-incremental", repeat for every input char until 'q' is entered.
  298. * <li>If args include "-incrementalTagFile {file}", repeat every time we detect that {file} modification time has changed.</li>
  299. * <li>Either way, list files recompiled each time if args includes "-verbose".</li>
  300. * <li>Exit when the commmand/compiler throws any Throwable.</li>
  301. * </ul>
  302. * When complete, this contains all the messages of the final run of the command and/or any FAIL messages produced in running
  303. * the command, including any Throwable thrown by the command itself.
  304. *
  305. * @param args the String[] command line for the compiler
  306. * @param holder the MessageHandler sink for messages.
  307. */
  308. public void run(String[] args, IMessageHolder holder) {
  309. PrintStream logStream = null;
  310. FileOutputStream fos = null;
  311. String logFileName = parmInArgs("-log", args);
  312. if (null != logFileName) {
  313. File logFile = new File(logFileName);
  314. try {
  315. logFile.createNewFile();
  316. fos = new FileOutputStream(logFileName, true);
  317. logStream = new PrintStream(fos, true);
  318. } catch (Exception e) {
  319. fail(holder, "Couldn't open log file: " + logFileName, e);
  320. }
  321. Date now = new Date();
  322. logStream.println(now);
  323. if (flagInArgs("-verbose", args)) {
  324. ourHandler.setInterceptor(new LogModeMessagePrinter(true, logStream));
  325. } else {
  326. ourHandler.ignore(IMessage.INFO);
  327. ourHandler.setInterceptor(new LogModeMessagePrinter(false, logStream));
  328. }
  329. holder = ourHandler;
  330. }
  331. if (LangUtil.isEmpty(args)) {
  332. args = new String[] { "-?" };
  333. } else if (controller.running()) {
  334. fail(holder, "already running with controller: " + controller, null);
  335. return;
  336. }
  337. args = controller.init(args, holder);
  338. if (0 < holder.numMessages(IMessage.ERROR, true)) {
  339. return;
  340. }
  341. if (command == null) {
  342. command = ReflectionFactory.makeCommand(commandName, holder);
  343. }
  344. if (0 < holder.numMessages(IMessage.ERROR, true)) {
  345. return;
  346. }
  347. try {
  348. outer: while (true) {
  349. boolean passed = command.runCommand(args, holder);
  350. if (report(passed, holder) && controller.incremental()) {
  351. while (controller.doRepeatCommand(command)) {
  352. holder.clearMessages();
  353. if (controller.buildFresh()) {
  354. continue outer;
  355. } else {
  356. passed = command.repeatCommand(holder);
  357. }
  358. if (!report(passed, holder)) {
  359. break;
  360. }
  361. }
  362. }
  363. break;
  364. }
  365. } catch (AbortException ae) {
  366. if (ae.isSilent()) {
  367. quit();
  368. } else {
  369. IMessage message = ae.getIMessage();
  370. Throwable thrown = ae.getThrown();
  371. if (null == thrown) { // toss AbortException wrapper
  372. if (null != message) {
  373. holder.handleMessage(message);
  374. } else {
  375. fail(holder, "abort without message", ae);
  376. }
  377. } else if (null == message) {
  378. fail(holder, "aborted", thrown);
  379. } else {
  380. String mssg = MessageUtil.MESSAGE_MOST.renderToString(message);
  381. fail(holder, mssg, thrown);
  382. }
  383. }
  384. } catch (Throwable t) {
  385. fail(holder, "unexpected exception", t);
  386. } finally {
  387. if (logStream != null) {
  388. logStream.close();
  389. }
  390. if (fos != null) {
  391. try {
  392. fos.close();
  393. } catch (IOException e) {
  394. fail(holder, "unexpected exception", e);
  395. }
  396. }
  397. command = null;
  398. }
  399. }
  400. /** call this to stop after the next iteration of incremental compile */
  401. public void quit() {
  402. controller.quit();
  403. }
  404. /**
  405. * Set holder to be passed all messages. When holder is set, messages will not be printed by default.
  406. *
  407. * @param holder the IMessageHolder sink for all messages (use null to restore default behavior)
  408. */
  409. public void setHolder(IMessageHolder holder) {
  410. clientHolder = holder;
  411. }
  412. public IMessageHolder getHolder() {
  413. return clientHolder;
  414. }
  415. /**
  416. * Install a Runnable to be invoked synchronously after each compile completes.
  417. *
  418. * @param runner the Runnable to invoke - null to disable
  419. */
  420. public void setCompletionRunner(Runnable runner) {
  421. this.completionRunner = runner;
  422. }
  423. /**
  424. * Call System.exit(int) with values derived from the number of failures/aborts or errors in messages.
  425. *
  426. */
  427. protected void systemExit() {
  428. int num = lastFails;
  429. if (0 < num) {
  430. System.exit(-num);
  431. }
  432. num = lastErrors;
  433. if (0 < num) {
  434. System.exit(num);
  435. }
  436. System.exit(0);
  437. }
  438. /** Messages to the user */
  439. protected void outMessage(String message) { // XXX coordinate with MessagePrinter
  440. System.out.print(message);
  441. System.out.flush();
  442. }
  443. /**
  444. * Report results from a (possibly-incremental) compile run. This delegates to any reportHandler or otherwise prints summary
  445. * counts of errors/warnings to System.err (if any errors) or System.out (if only warnings). WARNING: this silently ignores
  446. * other messages like FAIL, but clears the handler of all messages when returning true. XXX false
  447. *
  448. * This implementation ignores the pass parameter but clears the holder after reporting on the assumption messages were
  449. * handled/printed already. (ignoring UnsupportedOperationException from holder.clearMessages()).
  450. *
  451. * @param pass true result of the command
  452. * @param holder IMessageHolder with messages from the command
  453. * @return false if the process should abort
  454. */
  455. protected boolean report(boolean pass, IMessageHolder holder) {
  456. lastFails = holder.numMessages(IMessage.FAIL, true);
  457. boolean result = (0 == lastFails);
  458. final Runnable runner = completionRunner;
  459. if (null != runner) {
  460. runner.run();
  461. }
  462. if (holder == ourHandler) {
  463. lastErrors = holder.numMessages(IMessage.ERROR, false);
  464. int warnings = holder.numMessages(IMessage.WARNING, false);
  465. StringBuilder sb = new StringBuilder();
  466. appendNLabel(sb, "fail|abort", lastFails);
  467. appendNLabel(sb, "error", lastErrors);
  468. appendNLabel(sb, "warning", warnings);
  469. if (0 < sb.length()) {
  470. PrintStream out = (0 < (lastErrors + lastFails) ? System.err : System.out);
  471. out.println(""); // XXX "wrote class file" messages no eol?
  472. out.println(sb);
  473. }
  474. }
  475. return result;
  476. }
  477. /** convenience API to make fail messages (without MessageUtils's fail prefix) */
  478. protected static void fail(IMessageHandler handler, String message, Throwable thrown) {
  479. handler.handleMessage(new Message(message, IMessage.FAIL, thrown, null));
  480. }
  481. /**
  482. * interceptor IMessageHandler to print as we go. This formats all messages to the user.
  483. */
  484. public static class MessagePrinter implements IMessageHandler {
  485. public static final IMessageHandler VERBOSE = new MessagePrinter(true);
  486. public static final IMessageHandler TERSE = new MessagePrinter(false);
  487. final boolean verbose;
  488. protected MessagePrinter(boolean verbose) {
  489. this.verbose = verbose;
  490. }
  491. /**
  492. * Print errors and warnings to System.err, and optionally info to System.out, rendering message String only.
  493. *
  494. * @return false always
  495. */
  496. @Override
  497. public boolean handleMessage(IMessage message) {
  498. if (null != message) {
  499. PrintStream out = getStreamFor(message.getKind());
  500. if (null != out) {
  501. out.println(render(message));
  502. }
  503. }
  504. return false;
  505. }
  506. /**
  507. * Render message differently. If abort, then prefix stack trace with feedback request. If the actual message is empty, then
  508. * use toString on the whole. Prefix message part with file:line; If it has context, suffix message with context.
  509. *
  510. * @param message the IMessage to render
  511. * @return String rendering IMessage (never null)
  512. */
  513. public static String render(IMessage message) {
  514. // IMessage.Kind kind = message.getKind();
  515. StringBuilder sb = new StringBuilder();
  516. String text = message.getMessage();
  517. if (text.equals(AbortException.NO_MESSAGE_TEXT)) {
  518. text = null;
  519. }
  520. boolean toString = (LangUtil.isEmpty(text));
  521. if (toString) {
  522. text = message.toString();
  523. }
  524. ISourceLocation loc = message.getSourceLocation();
  525. String context = null;
  526. if (null != loc) {
  527. File file = loc.getSourceFile();
  528. if (null != file) {
  529. String name = file.getName();
  530. if (!toString || (!text.contains(name))) {
  531. sb.append(FileUtil.getBestPath(file));
  532. if (loc.getLine() > 0) {
  533. sb.append(":" + loc.getLine());
  534. }
  535. int col = loc.getColumn();
  536. if (0 < col) {
  537. sb.append(":").append(col);
  538. }
  539. sb.append(" ");
  540. }
  541. }
  542. context = loc.getContext();
  543. }
  544. // per Wes' suggestion on dev...
  545. if (message.getKind() == IMessage.ERROR) {
  546. sb.append("[error] ");
  547. } else if (message.getKind() == IMessage.WARNING) {
  548. sb.append("[warning] ");
  549. }
  550. sb.append(text);
  551. if (null != context) {
  552. sb.append(LangUtil.EOL);
  553. sb.append(context);
  554. }
  555. String details = message.getDetails();
  556. if (details != null) {
  557. sb.append(LangUtil.EOL);
  558. sb.append('\t');
  559. sb.append(details);
  560. }
  561. Throwable thrown = message.getThrown();
  562. if (null != thrown) {
  563. sb.append(LangUtil.EOL);
  564. sb.append(Main.renderExceptionForUser(thrown));
  565. }
  566. if (message.getExtraSourceLocations().isEmpty()) {
  567. return sb.toString();
  568. } else {
  569. return MessageUtil.addExtraSourceLocations(message, sb.toString());
  570. }
  571. }
  572. @Override
  573. public boolean isIgnoring(IMessage.Kind kind) {
  574. return (null != getStreamFor(kind));
  575. }
  576. /**
  577. * No-op
  578. *
  579. * @see org.aspectj.bridge.IMessageHandler#isIgnoring(org.aspectj.bridge.IMessage.Kind)
  580. * @param kind
  581. */
  582. @Override
  583. public void dontIgnore(IMessage.Kind kind) {
  584. }
  585. /**
  586. * @return System.err for FAIL, ABORT, ERROR, and WARNING, System.out for INFO if -verbose and WEAVEINFO if -showWeaveInfo.
  587. */
  588. protected PrintStream getStreamFor(IMessage.Kind kind) {
  589. if (IMessage.WARNING.isSameOrLessThan(kind)) {
  590. return System.err;
  591. } else if (verbose && IMessage.INFO.equals(kind)) {
  592. return System.out;
  593. } else if (IMessage.USAGE.equals(kind)) {
  594. return System.out;
  595. } else if (IMessage.WEAVEINFO.equals(kind)) {
  596. return System.out;
  597. } else {
  598. return null;
  599. }
  600. }
  601. /**
  602. * No-op
  603. *
  604. * @see org.aspectj.bridge.IMessageHandler#ignore(org.aspectj.bridge.IMessage.Kind)
  605. * @param kind
  606. */
  607. @Override
  608. public void ignore(Kind kind) {
  609. }
  610. }
  611. public static class LogModeMessagePrinter extends MessagePrinter {
  612. protected final PrintStream logStream;
  613. public LogModeMessagePrinter(boolean verbose, PrintStream logStream) {
  614. super(verbose);
  615. this.logStream = logStream;
  616. }
  617. @Override
  618. protected PrintStream getStreamFor(IMessage.Kind kind) {
  619. if (IMessage.WARNING.isSameOrLessThan(kind)) {
  620. return logStream;
  621. } else if (verbose && IMessage.INFO.equals(kind)) {
  622. return logStream;
  623. } else if (IMessage.WEAVEINFO.equals(kind)) {
  624. return logStream;
  625. } else {
  626. return null;
  627. }
  628. }
  629. }
  630. /** controller for repeatable command delays until input or file changed or removed */
  631. public static class CommandController {
  632. public static String TAG_FILE_OPTION = "-XincrementalFile";
  633. public static String INCREMENTAL_OPTION = "-incremental";
  634. /** maximum 10-minute delay between filesystem checks */
  635. public static long MAX_DELAY = 1000 * 600;
  636. /** default 5-second delay between filesystem checks */
  637. public static long DEFAULT_DELAY = 1000 * 5;
  638. /** @see init(String[]) */
  639. private static final String[][] OPTIONS = new String[][] {
  640. new String[] { INCREMENTAL_OPTION },
  641. new String[] { TAG_FILE_OPTION, null }
  642. };
  643. /** true between init(String[]) and doRepeatCommand() that returns false */
  644. private boolean running;
  645. /** true after quit() called */
  646. private boolean quit;
  647. /** true if incremental mode, waiting for input other than 'q' */
  648. private boolean incremental;
  649. /** true if incremental mode, waiting for file to change (repeat) or disappear (quit) */
  650. private File tagFile;
  651. /** last modification time for tagFile as of last command - 0 to start */
  652. private long fileModTime;
  653. /** delay between filesystem checks for tagFile modification time */
  654. private long delay;
  655. /** true just after user types 'r' for rebuild */
  656. private boolean buildFresh;
  657. public CommandController() {
  658. delay = DEFAULT_DELAY;
  659. }
  660. /**
  661. * @param args read and strip incremental args from this
  662. * @param sink IMessageHandler for error messages
  663. * @return String[] remainder of args
  664. */
  665. public String[] init(String[] args, IMessageHandler sink) {
  666. running = true;
  667. // String[] unused;
  668. if (!LangUtil.isEmpty(args)) {
  669. String[][] options = LangUtil.copyStrings(OPTIONS);
  670. /* unused = */LangUtil.extractOptions(args, options);
  671. incremental = (null != options[0][0]);
  672. if (null != options[1][0]) {
  673. File file = new File(options[1][1]);
  674. if (!file.exists()) {
  675. MessageUtil.abort(sink, "tag file does not exist: " + file);
  676. } else {
  677. tagFile = file;
  678. fileModTime = tagFile.lastModified();
  679. }
  680. }
  681. }
  682. return args;
  683. }
  684. /**
  685. * @return true if init(String[]) called but doRepeatCommand has not returned false
  686. */
  687. public boolean running() {
  688. return running;
  689. }
  690. /** @param delay milliseconds between filesystem checks */
  691. public void setDelay(long delay) {
  692. if ((delay > -1) && (delay < MAX_DELAY)) {
  693. this.delay = delay;
  694. }
  695. }
  696. /** @return true if INCREMENTAL_OPTION or TAG_FILE_OPTION was in args */
  697. public boolean incremental() {
  698. return (incremental || (null != tagFile));
  699. }
  700. /** @return true if INCREMENTAL_OPTION was in args */
  701. public boolean commandLineIncremental() {
  702. return incremental;
  703. }
  704. public void quit() {
  705. if (!quit) {
  706. quit = true;
  707. }
  708. }
  709. /** @return true just after user typed 'r' */
  710. boolean buildFresh() {
  711. return buildFresh;
  712. }
  713. /** @return false if we should quit, true to do another command */
  714. boolean doRepeatCommand(ICommand command) {
  715. if (!running) {
  716. return false;
  717. }
  718. boolean result = false;
  719. if (quit) {
  720. result = false;
  721. } else if (incremental) {
  722. try {
  723. if (buildFresh) { // reset before input request
  724. buildFresh = false;
  725. }
  726. System.out.println(" press enter to recompile, r to rebuild, q to quit: ");
  727. System.out.flush();
  728. // boolean doMore = false;
  729. // seek for one q or a series of [\n\r]...
  730. do {
  731. int input = System.in.read();
  732. if ('q' == input) {
  733. break; // result = false;
  734. } else if ('r' == input) {
  735. buildFresh = true;
  736. result = true;
  737. } else if (('\n' == input) || ('\r' == input)) {
  738. result = true;
  739. } // else eat anything else
  740. } while (!result);
  741. System.in.skip(Integer.MAX_VALUE);
  742. } catch (IOException e) { // XXX silence for error?
  743. result = false;
  744. }
  745. } else if (null != tagFile) {
  746. long curModTime;
  747. while (true) {
  748. if (!tagFile.exists()) {
  749. result = false;
  750. break;
  751. } else if (fileModTime == (curModTime = tagFile.lastModified())) {
  752. fileCheckDelay();
  753. } else {
  754. fileModTime = curModTime;
  755. result = true;
  756. break;
  757. }
  758. }
  759. } // else, not incremental - false
  760. if (!result && running) {
  761. running = false;
  762. }
  763. return result;
  764. }
  765. /** delay between filesystem checks, returning if quit is set */
  766. protected void fileCheckDelay() {
  767. // final Thread thread = Thread.currentThread();
  768. long targetTime = System.currentTimeMillis() + delay;
  769. // long curTime;
  770. while (targetTime > System.currentTimeMillis()) {
  771. if (quit) {
  772. return;
  773. }
  774. try {
  775. Thread.sleep(300);
  776. } // 1/3-second delta for quit check
  777. catch (InterruptedException e) {
  778. }
  779. }
  780. }
  781. }
  782. }