diff options
Diffstat (limited to 'core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/comment/CommentingStrategy.java')
-rw-r--r-- | core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/comment/CommentingStrategy.java | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/comment/CommentingStrategy.java b/core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/comment/CommentingStrategy.java new file mode 100644 index 0000000000..c46248c4fd --- /dev/null +++ b/core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/comment/CommentingStrategy.java @@ -0,0 +1,355 @@ +/******************************************************************************* + * Copyright (c) 2010 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.sse.ui.internal.comment; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.sse.ui.internal.Logger; + +/** + * <p>Defines a commenting strategy defined by the <code>org.eclipse.wst.sse.ui.commentinStrategy</code> + * extension point and tracked by the {@link CommentingStrategyRegistry}. Though it is important to + * note it is not a one to one relationship of {@link CommentingStrategy}s to extensions, there is actually + * one {@link CommentingStrategy} for each content type defined for each + * <code>org.eclipse.wst.sse.ui.commentinStrategy</code> extension.<p> + * + * <p>The expected use case is that a {@link CommentingStrategy} is created off the basic configuration + * of the extension point and then cloned for each associated content type and then each clone has + * its partition information set using {@link #setPartitionInformation}. + * Then the {@link CommentingStrategyRegistry} can be used to retrieve applicable {@link CommentingStrategy}s + * and apply them to documents.</p> + * + * <p>It is important to note that any instance of a {@link CommentingStrategy} is only valid for a specific + * content type ID but this relationship is tracked by the {@link CommentingStrategyRegistry} and not by + * the strategy itself. Thus any reference to the strategy being valid for specific or general partition + * types is implying it is already only valid for a specific content type</p> + */ +public abstract class CommentingStrategy { + /** <code>true</code> if this strategy has any required partition types, <code>false</code> otherwise */ + private boolean fHasRequiredPartitionTypes; + + /** + * <p>required partition type IDs that must be seen for this strategy to be valid, the number of them + * that must be seen for the strategy to be valid is determined by {@link #fRequireAllRequiredPartitionTypes} + * this requirement is waved if the optional {@link #fAssociatedCommentPartitionTypeID} is specified and + * present because this strategy must be valid if its {@link #fAssociatedCommentPartitionTypeID} is present</p> + * + * @see #fRequireAllRequiredPartitionTypes + * @see #fAssociatedCommentPartitionTypeID + */ + private List fRequriedPartitionTypeIDs; + + /** + * <p>if <code>true</code> then {@link #fAllowablePartitionTypeIDs} is ignored because + * this strategy is valid for any partition type, if <code>false</code> then this + * strategy is only valid for those partition types listed in {@link #fAllowablePartitionTypeIDs}</p> + * + * @see #fAllowablePartitionTypeIDs + */ + private boolean fAllPartitionTypesAllowable; + + /** + * <p>the partition types that this strategy is valid for, it is also automatically valid for + * any partition types listed in {@link #fRequriedPartitionTypeIDs} and the optionally + * specified {@link #fAssociatedCommentPartitionTypeID}</p> + * + * @see #fAllPartitionTypesAllowable + * @see #fRequriedPartitionTypeIDs + * @see #fAssociatedCommentPartitionTypeID + */ + private List fAllowablePartitionTypeIDs; + + /** + * an optional associated comment partition type ID, if this partition type is seen then the + * the {@link #fRequriedPartitionTypeIDs} requirement is waved as to weather this strategy is + * valid or not + * + * @see #fRequriedPartitionTypeIDs + */ + private String fAssociatedCommentPartitionTypeID; + + /** + * <p>Default constructor, the specific initialization is done by + * {@link #setPartitionInformation}</p> + */ + public CommentingStrategy() { + this.fAssociatedCommentPartitionTypeID = null; + this.fRequriedPartitionTypeIDs = Collections.EMPTY_LIST; + this.fAllowablePartitionTypeIDs = Collections.EMPTY_LIST; + this.fHasRequiredPartitionTypes = false; + this.fAllPartitionTypesAllowable = false; + } + + /** + * <p>Used to set up the partition information for this strategy</p> + * <p>This information is used to determine if a strategy is valid for a set of + * {@link ITypedRegion}s.</p> + * + * @param allowablePartitionTypeIDs the partition types this strategy is valid for, the strategy will also + * be considered valid for any of the required partition types + * @param allPartitionTypesAllowable if <code>true</code> then this strategy is valid for any partition types + * thus ignoring the <code>allowablePartitionTypeIDs</code>, <code>false</code> otherwise + * @param requiredPartitionTypeIDs partition type IDs that must be seen for this strategy to be valid, there + * are exceptions to this rule, see {@link #isApplicableFor(ITypedRegion[])} + * @param requireAllRequiredPartitionTypes <code>true</code> if all of the <code>requiredPartitionTypeIDs must + * be seen for this strategy to be valid, <code>false</code> otherwise, there are exceptions to these rules, see + * {@link #isApplicableFor(ITypedRegion[])} + * @param associatedCommentPartitionTypeID optional comment partition type associated with this strategy, + * maybe <code>null</code> + * + * @see #isApplicableFor(ITypedRegion[]) + */ + protected final void setPartitionInformation(List allowablePartitionTypeIDs, boolean allPartitionTypesAllowable, + List requiredPartitionTypeIDs, String associatedCommentPartitionTypeID) { + + this.fAllPartitionTypesAllowable = allPartitionTypesAllowable; + + this.fRequriedPartitionTypeIDs = requiredPartitionTypeIDs; + if(this.fRequriedPartitionTypeIDs == null) { + this.fRequriedPartitionTypeIDs = Collections.EMPTY_LIST; + } + + this.fHasRequiredPartitionTypes = this.fRequriedPartitionTypeIDs.size() != 0; + + this.fAllowablePartitionTypeIDs = allowablePartitionTypeIDs; + if(this.fAllowablePartitionTypeIDs == null) { + this.fAllowablePartitionTypeIDs = Collections.EMPTY_LIST; + } + + this.fAssociatedCommentPartitionTypeID = associatedCommentPartitionTypeID; + } + + /** + * <p>Applies this strategy to the given model starting at the given offset for the given length</p> + * + * @param document {@link IStructuredDocument} to apply this strategy too + * @param offset the offset to start this comment at + * @param length the length of the region to apply this comment too + * + * @throws BadLocationException it is not the fault of the strategy if callers passes a bad + * <code>offset</code> and/or <code>length</code> for the given <code>model</code> + */ + public abstract void apply(IStructuredDocument document, int offset, int length) throws BadLocationException; + + /** + * <p>Remove any comments associated with this strategy from the given model for the given offset to + * the given length. Weather a comment surrounding the given range should be removed can also be + * specified</p> + * + * @param document {@link IStructuredDocument} to remove comments associated with this strategy from + * @param offset the location to start removing comments associated with this strategy from + * @param length the length of the region to remove associated comments from + * @param removeEnclosing weather a comment should be removed if it incloses the region specified + * by the given <code>offset</code> and <code>length</code> + * + * @throws BadLocationException it is not the fault of the strategy if callers passes a bad + * <code>offset</code> and/or <code>length</code> for the given <code>model</code> + */ + public abstract void remove(IStructuredDocument document, int offset, int length, boolean removeEnclosing) throws BadLocationException; + + /** + * <p>Determines if the given region is a comment region commented by this strategy.</p> + * + * @param document {@link IStructuredDocument} containing the given <code>region</code> + * @param region determine if this region is a comment region commented by this strategy + * @return <code>true</code> if the given <code>region</code> has already been + * commented by this strategy, <code>false</code> otherwise + * + * @throws BadLocationException it is not the fault of the strategy if callers passes a bad + * <code>offset</code> and/or <code>length</code> for the given <code>model</code> + */ + public abstract boolean alreadyCommenting(IStructuredDocument document, IRegion region) throws BadLocationException; + + /** + * <p>Implementers should return a copy of themselves</p> + * <p>Allows the {@link CommentingStrategyRegistry} to create a {@link CommentingStrategy} for + * each of its associated content types.</p> + * + * @return implementers should return an object of type {@link CommentingStrategy} + * + * @see java.lang.Object#clone() + */ + public abstract Object clone(); + + /** + * <p>Determines if this strategy is applicable for the given regions for either commenting or un-commenting. + * For this strategy to be applicable the given regions must contain at least one or all of the + * {@link #fRequriedPartitionTypeIDs} (depending on the value of {@link #fRequireAllRequiredPartitionTypes}) + * or contain at least one region of type {@link #fAssociatedCommentPartitionTypeID}. Also if the value of + * {@link #fAllPartitionTypesAllowable} is <code>false</code> the given regions must all be of type + * {@link #fAllowablePartitionTypeIDs} and/or {@link #fRequriedPartitionTypeIDs} and/or + * {@link #fAssociatedCommentPartitionTypeID} otherwise the regions can be of any type except for they still + * must beet the required partition type requirements</p> + * + * @param regions determine if this strategy is applicable for these regions + * @return <code>true</code> if this strategy is applicable for the given <code>regions</code> + * <code>false</code> otherwise. + */ + public final boolean isApplicableFor(ITypedRegion[] regions) { + List partitionTypeIDs = getPartitionTypeIDs(regions); + + boolean foundAssociatedCommentPartitions = false; + if(this.fAssociatedCommentPartitionTypeID != null) { + foundAssociatedCommentPartitions = partitionTypeIDs.contains(this.fAssociatedCommentPartitionTypeID); + if(foundAssociatedCommentPartitions) { + //remove all instances of the comment partition type + boolean removed; + do { + removed = partitionTypeIDs.remove(this.fAssociatedCommentPartitionTypeID); + } while(removed); + } + } + + //determine if required partitions requirements are met + boolean requiredPartitionsRequirementsMet = !this.fHasRequiredPartitionTypes || + partitionTypeIDs.removeAll(this.fRequriedPartitionTypeIDs); + + //determine if allowable partitions requirements are met + boolean allowablePartitionsRequirementsMet = false; + if(this.fAllPartitionTypesAllowable) { + allowablePartitionsRequirementsMet = true; + } else { + partitionTypeIDs.removeAll(this.fAllowablePartitionTypeIDs); + + //at this point all required partitions and allowable partitions have been removed + allowablePartitionsRequirementsMet = partitionTypeIDs.size() == 0; + } + + return (requiredPartitionsRequirementsMet || foundAssociatedCommentPartitions) && allowablePartitionsRequirementsMet; + } + + /** + * <p>Convenience method to take a list of regions and create one encompassing region to pass to + * {@link #alreadyCommenting(IDocument, IRegion)}</p> + * + * @param document {@link IDocument} that contains the given <code>regions</code> + * @param regions {@link IRegion}s to combine into one region and pass onto + * {@link #alreadyCommenting(IDocument, IRegion)} + * + * @return the result of a call to {@link #alreadyCommenting(IDocument, IRegion)} combining + * all of the given <code>regions</code> into one region + * + * @throws BadLocationException it is not the fault of the strategy if callers passes a bad + * <code>offset</code> and/or <code>length</code> for the given <code>model</code> + */ + public final boolean alreadyCommenting(IStructuredDocument document, IRegion[] regions) throws BadLocationException { + boolean alreadyCommenting = false; + if(regions != null && regions.length > 0) { + //create one region spanning all the given regions + int offset = regions[0].getOffset(); + int length = regions[regions.length-1].getOffset() + + regions[regions.length-1].getLength() - offset; + + IRegion region = new Region(offset, length); + alreadyCommenting = this.alreadyCommenting(document, region); + } + + return alreadyCommenting; + } + /** + * <p>Given a list of {@link ITypedRegion}s returns the sub set of that list that + * are of the comment region type associated with this strategy</p> + * + * @param typedRegions {@link ITypedRegion}s to filter down to only the comment regions + * associated with this strategy + * + * @return {@link List} of {@link ITypedRegion}s from the given <code>typedRegions</code> + * that are of the comment partition type associated with this strategy + */ + protected List getAssociatedCommentedRegions(ITypedRegion[] typedRegions) { + List commentedRegions = new ArrayList(); + + for(int i = 0; i < typedRegions.length; ++i) { + if(typedRegions[i].getType().equals(this.fAssociatedCommentPartitionTypeID)) { + commentedRegions.add(typedRegions[i]); + } + } + + return commentedRegions; + } + + /** + * <p>Given a list of {@link ITypedRegion}s returns a list of the partition + * type IDs taken from the given regions.</p> + * + * @param regions {@link ITypedRegion}s to get the partition type IDs from + * @return {@link List} of the partition type IDs taken from the given <code>regions</code> + */ + private static List getPartitionTypeIDs(ITypedRegion[] regions) { + List partitionTypes = new ArrayList(regions.length); + for(int i = 0; i < regions.length; ++i) { + partitionTypes.add(regions[i].getType()); + } + + return partitionTypes; + } + + /** + * <p>This method modifies the given document to remove the given comment + * prefix at the given comment prefix offset and the given comment + * suffix at the given comment suffix offset. In the case of removing + * a line comment that does not have a suffix, pass <code>null</code> + * for the comment suffix and it and its associated offset will + * be ignored.</p> + * + * <p><b>NOTE:</b> it is a good idea if a model is at hand when calling this to + * warn the model of an impending update</p> + * + * @param document the document to remove the comment from + * @param commentPrefixOffset the offset of the comment prefix + * @param commentSuffixOffset the offset of the comment suffix + * (ignored if <code>commentSuffix</code> is <code>null</code>) + * @param commentPrefix the prefix of the comment to remove from its associated given offset + * @param commentSuffix the suffix of the comment to remove from its associated given offset, + * or null if there is not suffix to remove for this comment + */ + protected static void uncomment(IDocument document, int commentPrefixOffset, String commentPrefix, + int commentSuffixOffset, String commentSuffix) { + + try { + //determine if there is a space after the comment prefix that should also be removed + int commentPrefixLength = commentPrefix.length(); + String postCommentPrefixChar = document.get(commentPrefixOffset + commentPrefix.length(), 1); + if(postCommentPrefixChar.equals(" ")) { + commentPrefixLength++; + } + + //remove the comment prefix + document.replace(commentPrefixOffset, commentPrefixLength, ""); //$NON-NLS-1$ + + if(commentSuffix != null) { + commentSuffixOffset -= commentPrefixLength; + + //determine if there is a space before the comment suffix that should also be removed + int commentSuffixLength = commentSuffix.length(); + String preCommentSuffixChar = document.get(commentSuffixOffset-1, 1); + if(preCommentSuffixChar.equals(" ")) { + commentSuffixLength++; + commentSuffixOffset--; + } + + //remove the comment suffix + document.replace(commentSuffixOffset, commentSuffixLength, ""); //$NON-NLS-1$ + } + } + catch (BadLocationException e) { + Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e); + } + } +} |