選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

Main.java 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  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. System.err.println(MINIMAL_JRE_VERSION_ERROR);
  221. if (doExit)
  222. System.exit(-1);
  223. return;
  224. }
  225. // Urk - default no check for AJDT, enabled here for Ant, command-line
  226. AjBuildManager.enableRuntimeVersionCheck(this);
  227. final boolean verbose = flagInArgs("-verbose", args);
  228. final boolean timers = flagInArgs("-timers", args);
  229. if (null == this.clientHolder) {
  230. this.clientHolder = checkForCustomMessageHolder(args);
  231. }
  232. IMessageHolder holder = clientHolder;
  233. if (null == holder) {
  234. holder = ourHandler;
  235. if (verbose) {
  236. ourHandler.setInterceptor(MessagePrinter.VERBOSE);
  237. } else {
  238. ourHandler.ignore(IMessage.INFO);
  239. ourHandler.setInterceptor(MessagePrinter.TERSE);
  240. }
  241. }
  242. // make sure we handle out of memory gracefully...
  243. try {
  244. // byte[] b = new byte[100000000]; for testing OoME only!
  245. long stime = System.currentTimeMillis();
  246. // uncomment next line to pause before startup (attach jconsole)
  247. // try {Thread.sleep(5000); }catch(Exception e) {}
  248. run(args, holder);
  249. long etime = System.currentTimeMillis();
  250. if (timers) {
  251. System.out.println("Compiler took " + (etime - stime) + "ms");
  252. }
  253. holder.handleMessage(MessageUtil.info("Compiler took " + (etime - stime) + "ms"));
  254. // uncomment next line to pause at end (keeps jconsole alive!)
  255. // try { System.in.read(); } catch (Exception e) {}
  256. } catch (OutOfMemoryError outOfMemory) {
  257. IMessage outOfMemoryMessage = new Message(OUT_OF_MEMORY_MSG, null, true);
  258. holder.handleMessage(outOfMemoryMessage);
  259. System.exit(-1); // we can't reasonably continue from this point.
  260. } finally {
  261. CompilationAndWeavingContext.reset();
  262. Dump.reset();
  263. }
  264. if (doExit)
  265. systemExit();
  266. }
  267. // put calls around run() call above to allowing connecting jconsole
  268. // private void pause(int ms) {
  269. // try {
  270. // System.err.println("Pausing for "+ms+"ms");
  271. // System.gc();
  272. // Thread.sleep(ms);
  273. // System.gc();
  274. // System.err.println("Continuing");
  275. // } catch (Exception e) {}
  276. // }
  277. /**
  278. * @param args
  279. */
  280. private IMessageHolder checkForCustomMessageHolder(String[] args) {
  281. IMessageHolder holder = null;
  282. final String customMessageHolder = parmInArgs(MESSAGE_HOLDER_OPTION, args);
  283. if (customMessageHolder != null) {
  284. try {
  285. holder = (IMessageHolder) Class.forName(customMessageHolder).getDeclaredConstructor().newInstance();
  286. } catch (Exception ex) {
  287. throw new AbortException("Failed to create custom message holder of class '" + customMessageHolder + "' : " + ex);
  288. }
  289. }
  290. return holder;
  291. }
  292. /**
  293. * Run without using System.exit(..), putting all messages in holder:
  294. * <ul>
  295. * <li>ERROR: compiler error</li>
  296. * <li>WARNING: compiler warning</li>
  297. * <li>FAIL: command error (bad arguments, exception thrown)</li>
  298. * </ul>
  299. * This handles incremental behavior:
  300. * <ul>
  301. * <li>If args include "-incremental", repeat for every input char until 'q' is entered.
  302. * <li>If args include "-incrementalTagFile {file}", repeat every time we detect that {file} modification time has changed.</li>
  303. * <li>Either way, list files recompiled each time if args includes "-verbose".</li>
  304. * <li>Exit when the commmand/compiler throws any Throwable.</li>
  305. * </ul>
  306. * When complete, this contains all the messages of the final run of the command and/or any FAIL messages produced in running
  307. * the command, including any Throwable thrown by the command itself.
  308. *
  309. * @param args the String[] command line for the compiler
  310. * @param holder the MessageHandler sink for messages.
  311. */
  312. public void run(String[] args, IMessageHolder holder) {
  313. PrintStream logStream = null;
  314. FileOutputStream fos = null;
  315. String logFileName = parmInArgs("-log", args);
  316. if (null != logFileName) {
  317. File logFile = new File(logFileName);
  318. try {
  319. logFile.createNewFile();
  320. fos = new FileOutputStream(logFileName, true);
  321. logStream = new PrintStream(fos, true);
  322. } catch (Exception e) {
  323. fail(holder, "Couldn't open log file: " + logFileName, e);
  324. }
  325. Date now = new Date();
  326. logStream.println(now);
  327. if (flagInArgs("-verbose", args)) {
  328. ourHandler.setInterceptor(new LogModeMessagePrinter(true, logStream));
  329. } else {
  330. ourHandler.ignore(IMessage.INFO);
  331. ourHandler.setInterceptor(new LogModeMessagePrinter(false, logStream));
  332. }
  333. holder = ourHandler;
  334. }
  335. if (LangUtil.isEmpty(args)) {
  336. args = new String[] { "-?" };
  337. } else if (controller.running()) {
  338. fail(holder, "already running with controller: " + controller, null);
  339. return;
  340. }
  341. args = controller.init(args, holder);
  342. if (0 < holder.numMessages(IMessage.ERROR, true)) {
  343. return;
  344. }
  345. if (command == null) {
  346. command = ReflectionFactory.makeCommand(commandName, holder);
  347. }
  348. if (0 < holder.numMessages(IMessage.ERROR, true)) {
  349. return;
  350. }
  351. try {
  352. outer: while (true) {
  353. boolean passed = command.runCommand(args, holder);
  354. if (report(passed, holder) && controller.incremental()) {
  355. while (controller.doRepeatCommand(command)) {
  356. holder.clearMessages();
  357. if (controller.buildFresh()) {
  358. continue outer;
  359. } else {
  360. passed = command.repeatCommand(holder);
  361. }
  362. if (!report(passed, holder)) {
  363. break;
  364. }
  365. }
  366. }
  367. break;
  368. }
  369. } catch (AbortException ae) {
  370. if (ae.isSilent()) {
  371. quit();
  372. } else {
  373. IMessage message = ae.getIMessage();
  374. Throwable thrown = ae.getThrown();
  375. if (null == thrown) { // toss AbortException wrapper
  376. if (null != message) {
  377. holder.handleMessage(message);
  378. } else {
  379. fail(holder, "abort without message", ae);
  380. }
  381. } else if (null == message) {
  382. fail(holder, "aborted", thrown);
  383. } else {
  384. String mssg = MessageUtil.MESSAGE_MOST.renderToString(message);
  385. fail(holder, mssg, thrown);
  386. }
  387. }
  388. } catch (Throwable t) {
  389. fail(holder, "unexpected exception", t);
  390. } finally {
  391. if (logStream != null) {
  392. logStream.close();
  393. }
  394. if (fos != null) {
  395. try {
  396. fos.close();
  397. } catch (IOException e) {
  398. fail(holder, "unexpected exception", e);
  399. }
  400. }
  401. command = null;
  402. }
  403. }
  404. /** call this to stop after the next iteration of incremental compile */
  405. public void quit() {
  406. controller.quit();
  407. }
  408. /**
  409. * Set holder to be passed all messages. When holder is set, messages will not be printed by default.
  410. *
  411. * @param holder the IMessageHolder sink for all messages (use null to restore default behavior)
  412. */
  413. public void setHolder(IMessageHolder holder) {
  414. clientHolder = holder;
  415. }
  416. public IMessageHolder getHolder() {
  417. return clientHolder;
  418. }
  419. /**
  420. * Install a Runnable to be invoked synchronously after each compile completes.
  421. *
  422. * @param runner the Runnable to invoke - null to disable
  423. */
  424. public void setCompletionRunner(Runnable runner) {
  425. this.completionRunner = runner;
  426. }
  427. /**
  428. * Call System.exit(int) with values derived from the number of failures/aborts or errors in messages.
  429. *
  430. */
  431. protected void systemExit() {
  432. int num = lastFails;
  433. if (0 < num) {
  434. System.exit(-num);
  435. }
  436. num = lastErrors;
  437. if (0 < num) {
  438. System.exit(num);
  439. }
  440. System.exit(0);
  441. }
  442. /** Messages to the user */
  443. protected void outMessage(String message) { // XXX coordinate with MessagePrinter
  444. System.out.print(message);
  445. System.out.flush();
  446. }
  447. /**
  448. * Report results from a (possibly-incremental) compile run. This delegates to any reportHandler or otherwise prints summary
  449. * counts of errors/warnings to System.err (if any errors) or System.out (if only warnings). WARNING: this silently ignores
  450. * other messages like FAIL, but clears the handler of all messages when returning true. XXX false
  451. *
  452. * This implementation ignores the pass parameter but clears the holder after reporting on the assumption messages were
  453. * handled/printed already. (ignoring UnsupportedOperationException from holder.clearMessages()).
  454. *
  455. * @param pass true result of the command
  456. * @param holder IMessageHolder with messages from the command
  457. * @return false if the process should abort
  458. */
  459. protected boolean report(boolean pass, IMessageHolder holder) {
  460. lastFails = holder.numMessages(IMessage.FAIL, true);
  461. boolean result = (0 == lastFails);
  462. final Runnable runner = completionRunner;
  463. if (null != runner) {
  464. runner.run();
  465. }
  466. if (holder == ourHandler) {
  467. lastErrors = holder.numMessages(IMessage.ERROR, false);
  468. int warnings = holder.numMessages(IMessage.WARNING, false);
  469. StringBuilder sb = new StringBuilder();
  470. appendNLabel(sb, "fail|abort", lastFails);
  471. appendNLabel(sb, "error", lastErrors);
  472. appendNLabel(sb, "warning", warnings);
  473. if (0 < sb.length()) {
  474. PrintStream out = (0 < (lastErrors + lastFails) ? System.err : System.out);
  475. out.println(""); // XXX "wrote class file" messages no eol?
  476. out.println(sb);
  477. }
  478. }
  479. return result;
  480. }
  481. /** convenience API to make fail messages (without MessageUtils's fail prefix) */
  482. protected static void fail(IMessageHandler handler, String message, Throwable thrown) {
  483. handler.handleMessage(new Message(message, IMessage.FAIL, thrown, null));
  484. }
  485. /**
  486. * interceptor IMessageHandler to print as we go. This formats all messages to the user.
  487. */
  488. public static class MessagePrinter implements IMessageHandler {
  489. public static final IMessageHandler VERBOSE = new MessagePrinter(true);
  490. public static final IMessageHandler TERSE = new MessagePrinter(false);
  491. final boolean verbose;
  492. protected MessagePrinter(boolean verbose) {
  493. this.verbose = verbose;
  494. }
  495. /**
  496. * Print errors and warnings to System.err, and optionally info to System.out, rendering message String only.
  497. *
  498. * @return false always
  499. */
  500. @Override
  501. public boolean handleMessage(IMessage message) {
  502. if (null != message) {
  503. PrintStream out = getStreamFor(message.getKind());
  504. if (null != out) {
  505. out.println(render(message));
  506. }
  507. }
  508. return false;
  509. }
  510. /**
  511. * Render message differently. If abort, then prefix stack trace with feedback request. If the actual message is empty, then
  512. * use toString on the whole. Prefix message part with file:line; If it has context, suffix message with context.
  513. *
  514. * @param message the IMessage to render
  515. * @return String rendering IMessage (never null)
  516. */
  517. public static String render(IMessage message) {
  518. // IMessage.Kind kind = message.getKind();
  519. StringBuilder sb = new StringBuilder();
  520. String text = message.getMessage();
  521. if (text.equals(AbortException.NO_MESSAGE_TEXT)) {
  522. text = null;
  523. }
  524. boolean toString = (LangUtil.isEmpty(text));
  525. if (toString) {
  526. text = message.toString();
  527. }
  528. ISourceLocation loc = message.getSourceLocation();
  529. String context = null;
  530. if (null != loc) {
  531. File file = loc.getSourceFile();
  532. if (null != file) {
  533. String name = file.getName();
  534. if (!toString || (!text.contains(name))) {
  535. sb.append(FileUtil.getBestPath(file));
  536. if (loc.getLine() > 0) {
  537. sb.append(":" + loc.getLine());
  538. }
  539. int col = loc.getColumn();
  540. if (0 < col) {
  541. sb.append(":").append(col);
  542. }
  543. sb.append(" ");
  544. }
  545. }
  546. context = loc.getContext();
  547. }
  548. // per Wes' suggestion on dev...
  549. if (message.getKind() == IMessage.ERROR) {
  550. sb.append("[error] ");
  551. } else if (message.getKind() == IMessage.WARNING) {
  552. sb.append("[warning] ");
  553. }
  554. sb.append(text);
  555. if (null != context) {
  556. sb.append(LangUtil.EOL);
  557. sb.append(context);
  558. }
  559. String details = message.getDetails();
  560. if (details != null) {
  561. sb.append(LangUtil.EOL);
  562. sb.append('\t');
  563. sb.append(details);
  564. }
  565. Throwable thrown = message.getThrown();
  566. if (null != thrown) {
  567. sb.append(LangUtil.EOL);
  568. sb.append(Main.renderExceptionForUser(thrown));
  569. }
  570. if (message.getExtraSourceLocations().isEmpty()) {
  571. return sb.toString();
  572. } else {
  573. return MessageUtil.addExtraSourceLocations(message, sb.toString());
  574. }
  575. }
  576. @Override
  577. public boolean isIgnoring(IMessage.Kind kind) {
  578. return (null != getStreamFor(kind));
  579. }
  580. /**
  581. * No-op
  582. *
  583. * @see org.aspectj.bridge.IMessageHandler#isIgnoring(org.aspectj.bridge.IMessage.Kind)
  584. * @param kind
  585. */
  586. @Override
  587. public void dontIgnore(IMessage.Kind kind) {
  588. }
  589. /**
  590. * @return System.err for FAIL, ABORT, ERROR, and WARNING, System.out for INFO if -verbose and WEAVEINFO if -showWeaveInfo.
  591. */
  592. protected PrintStream getStreamFor(IMessage.Kind kind) {
  593. if (IMessage.WARNING.isSameOrLessThan(kind)) {
  594. return System.err;
  595. } else if (verbose && IMessage.INFO.equals(kind)) {
  596. return System.out;
  597. } else if (IMessage.USAGE.equals(kind)) {
  598. return System.out;
  599. } else if (IMessage.WEAVEINFO.equals(kind)) {
  600. return System.out;
  601. } else {
  602. return null;
  603. }
  604. }
  605. /**
  606. * No-op
  607. *
  608. * @see org.aspectj.bridge.IMessageHandler#ignore(org.aspectj.bridge.IMessage.Kind)
  609. * @param kind
  610. */
  611. @Override
  612. public void ignore(Kind kind) {
  613. }
  614. }
  615. public static class LogModeMessagePrinter extends MessagePrinter {
  616. protected final PrintStream logStream;
  617. public LogModeMessagePrinter(boolean verbose, PrintStream logStream) {
  618. super(verbose);
  619. this.logStream = logStream;
  620. }
  621. @Override
  622. protected PrintStream getStreamFor(IMessage.Kind kind) {
  623. if (IMessage.WARNING.isSameOrLessThan(kind)) {
  624. return logStream;
  625. } else if (verbose && IMessage.INFO.equals(kind)) {
  626. return logStream;
  627. } else if (IMessage.WEAVEINFO.equals(kind)) {
  628. return logStream;
  629. } else {
  630. return null;
  631. }
  632. }
  633. }
  634. /** controller for repeatable command delays until input or file changed or removed */
  635. public static class CommandController {
  636. public static String TAG_FILE_OPTION = "-XincrementalFile";
  637. public static String INCREMENTAL_OPTION = "-incremental";
  638. /** maximum 10-minute delay between filesystem checks */
  639. public static long MAX_DELAY = 1000 * 600;
  640. /** default 5-second delay between filesystem checks */
  641. public static long DEFAULT_DELAY = 1000 * 5;
  642. /** @see init(String[]) */
  643. private static final String[][] OPTIONS = new String[][] {
  644. new String[] { INCREMENTAL_OPTION },
  645. new String[] { TAG_FILE_OPTION, null }
  646. };
  647. /** true between init(String[]) and doRepeatCommand() that returns false */
  648. private boolean running;
  649. /** true after quit() called */
  650. private boolean quit;
  651. /** true if incremental mode, waiting for input other than 'q' */
  652. private boolean incremental;
  653. /** true if incremental mode, waiting for file to change (repeat) or disappear (quit) */
  654. private File tagFile;
  655. /** last modification time for tagFile as of last command - 0 to start */
  656. private long fileModTime;
  657. /** delay between filesystem checks for tagFile modification time */
  658. private long delay;
  659. /** true just after user types 'r' for rebuild */
  660. private boolean buildFresh;
  661. public CommandController() {
  662. delay = DEFAULT_DELAY;
  663. }
  664. /**
  665. * @param args read and strip incremental args from this
  666. * @param sink IMessageHandler for error messages
  667. * @return String[] remainder of args
  668. */
  669. public String[] init(String[] args, IMessageHandler sink) {
  670. running = true;
  671. // String[] unused;
  672. if (!LangUtil.isEmpty(args)) {
  673. String[][] options = LangUtil.copyStrings(OPTIONS);
  674. /* unused = */LangUtil.extractOptions(args, options);
  675. incremental = (null != options[0][0]);
  676. if (null != options[1][0]) {
  677. File file = new File(options[1][1]);
  678. if (!file.exists()) {
  679. MessageUtil.abort(sink, "tag file does not exist: " + file);
  680. } else {
  681. tagFile = file;
  682. fileModTime = tagFile.lastModified();
  683. }
  684. }
  685. }
  686. return args;
  687. }
  688. /**
  689. * @return true if init(String[]) called but doRepeatCommand has not returned false
  690. */
  691. public boolean running() {
  692. return running;
  693. }
  694. /** @param delay milliseconds between filesystem checks */
  695. public void setDelay(long delay) {
  696. if ((delay > -1) && (delay < MAX_DELAY)) {
  697. this.delay = delay;
  698. }
  699. }
  700. /** @return true if INCREMENTAL_OPTION or TAG_FILE_OPTION was in args */
  701. public boolean incremental() {
  702. return (incremental || (null != tagFile));
  703. }
  704. /** @return true if INCREMENTAL_OPTION was in args */
  705. public boolean commandLineIncremental() {
  706. return incremental;
  707. }
  708. public void quit() {
  709. if (!quit) {
  710. quit = true;
  711. }
  712. }
  713. /** @return true just after user typed 'r' */
  714. boolean buildFresh() {
  715. return buildFresh;
  716. }
  717. /** @return false if we should quit, true to do another command */
  718. boolean doRepeatCommand(ICommand command) {
  719. if (!running) {
  720. return false;
  721. }
  722. boolean result = false;
  723. if (quit) {
  724. result = false;
  725. } else if (incremental) {
  726. try {
  727. if (buildFresh) { // reset before input request
  728. buildFresh = false;
  729. }
  730. System.out.println(" press enter to recompile, r to rebuild, q to quit: ");
  731. System.out.flush();
  732. // boolean doMore = false;
  733. // seek for one q or a series of [\n\r]...
  734. do {
  735. int input = System.in.read();
  736. if ('q' == input) {
  737. break; // result = false;
  738. } else if ('r' == input) {
  739. buildFresh = true;
  740. result = true;
  741. } else if (('\n' == input) || ('\r' == input)) {
  742. result = true;
  743. } // else eat anything else
  744. } while (!result);
  745. System.in.skip(Integer.MAX_VALUE);
  746. } catch (IOException e) { // XXX silence for error?
  747. result = false;
  748. }
  749. } else if (null != tagFile) {
  750. long curModTime;
  751. while (true) {
  752. if (!tagFile.exists()) {
  753. result = false;
  754. break;
  755. } else if (fileModTime == (curModTime = tagFile.lastModified())) {
  756. fileCheckDelay();
  757. } else {
  758. fileModTime = curModTime;
  759. result = true;
  760. break;
  761. }
  762. }
  763. } // else, not incremental - false
  764. if (!result && running) {
  765. running = false;
  766. }
  767. return result;
  768. }
  769. /** delay between filesystem checks, returning if quit is set */
  770. protected void fileCheckDelay() {
  771. // final Thread thread = Thread.currentThread();
  772. long targetTime = System.currentTimeMillis() + delay;
  773. // long curTime;
  774. while (targetTime > System.currentTimeMillis()) {
  775. if (quit) {
  776. return;
  777. }
  778. try {
  779. Thread.sleep(300);
  780. } // 1/3-second delta for quit check
  781. catch (InterruptedException e) {
  782. }
  783. }
  784. }
  785. }
  786. }