package net.bitpot.railways.parser.route; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parses a single route to break it down into tokens. * * @author Basil Gren * on 21.02.14. */ public class RoutePathParser extends TextChunkHighlighter { private static final Pattern PLAIN_URI = Pattern.compile("^[^(:]+"); private static final Pattern PARAMETER = Pattern.compile("^:[a-zA-Z_]+"); private static RoutePathParser instance = null; public static RoutePathParser getInstance() { if (instance == null) instance = new RoutePathParser(); return instance; } /** * Parses Rails route and returns array of tokens. * * @param routePath String with route path. * @return Array of RouteTokens. */ @NotNull public List<TextChunk> parse(String routePath) { RoutePathChunk chunk; ArrayList<TextChunk> chunks = new ArrayList<TextChunk>(); int pos = 0; // Try to find chunk while((chunk = parseToken(routePath, pos)) != null) { chunks.add(chunk); pos = chunk.getEndOffset(); } return chunks; } private static RoutePathChunk parseToken(String routePart, int startPos) { RoutePathChunk chunk; chunk = parseOptionalPart(routePart, startPos); if (chunk == null) chunk = parsePlainToken(routePart, startPos); if (chunk == null) chunk = parseSymbolToken(routePart, startPos); return chunk; } private static RoutePathChunk parsePlainToken(String routePart, int startPos) { RoutePathChunk token = null; // Firstly try to find text Matcher matcher = PLAIN_URI.matcher(routePart); matcher.region(startPos, routePart.length()); if (matcher.find()) token = new RoutePathChunk( matcher.group(), RoutePathChunk.PLAIN, matcher.start()); return token; } private static RoutePathChunk parseSymbolToken(String routePart, int startPos) { RoutePathChunk chunk = null; Matcher matcher = PARAMETER.matcher(routePart); matcher.region(startPos, routePart.length()); if (matcher.find()) chunk = new RoutePathChunk( matcher.group(), RoutePathChunk.PARAMETER, matcher.start()); return chunk; } private static RoutePathChunk parseOptionalPart(String routePart, int startPos) { if (startPos >= routePart.length() || routePart.charAt(startPos) != '(') return null; // As we check that our string should start from '(', we count that we // already have one opening bracket. int openedCount = 0; boolean isBalanced = false; int endPos = startPos; while (endPos < routePart.length()) { char c = routePart.charAt(endPos); if (c == ')') openedCount--; else if (c == '(') openedCount++; endPos++; if (openedCount == 0) { isBalanced = true; break; } } return new RoutePathChunk(routePart.substring(startPos, endPos), isBalanced ? RoutePathChunk.OPTIONAL : RoutePathChunk.PLAIN, startPos); } @NotNull @Override protected TextChunk createChunk(@NotNull String text, int chunkType, int offsetAbs) { return new RoutePathChunk(text, chunkType, offsetAbs); } }