| /******************************************************************************* |
| * 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.TypeDeclaration; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jpt.core.internal.ITextRange; |
| 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; |
| |
| |
| // ********** constructor ********** |
| |
| Member(IMember jdtMember, CommandExecutorProvider modifySharedDocumentCommandExecutorProvider) { |
| super(); |
| this.jdtMember = jdtMember; |
| IType jdtDeclaringType = jdtMember.getDeclaringType(); |
| this.declaringType = (jdtDeclaringType == null) ? null : new Type(jdtDeclaringType, modifySharedDocumentCommandExecutorProvider); |
| this.modifySharedDocumentCommandExecutorProvider = modifySharedDocumentCommandExecutorProvider; |
| } |
| |
| |
| // ********** 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; |
| } |
| |
| |
| // ********** 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 ITextRange textRange() { |
| return this.textRange(this.lightweightASTRoot()); |
| } |
| |
| public ITextRange textRange(CompilationUnit astRoot) { |
| return this.textRange(this.bodyDeclaration(astRoot)); |
| } |
| |
| ITextRange textRange(ASTNode astNode) { |
| return (astNode == null) ? null : new ASTNodeTextRange(astNode); |
| } |
| |
| public ITextRange nameTextRange() { |
| return this.nameTextRange(this.lightweightASTRoot()); |
| } |
| |
| public ITextRange 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); |
| } |
| } |
| |
| /** |
| * this will throw a NPE for a top-level type |
| */ |
| TypeDeclaration declaringTypeDeclaration(CompilationUnit astRoot) { |
| return this.declaringType.bodyDeclaration(astRoot); |
| } |
| |
| @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 ITextRange 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 ITextRange 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 ITextRange 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 ITextRange 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 Persistence Properties 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 DefaultAnnotationEditFormatter.instance(); |
| } |
| |
| 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); |
| } |
| } |
| |
| } |
| |
| } |