blob: 6d15fb2556fccaa65edf47e33e32b32e007957c4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2016 GK Software AG.
* 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:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.core.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
import org.eclipse.jdt.internal.compiler.lookup.SignatureWrapper;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.eclipse.jdt.internal.core.util.KeyToSignature;
/**
* Utilities for accessing and manipulating text files that externally define annotations for a given Java type.
* Files are assumed to be in ".eea format", a textual representation of annotated signatures of members of a given type.
*
* @since 3.11
* @noinstantiate This class is not intended to be instantiated by clients.
*/
public final class ExternalAnnotationUtil {
/** Representation of a 'nullable' annotation, independent of the concrete annotation name used in Java sources. */
public static final char NULLABLE = ExternalAnnotationProvider.NULLABLE;
/** Representation of a 'nonnull' annotation, independent of the concrete annotation name used in Java sources. */
public static final char NONNULL = ExternalAnnotationProvider.NONNULL;
/**
* Represents absence of a null annotation. Useful for removing an existing null annotation.
* This character is used only internally, it is not part of the Eclipse External Annotation file format.
*/
public static final char NO_ANNOTATION = ExternalAnnotationProvider.NO_ANNOTATION;
/** Strategy for merging a new signature with an existing (possibly annotated) signature. */
public static enum MergeStrategy {
/** Unconditionally replace the signature. */
REPLACE_SIGNATURE,
/** Override existing annotations, keeping old annotations in locations that are not annotated in the new signature. */
OVERWRITE_ANNOTATIONS,
/** Only add new annotations, never remove or overwrite existing annotations. */
ADD_ANNOTATIONS
}
private static final int POSITION_RETURN_TYPE = -1;
private static final int POSITION_FULL_SIGNATURE = -2;
/**
* Answer the give method's signature in class file format.
* @param methodBinding binding representing a method
* @return a signature in class file format
*/
public static String extractGenericSignature(IMethodBinding methodBinding) {
// Note that IMethodBinding.binding is not accessible, hence we need to recover the signature from the key:
KeyToSignature parser = new KeyToSignature(methodBinding.getKey(), KeyToSignature.SIGNATURE, true);
parser.parse();
return parser.toString();
}
/**
* Answer the given types's signature in class file format.
* @param type binding representing a type
* @return a signature in class file format
*/
public static String extractGenericTypeSignature(ITypeBinding type) {
KeyToSignature parser = new KeyToSignature(type.getKey(), KeyToSignature.SIGNATURE, true);
parser.parse();
return parser.toString();
}
/**
* Insert an encoded annotation into the given methodSignature affecting its return type.
* <p>
* This method is suitable for declaration annotations.
* </p>
* @param methodSignature a method signature in class file format
* @param annotation one of {@link #NULLABLE} and {@link #NONNULL}.
* @param mergeStrategy when passing {@link MergeStrategy#ADD_ANNOTATIONS} this method will
* refuse to overwrite any existing annotation in the specified location
* @return the modified method signature, or the original signature if modification would
* conflict with the given merge strategy.
* @throws IllegalArgumentException if the method signature is malformed or its return type is not a reference type.
*/
public static String insertReturnAnnotation(String methodSignature, char annotation, MergeStrategy mergeStrategy) {
int close = methodSignature.indexOf(')');
if (close == -1 || close > methodSignature.length()-4)
throw new IllegalArgumentException("Malformed method signature"); //$NON-NLS-1$
switch (methodSignature.charAt(close+1)) {
case 'L': case 'T': case '[':
return insertAt(methodSignature, close+2, annotation, mergeStrategy);
}
throw new IllegalArgumentException("Return type is not a reference type"); //$NON-NLS-1$
}
/**
* Insert an encoded annotation into the given methodSignature affecting one of its parameters.
* <p>
* This method is suitable for declaration annotations.
* </p>
* @param methodSignature a method signature in class file format
* @param paramIdx 0-based index of the parameter to which the annotation should be attached
* @param annotation one of {@link #NULLABLE} and {@link #NONNULL}.
* @param mergeStrategy when passing {@link MergeStrategy#ADD_ANNOTATIONS} this method will
* refuse to overwrite any existing annotation in the specified location
* @return the modified method signature, or the original signature if modification would
* conflict with the given merge strategy.
* @throws IllegalArgumentException if the method signature is malformed or its specified parameter type is not a reference type.
*/
public static String insertParameterAnnotation(String methodSignature, int paramIdx, char annotation, MergeStrategy mergeStrategy)
{
SignatureWrapper wrapper = new SignatureWrapper(methodSignature.toCharArray());
wrapper.start = 1;
for (int i = 0; i < paramIdx; i++)
wrapper.start = wrapper.computeEnd() + 1;
int start = wrapper.start;
switch (methodSignature.charAt(start)) {
case 'L': case 'T': case '[':
return insertAt(methodSignature, start+1, annotation, mergeStrategy);
}
throw new IllegalArgumentException("Paramter type is not a reference type"); //$NON-NLS-1$
}
/**
* Answer the external annotation file corresponding to the given type as seen from the given project.
* Note that manipulation of external annotations is only supported for annotation files in the workspace,
* and only in directory layout, not from zip files.
* @param project current project that references the given type from a jar file.
* @param type the type for which external annotations are sought
* @param monitor progress monitor to be passed through into file operations
* @return a file assumed (but not checked) to be in .eea format. The file may not "exist".
* Can be null if the given type is not contained in a jar file for which an external annotation path
* has been defined in the context of the given project.
* @throws CoreException Signals a problem in accessing any of the relevant elements: the project, the type,
* the containing jar file and finally the sought annotation file.
*/
public static IFile getAnnotationFile(IJavaProject project, ITypeBinding type, IProgressMonitor monitor) throws CoreException {
IType targetType = project.findType(type.getErasure().getQualifiedName());
if (!targetType.exists())
return null;
String binaryTypeName = targetType.getFullyQualifiedName('$').replace('.', '/');
IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) targetType.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
IClasspathEntry entry = packageRoot.getResolvedClasspathEntry();
IPath annotationPath = ClasspathEntry.getExternalAnnotationPath(entry, project.getProject(), false);
if (annotationPath == null)
return null;
IWorkspaceRoot workspaceRoot = project.getProject().getWorkspace().getRoot();
if (annotationPath.segmentCount() > 1) {
IFile annotationZip = workspaceRoot.getFile(annotationPath);
if (annotationZip.exists())
return null;
}
annotationPath = annotationPath.append(binaryTypeName).addFileExtension(ExternalAnnotationProvider.ANNOTATION_FILE_EXTENSION);
return workspaceRoot.getFile(annotationPath);
}
/**
* Update the given external annotation file with details regarding annotations of one specific method or field.
* If the specified member already has external annotations, old and new annotations will be merged,
* with priorities controlled by the parameter 'mergeStrategy'.
* <p>
* This method is suitable for declaration annotations and type use annotations.
* </p>
* @param typeName binary name (slash separated) of the type being annotated
* @param file a file assumed to be in .eea format, will be created if it doesn't exist.
* @param selector selector of the method or field
* @param originalSignature unannotated signature of the member, used for identification
* @param annotatedSignature new signatures whose annotations should be superimposed on the member
* @param mergeStrategy controls how old and new signatures should be merged
* @param monitor progress monitor to be passed through into file operations, or null if no reporting is desired
* @throws CoreException if access to the file fails
* @throws IOException if reading file content fails
*/
public static void annotateMember(String typeName, IFile file, String selector, String originalSignature, String annotatedSignature,
MergeStrategy mergeStrategy, IProgressMonitor monitor)
throws CoreException, IOException
{
annotateMember(typeName, file, selector, originalSignature, annotatedSignature, POSITION_FULL_SIGNATURE, mergeStrategy, monitor);
}
/**
* Update the given external annotation file with details regarding annotations of the return type of a given method.
* If the specified method already has external annotations, old and new annotations will be merged,
* with priorities controlled by the parameter 'mergeStrategy'.
* <p>
* This method is suitable for declaration annotations and type use annotations.
* </p>
* @param typeName binary name (slash separated) of the type being annotated
* @param file a file assumed to be in .eea format, will be created if it doesn't exist.
* @param selector selector of the method
* @param originalSignature unannotated signature of the member, used for identification
* @param annotatedReturnType signature of the new return type whose annotations should be superimposed on the method
* @param mergeStrategy controls how old and new signatures should be merged
* @param monitor progress monitor to be passed through into file operations, or null if no reporting is desired
* @throws CoreException if access to the file fails
* @throws IOException if reading file content fails
* @throws IllegalArgumentException if the annotatedReturnType does not structurally match to originalSignature
*/
public static void annotateMethodReturnType(String typeName, IFile file, String selector, String originalSignature,
String annotatedReturnType, MergeStrategy mergeStrategy, IProgressMonitor monitor)
throws CoreException, IOException, IllegalArgumentException
{
annotateMember(typeName, file, selector, originalSignature, annotatedReturnType, POSITION_RETURN_TYPE, mergeStrategy, monitor);
}
/**
* Update the given external annotation file with details regarding annotations of a parameter type of a given method.
* If the specified method already has external annotations, old and new annotations will be merged,
* with priorities controlled by the parameter 'mergeStrategy'.
* <p>
* This method is suitable for declaration annotations and type use annotations.
* </p>
* @param typeName binary name (slash separated) of the type being annotated
* @param file a file assumed to be in .eea format, will be created if it doesn't exist.
* @param selector selector of the method
* @param originalSignature unannotated signature of the member, used for identification
* @param annotatedParameterType signature of the new parameter type whose annotations should be superimposed on the method
* @param paramIdx 0-based index of the parameter to which the annotation should be attached
* @param mergeStrategy controls how old and new signatures should be merged
* @param monitor progress monitor to be passed through into file operations, or null if no reporting is desired
* @throws CoreException if access to the file fails
* @throws IOException if reading file content fails
* @throws IllegalArgumentException if the annotatedParameterType does not structurally match to originalSignature
*/
public static void annotateMethodParameterType(String typeName, IFile file, String selector, String originalSignature,
String annotatedParameterType, int paramIdx, MergeStrategy mergeStrategy, IProgressMonitor monitor)
throws CoreException, IOException, IllegalArgumentException
{
annotateMember(typeName, file, selector, originalSignature, annotatedParameterType, paramIdx, mergeStrategy, monitor);
}
private static void annotateMember(String typeName, IFile file, String selector, String originalSignature, String annotatedSignature,
int updatePosition, MergeStrategy mergeStrategy, IProgressMonitor monitor)
throws CoreException, IOException, IllegalArgumentException
{
if (!file.exists()) {
// assemble full annotatedSignature (don't bother merging since no previous signature exists):
annotatedSignature = updateSignature(originalSignature, annotatedSignature, updatePosition, MergeStrategy.REPLACE_SIGNATURE);
StringBuffer newContent= new StringBuffer();
// header:
newContent.append(ExternalAnnotationProvider.CLASS_PREFIX);
newContent.append(typeName).append('\n');
// new entry:
newContent.append(selector).append('\n');
newContent.append(' ').append(originalSignature).append('\n');
newContent.append(' ').append(annotatedSignature).append('\n');
createNewFile(file, newContent.toString(), monitor);
} else {
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getContents()));
StringBuffer newContent = new StringBuffer();
try {
newContent.append(reader.readLine()).append('\n'); // skip class name
String line;
while ((line = reader.readLine()) != null) {
if (line.isEmpty()) {
newContent.append('\n');
continue;
}
if (!Character.isJavaIdentifierStart(line.charAt(0))) {
newContent.append(line).append('\n');
continue;
}
// compare selectors:
int relation = line.compareTo(selector);
if (relation > 0) { // past the insertion point
break;
}
if (relation < 0) {
newContent.append(line).append('\n');
continue;
}
if (relation == 0) {
StringBuffer pending = new StringBuffer(line).append('\n');
pending.append(line = reader.readLine());
if (line == null) {
break; // found only the selector at EOF, append right here, ignoring 'pending'
}
// compare original signatures:
relation = line.trim().compareTo(originalSignature);
if (relation > 0) { // past the insertion point
// add new entry (below)
line = pending.toString(); // push back
break;
}
newContent.append(pending).append('\n');
if (relation < 0)
continue;
if (relation == 0) {
// update existing entry:
String annotationLine = reader.readLine();
String nextLine = null;
if (annotationLine == null || annotationLine.isEmpty() || !annotationLine.startsWith(" ")) { //$NON-NLS-1$
nextLine = annotationLine; // push back, since not a signature line
annotationLine = line; // no annotated line yet, use unannotated line instead
}
if (annotationLine.startsWith(" ")) { //$NON-NLS-1$
switch (mergeStrategy) {
case REPLACE_SIGNATURE:
break; // unconditionally use annotatedSignature
case OVERWRITE_ANNOTATIONS:
case ADD_ANNOTATIONS:
annotatedSignature = updateSignature(annotationLine.trim(), annotatedSignature, updatePosition, mergeStrategy);
break;
default:
JavaCore.getJavaCore().getLog().log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID,
"Unexpected value for enum MergeStrategy")); //$NON-NLS-1$
}
}
writeFile(file, newContent, annotatedSignature, nextLine, reader, monitor);
return;
}
}
}
// add new entry:
newContent.append(selector).append('\n');
newContent.append(' ').append(originalSignature).append('\n');
annotatedSignature = updateSignature(originalSignature, annotatedSignature, updatePosition, mergeStrategy);
writeFile(file, newContent, annotatedSignature, line, reader, monitor);
} finally {
reader.close();
}
}
}
private static String updateSignature(String originalSignature, String annotatedSignature, int updatePosition, MergeStrategy mergeStrategy) {
StringBuffer buf = new StringBuffer();
String signatureToReplace;
String postfix = null;
switch (updatePosition) {
case POSITION_FULL_SIGNATURE:
signatureToReplace = originalSignature;
break;
case POSITION_RETURN_TYPE:
assert originalSignature.charAt(0) == '(' || originalSignature.charAt(0) == '<': "signature must start with '(' or '<'"; //$NON-NLS-1$
int close = originalSignature.indexOf(')');
buf.append(originalSignature, 0, close+1);
signatureToReplace = originalSignature.substring(close+1);
break;
default: // parameter
SignatureWrapper wrapper = new SignatureWrapper(originalSignature.toCharArray(), true, true); // may already contain annotations
wrapper.start = CharOperation.indexOf('(', wrapper.signature) + 1; // possibly skipping type parameters
for (int i = 0; i < updatePosition; i++)
wrapper.start = wrapper.skipAngleContents(wrapper.computeEnd()) + 1;
int start = wrapper.start;
int end = wrapper.skipAngleContents(wrapper.computeEnd());
buf.append(originalSignature, 0, start);
signatureToReplace = originalSignature.substring(start, end+1);
postfix = originalSignature.substring(end+1, originalSignature.length());
}
updateType(buf, signatureToReplace.toCharArray(), annotatedSignature.toCharArray(), mergeStrategy);
if (postfix != null)
buf.append(postfix);
return buf.toString();
}
/**
* Insert that given annotation at the given position into the given signature.
* @param mergeStrategy if set to {@link MergeStrategy#ADD_ANNOTATIONS}, refuse to
* overwrite any existing annotation in the specified location.
*/
private static String insertAt(String signature, int position, char annotation, MergeStrategy mergeStrategy) {
StringBuffer result = new StringBuffer();
result.append(signature, 0, position);
result.append(annotation);
char next = signature.charAt(position);
switch (next) {
case NULLABLE: case NONNULL:
if (mergeStrategy == MergeStrategy.ADD_ANNOTATIONS)
return signature; // refuse any change
position++; // skip old annotation
}
result.append(signature, position, signature.length());
return result.toString();
}
/**
* Update 'oldType' with annotations from 'newType' guided by 'mergeStrategy'.
* The result is written into 'buf' as we go.
*/
private static boolean updateType(StringBuffer buf, char[] oldType, char[] newType, MergeStrategy mergeStrategy) {
if (mergeStrategy == MergeStrategy.REPLACE_SIGNATURE) {
buf.append(newType);
return false;
}
try {
SignatureWrapper oWrap = new SignatureWrapper(oldType, true, true); // may already contain annotations
SignatureWrapper nWrap = new SignatureWrapper(newType, true, true); // may already contain annotations
if (match(buf, oWrap, nWrap, 'L', false)
|| match(buf, oWrap, nWrap, 'T', false))
{
mergeAnnotation(buf, oWrap, nWrap, mergeStrategy);
buf.append(oWrap.nextName());
nWrap.nextName(); // skip
if (match(buf, oWrap, nWrap, '<', false)) {
do {
int oStart = oWrap.start;
int nStart = nWrap.start;
oWrap.computeEnd();
nWrap.computeEnd();
if (updateType(buf, oWrap.getFrom(oStart), nWrap.getFrom(nStart), mergeStrategy))
mergeAnnotation(buf, oWrap, nWrap, mergeStrategy);
} while (!match(buf, oWrap, nWrap, '>', false));
}
match(buf, oWrap, nWrap, ';', true);
} else if (match(buf, oWrap, nWrap, '[', false)) {
mergeAnnotation(buf, oWrap, nWrap, mergeStrategy);
updateType(buf, oWrap.tail(), nWrap.tail(), mergeStrategy);
} else if (match(buf, oWrap, nWrap, '*', false)
|| match(buf, oWrap, nWrap, '+', false)
|| match(buf, oWrap, nWrap, '-', false))
{
return true; // annotation allowed after this (not included in oldType / newType)
} else {
buf.append(oldType);
}
} catch (ArrayIndexOutOfBoundsException aioobe) { // from several locations inside match() or mergeAnnotation().
StringBuilder msg = new StringBuilder("Structural mismatch between ").append(oldType).append(" and ").append(newType); //$NON-NLS-1$ //$NON-NLS-2$
throw new IllegalArgumentException(msg.toString(), aioobe);
}
return false;
}
/**
* Does the current char at both given signatures match the 'expected' char?
* If yes, print it into 'buf' and answer true.
* If no, if 'force' raise an exception, else quietly answer false without updating 'buf'.
*/
private static boolean match(StringBuffer buf, SignatureWrapper sig1, SignatureWrapper sig2, char expected, boolean force) {
boolean match1 = sig1.signature[sig1.start] == expected;
boolean match2 = sig2.signature[sig2.start] == expected;
if (match1 != match2) {
StringBuilder msg = new StringBuilder("Mismatching type structures ") //$NON-NLS-1$
.append(sig1.signature).append(" vs ").append(sig2.signature); //$NON-NLS-1$
throw new IllegalArgumentException(msg.toString());
}
if (match1) {
buf.append(expected);
sig1.start++;
sig2.start++;
return true;
} else if (force) {
throw new IllegalArgumentException("Expected char "+expected+" not found in "+new String(sig1.signature)); //$NON-NLS-1$ //$NON-NLS-2$
} else {
return false;
}
}
/**
* If a current char of 'oldS' and/or 'newS' represents a null annotation, insert it into 'buf' guided by 'mergeStrategy'.
* If the new char is NO_ANNOTATION and strategy is OVERWRITE_ANNOTATIONS, silently skip over any null annotations in 'oldS'.
*/
private static void mergeAnnotation(StringBuffer buf, SignatureWrapper oldS, SignatureWrapper newS, MergeStrategy mergeStrategy) {
// if atEnd use a char that's different from NULLABLE, NONNULL and NO_ANNOTATION:
char oldAnn = !oldS.atEnd() ? oldS.signature[oldS.start] : '\0';
char newAnn = !newS.atEnd() ? newS.signature[newS.start] : '\0';
switch (mergeStrategy) {
case ADD_ANNOTATIONS:
switch (oldAnn) {
case NULLABLE: case NONNULL:
oldS.start++;
buf.append(oldAnn); // old exists, so it remains
switch (newAnn) { case NULLABLE: case NONNULL: newS.start++; } // just skip
return;
}
//$FALL-THROUGH$
case OVERWRITE_ANNOTATIONS:
switch (newAnn) {
case NULLABLE: case NONNULL:
newS.start++;
buf.append(newAnn); // new exists and is not suppressed by "ADD & old exists"
switch (oldAnn) { case NULLABLE: case NONNULL: oldS.start++; } // just skip
break;
case NO_ANNOTATION:
newS.start++; // don't insert
switch (oldAnn) { case NULLABLE: case NONNULL: oldS.start++; } // just skip
break;
default:
switch (oldAnn) {
case NULLABLE: case NONNULL:
oldS.start++;
buf.append(oldAnn); // keep
}
}
break;
default:
throw new IllegalArgumentException("Unexpected merge strategy"); // REPLACE_SIGNATURE does not reach this point, see initial check in updateType() //$NON-NLS-1$
}
}
/**
* Write back the given annotationFile, with the following content:
* - head (assumed to include a member and its original signature
* - annotatedSignature
* - nextLines (optionally, may be null)
* - the still unconsumed content of tailReader
*/
private static void writeFile(IFile annotationFile, StringBuffer head, String annotatedSignature,
String nextLines, BufferedReader tailReader, IProgressMonitor monitor)
throws CoreException, IOException
{
head.append(' ').append(annotatedSignature).append('\n');
if (nextLines != null)
head.append(nextLines).append('\n');
String line;
while ((line = tailReader.readLine()) != null)
head.append(line).append('\n');
ByteArrayInputStream newContent = new ByteArrayInputStream(head.toString().getBytes("UTF-8")); //$NON-NLS-1$
annotationFile.setContents(newContent, IResource.KEEP_HISTORY, monitor);
}
private static void createNewFile(IFile file, String newContent, IProgressMonitor monitor) throws CoreException {
ensureExists(file.getParent(), monitor);
try {
file.create(new ByteArrayInputStream(newContent.getBytes("UTF-8")), false, monitor); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, e.getMessage(), e));
}
}
private static void ensureExists(IContainer container, IProgressMonitor monitor) throws CoreException {
if (container.exists()) return;
if (!(container instanceof IFolder)) throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, "not a folder: "+container)); //$NON-NLS-1$
IContainer parent= container.getParent();
if (parent instanceof IFolder) {
ensureExists(parent, monitor);
}
((IFolder) container).create(false, true, monitor);
}
/**
* Retrieve the annotated signature of a specified member as found in the given external annotation file, if any.
* @param typeName fully qualified slash-separated name of the type for which the file defines external annotations
* @param file a file assumed to be in .eea format, must not be null, but may not exist
* @param selector name of the member whose annotation we are looking for
* @param originalSignature the unannotated signature by which the member is identified
* @return the annotated signature as found in the file, or null.
*/
public static String getAnnotatedSignature(String typeName, IFile file, String selector, String originalSignature) {
if (file.exists()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getContents()))) {
ExternalAnnotationProvider.assertClassHeader(reader.readLine(), typeName);
while (true) {
String line = reader.readLine();
// selector:
if (selector.equals(line)) {
// original signature:
line = reader.readLine();
if (originalSignature.equals(ExternalAnnotationProvider.extractSignature(line))) {
// annotated signature:
return ExternalAnnotationProvider.extractSignature(reader.readLine());
}
}
if (line == null)
break;
}
} catch (IOException | CoreException e) {
return null;
}
}
return null;
}
/**
* Apply the specified changes on the given type.
* This method can be used as a dry run without modifying an annotation file.
*
* @param originalSignature the original type signature, may be annotated already
* @param annotatedType a type signature with additional annotations (incl. {@link #NO_ANNOTATION}).
* @param mergeStrategy controls how old and new signatures should be merged
* @return an array of length four: <ul>
* <li>prefix up-to the changed type</li>
* <li>original type</li>
* <li>changed type</li>
* <li>postfix after the changed type <em>(here: empty string)</li>
* </ul>
*/
public static String[] annotateType(String originalSignature, String annotatedType, MergeStrategy mergeStrategy)
{
String[] result = new String[4]; // prefix, orig, replacement, postfix
StringBuffer buf;
result[0] = ""; //$NON-NLS-1$
buf = new StringBuffer();
result[1] = originalSignature;
updateType(buf, originalSignature.toCharArray(), annotatedType.toCharArray(), mergeStrategy);
result[2] = buf.toString();
result[3] = ""; //$NON-NLS-1$
return result;
}
/**
* Apply the specified changes on the return type of the given signature.
* This method can be used as a dry run without modifying an annotation file.
*
* @param originalSignature the original full signature, may be annotated already
* @param annotatedType a type signature with additional annotations (incl. {@link #NO_ANNOTATION}).
* @param mergeStrategy controls how old and new signatures should be merged
* @return an array of length four: <ul>
* <li>prefix up-to the changed type</li>
* <li>original type</li>
* <li>changed type</li>
* <li>postfix after the changed type <em>(here: empty string)</li>
* </ul>
*/
public static String[] annotateReturnType(String originalSignature, String annotatedType, MergeStrategy mergeStrategy)
{
String[] result = new String[4]; // prefix, orig, replacement, postfix
StringBuffer buf;
assert originalSignature.charAt(0) == '(' || originalSignature.charAt(0) == '<': "signature must start with '(' or '<'"; //$NON-NLS-1$
int close = originalSignature.indexOf(')');
result[0] = originalSignature.substring(0, close+1);
buf = new StringBuffer();
result[1] = originalSignature.substring(close+1);
updateType(buf, result[1].toCharArray(), annotatedType.toCharArray(), mergeStrategy);
result[2] = buf.toString();
result[3] = ""; //$NON-NLS-1$
return result;
}
/**
* Apply the specified changes on a parameter within the given signature.
* This method can be used as a dry run without modifying an annotation file.
*
* @param originalSignature the original full signature, may be annotated already
* @param annotatedType a type signature with additional annotations (incl. {@link #NO_ANNOTATION}).
* @param paramIdx the index of a parameter to annotated
* @param mergeStrategy controls how old and new signatures should be merged
* @return an array of length four: <ul>
* <li>prefix up-to the changed type</li>
* <li>original type</li>
* <li>changed type</li>
* <li>postfix after the changed type</li>
* </ul>
*/
public static String[] annotateParameterType(String originalSignature, String annotatedType, int paramIdx, MergeStrategy mergeStrategy)
{
String[] result = new String[4]; // prefix, orig, replacement, postfix
StringBuffer buf;
SignatureWrapper wrapper = new SignatureWrapper(originalSignature.toCharArray(), true, true); // may already contain annotations
wrapper.start = CharOperation.indexOf('(', wrapper.signature) + 1; // possibly skip type parameters
for (int i = 0; i < paramIdx; i++)
wrapper.start = wrapper.skipAngleContents(wrapper.computeEnd()) + 1;
int start = wrapper.start;
int end = wrapper.skipAngleContents(wrapper.computeEnd());
result[0] = originalSignature.substring(0, start);
buf = new StringBuffer();
result[1] = originalSignature.substring(start, end+1);
updateType(buf, result[1].toCharArray(), annotatedType.toCharArray(), mergeStrategy);
result[2] = buf.toString();
result[3] = originalSignature.substring(end+1, originalSignature.length());
return result;
}
}