/******************************************************************************* * Copyright (c) 2000, 2008 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.jsdt.internal.ui.actions; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPartitioningException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DefaultPositionUpdater; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.IRewriteTarget; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Position; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.ITextEditorExtension2; import org.eclipse.ui.texteditor.TextEditorAction; /** * Common block comment code. * * */ public abstract class BlockCommentAction extends TextEditorAction { /** * Creates a new instance. * @param bundle * @param prefix * @param editor */ public BlockCommentAction(ResourceBundle bundle, String prefix, ITextEditor editor) { super(bundle, prefix, editor); } /** * An edit is a kind of <code>DocumentEvent</code>, in this case an edit instruction, that is * affiliated with a <code>Position</code> on a document. The offset of the document event is * not stored statically, but taken from the affiliated <code>Position</code>, which gets * updated when other edits occur. */ static class Edit extends DocumentEvent { /** * Factory for edits which manages the creation, installation and destruction of * position categories, position updaters etc. on a certain document. Once a factory has * been obtained, <code>Edit</code> objects can be obtained from it which will be linked to * the document by positions of one position category. * <p>Clients are required to call <code>release</code> once the <code>Edit</code>s are not * used any more, so the positions can be discarded.</p> */ public static class EditFactory { /** The position category basename for this edits. */ private static final String CATEGORY= "__positionalEditPositionCategory"; //$NON-NLS-1$ /** The count of factories. */ private static int fgCount= 0; /** This factory's category. */ private final String fCategory; private IDocument fDocument; private IPositionUpdater fUpdater; /** * Creates a new <code>EditFactory</code> with an unambiguous position category name. * @param document the document that is being edited. */ public EditFactory(IDocument document) { fCategory= CATEGORY + fgCount++; fDocument= document; } /** * Creates a new edition on the document of this factory. * * @param offset the offset of the edition at the point when is created. * @param length the length of the edition (not updated via the position update mechanism) * @param text the text to be replaced on the document * @return an <code>Edit</code> reflecting the edition on the document */ public Edit createEdit(int offset, int length, String text) throws BadLocationException { if (!fDocument.containsPositionCategory(fCategory)) { fDocument.addPositionCategory(fCategory); fUpdater= new DefaultPositionUpdater(fCategory); fDocument.addPositionUpdater(fUpdater); } Position position= new Position(offset); try { fDocument.addPosition(fCategory, position); } catch (BadPositionCategoryException e) { Assert.isTrue(false); } return new Edit(fDocument, length, text, position); } /** * Releases the position category on the document and uninstalls the position updater. * <code>Edit</code>s managed by this factory are not updated after this call. */ public void release() { if (fDocument != null && fDocument.containsPositionCategory(fCategory)) { fDocument.removePositionUpdater(fUpdater); try { fDocument.removePositionCategory(fCategory); } catch (BadPositionCategoryException e) { Assert.isTrue(false); } fDocument= null; fUpdater= null; } } } /** The position in the document where this edit be executed. */ private Position fPosition; /** * Creates a new edition on <code>document</code>, taking its offset from <code>position</code>. * * @param document the document being edited * @param length the length of the edition * @param text the replacement text of the edition * @param position the position keeping the edition's offset */ protected Edit(IDocument document, int length, String text, Position position) { super(document, 0, length, text); fPosition= position; } /* * @see org.eclipse.jface.text.DocumentEvent#getOffset() */ public int getOffset() { return fPosition.getOffset(); } /** * Executes the edition on document. The offset is taken from the position. * * @throws BadLocationException if the execution of the document fails. */ public void perform() throws BadLocationException { getDocument().replace(getOffset(), getLength(), getText()); } } public void run() { if (!isEnabled()) return; ITextEditor editor= getTextEditor(); if (editor == null || !ensureEditable(editor)) return; ITextSelection selection= getCurrentSelection(); if (!isValidSelection(selection)) return; if (!validateEditorInputState()) return; IDocumentProvider docProvider= editor.getDocumentProvider(); IEditorInput input= editor.getEditorInput(); if (docProvider == null || input == null) return; IDocument document= docProvider.getDocument(input); if (document == null) return; IDocumentExtension3 docExtension; if (document instanceof IDocumentExtension3) docExtension= (IDocumentExtension3) document; else return; IRewriteTarget target= (IRewriteTarget)editor.getAdapter(IRewriteTarget.class); if (target != null) { target.beginCompoundChange(); } Edit.EditFactory factory= new Edit.EditFactory(document); try { runInternal(selection, docExtension, factory); } catch (BadLocationException e) { // can happen on concurrent modification, deletion etc. of the document // -> don't complain, just bail out } catch (BadPartitioningException e) { // should not happen Assert.isTrue(false, "bad partitioning"); //$NON-NLS-1$ } finally { factory.release(); if (target != null) { target.endCompoundChange(); } } } /** * Calls <code>perform</code> on all <code>Edit</code>s in <code>edits</code>. * * @param edits a list of <code>Edit</code>s * @throws BadLocationException if an <code>Edit</code> threw such an exception. */ protected void executeEdits(List edits) throws BadLocationException { for (Iterator it= edits.iterator(); it.hasNext();) { Edit edit= (Edit) it.next(); edit.perform(); } } /** * Ensures that the editor is modifyable. If the editor is an instance of * <code>ITextEditorExtension2</code>, its <code>validateEditorInputState</code> method * is called, otherwise, the result of <code>isEditable</code> is returned. * * @param editor the editor to be checked * @return <code>true</code> if the editor is editable, <code>false</code> otherwise */ protected boolean ensureEditable(ITextEditor editor) { Assert.isNotNull(editor); if (editor instanceof ITextEditorExtension2) { ITextEditorExtension2 ext= (ITextEditorExtension2) editor; return ext.validateEditorInputState(); } return editor.isEditable(); } /* * @see org.eclipse.ui.texteditor.IUpdate#update() */ public void update() { super.update(); if (isEnabled()) { if (!canModifyEditor() || !isValidSelection(getCurrentSelection())) setEnabled(false); } } /** * Returns the editor's selection, or <code>null</code> if no selection can be obtained or the * editor is <code>null</code>. * * @return the selection of the action's editor, or <code>null</code> */ protected ITextSelection getCurrentSelection() { ITextEditor editor= getTextEditor(); if (editor != null) { ISelectionProvider provider= editor.getSelectionProvider(); if (provider != null) { ISelection selection= provider.getSelection(); if (selection instanceof ITextSelection) return (ITextSelection) selection; } } return null; } /** * Runs the real command once all the editor, document, and selection checks have succeeded. * * @param selection the current selection we are being called for * @param docExtension the document extension where we get the partitioning from * @param factory the edit factory we can use to create <code>Edit</code>s * @throws BadLocationException if an edition fails * @throws BadPartitioningException if a partitioning call fails */ protected abstract void runInternal(ITextSelection selection, IDocumentExtension3 docExtension, Edit.EditFactory factory) throws BadLocationException, BadPartitioningException; /** * Checks whether <code>selection</code> is valid. * * @param selection the selection to check * @return <code>true</code> if the selection is valid, <code>false</code> otherwise */ protected abstract boolean isValidSelection(ITextSelection selection); /** * Returns the text to be inserted at the selection start. * * @return the text to be inserted at the selection start */ protected String getCommentStart() { // for now: no space story return "/*"; //$NON-NLS-1$ } /** * Returns the text to be inserted at the selection end. * * @return the text to be inserted at the selection end */ protected String getCommentEnd() { // for now: no space story return "*/"; //$NON-NLS-1$ } }