001/* 002 * Copyright (C) 2009-2011 Mathias Doenitz 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.parboiled.support; 018 019import org.parboiled.Context; 020import org.parboiled.matchers.Matcher; 021 022import static org.parboiled.common.Preconditions.checkArgNotNull; 023import static org.parboiled.common.Preconditions.checkArgument; 024 025/** 026 * Holds a snapshot of the current {@link Matcher} stack at a certain point during the parsing process. 027 * Implemented as a specialized, immutable single-linked list of Element objects with the deepest stack Element 028 * in the first position and the root at the end. 029 */ 030public class MatcherPath { 031 032 public static class Element { 033 public final Matcher matcher; 034 public final int startIndex; 035 public final int level; 036 037 public Element(Matcher matcher, int startIndex, int level) { 038 this.matcher = matcher; 039 this.startIndex = startIndex; 040 this.level = level; 041 } 042 } 043 044 public final Element element; 045 public final MatcherPath parent; 046 047 /** 048 * Constructs a new MatcherPath wrapping the given elements. 049 * Normally you don't construct a MatcherPath directly but rather call {@link Context#getPath()} to 050 * get one. 051 * 052 * @param element the last element of this path 053 * @param parent the parent path 054 */ 055 public MatcherPath(Element element, MatcherPath parent) { 056 this.element = checkArgNotNull(element, "element"); 057 this.parent = parent; 058 } 059 060 /** 061 * @return the length of this path, i.e. the number of matchers contained in it 062 */ 063 public int length() { 064 return element.level + 1; 065 } 066 067 /** 068 * Determines whether this path is a prefix of the given other path. 069 * 070 * @param that the other path 071 * @return true if this path is a prefix of the given other path 072 */ 073 public boolean isPrefixOf(MatcherPath that) { 074 checkArgNotNull(that, "that"); 075 return element.level <= that.element.level && 076 (this == that || (that.parent != null && isPrefixOf(that.parent))); 077 } 078 079 /** 080 * Returns the Element at the given level. 081 * @param level the level to get the element from 082 * @return the element 083 */ 084 public Element getElementAtLevel(int level) { 085 checkArgument(level >= 0); 086 if (level > element.level) return null; 087 if (level < element.level) return parent.getElementAtLevel(level); 088 return element; 089 } 090 091 /** 092 * Returns the common prefix of this MatcherPath and the given other one. 093 * 094 * @param that the other path 095 * @return the common prefix or null 096 */ 097 public MatcherPath commonPrefix(MatcherPath that) { 098 checkArgNotNull(that, "that"); 099 if (element.level > that.element.level) return parent.commonPrefix(that); 100 if (element.level < that.element.level) return commonPrefix(that.parent); 101 if (this == that) return this; 102 return (parent != null && that.parent != null) ? parent.commonPrefix(that.parent) : null; 103 } 104 105 /** 106 * Determines whether the given matcher is contained in this path. 107 * 108 * @param matcher the matcher 109 * @return true if contained 110 */ 111 public boolean contains(Matcher matcher) { 112 return element.matcher == matcher || (parent != null && parent.contains(matcher)); 113 } 114 115 @Override 116 public String toString() { 117 return toString(null); 118 } 119 120 public String toString(MatcherPath skipPrefix) { 121 return print(new StringBuilder(), skipPrefix).toString(); 122 } 123 124 private StringBuilder print(StringBuilder sb, MatcherPath skipPrefix) { 125 return (parent == skipPrefix ? sb : parent.print(sb, skipPrefix).append('/')).append(element.matcher); 126 } 127 128}