/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.civilian-framework.org/license.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.civilian.resource;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* PathScanner is a helper class used during resource dispatch.
* It allows iteration of the segments of a request path.
*/
// PathScanner avoids splitting the path at segment boundaries which
public class PathScanner
{
/**
* Creates a new PathScanner for a path string.
* If the last segment of the path has an extension
* it will be ignored.
* If the then last segment equals "/index" it will also be ignored.
*/
public PathScanner(String path)
{
path_ = path != null ? path : "";
end_ = path_.length();
// ignore trailing extension
int lastSlashPos = path_.lastIndexOf('/');
int extPos = path_.indexOf('.', lastSlashPos + 1);
if (extPos >= 0)
end_ = extPos;
// ignore trailing '/index'
if ((end_ >= 6) && path_.regionMatches(end_ - 6, "/index", 0, 6))
end_ -= 6;
// ignore leading '/'
if ((end_ > 0) && (path_.charAt(0) == '/'))
segmentStart_ = 1;
initNextSegment();
}
/**
* Returns the whole path of the scanner.
*/
public String getPath()
{
return path_;
}
/**
* Returns the current scan position in the path.
*/
public int getPosition()
{
return segmentStart_;
}
public void setPosition(int position)
{
segmentStart_ = position;
initNextSegment();
}
private void initNextSegment()
{
int p = path_.indexOf('/', segmentStart_);
segmentEnd_ = p < 0 ? end_ : p;
segmentLen_ = segmentEnd_ - segmentStart_;
}
/**
* Returns true if the scanner has not yet reached
* the end of the path.
*/
public boolean hasMore()
{
return segmentStart_ < end_;
}
/**
* Returns the current segment.
*/
public String getSegment()
{
return path_.substring(segmentStart_, segmentEnd_);
}
/**
* Returns if the current segment equals the given segment.
*/
public boolean matchSegment(String segment)
{
return (segment.length() == segmentLen_) &&
path_.regionMatches(false, segmentStart_, segment, 0, segmentLen_);
}
/**
* Returns if the path substring starting at the current scan position
* matches the pattern. The pattern can match one or more path segments
* (therefore allowing a lookahead for multiple segments).
* It can also match only whole segments.
*/
public MatchResult matchPattern(Pattern pattern)
{
Matcher matcher = pattern.matcher(path_);
matcher.region(segmentStart_, end_);
return matcher.lookingAt() && isSegmentEnd(matcher.end()) ?
matcher.toMatchResult() :
null;
}
/**
* Advances the scanner to the next segment.
* Does nothing if the scanner is already at the end of the path.
*/
public void next()
{
segmentStart_ = segmentEnd_ + 1;
if (!hasMore())
{
segmentEnd_ = segmentStart_;
segmentLen_ = 0;
}
else
initNextSegment();
}
/**
* Positions the scanner at the given position.
* @throws IllegalArgumentException if the position is not a valid segment boundary.
*/
public void next(int end)
{
if (!isSegmentEnd(end))
throw new IllegalArgumentException("not a segment or path end: " + end);
segmentEnd_ = end;
next();
}
/**
* Positions the scanner at the end of the matched string.
* @param result a MatchResult obtained by a call to {@link #matchPattern(Pattern)}
*/
public void next(MatchResult result)
{
next(result.end());
}
private boolean isSegmentEnd(int pos)
{
return (pos > segmentStart_) && ((pos == end_ ) || ((pos < end_) && (path_.charAt(pos) == '/')));
}
private int end_;
private String path_;
private int segmentStart_;
private int segmentEnd_;
private int segmentLen_;
}