From 6eb01e4dd67f6870f2430dec6614bebd351a229b Mon Sep 17 00:00:00 2001 From: Roel Spilker Date: Wed, 25 Jun 2014 00:11:34 +0200 Subject: [issue 699] [issue 682] [issue 683] lots of fixes for eclipse/ecj based issues surrounding path finding. --- .../core/configuration/FileSystemSourceCache.java | 77 +++++-------- src/core/lombok/core/debug/ProblemReporter.java | 127 +++++++++++++++++++++ .../eclipse/handlers/EclipseHandlerUtil.java | 88 ++------------ 3 files changed, 160 insertions(+), 132 deletions(-) create mode 100644 src/core/lombok/core/debug/ProblemReporter.java (limited to 'src') diff --git a/src/core/lombok/core/configuration/FileSystemSourceCache.java b/src/core/lombok/core/configuration/FileSystemSourceCache.java index 12516557..c59c397c 100644 --- a/src/core/lombok/core/configuration/FileSystemSourceCache.java +++ b/src/core/lombok/core/configuration/FileSystemSourceCache.java @@ -24,7 +24,6 @@ package lombok.core.configuration; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.InputStream; import java.net.URI; import java.util.Collections; import java.util.Iterator; @@ -35,64 +34,40 @@ import java.util.concurrent.TimeUnit; import lombok.ConfigurationKeys; import lombok.core.configuration.ConfigurationSource.Result; -import lombok.eclipse.handlers.EclipseHandlerUtil; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.ResourcesPlugin; +import lombok.core.debug.ProblemReporter; public class FileSystemSourceCache { private static String LOMBOK_CONFIG_FILENAME = "lombok.config"; private static final long RECHECK_FILESYSTEM = TimeUnit.SECONDS.toMillis(2); - private static final long MISSING = -1; + private static final long NEVER_CHECKED = -1; + private static final long MISSING = -88; // Magic value; any lombok.config with this exact epochmillis last modified will never be read, so, let's ensure nobody accidentally has one with that exact last modified stamp. private final ConcurrentMap cache = new ConcurrentHashMap(); public Iterable sourcesForJavaFile(URI javaFile, ConfigurationProblemReporter reporter) { if (javaFile == null) return Collections.emptyList(); URI uri = javaFile.normalize(); - if (!uri.isAbsolute()) { - uri = new File(".").toURI().resolve(uri); - reporter.report(javaFile.toString(), "Somehow ended up with a relative path. This is a bug that the lombok authors cannot reproduce, so please help us out! Is this path: \"" + uri.toString() + "\" the correct absolute path for resource \"" + javaFile + "\"? If yes, or no, please report back to: https://code.google.com/p/projectlombok/issues/detail?id=683 and let us know. Thanks!", 0, ""); - } + if (!uri.isAbsolute()) uri = URI.create("file:" + uri.toString()); + + File file; try { - return sourcesForDirectory(new File(uri).getParentFile(), reporter); - } catch (Exception e) { - // possibly eclipse knows how to open this thing. Let's try! - int filesOpenedWithEclipse = 0; - String specialEclipseMessage = null; - try { - IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri); - if (files == null) specialEclipseMessage = ".findFilesForLocationURI returned 'null'"; - for (IFile file : files) { - InputStream in = file.getContents(true); - if (in != null) { - filesOpenedWithEclipse++; - in.close(); - } - } - if (filesOpenedWithEclipse == 0) specialEclipseMessage = ".findFilesForLocationURI did work and returned " + files.length + " entries, but none of those resulted in readable contents."; - } catch (Throwable t) { - // That's unfortunate. - } - - StringBuilder sb = new StringBuilder(); - sb.append("Lombok is trying to find the directory on disk where source file \"").append(javaFile.toString()); - sb.append("\" is located. We're trying to turn this URL into a file: \"").append(uri.toString()); - sb.append("\" but that isn't working. Please help us out by going to "); - sb.append("https://code.google.com/p/projectlombok/issues/detail?id=683 and reporting this error. Thanks!\n\n"); - sb.append("Exception thrown: ").append(e.getClass().getName()).append("\nException msg: ").append(e.getMessage()); - if (specialEclipseMessage == null && filesOpenedWithEclipse > 0) { - sb.append("\n\n Alternate strategy to read this resource via eclipse DID WORK however!! files read: " + filesOpenedWithEclipse); - } else if (specialEclipseMessage != null) { - sb.append("\n\n Alternate strategy to read this resource via eclipse produced a noteworthy result: ").append(specialEclipseMessage).append(" files read: ").append(filesOpenedWithEclipse); - } + file = new File(uri); + if (!file.exists()) throw new IllegalArgumentException("File does not exist: " + uri); + return sourcesForDirectory(file.getParentFile(), reporter); + } catch (IllegalArgumentException e) { + // This means that the file as passed is not actually a file at all, and some exotic path system is involved. + // examples: sourcecontrol://jazz stuff, or an actual relative path (uri.isAbsolute() is completely different, that checks presence of schema!), + // or it's eclipse trying to parse a snippet, which has "/Foo.java" as uri. + // At some point it might be worth investigating abstracting away the notion of "I can read lombok.config if present in + // current context, and I can give you may parent context", using ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(javaFile) as basis. - reporter.report(javaFile.toString(), sb.toString(), 0, ""); - try { - EclipseHandlerUtil.warning(sb.toString(), null); - } catch (Throwable ignore) {} - return Collections.emptyList(); + // For now, we just carry on as if there is no lombok.config. (intentional fallthrough) + } catch (Exception e) { + // Especially for eclipse's sake, exceptions here make eclipse borderline unusable, so let's play nice. + ProblemReporter.error("Can't find absolute path of file being compiled: " + javaFile, e); } + + return Collections.emptyList(); } public Iterable sourcesForDirectory(URI directory, ConfigurationProblemReporter reporter) { @@ -147,18 +122,18 @@ public class FileSystemSourceCache { } ConfigurationSource getSourceForDirectory(File directory, ConfigurationProblemReporter reporter) { - if (!directory.exists() && !directory.isDirectory()) throw new IllegalArgumentException("Not a directory: " + directory); + if (!directory.exists() || !directory.isDirectory()) throw new IllegalArgumentException("Not a directory: " + directory); long now = System.currentTimeMillis(); File configFile = new File(directory, LOMBOK_CONFIG_FILENAME); Content content = ensureContent(directory); synchronized (content) { - if (content.lastChecked != MISSING && now - content.lastChecked < RECHECK_FILESYSTEM && getLastModified(configFile) == content.lastModified) { + if (content.lastChecked != NEVER_CHECKED && now - content.lastChecked < RECHECK_FILESYSTEM && getLastModifiedOrMissing(configFile) == content.lastModified) { return content.source; } content.lastChecked = now; long previouslyModified = content.lastModified; - content.lastModified = getLastModified(configFile); + content.lastModified = getLastModifiedOrMissing(configFile); if (content.lastModified != previouslyModified) content.source = content.lastModified == MISSING ? null : parse(configFile, reporter); return content.source; } @@ -205,7 +180,7 @@ public class FileSystemSourceCache { } } - private static final long getLastModified(File file) { + private static final long getLastModifiedOrMissing(File file) { if (!file.exists() || !file.isFile()) return MISSING; return file.lastModified(); } @@ -222,7 +197,7 @@ public class FileSystemSourceCache { } static Content empty() { - return new Content(null, MISSING, MISSING); + return new Content(null, MISSING, NEVER_CHECKED); } } } \ No newline at end of file diff --git a/src/core/lombok/core/debug/ProblemReporter.java b/src/core/lombok/core/debug/ProblemReporter.java new file mode 100644 index 00000000..489bdfea --- /dev/null +++ b/src/core/lombok/core/debug/ProblemReporter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.debug; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.Bundle; + +public class ProblemReporter { + public static void warning(String msg, Throwable ex) { + init(); + try { + logger.warning(msg, ex); + } catch (Throwable t) { + logger = new TerminalLogger(); + logger.warning(msg, ex); + } + } + + public static void error(String msg, Throwable ex) { + init(); + try { + logger.error(msg, ex); + } catch (Throwable t) { + logger = new TerminalLogger(); + logger.error(msg, ex); + } + } + + private static void init() { + if (logger != null) return; + try { + logger = new EclipseWorkspaceLogger(); + } catch (Throwable t) { + logger = new TerminalLogger(); + } + } + + private static ErrorLogger logger; + + private interface ErrorLogger { + void error(String message, Throwable ex); + void warning(String message, Throwable ex); + } + + private static class TerminalLogger implements ErrorLogger { + @Override + public void error(String message, Throwable ex) { + System.err.println(message); + if (ex != null) ex.printStackTrace(); + } + + @Override + public void warning(String message, Throwable ex) { + System.err.println(message); + if (ex != null) ex.printStackTrace(); + } + } + + private static class EclipseWorkspaceLogger implements ErrorLogger { + private static final String DEFAULT_BUNDLE_NAME = "org.eclipse.jdt.core"; + private static final Bundle bundle; + private static final int MAX_LOG = 200; + private static final long SQUELCH_TIMEOUT = TimeUnit.HOURS.toMillis(1); + private static final AtomicInteger counter = new AtomicInteger(); + private static volatile long squelchTimeout = 0L; + + + static { + bundle = Platform.getBundle(DEFAULT_BUNDLE_NAME); + if (bundle == null) throw new NoClassDefFoundError(); // this means some weird RCP build or possible ecj. At any rate, we can't report this way so act as if this isn't an eclipse. + } + + @Override + public void error(String message, Throwable error) { + msg(IStatus.ERROR, message, error); + } + + @Override + public void warning(String message, Throwable error) { + msg(IStatus.WARNING, message, error); + } + + private void msg(int msgType, String message, Throwable error) { + int ct = squelchTimeout != 0L ? 0 : counter.incrementAndGet(); + boolean printSquelchWarning = false; + if (squelchTimeout != 0L) { + long now = System.currentTimeMillis(); + if (squelchTimeout > now) return; + squelchTimeout = now + SQUELCH_TIMEOUT; + printSquelchWarning = true; + } else if (ct >= MAX_LOG) { + squelchTimeout = System.currentTimeMillis() + SQUELCH_TIMEOUT; + printSquelchWarning = true; + } + ILog log = Platform.getLog(bundle); + log.log(new Status(msgType, DEFAULT_BUNDLE_NAME, message, error)); + if (printSquelchWarning) { + log.log(new Status(IStatus.WARNING, DEFAULT_BUNDLE_NAME, "Lombok has logged too many messages; to avoid memory issues, further lombok logs will be squelched for a while. Restart eclipse to start over.")); + } + } + } +} diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 546beb9f..8326e1d0 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -46,16 +46,13 @@ import lombok.core.AnnotationValues; import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.TypeResolver; import lombok.core.configuration.NullCheckExceptionType; +import lombok.core.debug.ProblemReporter; import lombok.core.handlers.HandlerUtil; import lombok.eclipse.EclipseAST; import lombok.eclipse.EclipseNode; import lombok.experimental.Accessors; import lombok.experimental.Tolerate; -import org.eclipse.core.runtime.ILog; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Platform; -import org.eclipse.core.runtime.Status; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; @@ -106,7 +103,6 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; -import org.osgi.framework.Bundle; /** * Container for static utility methods useful to handlers written for eclipse. @@ -116,36 +112,16 @@ public class EclipseHandlerUtil { //Prevent instantiation } - private static final String DEFAULT_BUNDLE = "org.eclipse.jdt.core"; - - /** - * Generates an error in the Eclipse error log. Note that most people never look at it! - * - * @param cud The {@code CompilationUnitDeclaration} where the error occurred. - * An error will be generated on line 0 linking to the error log entry. Can be {@code null}. - * @param message Human readable description of the problem. - * @param error The associated exception. Can be {@code null}. - */ - public static void error(CompilationUnitDeclaration cud, String message, Throwable error) { - error(cud, message, null, error); - } - /** * Generates an error in the Eclipse error log. Note that most people never look at it! * * @param cud The {@code CompilationUnitDeclaration} where the error occurred. * An error will be generated on line 0 linking to the error log entry. Can be {@code null}. * @param message Human readable description of the problem. - * @param bundleName Can be {@code null} to default to {@code org.eclipse.jdt.core} which is usually right. - * @param error The associated exception. Can be {@code null}. + * @param ex The associated exception. Can be {@code null}. */ - public static void error(CompilationUnitDeclaration cud, String message, String bundleName, Throwable error) { - if (bundleName == null) bundleName = DEFAULT_BUNDLE; - try { - new EclipseWorkspaceLogger().error(message, bundleName, error); - } catch (NoClassDefFoundError e) { //standalone ecj does not java Platform, ILog, IStatus, and friends. - new TerminalLogger().error(message, bundleName, error); - } + public static void error(CompilationUnitDeclaration cud, String message, Throwable ex) { + ProblemReporter.error(message, ex); if (cud != null) EclipseAST.addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, false, message + " - See error log.", 0, 0); } @@ -153,60 +129,10 @@ public class EclipseHandlerUtil { * Generates a warning in the Eclipse error log. Note that most people never look at it! * * @param message Human readable description of the problem. - * @param error The associated exception. Can be {@code null}. - */ - public static void warning(String message, Throwable error) { - warning(message, null, error); - } - - /** - * Generates a warning in the Eclipse error log. Note that most people never look at it! - * - * @param message Human readable description of the problem. - * @param bundleName Can be {@code null} to default to {@code org.eclipse.jdt.core} which is usually right. - * @param error The associated exception. Can be {@code null}. + * @param ex The associated exception. Can be {@code null}. */ - public static void warning(String message, String bundleName, Throwable error) { - if (bundleName == null) bundleName = DEFAULT_BUNDLE; - try { - new EclipseWorkspaceLogger().warning(message, bundleName, error); - } catch (NoClassDefFoundError e) { //standalone ecj does not jave Platform, ILog, IStatus, and friends. - new TerminalLogger().warning(message, bundleName, error); - } - } - - private static class TerminalLogger { - void error(String message, String bundleName, Throwable error) { - System.err.println(message); - if (error != null) error.printStackTrace(); - } - - void warning(String message, String bundleName, Throwable error) { - System.err.println(message); - if (error != null) error.printStackTrace(); - } - } - - private static class EclipseWorkspaceLogger { - void error(String message, String bundleName, Throwable error) { - msg(IStatus.ERROR, message, bundleName, error); - } - - void warning(String message, String bundleName, Throwable error) { - msg(IStatus.WARNING, message, bundleName, error); - } - - private void msg(int msgType, String message, String bundleName, Throwable error) { - Bundle bundle = Platform.getBundle(bundleName); - if (bundle == null) { - System.err.printf("Can't find bundle %s while trying to report error:\n%s\n%s\n", bundleName, message, error); - return; - } - - ILog log = Platform.getLog(bundle); - - log.log(new Status(msgType, bundleName, message, error)); - } + public static void warning(String message, Throwable ex) { + ProblemReporter.warning(message, ex); } public static ASTNode getGeneratedBy(ASTNode node) { -- cgit