diff options
author | Stephan Herrmann | 2015-01-31 01:01:14 +0000 |
---|---|---|
committer | Stephan Herrmann | 2015-01-31 01:34:27 +0000 |
commit | 201cf9c2eb11ce7ae348436cd3a3b55863b9eb2f (patch) | |
tree | 5d93752b2517faad3f0cc003ed92c73d1fba785d | |
parent | 2117d348df3fc8f7646ee8f84473f25f24d25460 (diff) | |
download | eclipse.jdt.core-201cf9c2eb11ce7ae348436cd3a3b55863b9eb2f.tar.gz eclipse.jdt.core-201cf9c2eb11ce7ae348436cd3a3b55863b9eb2f.tar.xz eclipse.jdt.core-201cf9c2eb11ce7ae348436cd3a3b55863b9eb2f.zip |
Bug 440477 - [null] Infrastructure for feeding external annotations into
compilation
- further improvements in change propagation
- hook into classpath change analysis
- fine tune resource change listener
- make eea reading more robust against white space
7 files changed, 122 insertions, 96 deletions
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java index 9318036c7b..181ad0e8bb 100644 --- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java +++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java @@ -104,9 +104,11 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN if (reader != null) { if (this.annotationPath != null) { String qualifiedClassName = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length()-SuffixConstants.EXTENSION_CLASS.length()-1); - ZipFile[] zipFileInOut = new ZipFile[] { this.annotationZipFile }; - reader.setExternalAnnotationProvider(this.annotationPath, qualifiedClassName, zipFileInOut, null); - this.annotationZipFile = zipFileInOut[0]; + try { + this.annotationZipFile = reader.setExternalAnnotationProvider(this.annotationPath, qualifiedClassName, this.annotationZipFile, null); + } catch (IOException e) { + // don't let error on annotations fail class reading + } } return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName)); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java index cb4a203fc5..c58a627ebf 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.zip.ZipEntry; -import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.eclipse.jdt.core.compiler.CharOperation; @@ -406,51 +405,46 @@ public ClassFileReader(byte[] classFileBytes, char[] fileName, boolean fullyInit } } -/** Auxiliary interface for {@link #setExternalAnnotationProvider(String,String,ZipFile[],ZipFileProducer)}. */ -public interface ZipFileProducer { ZipFile produce(); } +/** Auxiliary interface for {@link #setExternalAnnotationProvider(String,String,ZipFile,ZipFileProducer)}. */ +public interface ZipFileProducer { ZipFile produce() throws IOException; } /** * Create and remember a provider for external annotations using the given basePath, * which is either a directory holding .eea text files, or a zip file of entries of the same format. * @param basePath resolved filesystem path of either directory or zip file * @param qualifiedBinaryTypeName slash-separated type name - * @param zipFileInOut Input: an existing zip file for the same basePath, or null. - * Output: will be filled with a fresh new ZipFile when appropriate, to let clients cache it, if desired. + * @param zipFile an existing zip file for the same basePath, or null. + * Output: wl be filled with * @param producer an optional helper to produce the zipFile when needed. - * @return true if external annotations could be found for the given basePath and type name. + * @return the client provided zip file; + * or else a fresh new zip file, to let clients cache it, if desired; + * or null to signal that basePath is not a zip file, but a directory. + * @throws IOException any unexpected errors during file access. File not found while + * accessing an individual file if basePath is a directory <em>is</em> expected, + * and simply answered with null. If basePath is neither a directory nor a zip file, + * this is unexpected. */ -public boolean setExternalAnnotationProvider(String basePath, String qualifiedBinaryTypeName, ZipFile[] zipFileInOut, ZipFileProducer producer) { +public ZipFile setExternalAnnotationProvider(String basePath, String qualifiedBinaryTypeName, ZipFile zipFile, ZipFileProducer producer) throws IOException { String qualifiedBinaryFileName = qualifiedBinaryTypeName + ExternalAnnotationProvider.ANNOTATION_FILE_SUFFIX; - try { - ZipFile zipFile = zipFileInOut[0]; - if (zipFile == null) { - File annotationBase = new File(basePath); - if (annotationBase.isDirectory()) { - try { - setExternalAnnotationProvider(new FileInputStream(annotationBase.getAbsolutePath()+'/'+qualifiedBinaryFileName)); - } catch (FileNotFoundException e) { - return false; // expected, no need to report an error here - } - return true; + if (zipFile == null) { + File annotationBase = new File(basePath); + if (annotationBase.isDirectory()) { + try { + String filePath = annotationBase.getAbsolutePath()+'/'+qualifiedBinaryFileName; + this.annotationProvider = new ExternalAnnotationProvider(new FileInputStream(filePath), String.valueOf(getName())); + } catch (FileNotFoundException e) { + // expected, no need to report an error here } - zipFileInOut[0] = zipFile = (producer != null ? producer.produce() : new ZipFile(annotationBase)); - if (zipFile == null) - return false; + return null; // no zipFile } - ZipEntry entry = zipFile.getEntry(qualifiedBinaryFileName); - if (entry != null) - return setExternalAnnotationProvider(zipFile.getInputStream(entry)); - } catch (ZipException e) { - // treat as missing, FIXME: error reporting - } catch (IOException e) { - // treat as missing, FIXME: error reporting + if (!annotationBase.exists()) + return null; // no zipFile, treat as not-yet-created directory + zipFile = (producer != null ? producer.produce() : new ZipFile(annotationBase)); } - return false; -} - -boolean setExternalAnnotationProvider(InputStream inputStream) throws IOException { - this.annotationProvider = new ExternalAnnotationProvider(inputStream, String.valueOf(getName())); - return true; + ZipEntry entry = zipFile.getEntry(qualifiedBinaryFileName); + if (entry != null) + this.annotationProvider = new ExternalAnnotationProvider(zipFile.getInputStream(entry), String.valueOf(getName())); + return zipFile; } /** If a provider for external annotations has been registered try to retrieve an annotation walker for type parameters of the current type. */ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java index ce03b603d4..2bd85ff75c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java @@ -78,10 +78,11 @@ public class ExternalAnnotationProvider { if (line.startsWith(TYPE_PARAMETER_PREFIX)) { this.typeParameterAnnotationSource = line.substring(TYPE_PARAMETER_PREFIX.length()); if ((line = reader.readLine()) == null) - return; + return; } } do { + line = line.trim(); if (line.isEmpty()) continue; String rawSig = null, annotSig = null; // selector: diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java index 2247da8ecf..6611749034 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java @@ -36,6 +36,7 @@ import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; +import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider; import org.eclipse.jdt.internal.compiler.env.IBinaryType; import org.eclipse.jdt.internal.compiler.env.IDependent; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; @@ -52,7 +53,6 @@ public class ClassFile extends Openable implements IClassFile, SuffixConstants { protected String name; protected BinaryType binaryType = null; - private boolean hasExternalAnnotations = false; /* * Creates a handle to a class file. */ @@ -389,30 +389,33 @@ private void setupExternalAnnotationProvider(IProject project, final IPath exter String resolvedPath = resource.exists() ? resource.getLocation().toString() // workspace lookup succeeded -> resolve it : externalAnnotationPath.toString(); // not in workspace, use as is - ZipFile[] zipFileInOut = new ZipFile[] { annotationZip }; - if (reader.setExternalAnnotationProvider(resolvedPath, typeName, zipFileInOut, new ClassFileReader.ZipFileProducer() { - @Override public ZipFile produce() { - try { - return JavaModelManager.getJavaModelManager().getZipFile(externalAnnotationPath); // use (absolute, but) unresolved path here - } catch (CoreException e) { - Util.log(e, "Failed to read annotation file for "+typeName+" from "+externalAnnotationPath.toString()); //$NON-NLS-1$ //$NON-NLS-2$ - return null; - } - }})) - { - annotationZip = zipFileInOut[0]; - this.hasExternalAnnotations = true; + try { + annotationZip = reader.setExternalAnnotationProvider(resolvedPath, typeName, annotationZip, new ClassFileReader.ZipFileProducer() { + @Override public ZipFile produce() throws IOException { + try { + return JavaModelManager.getJavaModelManager().getZipFile(externalAnnotationPath); // use (absolute, but) unresolved path here + } catch (CoreException e) { + throw new IOException("Failed to read annotation file for "+typeName+" from "+externalAnnotationPath.toString(), e); //$NON-NLS-1$ //$NON-NLS-2$ + } + }}); + } catch (IOException e) { + Util.log(e); + return; } - if (this.hasExternalAnnotations && annotationZip == null) { - // additional change listening for individual types only when annotations are in individual files: + if (annotationZip == null) { + // Additional change listening for individual types only when annotations are in individual files. + // Note that we also listen for classes that don't yet have an annotation file, to detect its creation + final IPath workspaceFilePath = externalAnnotationPath + .append(new Path(typeName)) + .addFileExtension(ExternalAnnotationProvider.ANNOTION_FILE_EXTENSION); final IWorkspace workspace = project.getWorkspace(); workspace.addResourceChangeListener(new IResourceChangeListener() { @Override public void resourceChanged(IResourceChangeEvent event) { - if (event.getDelta().findMember(externalAnnotationPath) != null) { + if (event.getDelta().findMember(workspaceFilePath) != null) { workspace.removeResourceChangeListener(this); try { - ClassFile.this.close(); + ClassFile.this.closeAndRemoveFromJarTypeCache(); } catch (JavaModelException e) { Util.log(e, "Failed to close ClassFile "+ClassFile.this.name); //$NON-NLS-1$ } @@ -422,6 +425,11 @@ private void setupExternalAnnotationProvider(IProject project, final IPath exter IResourceChangeEvent.POST_CHANGE); } } +void closeAndRemoveFromJarTypeCache() throws JavaModelException { + close(); + // triggered when external annotations have changed we need to recreate this class file + JavaModelManager.getJavaModelManager().removeFromJarTypeCache(this.binaryType); +} public IBuffer getBuffer() throws JavaModelException { IStatus status = validateClassFile(); if (status.isOK()) { @@ -901,11 +909,4 @@ protected IStatus validateExistence(IResource underlyingResource) { public ISourceRange getNameRange() { return null; } -@Override -public void close() throws JavaModelException { - super.close(); - if (this.binaryType != null && this.hasExternalAnnotations) - // triggered when external annotations have changed we need to recreate this class file - JavaModelManager.getJavaModelManager().removeFromJarTypeCache(this.binaryType); -} } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathChange.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathChange.java index 247a132bf8..7d1df251b4 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathChange.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathChange.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. + * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -7,6 +7,8 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Stephan Herrmann - Contribution for + * Bug 440477 - [null] Infrastructure for feeding external annotations into compilation *******************************************************************************/ package org.eclipse.jdt.internal.core; @@ -27,6 +29,7 @@ import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.compiler.util.ObjectVector; import org.eclipse.jdt.internal.core.DeltaProcessor.RootInfo; @@ -122,6 +125,17 @@ public class ClasspathChange { continue nextEntry; } } + if (JavaCore.ENABLED.equals(this.project.getOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, true))) { + // if null annotations are enabled, also check for changes in external annotation attachment + String annotationPath = ClasspathEntry.getRawExternalAnnotationPath(entry); + String otherAnnotationPath = ClasspathEntry.getRawExternalAnnotationPath(other); + if (annotationPath != null && otherAnnotationPath != null) { + if (!annotationPath.equals(otherAnnotationPath)) + continue; + } else if (annotationPath != otherAnnotationPath) { + continue; // null and not-null + } + } return i; } } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java index f024ca491f..b5acca9628 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java @@ -1275,43 +1275,55 @@ public class ClasspathEntry implements IClasspathEntry { * @return a path (in the workspace or filesystem-absolute) or null */ public static IPath getExternalAnnotationPath(IClasspathEntry entry, IProject project, boolean resolve) { + String rawAnnotationPath = getRawExternalAnnotationPath(entry); + if (rawAnnotationPath != null) { + IPath annotationPath = new Path(rawAnnotationPath); + if (annotationPath.isAbsolute()) { + if (!resolve) + return annotationPath; + + if (annotationPath.segmentCount() > 1) { + // try Workspace-absolute: + IProject targetProject = project.getWorkspace().getRoot().getProject(annotationPath.segment(0)); + if (targetProject.exists()) + return targetProject.getLocation().append(annotationPath.removeFirstSegments(1)); + } + // absolute, not in workspace, must be Filesystem-absolute: + return annotationPath; + } else { + // try Variable (always resolved): + IPath resolved = JavaCore.getResolvedVariablePath(annotationPath); + if (resolved != null) + return resolved; + + // Project-relative: + if (project != null) { + if (resolve) + return project.getLocation().append(annotationPath); + else + return new Path(project.getName()).append(annotationPath).makeAbsolute(); + } + } + } + return null; + } + + /** + * Answer the raw external annotation path as specified in .classpath, or null. + * @param entry where to look + * @return the attached external annotation path, or null. + */ + static String getRawExternalAnnotationPath(IClasspathEntry entry) { IClasspathAttribute[] extraAttributes = entry.getExtraAttributes(); for (int i = 0, length = extraAttributes.length; i < length; i++) { IClasspathAttribute attribute = extraAttributes[i]; if (IClasspathAttribute.EXTERNAL_ANNOTATION_PATH.equals(attribute.getName())) { - IPath annotationPath = new Path(attribute.getValue()); - - if (annotationPath.isAbsolute()) { - if (!resolve) - return annotationPath; - - if (annotationPath.segmentCount() > 1) { - // try Workspace-absolute: - IProject targetProject = project.getWorkspace().getRoot().getProject(annotationPath.segment(0)); - if (targetProject.exists()) - return targetProject.getLocation().append(annotationPath.removeFirstSegments(1)); - } - // absolute, not in workspace, must be Filesystem-absolute: - return annotationPath; - } else { - // try Variable (always resolved): - IPath resolved = JavaCore.getResolvedVariablePath(annotationPath); - if (resolved != null) - return resolved; - - // Project-relative: - if (project != null) { - if (resolve) - return project.getLocation().append(annotationPath); - else - return new Path(project.getName()).append(annotationPath).makeAbsolute(); - } - } + return attribute.getValue(); } } return null; } - + public IClasspathEntry getReferencingEntry() { return this.referencingEntry; } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java index ce869209dc..1bda5fb27a 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java @@ -169,9 +169,11 @@ public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPa if (reader != null) { String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length); if (this.externalAnnotationPath != null) { - ZipFile[] zipFileInOut = new ZipFile[] { this.annotationZipFile }; - reader.setExternalAnnotationProvider(this.externalAnnotationPath, fileNameWithoutExtension, zipFileInOut, null); - this.annotationZipFile = zipFileInOut[0]; + try { + this.annotationZipFile = reader.setExternalAnnotationProvider(this.externalAnnotationPath, fileNameWithoutExtension, this.annotationZipFile, null); + } catch (IOException e) { + // don't let error on annotations fail class reading + } } if (this.accessRuleSet == null) return new NameEnvironmentAnswer(reader, null); |