Link Here
|
1 |
/******************************************************************************* |
2 |
* Copyright (c) 2010 IBM Corporation and others. |
3 |
* All rights reserved. This program and the accompanying materials |
4 |
* are made available under the terms of the Eclipse Public License v1.0 |
5 |
* which accompanies this distribution, and is available at |
6 |
* http://www.eclipse.org/legal/epl-v10.html |
7 |
* |
8 |
* Contributors: |
9 |
* IBM Corporation - initial API and implementation |
10 |
*******************************************************************************/ |
11 |
package org.eclipse.wst.sse.ui.internal.comment; |
12 |
|
13 |
import java.util.ArrayList; |
14 |
import java.util.Collections; |
15 |
import java.util.List; |
16 |
|
17 |
import org.eclipse.jface.text.BadLocationException; |
18 |
import org.eclipse.jface.text.IDocument; |
19 |
import org.eclipse.jface.text.IRegion; |
20 |
import org.eclipse.jface.text.ITypedRegion; |
21 |
import org.eclipse.jface.text.Region; |
22 |
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
23 |
import org.eclipse.wst.sse.ui.internal.Logger; |
24 |
|
25 |
/** |
26 |
* <p>Defines a commenting strategy defined by the <code>org.eclipse.wst.sse.ui.commentinStrategy</code> |
27 |
* extension point and tracked by the {@link CommentingStrategyRegistry}. Though it is important to |
28 |
* note it is note a one to one relationship of {@link CommentingStrategy}s to extensions, there is actually |
29 |
* one {@link CommentingStrategy} for each content type defined for each |
30 |
* <code>org.eclipse.wst.sse.ui.commentinStrategy</code> extension.<p> |
31 |
* |
32 |
* <p>The expected use case is that a {@link CommentingStrategy} is created off the basic configuration |
33 |
* of the extension point and then cloned for each associated content type and then each clone has |
34 |
* its partition information set using {@link #setPartitionInformation}. |
35 |
* Then the {@link CommentingStrategyRegistry} can be used to retrieve applicable {@link CommentingStrategy}s |
36 |
* and apply them to documents.</p> |
37 |
* |
38 |
* <p>It is important to note that any instance of a {@link CommentingStrategy} is only valid for a specific |
39 |
* content type ID but this relationship is tracked by the {@link CommentingStrategyRegistry} and not by |
40 |
* the strategy itself. Thus any reference to the strategy being valid for specific or general partition |
41 |
* types is implying it is already only valid for a specific content type</p> |
42 |
*/ |
43 |
public abstract class CommentingStrategy { |
44 |
/** <code>true</code> if this strategy has any required partition types, <code>false</code> otherwise */ |
45 |
private boolean fHasRequiredPartitionTypes; |
46 |
|
47 |
/** |
48 |
* <p><code>true</code> if for this strategy to be valid all of the required partition types must be seen, |
49 |
* <code>false</code> if only at least one of the required partition types must be seen for this |
50 |
* strategy to be valid.</p> |
51 |
* |
52 |
* @see #fRequriedPartitionTypeIDs |
53 |
*/ |
54 |
private boolean fRequireAllRequiredPartitionTypes; |
55 |
|
56 |
/** |
57 |
* <p>required partition type IDs that must be seen for this strategy to be valid, the number of them |
58 |
* that must be seen for the strategy to be valid is determined by {@link #fRequireAllRequiredPartitionTypes} |
59 |
* this requirement is waved if the optional {@link #fAssociatedCommentPartitionTypeID} is specified and |
60 |
* present because this strategy must be valid if its {@link #fAssociatedCommentPartitionTypeID} is present</p> |
61 |
* |
62 |
* @see #fRequireAllRequiredPartitionTypes |
63 |
* @see #fAssociatedCommentPartitionTypeID |
64 |
*/ |
65 |
private List fRequriedPartitionTypeIDs; |
66 |
|
67 |
/** |
68 |
* <p>if <code>true</code> then {@link #fAllowablePartitionTypeIDs} is ignored because |
69 |
* this strategy is valid for any partition type, if <code>false</code> then this |
70 |
* strategy is only valid for those partition types listed in {@link #fAllowablePartitionTypeIDs}</p> |
71 |
* |
72 |
* @see #fAllowablePartitionTypeIDs |
73 |
*/ |
74 |
private boolean fAllPartitionTypesAllowable; |
75 |
|
76 |
/** |
77 |
* <p>the partition types that this strategy is valid for, it is also automatically valid for |
78 |
* any partition types listed in {@link #fRequriedPartitionTypeIDs} and the optionally |
79 |
* specified {@link #fAssociatedCommentPartitionTypeID}</p> |
80 |
* |
81 |
* @see #fAllPartitionTypesAllowable |
82 |
* @see #fRequriedPartitionTypeIDs |
83 |
* @see #fAssociatedCommentPartitionTypeID |
84 |
*/ |
85 |
private List fAllowablePartitionTypeIDs; |
86 |
|
87 |
/** |
88 |
* an optional associated comment partition type ID, if this partition type is seen then the |
89 |
* the {@link #fRequriedPartitionTypeIDs} requirement is waved as to weather this strategy is |
90 |
* valid or not |
91 |
* |
92 |
* @see #fRequriedPartitionTypeIDs |
93 |
*/ |
94 |
private String fAssociatedCommentPartitionTypeID; |
95 |
|
96 |
/** |
97 |
* <p>Default constructor, the specific initialization is done by |
98 |
* {@link #setPartitionInformation}</p> |
99 |
*/ |
100 |
public CommentingStrategy() { |
101 |
this.fAssociatedCommentPartitionTypeID = null; |
102 |
this.fRequriedPartitionTypeIDs = Collections.EMPTY_LIST; |
103 |
this.fAllowablePartitionTypeIDs = Collections.EMPTY_LIST; |
104 |
this.fHasRequiredPartitionTypes = false; |
105 |
this.fRequireAllRequiredPartitionTypes = false; |
106 |
this.fAllPartitionTypesAllowable = false; |
107 |
} |
108 |
|
109 |
/** |
110 |
* <p>Used to set up the partition information for this strategy</p> |
111 |
* <p>This information is used to determine if a strategy is valid for a set of |
112 |
* {@link ITypedRegion}s.</p> |
113 |
* |
114 |
* @param allowablePartitionTypeIDs the partition types this strategy is valid for, the strategy will also |
115 |
* be considered valid for any of the required partition types |
116 |
* @param allPartitionTypesAllowable if <code>true</code> then this strategy is valid for any partition types |
117 |
* thus ignoring the <code>allowablePartitionTypeIDs</code>, <code>false</code> otherwise |
118 |
* @param requiredPartitionTypeIDs partition type IDs that must be seen for this strategy to be valid, there |
119 |
* are exceptions to this rule, see {@link #isApplicableFor(ITypedRegion[])} |
120 |
* @param requireAllRequiredPartitionTypes <code>true</code> if all of the <code>requiredPartitionTypeIDs must |
121 |
* be seen for this strategy to be valid, <code>false</code> otherwise, there are exceptions to these rules, see |
122 |
* {@link #isApplicableFor(ITypedRegion[])} |
123 |
* @param associatedCommentPartitionTypeID optional comment partition type associated with this strategy, |
124 |
* maybe <code>null</code> |
125 |
* |
126 |
* @see #isApplicableFor(ITypedRegion[]) |
127 |
*/ |
128 |
protected final void setPartitionInformation(List allowablePartitionTypeIDs, boolean allPartitionTypesAllowable, |
129 |
List requiredPartitionTypeIDs, boolean requireAllRequiredPartitionTypes, String associatedCommentPartitionTypeID) { |
130 |
|
131 |
this.fAllPartitionTypesAllowable = allPartitionTypesAllowable; |
132 |
this.fRequireAllRequiredPartitionTypes = requireAllRequiredPartitionTypes; |
133 |
|
134 |
this.fRequriedPartitionTypeIDs = requiredPartitionTypeIDs; |
135 |
if(this.fRequriedPartitionTypeIDs == null) { |
136 |
this.fRequriedPartitionTypeIDs = Collections.EMPTY_LIST; |
137 |
} |
138 |
|
139 |
this.fHasRequiredPartitionTypes = this.fRequriedPartitionTypeIDs.size() != 0; |
140 |
|
141 |
this.fAllowablePartitionTypeIDs = allowablePartitionTypeIDs; |
142 |
if(this.fAllowablePartitionTypeIDs == null) { |
143 |
this.fAllowablePartitionTypeIDs = Collections.EMPTY_LIST; |
144 |
} |
145 |
|
146 |
this.fAssociatedCommentPartitionTypeID = associatedCommentPartitionTypeID; |
147 |
} |
148 |
|
149 |
/** |
150 |
* <p>Applies this strategy to the given model starting at the given offset for the given length</p> |
151 |
* |
152 |
* @param document {@link IStructuredDocument} to apply this strategy too |
153 |
* @param offset the offset to start this comment at |
154 |
* @param length the length of the region to apply this comment too |
155 |
* |
156 |
* @throws BadLocationException it is not the fault of the strategy if callers passes a bad |
157 |
* <code>offset</code> and/or <code>length</code> for the given <code>model</code> |
158 |
*/ |
159 |
public abstract void apply(IStructuredDocument document, int offset, int length) throws BadLocationException; |
160 |
|
161 |
/** |
162 |
* <p>Remove any comments associated with this strategy from the given model for the given offset to |
163 |
* the given length. Weather a comment surrounding the given range should be removed can also be |
164 |
* specified</p> |
165 |
* |
166 |
* @param document {@link IStructuredDocument} to remove comments associated with this strategy from |
167 |
* @param offset the location to start removing comments associated with this strategy from |
168 |
* @param length the length of the region to remove associated comments from |
169 |
* @param removeEnclosing weather a comment should be removed if it incloses the region specified |
170 |
* by the given <code>offset</code> and <code>length</code> |
171 |
* |
172 |
* @throws BadLocationException it is not the fault of the strategy if callers passes a bad |
173 |
* <code>offset</code> and/or <code>length</code> for the given <code>model</code> |
174 |
*/ |
175 |
public abstract void remove(IStructuredDocument document, int offset, int length, boolean removeEnclosing) throws BadLocationException; |
176 |
|
177 |
/** |
178 |
* <p>Determines if the given region is a comment region commented by this strategy.</p> |
179 |
* |
180 |
* @param document {@link IStructuredDocument} containing the given <code>region</code> |
181 |
* @param region determine if this region is a comment region commented by this strategy |
182 |
* @return <code>true</code> if the given <code>region</code> has already been |
183 |
* commented by this strategy, <code>false</code> otherwise |
184 |
* |
185 |
* @throws BadLocationException it is not the fault of the strategy if callers passes a bad |
186 |
* <code>offset</code> and/or <code>length</code> for the given <code>model</code> |
187 |
*/ |
188 |
public abstract boolean alreadyCommenting(IStructuredDocument document, IRegion region) throws BadLocationException; |
189 |
|
190 |
/** |
191 |
* <p>Implementers should return a copy of themselves</p> |
192 |
* <p>Allows the {@link CommentingStrategyRegistry} to create a {@link CommentingStrategy} for |
193 |
* each of its associated content types.</p> |
194 |
* |
195 |
* @return implementers should return an object of type {@link CommentingStrategy} |
196 |
* |
197 |
* @see java.lang.Object#clone() |
198 |
*/ |
199 |
public abstract Object clone(); |
200 |
|
201 |
/** |
202 |
* <p>Determines if this strategy is applicable for the given regions for either commenting or un-commenting. |
203 |
* For this strategy to be applicable the given regions must contain at least one or all of the |
204 |
* {@link #fRequriedPartitionTypeIDs} (depending on the value of {@link #fRequireAllRequiredPartitionTypes}) |
205 |
* or contain at least one region of type {@link #fAssociatedCommentPartitionTypeID}. Also if the value of |
206 |
* {@link #fAllPartitionTypesAllowable} is <code>false</code> the given regions must all be of type |
207 |
* {@link #fAllowablePartitionTypeIDs} and/or {@link #fRequriedPartitionTypeIDs} and/or |
208 |
* {@link #fAssociatedCommentPartitionTypeID} otherwise the regions can be of any type except for they still |
209 |
* must beet the required partition type requirements</p> |
210 |
* |
211 |
* @param regions determine if this strategy is applicable for these regions |
212 |
* @return <code>true</code> if this strategy is applicable for the given <code>regions</code> |
213 |
* <code>false</code> otherwise. |
214 |
*/ |
215 |
public final boolean isApplicableFor(ITypedRegion[] regions) { |
216 |
List partitionTypeIDs = getPartitionTypeIDs(regions); |
217 |
|
218 |
boolean foundAssociatedCommentPartitions = false; |
219 |
if(this.fAssociatedCommentPartitionTypeID != null) { |
220 |
foundAssociatedCommentPartitions = partitionTypeIDs.contains(this.fAssociatedCommentPartitionTypeID); |
221 |
if(foundAssociatedCommentPartitions) { |
222 |
//remove all instances of the comment partition type |
223 |
boolean removed; |
224 |
do { |
225 |
removed = partitionTypeIDs.remove(this.fAssociatedCommentPartitionTypeID); |
226 |
} while(removed); |
227 |
} |
228 |
} |
229 |
|
230 |
//determine if required partitions requirements are met |
231 |
boolean requiredPartitionsRequirementsMet = false; |
232 |
if(this.fHasRequiredPartitionTypes) { |
233 |
/* either all specified required partitions must be found, |
234 |
* or just at least one of them must be found |
235 |
*/ |
236 |
if(this.fRequireAllRequiredPartitionTypes) { |
237 |
requiredPartitionsRequirementsMet = |
238 |
partitionTypeIDs.containsAll(this.fRequriedPartitionTypeIDs); |
239 |
partitionTypeIDs.removeAll(this.fRequriedPartitionTypeIDs); |
240 |
} else { |
241 |
requiredPartitionsRequirementsMet = |
242 |
partitionTypeIDs.removeAll(this.fRequriedPartitionTypeIDs); |
243 |
} |
244 |
} else { |
245 |
requiredPartitionsRequirementsMet = true; |
246 |
} |
247 |
|
248 |
//determine if allowable partitions requirements are met |
249 |
boolean allowablePartitionsRequirementsMet = false; |
250 |
if(this.fAllPartitionTypesAllowable) { |
251 |
allowablePartitionsRequirementsMet = true; |
252 |
} else { |
253 |
partitionTypeIDs.removeAll(this.fAllowablePartitionTypeIDs); |
254 |
|
255 |
//at this point all required partitions and allowable partitions have been removed |
256 |
allowablePartitionsRequirementsMet = partitionTypeIDs.size() == 0; |
257 |
} |
258 |
|
259 |
return (requiredPartitionsRequirementsMet || foundAssociatedCommentPartitions) && allowablePartitionsRequirementsMet; |
260 |
} |
261 |
|
262 |
/** |
263 |
* <p>Convenience method to take a list of regions and create one encompassing region to pass to |
264 |
* {@link #alreadyCommenting(IDocument, IRegion)}</p> |
265 |
* |
266 |
* @param document {@link IDocument} that contains the given <code>regions</code> |
267 |
* @param regions {@link IRegion}s to combine into one region and pass onto |
268 |
* {@link #alreadyCommenting(IDocument, IRegion)} |
269 |
* |
270 |
* @return the result of a call to {@link #alreadyCommenting(IDocument, IRegion)} combining |
271 |
* all of the given <code>regions</code> into one region |
272 |
* |
273 |
* @throws BadLocationException it is not the fault of the strategy if callers passes a bad |
274 |
* <code>offset</code> and/or <code>length</code> for the given <code>model</code> |
275 |
*/ |
276 |
public final boolean alreadyCommenting(IStructuredDocument document, IRegion[] regions) throws BadLocationException { |
277 |
boolean alreadyCommenting = false; |
278 |
if(regions != null && regions.length > 0) { |
279 |
//create one region spanning all the given regions |
280 |
int offset = regions[0].getOffset(); |
281 |
int length = regions[regions.length-1].getOffset() + |
282 |
regions[regions.length-1].getLength() - offset; |
283 |
|
284 |
IRegion region = new Region(offset, length); |
285 |
alreadyCommenting = this.alreadyCommenting(document, region); |
286 |
} |
287 |
|
288 |
return alreadyCommenting; |
289 |
} |
290 |
/** |
291 |
* <p>Given a list of {@link ITypedRegion}s returns the sub set of that list that |
292 |
* are of the comment region type associated with this strategy</p> |
293 |
* |
294 |
* @param typedRegions {@link ITypedRegion}s to filter down to only the comment regions |
295 |
* associated with this strategy |
296 |
* |
297 |
* @return {@link List} of {@link ITypedRegion}s from the given <code>typedRegions</code> |
298 |
* that are of the comment partition type associated with this strategy |
299 |
*/ |
300 |
protected List getAssociatedCommentedRegions(ITypedRegion[] typedRegions) { |
301 |
List commentedRegions = new ArrayList(); |
302 |
|
303 |
for(int i = 0; i < typedRegions.length; ++i) { |
304 |
if(typedRegions[i].getType().equals(this.fAssociatedCommentPartitionTypeID)) { |
305 |
commentedRegions.add(typedRegions[i]); |
306 |
} |
307 |
} |
308 |
|
309 |
return commentedRegions; |
310 |
} |
311 |
|
312 |
/** |
313 |
* <p>Given a list of {@link ITypedRegion}s returns a list of the partition |
314 |
* type IDs taken from the given regions.</p> |
315 |
* |
316 |
* @param regions {@link ITypedRegion}s to get the partition type IDs from |
317 |
* @return {@link List} of the partition type IDs taken from the given <code>regions</code> |
318 |
*/ |
319 |
private static List getPartitionTypeIDs(ITypedRegion[] regions) { |
320 |
List partitionTypes = new ArrayList(regions.length); |
321 |
for(int i = 0; i < regions.length; ++i) { |
322 |
partitionTypes.add(regions[i].getType()); |
323 |
} |
324 |
|
325 |
return partitionTypes; |
326 |
} |
327 |
|
328 |
/** |
329 |
* <p>This method modifies the given document to remove the given comment |
330 |
* prefix at the given comment prefix offset and the given comment |
331 |
* suffix at the given comment suffix offset. In the case of removing |
332 |
* a line comment that does not have a suffix, pass <code>null</code> |
333 |
* for the comment suffix and it and its associated offset will |
334 |
* be ignored.</p> |
335 |
* |
336 |
* <p><b>NOTE:</b> it is a good idea if a model is at hand when calling this to |
337 |
* warn the model of an impending update</p> |
338 |
* |
339 |
* @param document the document to remove the comment from |
340 |
* @param commentPrefixOffset the offset of the comment prefix |
341 |
* @param commentSuffixOffset the offset of the comment suffix |
342 |
* (ignored if <code>commentSuffix</code> is <code>null</code>) |
343 |
* @param commentPrefix the prefix of the comment to remove from its associated given offset |
344 |
* @param commentSuffix the suffix of the comment to remove from its associated given offset, |
345 |
* or null if there is not suffix to remove for this comment |
346 |
*/ |
347 |
protected static void uncomment(IDocument document, int commentPrefixOffset, String commentPrefix, |
348 |
int commentSuffixOffset, String commentSuffix) { |
349 |
|
350 |
try { |
351 |
//determine if there is a space after the comment prefix that should also be removed |
352 |
int commentPrefixLength = commentPrefix.length(); |
353 |
String postCommentPrefixChar = document.get(commentPrefixOffset + commentPrefix.length(), 1); |
354 |
if(postCommentPrefixChar.equals(" ")) { |
355 |
commentPrefixLength++; |
356 |
} |
357 |
|
358 |
//remove the comment prefix |
359 |
document.replace(commentPrefixOffset, commentPrefixLength, ""); //$NON-NLS-1$ |
360 |
|
361 |
if(commentSuffix != null) { |
362 |
commentSuffixOffset -= commentPrefixLength; |
363 |
|
364 |
//determine if there is a space before the comment suffix that should also be removed |
365 |
int commentSuffixLength = commentSuffix.length(); |
366 |
String preCommentSuffixChar = document.get(commentSuffixOffset-1, 1); |
367 |
if(preCommentSuffixChar.equals(" ")) { |
368 |
commentSuffixLength++; |
369 |
commentSuffixOffset--; |
370 |
} |
371 |
|
372 |
//remove the comment suffix |
373 |
document.replace(commentSuffixOffset, commentSuffixLength, ""); //$NON-NLS-1$ |
374 |
} |
375 |
} |
376 |
catch (BadLocationException e) { |
377 |
Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e); |
378 |
} |
379 |
} |
380 |
} |