001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.actions;
011:
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.ResourceBundle;
015:
016: import org.eclipse.core.runtime.Assert;
017:
018: import org.eclipse.jface.text.*;
019: import org.eclipse.jface.viewers.ISelection;
020: import org.eclipse.jface.viewers.ISelectionProvider;
021:
022: import org.eclipse.ui.IEditorInput;
023: import org.eclipse.ui.texteditor.IDocumentProvider;
024: import org.eclipse.ui.texteditor.ITextEditor;
025: import org.eclipse.ui.texteditor.ITextEditorExtension2;
026: import org.eclipse.ui.texteditor.TextEditorAction;
027:
028: /**
029: * Common block comment code.
030: *
031: * @since 3.0
032: */
033: public abstract class BlockCommentAction extends TextEditorAction {
034:
035: /**
036: * Creates a new instance.
037: * @param bundle
038: * @param prefix
039: * @param editor
040: */
041: public BlockCommentAction(ResourceBundle bundle, String prefix,
042: ITextEditor editor) {
043: super (bundle, prefix, editor);
044: }
045:
046: /**
047: * An edit is a kind of <code>DocumentEvent</code>, in this case an edit instruction, that is
048: * affiliated with a <code>Position</code> on a document. The offset of the document event is
049: * not stored statically, but taken from the affiliated <code>Position</code>, which gets
050: * updated when other edits occur.
051: */
052: static class Edit extends DocumentEvent {
053:
054: /**
055: * Factory for edits which manages the creation, installation and destruction of
056: * position categories, position updaters etc. on a certain document. Once a factory has
057: * been obtained, <code>Edit</code> objects can be obtained from it which will be linked to
058: * the document by positions of one position category.
059: * <p>Clients are required to call <code>release</code> once the <code>Edit</code>s are not
060: * used any more, so the positions can be discarded.</p>
061: */
062: public static class EditFactory {
063:
064: /** The position category basename for this edits. */
065: private static final String CATEGORY = "__positionalEditPositionCategory"; //$NON-NLS-1$
066:
067: /** The count of factories. */
068: private static int fgCount = 0;
069:
070: /** This factory's category. */
071: private final String fCategory;
072: private IDocument fDocument;
073: private IPositionUpdater fUpdater;
074:
075: /**
076: * Creates a new <code>EditFactory</code> with an unambiguous position category name.
077: * @param document the document that is being edited.
078: */
079: public EditFactory(IDocument document) {
080: fCategory = CATEGORY + fgCount++;
081: fDocument = document;
082: }
083:
084: /**
085: * Creates a new edition on the document of this factory.
086: *
087: * @param offset the offset of the edition at the point when is created.
088: * @param length the length of the edition (not updated via the position update mechanism)
089: * @param text the text to be replaced on the document
090: * @return an <code>Edit</code> reflecting the edition on the document
091: */
092: public Edit createEdit(int offset, int length, String text)
093: throws BadLocationException {
094:
095: if (!fDocument.containsPositionCategory(fCategory)) {
096: fDocument.addPositionCategory(fCategory);
097: fUpdater = new DefaultPositionUpdater(fCategory);
098: fDocument.addPositionUpdater(fUpdater);
099: }
100:
101: Position position = new Position(offset);
102: try {
103: fDocument.addPosition(fCategory, position);
104: } catch (BadPositionCategoryException e) {
105: Assert.isTrue(false);
106: }
107: return new Edit(fDocument, length, text, position);
108: }
109:
110: /**
111: * Releases the position category on the document and uninstalls the position updater.
112: * <code>Edit</code>s managed by this factory are not updated after this call.
113: */
114: public void release() {
115: if (fDocument != null
116: && fDocument
117: .containsPositionCategory(fCategory)) {
118: fDocument.removePositionUpdater(fUpdater);
119: try {
120: fDocument.removePositionCategory(fCategory);
121: } catch (BadPositionCategoryException e) {
122: Assert.isTrue(false);
123: }
124: fDocument = null;
125: fUpdater = null;
126: }
127: }
128: }
129:
130: /** The position in the document where this edit be executed. */
131: private Position fPosition;
132:
133: /**
134: * Creates a new edition on <code>document</code>, taking its offset from <code>position</code>.
135: *
136: * @param document the document being edited
137: * @param length the length of the edition
138: * @param text the replacement text of the edition
139: * @param position the position keeping the edition's offset
140: */
141: protected Edit(IDocument document, int length, String text,
142: Position position) {
143: super (document, 0, length, text);
144: fPosition = position;
145: }
146:
147: /*
148: * @see org.eclipse.jface.text.DocumentEvent#getOffset()
149: */
150: public int getOffset() {
151: return fPosition.getOffset();
152: }
153:
154: /**
155: * Executes the edition on document. The offset is taken from the position.
156: *
157: * @throws BadLocationException if the execution of the document fails.
158: */
159: public void perform() throws BadLocationException {
160: getDocument().replace(getOffset(), getLength(), getText());
161: }
162:
163: }
164:
165: public void run() {
166: if (!isEnabled())
167: return;
168:
169: ITextEditor editor = getTextEditor();
170: if (editor == null || !ensureEditable(editor))
171: return;
172:
173: ITextSelection selection = getCurrentSelection();
174: if (!isValidSelection(selection))
175: return;
176:
177: if (!validateEditorInputState())
178: return;
179:
180: IDocumentProvider docProvider = editor.getDocumentProvider();
181: IEditorInput input = editor.getEditorInput();
182: if (docProvider == null || input == null)
183: return;
184:
185: IDocument document = docProvider.getDocument(input);
186: if (document == null)
187: return;
188:
189: IDocumentExtension3 docExtension;
190: if (document instanceof IDocumentExtension3)
191: docExtension = (IDocumentExtension3) document;
192: else
193: return;
194:
195: IRewriteTarget target = (IRewriteTarget) editor
196: .getAdapter(IRewriteTarget.class);
197: if (target != null) {
198: target.beginCompoundChange();
199: }
200:
201: Edit.EditFactory factory = new Edit.EditFactory(document);
202:
203: try {
204: runInternal(selection, docExtension, factory);
205:
206: } catch (BadLocationException e) {
207: // can happen on concurrent modification, deletion etc. of the document
208: // -> don't complain, just bail out
209: } catch (BadPartitioningException e) {
210: // should not happen
211: Assert.isTrue(false, "bad partitioning"); //$NON-NLS-1$
212: } finally {
213: factory.release();
214:
215: if (target != null) {
216: target.endCompoundChange();
217: }
218: }
219: }
220:
221: /**
222: * Calls <code>perform</code> on all <code>Edit</code>s in <code>edits</code>.
223: *
224: * @param edits a list of <code>Edit</code>s
225: * @throws BadLocationException if an <code>Edit</code> threw such an exception.
226: */
227: protected void executeEdits(List edits) throws BadLocationException {
228: for (Iterator it = edits.iterator(); it.hasNext();) {
229: Edit edit = (Edit) it.next();
230: edit.perform();
231: }
232: }
233:
234: /**
235: * Ensures that the editor is modifyable. If the editor is an instance of
236: * <code>ITextEditorExtension2</code>, its <code>validateEditorInputState</code> method
237: * is called, otherwise, the result of <code>isEditable</code> is returned.
238: *
239: * @param editor the editor to be checked
240: * @return <code>true</code> if the editor is editable, <code>false</code> otherwise
241: */
242: protected boolean ensureEditable(ITextEditor editor) {
243: Assert.isNotNull(editor);
244:
245: if (editor instanceof ITextEditorExtension2) {
246: ITextEditorExtension2 ext = (ITextEditorExtension2) editor;
247: return ext.validateEditorInputState();
248: }
249:
250: return editor.isEditable();
251: }
252:
253: /*
254: * @see org.eclipse.ui.texteditor.IUpdate#update()
255: */
256: public void update() {
257: super .update();
258:
259: if (isEnabled()) {
260: if (!canModifyEditor()
261: || !isValidSelection(getCurrentSelection()))
262: setEnabled(false);
263: }
264: }
265:
266: /**
267: * Returns the editor's selection, or <code>null</code> if no selection can be obtained or the
268: * editor is <code>null</code>.
269: *
270: * @return the selection of the action's editor, or <code>null</code>
271: */
272: protected ITextSelection getCurrentSelection() {
273: ITextEditor editor = getTextEditor();
274: if (editor != null) {
275: ISelectionProvider provider = editor.getSelectionProvider();
276: if (provider != null) {
277: ISelection selection = provider.getSelection();
278: if (selection instanceof ITextSelection)
279: return (ITextSelection) selection;
280: }
281: }
282: return null;
283: }
284:
285: /**
286: * Runs the real command once all the editor, document, and selection checks have succeeded.
287: *
288: * @param selection the current selection we are being called for
289: * @param docExtension the document extension where we get the partitioning from
290: * @param factory the edit factory we can use to create <code>Edit</code>s
291: * @throws BadLocationException if an edition fails
292: * @throws BadPartitioningException if a partitioning call fails
293: */
294: protected abstract void runInternal(ITextSelection selection,
295: IDocumentExtension3 docExtension, Edit.EditFactory factory)
296: throws BadLocationException, BadPartitioningException;
297:
298: /**
299: * Checks whether <code>selection</code> is valid.
300: *
301: * @param selection the selection to check
302: * @return <code>true</code> if the selection is valid, <code>false</code> otherwise
303: */
304: protected abstract boolean isValidSelection(ITextSelection selection);
305:
306: /**
307: * Returns the text to be inserted at the selection start.
308: *
309: * @return the text to be inserted at the selection start
310: */
311: protected String getCommentStart() {
312: // for now: no space story
313: return "/*"; //$NON-NLS-1$
314: }
315:
316: /**
317: * Returns the text to be inserted at the selection end.
318: *
319: * @return the text to be inserted at the selection end
320: */
321: protected String getCommentEnd() {
322: // for now: no space story
323: return "*/"; //$NON-NLS-1$
324: }
325:
326: }
|