/**
* Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below.
* 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:
* Cloudsmith
*
*/
package org.cloudsmith.xtext.dommodel;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.Triple;
import org.eclipse.xtext.util.Tuples;
import com.google.common.base.Preconditions;
/**
* <p>
* An instance of RegionMatch matches a given {@link IDomNode} or (more detailed) a {@link CharSequence} and a <code>startOffset</code> with an
* {@link ITextRegion} to compute the match/relationship between the given text and the region.
* </p>
* <p>
* The matcher is then used to answer question about the relationship {@link #isOutside()}, or {@link #getIntersectionType()}, and can also apply a split based on
* the relationship using {@link #apply()}.
*
*/
public class RegionMatch {
public static enum IntersectionType {
BEFORE, CONTAINED, LASTPART_INSIDE, MIDPART_INSIDE, FIRSTPART_INSIDE, AFTER
}
final RegionMatch.IntersectionType type;
final int regionOffset;
final int regionLength;
final CharSequence text;
final int textOffset;
final int textLength;
private static final CharSequence emptySequence = "";
public RegionMatch(CharSequence s, int startOffset, ITextRegion region /* nullable */) {
Preconditions.checkArgument(startOffset >= 0);
text = s == null
? ""
: s;
this.textOffset = startOffset;
textLength = text.length();
int regionEnd = 0;
if(region == null) {
this.type = IntersectionType.CONTAINED;
regionOffset = 0;
regionLength = Integer.MAX_VALUE;
regionEnd = Integer.MAX_VALUE;
}
else {
regionOffset = region.getOffset();
regionLength = region.getLength();
regionEnd = regionOffset + Math.max(0, regionLength - 1);
int textEnd = textOffset + Math.max(0, textLength - 1);
if(startOffset > regionEnd) {
type = IntersectionType.AFTER;
}
else if(textEnd < regionOffset) {
type = IntersectionType.BEFORE;
}
else if(textOffset < regionOffset && textEnd > regionEnd) {
type = IntersectionType.MIDPART_INSIDE;
}
else if(startOffset < regionOffset) {
type = IntersectionType.LASTPART_INSIDE;
}
else if(textEnd > regionEnd) {
type = IntersectionType.FIRSTPART_INSIDE;
}
else
type = IntersectionType.CONTAINED;
}
}
public RegionMatch(IDomNode node, ITextRegion r) {
this(Preconditions.checkNotNull(node).getText(), node.getOffset(), r);
}
/**
* Applies the region match to the text captured from the node when the match was made.
* The return {@link Triple} returns first = part inside region, second = part before region, third = part after
* region. All three elements are always set - non existant parts are represented by empty sequences.
*
* @return a Triple with parts(before, inside, after)
*/
public Triple<CharSequence, CharSequence, CharSequence> apply() {
int limit = 0;
switch(type) {
case BEFORE:
return Tuples.create(emptySequence, text, emptySequence);
case AFTER:
return Tuples.create(emptySequence, emptySequence, text);
case CONTAINED:
return Tuples.create(text, emptySequence, emptySequence);
case LASTPART_INSIDE:
limit = regionOffset - textOffset;
return Tuples.create(text.subSequence(limit, textLength), text.subSequence(0, limit), emptySequence);
case MIDPART_INSIDE:
limit = regionOffset - textOffset;
return Tuples.create(
text.subSequence(limit, limit + regionLength), text.subSequence(0, limit),
text.subSequence(limit + regionLength, textLength));
case FIRSTPART_INSIDE:
limit = regionOffset + regionLength - textOffset;
return Tuples.create(text.subSequence(0, limit), emptySequence, text.subSequence(limit, textLength));
default:
throw new IllegalStateException("should not happen - not a supported region match case");
}
}
/**
* @return the text region intersection type
*/
public RegionMatch.IntersectionType getIntersectionType() {
return type;
}
/**
* @return true when both start and end are inside the region
*/
public boolean isContained() {
return type == IntersectionType.CONTAINED;
}
/**
* @return true when start or end is inside the region
*/
public boolean isInside() {
return !isOutside();
}
/**
* @return true when neither start nor end is inside the region
*/
public boolean isOutside() {
return type == IntersectionType.BEFORE || type == IntersectionType.AFTER;
}
}