Fix bug #672
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / actions / BlockCommentAction.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.actions;
12
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.ResourceBundle;
16
17 import org.eclipse.jface.text.Assert;
18 import org.eclipse.jface.text.BadLocationException;
19 import org.eclipse.jface.text.BadPartitioningException;
20 import org.eclipse.jface.text.BadPositionCategoryException;
21 import org.eclipse.jface.text.DefaultPositionUpdater;
22 import org.eclipse.jface.text.DocumentEvent;
23 import org.eclipse.jface.text.IDocument;
24 import org.eclipse.jface.text.IDocumentExtension3;
25 import org.eclipse.jface.text.IPositionUpdater;
26 import org.eclipse.jface.text.IRewriteTarget;
27 import org.eclipse.jface.text.ITextSelection;
28 import org.eclipse.jface.text.Position;
29 import org.eclipse.jface.viewers.ISelection;
30 import org.eclipse.jface.viewers.ISelectionProvider;
31 import org.eclipse.ui.IEditorInput;
32 import org.eclipse.ui.texteditor.IDocumentProvider;
33 import org.eclipse.ui.texteditor.ITextEditor;
34 import org.eclipse.ui.texteditor.ITextEditorExtension2;
35 import org.eclipse.ui.texteditor.TextEditorAction;
36
37 /**
38  * Common block comment code.
39  * 
40  * @since 3.0
41  */
42 public abstract class BlockCommentAction extends TextEditorAction {
43
44         /**
45          * Creates a new instance.
46          * 
47          * @param bundle
48          * @param prefix
49          * @param editor
50          */
51         public BlockCommentAction(ResourceBundle bundle, String prefix,
52                         ITextEditor editor) {
53                 super(bundle, prefix, editor);
54         }
55
56         /**
57          * An edit is a kind of <code>DocumentEvent</code>, in this case an edit
58          * instruction, that is affilitated with a <code>Position</code> on a
59          * document. The offset of the document event is not stored statically, but
60          * taken from the affiliated <code>Position</code>, which gets updated
61          * when other edits occurr.
62          */
63         static class Edit extends DocumentEvent {
64
65                 /**
66                  * Factory for edits which manages the creation, installation and
67                  * destruction of position categories, position updaters etc. on a
68                  * certain document. Once a factory has been obtained, <code>Edit</code>
69                  * objects can be obtained from it which will be linked to the document
70                  * by positions of one position category.
71                  * <p>
72                  * Clients are required to call <code>release</code> once the
73                  * <code>Edit</code>s are not used any more, so the positions can be
74                  * discarded.
75                  * </p>
76                  */
77                 public static class EditFactory {
78
79                         /** The position category basename for this edits. */
80                         private static final String CATEGORY = "__positionalEditPositionCategory"; //$NON-NLS-1$
81
82                         /** The count of factories. */
83                         private static int fgCount = 0;
84
85                         /** This factory's category. */
86                         private final String fCategory;
87
88                         private IDocument fDocument;
89
90                         private IPositionUpdater fUpdater;
91
92                         /**
93                          * Creates a new <code>EditFactory</code> with an unambiguous
94                          * position category name.
95                          * 
96                          * @param document
97                          *            the document that is being edited.
98                          */
99                         public EditFactory(IDocument document) {
100                                 fCategory = CATEGORY + fgCount++;
101                                 fDocument = document;
102                         }
103
104                         /**
105                          * Creates a new edition on the document of this factory.
106                          * 
107                          * @param offset
108                          *            the offset of the edition at the point when is
109                          *            created.
110                          * @param length
111                          *            the length of the edition (not updated via the
112                          *            position update mechanism)
113                          * @param text
114                          *            the text to be replaced on the document
115                          * @return an <code>Edit</code> reflecting the edition on the
116                          *         document
117                          */
118                         public Edit createEdit(int offset, int length, String text)
119                                         throws BadLocationException {
120
121                                 if (!fDocument.containsPositionCategory(fCategory)) {
122                                         fDocument.addPositionCategory(fCategory);
123                                         fUpdater = new DefaultPositionUpdater(fCategory);
124                                         fDocument.addPositionUpdater(fUpdater);
125                                 }
126
127                                 Position position = new Position(offset);
128                                 try {
129                                         fDocument.addPosition(fCategory, position);
130                                 } catch (BadPositionCategoryException e) {
131                                         Assert.isTrue(false);
132                                 }
133                                 return new Edit(fDocument, length, text, position);
134                         }
135
136                         /**
137                          * Releases the position category on the document and uninstalls the
138                          * position updater. <code>Edit</code>s managed by this factory
139                          * are not updated after this call.
140                          */
141                         public void release() {
142                                 if (fDocument != null
143                                                 && fDocument.containsPositionCategory(fCategory)) {
144                                         fDocument.removePositionUpdater(fUpdater);
145                                         try {
146                                                 fDocument.removePositionCategory(fCategory);
147                                         } catch (BadPositionCategoryException e) {
148                                                 Assert.isTrue(false);
149                                         }
150                                         fDocument = null;
151                                         fUpdater = null;
152                                 }
153                         }
154                 }
155
156                 /** The position in the document where this edit be executed. */
157                 private Position fPosition;
158
159                 /**
160                  * Creates a new edition on <code>document</code>, taking its offset
161                  * from <code>position</code>.
162                  * 
163                  * @param document
164                  *            the document being edited
165                  * @param length
166                  *            the length of the edition
167                  * @param text
168                  *            the replacement text of the edition
169                  * @param position
170                  *            the position keeping the edition's offset
171                  */
172                 protected Edit(IDocument document, int length, String text,
173                                 Position position) {
174                         super(document, 0, length, text);
175                         fPosition = position;
176                 }
177
178                 /*
179                  * @see org.eclipse.jface.text.DocumentEvent#getOffset()
180                  */
181                 public int getOffset() {
182                         return fPosition.getOffset();
183                 }
184
185                 /**
186                  * Executes the edition on document. The offset is taken from the
187                  * position.
188                  * 
189                  * @throws BadLocationException
190                  *             if the execution of the document fails.
191                  */
192                 public void perform() throws BadLocationException {
193                         getDocument().replace(getOffset(), getLength(), getText());
194                 }
195
196         }
197
198         public void run() {
199                 if (!isEnabled())
200                         return;
201
202                 ITextEditor editor = getTextEditor();
203                 if (editor == null || !ensureEditable(editor))
204                         return;
205
206                 ITextSelection selection = getCurrentSelection();
207                 if (!isValidSelection(selection))
208                         return;
209
210                 if (!validateEditorInputState())
211                         return;
212
213                 IDocumentProvider docProvider = editor.getDocumentProvider();
214                 IEditorInput input = editor.getEditorInput();
215                 if (docProvider == null || input == null)
216                         return;
217
218                 IDocument document = docProvider.getDocument(input);
219                 if (document == null)
220                         return;
221
222                 IDocumentExtension3 docExtension;
223                 if (document instanceof IDocumentExtension3)
224                         docExtension = (IDocumentExtension3) document;
225                 else
226                         return;
227
228                 IRewriteTarget target = (IRewriteTarget) editor
229                                 .getAdapter(IRewriteTarget.class);
230                 if (target != null) {
231                         target.beginCompoundChange();
232                 }
233
234                 Edit.EditFactory factory = new Edit.EditFactory(document);
235
236                 try {
237                         runInternal(selection, docExtension, factory);
238
239                 } catch (BadLocationException e) {
240                         // can happen on concurrent modification, deletion etc. of the
241                         // document
242                         // -> don't complain, just bail out
243                 } catch (BadPartitioningException e) {
244                         // should not happen
245                         Assert.isTrue(false, "bad partitioning"); //$NON-NLS-1$
246                 } finally {
247                         factory.release();
248
249                         if (target != null) {
250                                 target.endCompoundChange();
251                         }
252                 }
253         }
254
255         /**
256          * Calls <code>perform</code> on all <code>Edit</code>s in
257          * <code>edits</code>.
258          * 
259          * @param edits
260          *            a list of <code>Edit</code>s
261          * @throws BadLocationException
262          *             if an <code>Edit</code> threw such an exception.
263          */
264         protected void executeEdits(List edits) throws BadLocationException {
265                 for (Iterator it = edits.iterator(); it.hasNext();) {
266                         Edit edit = (Edit) it.next();
267                         edit.perform();
268                 }
269         }
270
271         /**
272          * Ensures that the editor is modifyable. If the editor is an instance of
273          * <code>ITextEditorExtension2</code>, its
274          * <code>validateEditorInputState</code> method is called, otherwise, the
275          * result of <code>isEditable</code> is returned.
276          * 
277          * @param editor
278          *            the editor to be checked
279          * @return <code>true</code> if the editor is editable, <code>false</code>
280          *         otherwise
281          */
282         protected boolean ensureEditable(ITextEditor editor) {
283                 Assert.isNotNull(editor);
284
285                 if (editor instanceof ITextEditorExtension2) {
286                         ITextEditorExtension2 ext = (ITextEditorExtension2) editor;
287                         return ext.validateEditorInputState();
288                 }
289
290                 return editor.isEditable();
291         }
292
293         /*
294          * @see org.eclipse.ui.texteditor.IUpdate#update()
295          */
296         public void update() {
297                 super.update();
298
299                 if (isEnabled()) {
300                         if (!canModifyEditor() || !isValidSelection(getCurrentSelection()))
301                                 setEnabled(false);
302                 }
303         }
304
305         /**
306          * Returns the editor's selection, or <code>null</code> if no selection
307          * can be obtained or the editor is <code>null</code>.
308          * 
309          * @return the selection of the action's editor, or <code>null</code>
310          */
311         protected ITextSelection getCurrentSelection() {
312                 ITextEditor editor = getTextEditor();
313                 if (editor != null) {
314                         ISelectionProvider provider = editor.getSelectionProvider();
315                         if (provider != null) {
316                                 ISelection selection = provider.getSelection();
317                                 if (selection instanceof ITextSelection)
318                                         return (ITextSelection) selection;
319                         }
320                 }
321                 return null;
322         }
323
324         /**
325          * Runs the real command once all the editor, document, and selection checks
326          * have succeeded.
327          * 
328          * @param selection
329          *            the current selection we are being called for
330          * @param docExtension
331          *            the document extension where we get the partitioning from
332          * @param factory
333          *            the edit factory we can use to create <code>Edit</code>s
334          * @throws BadLocationException
335          *             if an edition fails
336          * @throws BadPartitioningException
337          *             if a partitioning call fails
338          */
339         protected abstract void runInternal(ITextSelection selection,
340                         IDocumentExtension3 docExtension, Edit.EditFactory factory)
341                         throws BadLocationException, BadPartitioningException;
342
343         /**
344          * Checks whether <code>selection</code> is valid.
345          * 
346          * @param selection
347          *            the selection to check
348          * @return <code>true</code> if the selection is valid, <code>false</code>
349          *         otherwise
350          */
351         protected abstract boolean isValidSelection(ITextSelection selection);
352
353         /**
354          * Returns the text to be inserted at the selection start.
355          * 
356          * @return the text to be inserted at the selection start
357          */
358         protected String getCommentStart() {
359                 // for now: no space story
360                 return "/*"; //$NON-NLS-1$
361         }
362
363         /**
364          * Returns the text to be inserted at the selection end.
365          * 
366          * @return the text to be inserted at the selection end
367          */
368         protected String getCommentEnd() {
369                 // for now: no space story
370                 return "*/"; //$NON-NLS-1$
371         }
372
373 }