| /******************************************************************************* |
| * Copyright (c) 2016 Google, Inc. 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 |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Stefan Xenos <sxenos@gmail.com> (Google) - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.classfmt; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryField; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryMethod; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryType; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation; |
| import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker; |
| import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; |
| import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; |
| |
| /** |
| * A decorator for {@link IBinaryType} that allows external annotations to be attached. This can be used to change the |
| * result of {@link #enrichWithExternalAnnotationsFor} or {@link #getExternalAnnotationStatus}. |
| */ |
| public class ExternalAnnotationDecorator implements IBinaryType { |
| private IBinaryType inputType; |
| private ExternalAnnotationProvider annotationProvider; |
| private boolean isFromSource; |
| |
| /** Auxiliary interface for {@link #getAnnotationZipFile(String, ZipFileProducer)}. */ |
| public interface ZipFileProducer { ZipFile produce() throws IOException; } |
| |
| public ExternalAnnotationDecorator(IBinaryType toDecorate, ExternalAnnotationProvider externalAnnotationProvider) { |
| if (toDecorate == null) { |
| throw new NullPointerException("toDecorate"); //$NON-NLS-1$ |
| } |
| this.inputType = toDecorate; |
| this.annotationProvider = externalAnnotationProvider; |
| } |
| |
| public ExternalAnnotationDecorator(IBinaryType toDecorate, boolean isFromSource) { |
| if (toDecorate == null) { |
| throw new NullPointerException("toDecorate"); //$NON-NLS-1$ |
| } |
| this.isFromSource = isFromSource; |
| this.inputType = toDecorate; |
| } |
| |
| @Override |
| public char[] getFileName() { |
| return this.inputType.getFileName(); |
| } |
| |
| @Override |
| public boolean isBinaryType() { |
| return this.inputType.isBinaryType(); |
| } |
| |
| @Override |
| public IBinaryAnnotation[] getAnnotations() { |
| return this.inputType.getAnnotations(); |
| } |
| |
| @Override |
| public IBinaryTypeAnnotation[] getTypeAnnotations() { |
| return this.inputType.getTypeAnnotations(); |
| } |
| |
| @Override |
| public char[] getEnclosingMethod() { |
| return this.inputType.getEnclosingMethod(); |
| } |
| |
| @Override |
| public char[] getEnclosingTypeName() { |
| return this.inputType.getEnclosingTypeName(); |
| } |
| |
| @Override |
| public IBinaryField[] getFields() { |
| return this.inputType.getFields(); |
| } |
| |
| @Override |
| public char[] getGenericSignature() { |
| return this.inputType.getGenericSignature(); |
| } |
| |
| @Override |
| public char[][] getInterfaceNames() { |
| return this.inputType.getInterfaceNames(); |
| } |
| |
| @Override |
| public IBinaryNestedType[] getMemberTypes() { |
| return this.inputType.getMemberTypes(); |
| } |
| |
| @Override |
| public IBinaryMethod[] getMethods() { |
| return this.inputType.getMethods(); |
| } |
| |
| @Override |
| public char[][][] getMissingTypeNames() { |
| return this.inputType.getMissingTypeNames(); |
| } |
| |
| @Override |
| public char[] getName() { |
| return this.inputType.getName(); |
| } |
| |
| @Override |
| public char[] getSourceName() { |
| return this.inputType.getSourceName(); |
| } |
| |
| @Override |
| public char[] getSuperclassName() { |
| return this.inputType.getSuperclassName(); |
| } |
| |
| @Override |
| public long getTagBits() { |
| return this.inputType.getTagBits(); |
| } |
| |
| @Override |
| public boolean isAnonymous() { |
| return this.inputType.isAnonymous(); |
| } |
| |
| @Override |
| public boolean isLocal() { |
| return this.inputType.isLocal(); |
| } |
| |
| @Override |
| public boolean isMember() { |
| return this.inputType.isMember(); |
| } |
| |
| @Override |
| public char[] sourceFileName() { |
| return this.inputType.sourceFileName(); |
| } |
| |
| @Override |
| public int getModifiers() { |
| return this.inputType.getModifiers(); |
| } |
| |
| /** |
| * Returns the zip file containing external annotations, if any. Returns null if there are no external annotations |
| * or if the basePath refers to a directory. |
| * |
| * @param basePath |
| * resolved filesystem path of either directory or zip file |
| * @param producer |
| * an optional helper to produce the zipFile when needed. |
| * @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 static ZipFile getAnnotationZipFile(String basePath, ZipFileProducer producer) throws IOException { |
| File annotationBase = new File(basePath); |
| if (!annotationBase.isFile()) { |
| return null; |
| } |
| return (producer != null ? producer.produce() : new ZipFile(annotationBase)); |
| } |
| |
| /** |
| * Creates an external annotation 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 zipFile |
| * an existing zip file for the same basePath, or null. |
| * @return the annotation provider or null if there are no external annotations. |
| * @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 static ExternalAnnotationProvider externalAnnotationProvider(String basePath, String qualifiedBinaryTypeName, |
| ZipFile zipFile) throws IOException { |
| String qualifiedBinaryFileName = qualifiedBinaryTypeName + ExternalAnnotationProvider.ANNOTATION_FILE_SUFFIX; |
| if (zipFile == null) { |
| File annotationBase = new File(basePath); |
| if (annotationBase.isDirectory()) { |
| String filePath = annotationBase.getAbsolutePath() + '/' + qualifiedBinaryFileName; |
| try { |
| return new ExternalAnnotationProvider(new FileInputStream(filePath), qualifiedBinaryTypeName); |
| } catch (FileNotFoundException e) { |
| // Expected, no need to report an error here |
| return null; |
| } |
| } |
| } else { |
| ZipEntry entry = zipFile.getEntry(qualifiedBinaryFileName); |
| if (entry != null) { |
| return new ExternalAnnotationProvider(zipFile.getInputStream(entry), qualifiedBinaryTypeName); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Possibly wrap the provided binary type in a ClassWithExternalAnnotations to which a fresh provider for external |
| * annotations is associated. This provider is constructed using the given basePath, which is either a directory |
| * holding .eea text files, or a zip file of entries of the same format. If no such provider could be constructed, |
| * then the original binary type is returned unchanged. |
| * |
| * @param toDecorate |
| * the binary type to wrap, if needed |
| * @param basePath |
| * resolved filesystem path of either directory or zip file |
| * @param qualifiedBinaryTypeName |
| * slash-separated type name |
| * @param zipFile |
| * an existing zip file for the same basePath, or null. |
| * @return either a fresh ClassWithExternalAnnotations or the original binary type unchanged. |
| * @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 handled by not setting up an external |
| * annotation provider. If basePath is neither a directory nor a zip file, this is unexpected, resulting |
| * in an exception. |
| */ |
| public static IBinaryType create(IBinaryType toDecorate, String basePath, |
| String qualifiedBinaryTypeName, ZipFile zipFile) throws IOException { |
| ExternalAnnotationProvider externalAnnotationProvider = externalAnnotationProvider(basePath, qualifiedBinaryTypeName, zipFile); |
| if (externalAnnotationProvider == null) |
| return toDecorate; |
| return new ExternalAnnotationDecorator(toDecorate, externalAnnotationProvider); |
| } |
| |
| @Override |
| public ITypeAnnotationWalker enrichWithExternalAnnotationsFor(ITypeAnnotationWalker walker, Object member, |
| LookupEnvironment environment) { |
| if (walker == ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER && this.annotationProvider != null) { |
| if (member == null) { |
| return this.annotationProvider.forTypeHeader(environment); |
| } else if (member instanceof IBinaryField) { |
| IBinaryField field = (IBinaryField) member; |
| char[] fieldSignature = field.getGenericSignature(); |
| if (fieldSignature == null) |
| fieldSignature = field.getTypeName(); |
| return this.annotationProvider.forField(field.getName(), fieldSignature, environment); |
| } else if (member instanceof IBinaryMethod) { |
| IBinaryMethod method = (IBinaryMethod) member; |
| char[] methodSignature = method.getGenericSignature(); |
| if (methodSignature == null) |
| methodSignature = method.getMethodDescriptor(); |
| return this.annotationProvider.forMethod( |
| method.isConstructor() ? TypeConstants.INIT : method.getSelector(), methodSignature, |
| environment); |
| } |
| } |
| return walker; |
| } |
| |
| @Override |
| public ExternalAnnotationStatus getExternalAnnotationStatus() { |
| if (this.annotationProvider == null) { |
| if (this.isFromSource) { |
| return ExternalAnnotationStatus.FROM_SOURCE; |
| } |
| return ExternalAnnotationStatus.NO_EEA_FILE; |
| } |
| return ExternalAnnotationStatus.TYPE_IS_ANNOTATED; |
| } |
| } |