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