blob: 030b60107feeae956553f1c2db18cf59faceb43d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.core.internal.jdtutility;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jpt.core.TextRange;
import org.eclipse.jpt.core.internal.SimpleTextRange;
import org.eclipse.jpt.utility.internal.Command;
import org.eclipse.jpt.utility.internal.CommandExecutor;
import org.eclipse.jpt.utility.internal.CommandExecutorProvider;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
/**
* Adapt and extend a JDT member with simplified annotation handling.
*/
public abstract class Member {
private final IMember jdtMember;
/** this will be null for a top-level type */
private final Type declaringType;
private final CommandExecutorProvider modifySharedDocumentCommandExecutorProvider;
private final AnnotationEditFormatter annotationEditFormatter;
// ********** constructor **********
Member(IMember jdtMember, CommandExecutorProvider modifySharedDocumentCommandExecutorProvider) {
this(jdtMember, modifySharedDocumentCommandExecutorProvider, DefaultAnnotationEditFormatter.instance());
}
Member(IMember jdtMember, CommandExecutorProvider modifySharedDocumentCommandExecutorProvider, AnnotationEditFormatter annotationEditFormatter) {
super();
this.jdtMember = jdtMember;
IType jdtDeclaringType = jdtMember.getDeclaringType();
this.declaringType = (jdtDeclaringType == null) ? null : new Type(jdtDeclaringType, modifySharedDocumentCommandExecutorProvider);
this.modifySharedDocumentCommandExecutorProvider = modifySharedDocumentCommandExecutorProvider;
this.annotationEditFormatter = annotationEditFormatter;
}
// ********** accessors **********
public IMember getJdtMember() {
return this.jdtMember;
}
public boolean wraps(IMember member) {
return this.jdtMember.exists()
&& this.jdtMember.equals(member);
}
/**
* this will return null for a top-level type
*/
public Type getDeclaringType() {
return this.declaringType;
}
public abstract IBinding binding(CompilationUnit astRoot);
// ********** miscellaneous **********
public ICompilationUnit compilationUnit() {
return this.jdtMember.getCompilationUnit();
}
public String getName() {
return this.jdtMember.getElementName();
}
public Type topLevelDeclaringType() {
return this.declaringType.topLevelDeclaringType();
}
/**
* note: this creates a *new* AST
*/
public CompilationUnit astRoot() {
return JDTTools.buildASTRoot(this.jdtMember);
}
/**
* note: this creates a *new* AST
*/
private CompilationUnit lightweightASTRoot() {
return JDTTools.buildLightweightASTRoot(this.jdtMember);
}
public ModifiedDeclaration modifiedDeclaration() {
return this.modifiedDeclaration(this.astRoot());
}
public ModifiedDeclaration modifiedDeclaration(CompilationUnit astRoot) {
return new ModifiedDeclaration(this.bodyDeclaration(astRoot));
}
public TextRange textRange() {
return this.textRange(this.lightweightASTRoot());
}
public TextRange textRange(CompilationUnit astRoot) {
return this.textRange(this.bodyDeclaration(astRoot));
}
TextRange textRange(ASTNode astNode) {
return (astNode == null) ? null : new ASTNodeTextRange(astNode);
}
public TextRange nameTextRange() {
return this.nameTextRange(this.lightweightASTRoot());
}
public TextRange nameTextRange(CompilationUnit astRoot) {
ISourceRange sourceRange = this.nameSourceRange();
return
new SimpleTextRange(
sourceRange.getOffset(),
sourceRange.getLength(),
astRoot.getLineNumber(sourceRange.getOffset())
);
}
private ISourceRange nameSourceRange() {
try {
return this.jdtMember.getNameRange();
} catch (JavaModelException ex) {
throw new RuntimeException(ex);
}
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.getName());
}
// ********** abstract methods **********
/**
* Return the member's body declaration.
*/
public abstract BodyDeclaration bodyDeclaration(CompilationUnit astRoot);
// ********** annotations **********
public Annotation annotation(DeclarationAnnotationAdapter adapter, CompilationUnit astRoot) {
return adapter.getAnnotation(this.modifiedDeclaration(astRoot));
}
public Annotation annotation(DeclarationAnnotationAdapter adapter) {
return this.annotation(adapter, this.astRoot());
}
public boolean containsAnnotation(DeclarationAnnotationAdapter adapter, CompilationUnit astRoot) {
return this.annotation(adapter, astRoot) != null;
}
public boolean containsAnnotation(DeclarationAnnotationAdapter adapter) {
return this.containsAnnotation(adapter, this.astRoot());
}
/**
* Return whether the member contains any one of the specified annotations.
*/
public boolean containsAnyAnnotation(DeclarationAnnotationAdapter[] adapters) {
return this.containsAnyAnnotation(adapters, this.astRoot());
}
/**
* Return whether the member contains any one of the specified annotations.
*/
public boolean containsAnyAnnotation(DeclarationAnnotationAdapter[] adapters, CompilationUnit astRoot) {
for (DeclarationAnnotationAdapter adapter : adapters) {
if (this.containsAnnotation(adapter, astRoot)) {
return true;
}
}
return false;
}
/**
* Return the text range corresponding to the specified annotation.
* If the annotation is missing, return null.
*/
public TextRange annotationTextRange(DeclarationAnnotationAdapter adapter, CompilationUnit astRoot) {
return this.textRange(this.annotation(adapter, astRoot));
}
/**
* Return the text range corresponding to the specified annotation.
* If the annotation is missing, return null.
*/
public TextRange annotationTextRange(DeclarationAnnotationAdapter adapter) {
return this.annotationTextRange(adapter, this.astRoot());
}
/**
* Return the AST node corresponding to the specified annotation.
* If the annotation is missing, return its parent node.
*/
public ASTNode annotationASTNode(DeclarationAnnotationAdapter adapter, CompilationUnit astRoot) {
return adapter.astNode(this.modifiedDeclaration(astRoot));
}
/**
* Return the AST node corresponding to the specified annotation.
* If the annotation is missing, return its parent node.
*/
public ASTNode annotationASTNode(DeclarationAnnotationAdapter adapter) {
return this.annotationASTNode(adapter, this.astRoot());
}
/**
* Add the specified marker annotation to the member.
*/
public void newMarkerAnnotation(final DeclarationAnnotationAdapter adapter) {
this.edit(new Editor() {
public void edit(ModifiedDeclaration declaration) {
adapter.newMarkerAnnotation(declaration);
}
});
}
/**
* Add the specified single member annotation to the member.
*/
public void newSingleMemberAnnotation(final DeclarationAnnotationAdapter adapter) {
this.edit(new Editor() {
public void edit(ModifiedDeclaration declaration) {
adapter.newSingleMemberAnnotation(declaration);
}
});
}
/**
* Add the specified normal annotation to the member.
*/
public void newNormalAnnotation(final DeclarationAnnotationAdapter adapter) {
this.edit(new Editor() {
public void edit(ModifiedDeclaration declaration) {
adapter.newNormalAnnotation(declaration);
}
});
}
/**
* Remove the specified annotation from the member.
*/
public void removeAnnotation(final DeclarationAnnotationAdapter adapter) {
this.edit(new Editor() {
public void edit(ModifiedDeclaration declaration) {
adapter.removeAnnotation(declaration);
}
});
}
// ********** annotation elements **********
public <T> T annotationElementValue(DeclarationAnnotationElementAdapter<T> adapter, CompilationUnit astRoot) {
return adapter.getValue(this.modifiedDeclaration(astRoot));
}
public <T> T annotationElementValue(DeclarationAnnotationElementAdapter<T> adapter) {
return this.annotationElementValue(adapter, this.astRoot());
}
public Expression annotationElementExpression(DeclarationAnnotationElementAdapter<?> adapter, CompilationUnit astRoot) {
return adapter.expression(this.modifiedDeclaration(astRoot));
}
public Expression annotationElementExpression(DeclarationAnnotationElementAdapter<?> adapter) {
return this.annotationElementExpression(adapter, this.astRoot());
}
public boolean containsAnnotationElement(DeclarationAnnotationElementAdapter<?> adapter, CompilationUnit astRoot) {
return this.annotationElementExpression(adapter, astRoot) != null;
}
public boolean containsAnnotationElement(DeclarationAnnotationElementAdapter<?> adapter) {
return this.containsAnnotationElement(adapter, this.astRoot());
}
/**
* Return the text range corresponding to the specified element.
* If the element is missing, return null.
*/
public TextRange annotationElementTextRange(DeclarationAnnotationElementAdapter<?> adapter, CompilationUnit astRoot) {
return this.textRange(this.annotationElementExpression(adapter, astRoot));
}
/**
* Return the text range corresponding to the specified element.
* If the element is missing, return null.
*/
public TextRange annotationElementTextRange(DeclarationAnnotationElementAdapter<?> adapter) {
return this.annotationElementTextRange(adapter, this.astRoot());
}
/**
* Return the AST node corresponding to the specified element.
* If the element is missing, return its parent node.
*/
public ASTNode annotationElementASTNode(DeclarationAnnotationElementAdapter<?> adapter, CompilationUnit astRoot) {
return adapter.astNode(this.modifiedDeclaration(astRoot));
}
/**
* Return the AST node corresponding to the specified element.
* If the element is missing, return its parent node.
*/
public ASTNode annotationElementASTNode(DeclarationAnnotationElementAdapter<?> adapter) {
return this.annotationElementASTNode(adapter, this.astRoot());
}
/**
* Set the value of the specified element.
*/
public <T> void setAnnotationElementValue(final DeclarationAnnotationElementAdapter<T> adapter, final T value) {
this.edit(new Editor() {
public void edit(ModifiedDeclaration declaration) {
adapter.setValue(value, declaration);
}
});
}
// ********** editing **********
/**
* Edit the member with the specified editor.
* The editor will be invoked once the member's compilation unit
* is in an editable state.
*/
public void edit(Editor editor) {
try {
this.edit_(editor);
} catch (JavaModelException ex) {
throw new RuntimeException(ex);
} catch (BadLocationException ex) {
throw new RuntimeException(ex);
}
}
/**
* NB: Be careful changing this method.
* Things to look out for:
* - when editing via the JavaEditor there is no need to create a working copy
* - when editing headlessly, a "working copy" must be created
* (at least as far as I can tell ~kfm)
* - when editing via a plain text editor, make a working copy or else things are screwed
* up the second time you edit through the XmlPersistence XmlProperties View
*/
private void edit_(Editor editor) throws JavaModelException, BadLocationException {
ICompilationUnit compilationUnit = this.compilationUnit();
if ( ! compilationUnit.isWorkingCopy()) {
compilationUnit.becomeWorkingCopy(null);
}
ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(compilationUnit.getResource().getFullPath(), LocationKind.NORMALIZE);
boolean sharedDocument = (buffer != null); // documents are typically shared when they are already open in an editor
IDocument doc = sharedDocument ?
buffer.getDocument()
:
new Document(compilationUnit.getBuffer().getContents());
CompilationUnit astRoot = this.astRoot();
astRoot.recordModifications();
editor.edit(this.modifiedDeclaration(astRoot));
TextEdit edits = astRoot.rewrite(doc, compilationUnit.getJavaProject().getOptions(true));
if (sharedDocument) {
this.modifySharedDocumentCommandExecutor().execute(new ModifySharedDocumentCommand(edits, doc));
} else {
this.applyEdits(edits, doc);
}
if ( ! sharedDocument) {
compilationUnit.getBuffer().setContents(doc.get());
compilationUnit.commitWorkingCopy(true, null); // true="force"
compilationUnit.discardWorkingCopy();
}
}
/**
* apply the specified edits to the specified document,
* reformatting the document if necessary
*/
void applyEdits(TextEdit edits, IDocument doc) throws MalformedTreeException, BadLocationException {
edits.apply(doc, TextEdit.UPDATE_REGIONS);
this.annotationEditFormatter().format(doc, edits);
}
private AnnotationEditFormatter annotationEditFormatter() {
return this.annotationEditFormatter;
}
private CommandExecutor modifySharedDocumentCommandExecutor() {
return this.modifySharedDocumentCommandExecutorProvider.commandExecutor();
}
// ********** "editor" interface **********
/**
* This interface defines a callback that is invoked when the member's
* compilation unit is in a state to be manipulated.
*/
public interface Editor {
/**
* Edit the specified declaration.
*/
void edit(ModifiedDeclaration declaration);
}
// ********** modify shared document command class **********
/**
* simple command that calls back to the member to apply the edits
* in the same way as if the document were not shared
*/
class ModifySharedDocumentCommand implements Command {
private final TextEdit edits;
private final IDocument doc;
ModifySharedDocumentCommand(TextEdit edits, IDocument doc) {
super();
this.edits = edits;
this.doc = doc;
}
public void execute() {
try {
Member.this.applyEdits(this.edits, this.doc);
} catch (MalformedTreeException ex) {
throw new RuntimeException(ex);
} catch (BadLocationException ex) {
throw new RuntimeException(ex);
}
}
}
}