src/share/classes/com/sun/tools/javac/util/Log.java

Thu, 06 Mar 2008 10:25:04 -0800

author
jjg
date
Thu, 06 Mar 2008 10:25:04 -0800
changeset 10
508c01999047
parent 1
9a66ca7c79fa
child 50
b9bcea8bbe24
permissions
-rw-r--r--

6668802: javac handles diagnostics for last line badly, if line not terminated by newline
Summary: use CharBuffer.limit(), not the length of the backing array
Reviewed-by: mcimadamore

     1 /*
     2  * Copyright 1999-2006 Sun Microsystems, Inc.  All Rights Reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Sun designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Sun in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
    23  * have any questions.
    24  */
    26 package com.sun.tools.javac.util;
    28 import java.io.*;
    29 import java.nio.CharBuffer;
    30 import java.util.HashMap;
    31 import java.util.HashSet;
    32 import java.util.Map;
    33 import java.util.Set;
    34 import javax.tools.DiagnosticListener;
    35 import javax.tools.JavaFileObject;
    36 import com.sun.tools.javac.code.Source;
    37 import com.sun.tools.javac.tree.JCTree;
    38 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
    39 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
    40 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
    41 import static com.sun.tools.javac.util.LayoutCharacters.*;
    43 /** A class for error logs. Reports errors and warnings, and
    44  *  keeps track of error numbers and positions.
    45  *
    46  *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
    47  *  you write code that depends on this, you do so at your own risk.
    48  *  This code and its internal interfaces are subject to change or
    49  *  deletion without notice.</b>
    50  */
    51 public class Log {
    52     /** The context key for the log. */
    53     public static final Context.Key<Log> logKey
    54         = new Context.Key<Log>();
    56     /** The context key for the output PrintWriter. */
    57     public static final Context.Key<PrintWriter> outKey =
    58         new Context.Key<PrintWriter>();
    60     //@Deprecated
    61     public final PrintWriter errWriter;
    63     //@Deprecated
    64     public final PrintWriter warnWriter;
    66     //@Deprecated
    67     public final PrintWriter noticeWriter;
    69     /** The maximum number of errors/warnings that are reported.
    70      */
    71     public final int MaxErrors;
    72     public final int MaxWarnings;
    74     /** Whether or not to display the line of source containing a diagnostic.
    75      */
    76     private final boolean showSourceLine;
    78     /** Switch: prompt user on each error.
    79      */
    80     public boolean promptOnError;
    82     /** Switch: emit warning messages.
    83      */
    84     public boolean emitWarnings;
    86     /** Enforce mandatory warnings.
    87      */
    88     private boolean enforceMandatoryWarnings;
    90     /** Print stack trace on errors?
    91      */
    92     public boolean dumpOnError;
    94     /** Print multiple errors for same source locations.
    95      */
    96     public boolean multipleErrors;
    98     /**
    99      * Diagnostic listener, if provided through programmatic
   100      * interface to javac (JSR 199).
   101      */
   102     protected DiagnosticListener<? super JavaFileObject> diagListener;
   103     /**
   104      * Formatter for diagnostics
   105      */
   106     private DiagnosticFormatter diagFormatter;
   108     /**
   109      * Factory for diagnostics
   110      */
   111     private JCDiagnostic.Factory diags;
   114     /** Construct a log with given I/O redirections.
   115      */
   116     @Deprecated
   117     protected Log(Context context, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter) {
   118         context.put(logKey, this);
   119         this.errWriter = errWriter;
   120         this.warnWriter = warnWriter;
   121         this.noticeWriter = noticeWriter;
   123         this.diags = JCDiagnostic.Factory.instance(context);
   125         Options options = Options.instance(context);
   126         this.dumpOnError = options.get("-doe") != null;
   127         this.promptOnError = options.get("-prompt") != null;
   128         this.emitWarnings = options.get("-Xlint:none") == null;
   129         this.MaxErrors = getIntOption(options, "-Xmaxerrs", 100);
   130         this.MaxWarnings = getIntOption(options, "-Xmaxwarns", 100);
   131         this.showSourceLine = options.get("rawDiagnostics") == null;
   133         this.diagFormatter = DiagnosticFormatter.instance(context);
   134         @SuppressWarnings("unchecked") // FIXME
   135         DiagnosticListener<? super JavaFileObject> diagListener =
   136             context.get(DiagnosticListener.class);
   137         this.diagListener = diagListener;
   139         Source source = Source.instance(context);
   140         this.enforceMandatoryWarnings = source.enforceMandatoryWarnings();
   141     }
   142     // where
   143         private int getIntOption(Options options, String optionName, int defaultValue) {
   144             String s = options.get(optionName);
   145             try {
   146                 if (s != null) return Integer.parseInt(s);
   147             } catch (NumberFormatException e) {
   148                 // silently ignore ill-formed numbers
   149             }
   150             return defaultValue;
   151         }
   153     /** The default writer for diagnostics
   154      */
   155     static final PrintWriter defaultWriter(Context context) {
   156         PrintWriter result = context.get(outKey);
   157         if (result == null)
   158             context.put(outKey, result = new PrintWriter(System.err));
   159         return result;
   160     }
   162     /** Construct a log with default settings.
   163      */
   164     protected Log(Context context) {
   165         this(context, defaultWriter(context));
   166     }
   168     /** Construct a log with all output redirected.
   169      */
   170     protected Log(Context context, PrintWriter defaultWriter) {
   171         this(context, defaultWriter, defaultWriter, defaultWriter);
   172     }
   174     /** Get the Log instance for this context. */
   175     public static Log instance(Context context) {
   176         Log instance = context.get(logKey);
   177         if (instance == null)
   178             instance = new Log(context);
   179         return instance;
   180     }
   182     /** The file that's currently translated.
   183      */
   184     protected JCDiagnostic.DiagnosticSource source;
   186     /** The number of errors encountered so far.
   187      */
   188     public int nerrors = 0;
   190     /** The number of warnings encountered so far.
   191      */
   192     public int nwarnings = 0;
   194     /** A set of all errors generated so far. This is used to avoid printing an
   195      *  error message more than once. For each error, a pair consisting of the
   196      *  source file name and source code position of the error is added to the set.
   197      */
   198     private Set<Pair<JavaFileObject, Integer>> recorded = new HashSet<Pair<JavaFileObject,Integer>>();
   200     private Map<JavaFileObject, Map<JCTree, Integer>> endPosTables;
   202     /** The buffer containing the file that's currently translated.
   203      */
   204     private char[] buf = null;
   206     /** The length of useful data in buf
   207      */
   208     private int bufLen = 0;
   210     /** The position in the buffer at which last error was reported
   211      */
   212     private int bp;
   214     /** number of the current source line; first line is 1
   215      */
   216     private int line;
   218     /**  buffer index of the first character of the current source line
   219      */
   220     private int lineStart;
   222     public boolean hasDiagnosticListener() {
   223         return diagListener != null;
   224     }
   226     public void setEndPosTable(JavaFileObject name, Map<JCTree, Integer> table) {
   227         if (endPosTables == null)
   228             endPosTables = new HashMap<JavaFileObject, Map<JCTree, Integer>>();
   229         endPosTables.put(name, table);
   230     }
   232     /** Re-assign source, returning previous setting.
   233      */
   234     public JavaFileObject useSource(final JavaFileObject name) {
   235         JavaFileObject prev = currentSource();
   236         if (name != prev) {
   237             source = new JCDiagnostic.DiagnosticSource() {
   238                     public JavaFileObject getFile() {
   239                         return name;
   240                     }
   241                     public CharSequence getName() {
   242                         return JavacFileManager.getJavacBaseFileName(getFile());
   243                     }
   244                     public int getLineNumber(int pos) {
   245                         return Log.this.getLineNumber(pos);
   246                     }
   247                     public int getColumnNumber(int pos) {
   248                         return Log.this.getColumnNumber(pos);
   249                     }
   250                     public Map<JCTree, Integer> getEndPosTable() {
   251                         return (endPosTables == null ? null : endPosTables.get(name));
   252                     }
   253                 };
   254             buf = null;
   255         }
   256         return prev;
   257     }
   259     /** Re-assign source buffer for existing source name.
   260      */
   261     protected void setBuf(char[] newBuf) {
   262         buf = newBuf;
   263         bufLen = buf.length;
   264         bp = 0;
   265         lineStart = 0;
   266         line = 1;
   267     }
   269     protected char[] getBuf() {
   270         return buf;
   271     }
   273     /** Return current source name.
   274      */
   275     public JavaFileObject currentSource() {
   276         return source == null ? null : source.getFile();
   277     }
   279     /** Flush the logs
   280      */
   281     public void flush() {
   282         errWriter.flush();
   283         warnWriter.flush();
   284         noticeWriter.flush();
   285     }
   287     /** Returns true if an error needs to be reported for a given
   288      * source name and pos.
   289      */
   290     protected boolean shouldReport(JavaFileObject file, int pos) {
   291         if (multipleErrors || file == null)
   292             return true;
   294         Pair<JavaFileObject,Integer> coords = new Pair<JavaFileObject,Integer>(file, pos);
   295         boolean shouldReport = !recorded.contains(coords);
   296         if (shouldReport)
   297             recorded.add(coords);
   298         return shouldReport;
   299     }
   301     /** Prompt user after an error.
   302      */
   303     public void prompt() {
   304         if (promptOnError) {
   305             System.err.println(getLocalizedString("resume.abort"));
   306             char ch;
   307             try {
   308                 while (true) {
   309                     switch (System.in.read()) {
   310                     case 'a': case 'A':
   311                         System.exit(-1);
   312                         return;
   313                     case 'r': case 'R':
   314                         return;
   315                     case 'x': case 'X':
   316                         throw new AssertionError("user abort");
   317                     default:
   318                     }
   319                 }
   320             } catch (IOException e) {}
   321         }
   322     }
   324     /** Print the faulty source code line and point to the error.
   325      *  @param pos   Buffer index of the error position, must be on current line
   326      */
   327     private void printErrLine(int pos, PrintWriter writer) {
   328         if (!findLine(pos))
   329             return;
   331         int lineEnd = lineStart;
   332         while (lineEnd < bufLen && buf[lineEnd] != CR && buf[lineEnd] != LF)
   333             lineEnd++;
   334         if (lineEnd - lineStart == 0)
   335             return;
   336         printLines(writer, new String(buf, lineStart, lineEnd - lineStart));
   337         for (bp = lineStart; bp < pos; bp++) {
   338             writer.print((buf[bp] == '\t') ? "\t" : " ");
   339         }
   340         writer.println("^");
   341         writer.flush();
   342     }
   344     protected void initBuf(JavaFileObject fileObject) throws IOException {
   345         CharSequence cs = fileObject.getCharContent(true);
   346         if (cs instanceof CharBuffer) {
   347             CharBuffer cb = (CharBuffer) cs;
   348             buf = JavacFileManager.toArray(cb);
   349             bufLen = cb.limit();
   350         } else {
   351             buf = cs.toString().toCharArray();
   352             bufLen = buf.length;
   353         }
   354     }
   356     /** Find the line in the buffer that contains the current position
   357      * @param pos      Character offset into the buffer
   358      */
   359     private boolean findLine(int pos) {
   360         if (pos == Position.NOPOS || currentSource() == null)
   361             return false;
   362         try {
   363             if (buf == null) {
   364                 initBuf(currentSource());
   365                 lineStart = 0;
   366                 line = 1;
   367             } else if (lineStart > pos) { // messages don't come in order
   368                 lineStart = 0;
   369                 line = 1;
   370             }
   371             bp = lineStart;
   372             while (bp < bufLen && bp < pos) {
   373                 switch (buf[bp++]) {
   374                 case CR:
   375                     if (bp < bufLen && buf[bp] == LF) bp++;
   376                     line++;
   377                     lineStart = bp;
   378                     break;
   379                 case LF:
   380                     line++;
   381                     lineStart = bp;
   382                     break;
   383                 }
   384             }
   385             return bp <= bufLen;
   386         } catch (IOException e) {
   387             //e.printStackTrace();
   388             // FIXME: include e.getLocalizedMessage() in error message
   389             printLines(errWriter, getLocalizedString("source.unavailable"));
   390             errWriter.flush();
   391             buf = new char[0];
   392         }
   393         return false;
   394     }
   396     /** Print the text of a message, translating newlines appropriately
   397      *  for the platform.
   398      */
   399     public static void printLines(PrintWriter writer, String msg) {
   400         int nl;
   401         while ((nl = msg.indexOf('\n')) != -1) {
   402             writer.println(msg.substring(0, nl));
   403             msg = msg.substring(nl+1);
   404         }
   405         if (msg.length() != 0) writer.println(msg);
   406     }
   408     /** Report an error, unless another error was already reported at same
   409      *  source position.
   410      *  @param key    The key for the localized error message.
   411      *  @param args   Fields of the error message.
   412      */
   413     public void error(String key, Object ... args) {
   414         report(diags.error(source, null, key, args));
   415     }
   417     /** Report an error, unless another error was already reported at same
   418      *  source position.
   419      *  @param pos    The source position at which to report the error.
   420      *  @param key    The key for the localized error message.
   421      *  @param args   Fields of the error message.
   422      */
   423     public void error(DiagnosticPosition pos, String key, Object ... args) {
   424         report(diags.error(source, pos, key, args));
   425     }
   427     /** Report an error, unless another error was already reported at same
   428      *  source position.
   429      *  @param pos    The source position at which to report the error.
   430      *  @param key    The key for the localized error message.
   431      *  @param args   Fields of the error message.
   432      */
   433     public void error(int pos, String key, Object ... args) {
   434         report(diags.error(source, wrap(pos), key, args));
   435     }
   437     /** Report a warning, unless suppressed by the  -nowarn option or the
   438      *  maximum number of warnings has been reached.
   439      *  @param pos    The source position at which to report the warning.
   440      *  @param key    The key for the localized warning message.
   441      *  @param args   Fields of the warning message.
   442      */
   443     public void warning(String key, Object ... args) {
   444         report(diags.warning(source, null, key, args));
   445     }
   447     /** Report a warning, unless suppressed by the  -nowarn option or the
   448      *  maximum number of warnings has been reached.
   449      *  @param pos    The source position at which to report the warning.
   450      *  @param key    The key for the localized warning message.
   451      *  @param args   Fields of the warning message.
   452      */
   453     public void warning(DiagnosticPosition pos, String key, Object ... args) {
   454         report(diags.warning(source, pos, key, args));
   455     }
   457     /** Report a warning, unless suppressed by the  -nowarn option or the
   458      *  maximum number of warnings has been reached.
   459      *  @param pos    The source position at which to report the warning.
   460      *  @param key    The key for the localized warning message.
   461      *  @param args   Fields of the warning message.
   462      */
   463     public void warning(int pos, String key, Object ... args) {
   464         report(diags.warning(source, wrap(pos), key, args));
   465     }
   467     /** Report a warning.
   468      *  @param pos    The source position at which to report the warning.
   469      *  @param key    The key for the localized warning message.
   470      *  @param args   Fields of the warning message.
   471      */
   472     public void mandatoryWarning(DiagnosticPosition pos, String key, Object ... args) {
   473         if (enforceMandatoryWarnings)
   474             report(diags.mandatoryWarning(source, pos, key, args));
   475         else
   476             report(diags.warning(source, pos, key, args));
   477     }
   479     /** Report a warning that cannot be suppressed.
   480      *  @param pos    The source position at which to report the warning.
   481      *  @param key    The key for the localized warning message.
   482      *  @param args   Fields of the warning message.
   483      */
   484     public void strictWarning(DiagnosticPosition pos, String key, Object ... args) {
   485         writeDiagnostic(diags.warning(source, pos, key, args));
   486         nwarnings++;
   487     }
   489     /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
   490      *  @param key    The key for the localized notification message.
   491      *  @param args   Fields of the notification message.
   492      */
   493     public void note(String key, Object ... args) {
   494         report(diags.note(source, null, key, args));
   495     }
   497     /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
   498      *  @param key    The key for the localized notification message.
   499      *  @param args   Fields of the notification message.
   500      */
   501     public void note(DiagnosticPosition pos, String key, Object ... args) {
   502         report(diags.note(source, pos, key, args));
   503     }
   505     /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
   506      *  @param key    The key for the localized notification message.
   507      *  @param args   Fields of the notification message.
   508      */
   509     public void note(int pos, String key, Object ... args) {
   510         report(diags.note(source, wrap(pos), key, args));
   511     }
   513     /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
   514      *  @param key    The key for the localized notification message.
   515      *  @param args   Fields of the notification message.
   516      */
   517     public void mandatoryNote(final JavaFileObject file, String key, Object ... args) {
   518         JCDiagnostic.DiagnosticSource wrapper = null;
   519         if (file != null) {
   520             wrapper = new JCDiagnostic.DiagnosticSource() {
   521                     public JavaFileObject getFile() {
   522                         return file;
   523                     }
   524                     public CharSequence getName() {
   525                         return JavacFileManager.getJavacBaseFileName(getFile());
   526                     }
   527                     public int getLineNumber(int pos) {
   528                         return Log.this.getLineNumber(pos);
   529                     }
   530                     public int getColumnNumber(int pos) {
   531                         return Log.this.getColumnNumber(pos);
   532                     }
   533                     public Map<JCTree, Integer> getEndPosTable() {
   534                         return (endPosTables == null ? null : endPosTables.get(file));
   535                     }
   536                 };
   537         }
   538         if (enforceMandatoryWarnings)
   539             report(diags.mandatoryNote(wrapper, key, args));
   540         else
   541             report(diags.note(wrapper, null, key, args));
   542     }
   544     private DiagnosticPosition wrap(int pos) {
   545         return (pos == Position.NOPOS ? null : new SimpleDiagnosticPosition(pos));
   546     }
   548     /**
   549      * Common diagnostic handling.
   550      * The diagnostic is counted, and depending on the options and how many diagnostics have been
   551      * reported so far, the diagnostic may be handed off to writeDiagnostic.
   552      */
   553     public void report(JCDiagnostic diagnostic) {
   554         switch (diagnostic.getType()) {
   555         case FRAGMENT:
   556             throw new IllegalArgumentException();
   558         case NOTE:
   559             // Print out notes only when we are permitted to report warnings
   560             // Notes are only generated at the end of a compilation, so should be small
   561             // in number.
   562             if (emitWarnings || diagnostic.isMandatory()) {
   563                 writeDiagnostic(diagnostic);
   564             }
   565             break;
   567         case WARNING:
   568             if (emitWarnings || diagnostic.isMandatory()) {
   569                 if (nwarnings < MaxWarnings) {
   570                     writeDiagnostic(diagnostic);
   571                     nwarnings++;
   572                 }
   573             }
   574             break;
   576         case ERROR:
   577             if (nerrors < MaxErrors
   578                 && shouldReport(diagnostic.getSource(), diagnostic.getIntPosition())) {
   579                 writeDiagnostic(diagnostic);
   580                 nerrors++;
   581             }
   582             break;
   583         }
   584     }
   586     /**
   587      * Write out a diagnostic.
   588      */
   589     protected void writeDiagnostic(JCDiagnostic diag) {
   590         if (diagListener != null) {
   591             try {
   592                 diagListener.report(diag);
   593                 return;
   594             }
   595             catch (Throwable t) {
   596                 throw new ClientCodeException(t);
   597             }
   598         }
   600         PrintWriter writer = getWriterForDiagnosticType(diag.getType());
   602         printLines(writer, diagFormatter.format(diag));
   603         if (showSourceLine) {
   604             int pos = diag.getIntPosition();
   605             if (pos != Position.NOPOS) {
   606                 JavaFileObject prev = useSource(diag.getSource());
   607                 printErrLine(pos, writer);
   608                 useSource(prev);
   609             }
   610         }
   612         if (promptOnError) {
   613             switch (diag.getType()) {
   614             case ERROR:
   615             case WARNING:
   616                 prompt();
   617             }
   618         }
   620         if (dumpOnError)
   621             new RuntimeException().printStackTrace(writer);
   623         writer.flush();
   624     }
   626     @Deprecated
   627     protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) {
   628         switch (dt) {
   629         case FRAGMENT:
   630             throw new IllegalArgumentException();
   632         case NOTE:
   633             return noticeWriter;
   635         case WARNING:
   636             return warnWriter;
   638         case ERROR:
   639             return errWriter;
   641         default:
   642             throw new Error();
   643         }
   644     }
   646     /** Find a localized string in the resource bundle.
   647      *  @param key    The key for the localized string.
   648      *  @param args   Fields to substitute into the string.
   649      */
   650     public static String getLocalizedString(String key, Object ... args) {
   651         return Messages.getDefaultLocalizedString("compiler.misc." + key, args);
   652     }
   654 /***************************************************************************
   655  * raw error messages without internationalization; used for experimentation
   656  * and quick prototyping
   657  ***************************************************************************/
   659 /** print an error or warning message:
   660  */
   661     private void printRawError(int pos, String msg) {
   662         if (!findLine(pos)) {
   663             printLines(errWriter, "error: " + msg);
   664         } else {
   665             JavaFileObject file = currentSource();
   666             if (file != null)
   667                 printLines(errWriter,
   668                            JavacFileManager.getJavacFileName(file) + ":" +
   669                            line + ": " + msg);
   670             printErrLine(pos, errWriter);
   671         }
   672         errWriter.flush();
   673     }
   675 /** report an error:
   676  */
   677     public void rawError(int pos, String msg) {
   678         if (nerrors < MaxErrors && shouldReport(currentSource(), pos)) {
   679             printRawError(pos, msg);
   680             prompt();
   681             nerrors++;
   682         }
   683         errWriter.flush();
   684     }
   686 /** report a warning:
   687  */
   688     public void rawWarning(int pos, String msg) {
   689         if (nwarnings < MaxWarnings && emitWarnings) {
   690             printRawError(pos, "warning: " + msg);
   691         }
   692         prompt();
   693         nwarnings++;
   694         errWriter.flush();
   695     }
   697     /** Return the one-based line number associated with a given pos
   698      * for the current source file.  Zero is returned if no line exists
   699      * for the given position.
   700      */
   701     protected int getLineNumber(int pos) {
   702         if (findLine(pos))
   703             return line;
   704         return 0;
   705     }
   707     /** Return the one-based column number associated with a given pos
   708      * for the current source file.  Zero is returned if no column exists
   709      * for the given position.
   710      */
   711     protected int getColumnNumber(int pos) {
   712         if (findLine(pos)) {
   713             int column = 0;
   714             for (bp = lineStart; bp < pos; bp++) {
   715                 if (bp >= bufLen)
   716                     return 0;
   717                 if (buf[bp] == '\t')
   718                     column = (column / TabInc * TabInc) + TabInc;
   719                 else
   720                     column++;
   721             }
   722             return column + 1; // positions are one-based
   723         }
   724         return 0;
   725     }
   727     public static String format(String fmt, Object... args) {
   728         return String.format((java.util.Locale)null, fmt, args);
   729     }
   731 }

mercurial