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.matchers; 018 019import org.parboiled.MatcherContext; 020import org.parboiled.Rule; 021import org.parboiled.matchervisitors.MatcherVisitor; 022 023import java.util.List; 024 025import static org.parboiled.common.Preconditions.checkArgNotNull; 026 027/** 028 * A {@link Matcher} that delegates all {@link Rule} and {@link Matcher} interface methods to another {@link Matcher}. 029 * It can also hold a label and a leaf marker and lazily apply these to the underlying {@link Matcher} once it is available. 030 */ 031public class ProxyMatcher implements Matcher, Cloneable { 032 private Matcher target; 033 private String label; 034 private boolean nodeSuppressed; 035 private boolean subnodesSuppressed; 036 private boolean nodeSkipped; 037 private boolean memoMismatches; 038 private boolean dirty; 039 040 public List<Matcher> getChildren() { 041 if (dirty) apply(); 042 return target.getChildren(); 043 } 044 045 public void setLabel(String label) { 046 this.label = label; 047 updateDirtyFlag(); 048 } 049 050 private void setNodeSuppressed(boolean nodeSuppressed) { 051 this.nodeSuppressed = nodeSuppressed; 052 updateDirtyFlag(); 053 } 054 055 private void setSubnodesSuppressed(boolean subnodesSuppressed) { 056 this.subnodesSuppressed = subnodesSuppressed; 057 updateDirtyFlag(); 058 } 059 060 private void setNodeSkipped(boolean nodeSkipped) { 061 this.nodeSkipped = nodeSkipped; 062 updateDirtyFlag(); 063 } 064 065 private void setMemoMismatches(boolean memoMismatches) { 066 this.memoMismatches = memoMismatches; 067 updateDirtyFlag(); 068 } 069 070 private void updateDirtyFlag() { 071 dirty = label != null || nodeSuppressed || subnodesSuppressed || nodeSkipped || memoMismatches; 072 } 073 074 public <V> boolean match(MatcherContext<V> context) { 075 if (dirty) apply(); 076 return target.match(context); 077 } 078 079 public String getLabel() { 080 if (dirty) apply(); 081 return target.getLabel(); 082 } 083 084 public boolean hasCustomLabel() { 085 if (dirty) apply(); 086 return target.hasCustomLabel(); 087 } 088 089 public boolean isNodeSuppressed() { 090 if (dirty) apply(); 091 return target.isNodeSuppressed(); 092 } 093 094 public boolean areSubnodesSuppressed() { 095 if (dirty) apply(); 096 return target.areSubnodesSuppressed(); 097 } 098 099 public boolean isNodeSkipped() { 100 if (dirty) apply(); 101 return target.isNodeSkipped(); 102 } 103 104 public boolean areMismatchesMemoed() { 105 if (dirty) apply(); 106 return target.areMismatchesMemoed(); 107 } 108 109 public void setTag(Object tagObject) { 110 if (dirty) apply(); 111 target.setTag(tagObject); 112 } 113 114 public Object getTag() { 115 if (dirty) apply(); 116 return target.getTag(); 117 } 118 119 public <R> R accept(MatcherVisitor<R> visitor) { 120 checkArgNotNull(visitor, "visitor"); 121 if (dirty) apply(); 122 return target.accept(visitor); 123 } 124 125 @Override 126 public String toString() { 127 if (target == null) return super.toString(); 128 if (dirty) apply(); 129 return target.toString(); 130 } 131 132 private void apply() { 133 if (label != null) label(label); 134 if (nodeSuppressed) suppressNode(); 135 if (subnodesSuppressed) suppressSubnodes(); 136 if (nodeSkipped) skipNode(); 137 } 138 139 public Rule label(String label) { 140 if (target == null) { 141 // if we have no target yet we need to save the label and "apply" it later 142 if (this.label == null) { 143 setLabel(label); 144 return this; 145 } 146 147 // this proxy matcher is already waiting for its label application opportunity, 148 // so we need to create another proxy level 149 ProxyMatcher anotherProxy = createClone(); 150 anotherProxy.setLabel(label); 151 anotherProxy.arm(this); 152 return anotherProxy; 153 } 154 155 // we already have a target to which we can directly apply the label 156 Rule inner = unwrap(target); 157 target = (Matcher) inner.label(label); // since relabelling might change the instance we have to update it 158 setLabel(null); 159 return target; 160 } 161 162 public Rule suppressNode() { 163 if (target == null) { 164 // if we have no target yet we need to save the marker and "apply" it later 165 setNodeSuppressed(true); 166 return this; 167 } 168 169 // we already have a target to which we can directly apply the marker 170 Rule inner = unwrap(target); 171 target = (Matcher) inner.suppressNode(); // since this might change the instance we have to update it 172 setNodeSuppressed(false); 173 return target; 174 } 175 176 public Rule suppressSubnodes() { 177 if (target == null) { 178 // if we have no target yet we need to save the marker and "apply" it later 179 setSubnodesSuppressed(true); 180 return this; 181 } 182 183 // we already have a target to which we can directly apply the marker 184 Rule inner = unwrap(target); 185 target = (Matcher) inner.suppressSubnodes(); // since this might change the instance we have to update it 186 setSubnodesSuppressed(false); 187 return target; 188 } 189 190 public Rule skipNode() { 191 if (target == null) { 192 // if we have no target yet we need to save the marker and "apply" it later 193 setNodeSkipped(true); 194 return this; 195 } 196 197 // we already have a target to which we can directly apply the marker 198 Rule inner = unwrap(target); 199 target = (Matcher) inner.skipNode(); // since this might change the instance we have to update it 200 setNodeSkipped(false); 201 return target; 202 } 203 204 public Rule memoMismatches() { 205 if (target == null) { 206 // if we have no target yet we need to save the marker and "apply" it later 207 setMemoMismatches(true); 208 return this; 209 } 210 211 // we already have a target to which we can directly apply the marker 212 Rule inner = unwrap(target); 213 target = (Matcher) inner.memoMismatches(); // since this might change the instance we have to update it 214 setMemoMismatches(false); 215 return target; 216 } 217 218 /** 219 * Supplies this ProxyMatcher with its underlying delegate. 220 * 221 * @param target the Matcher to delegate to 222 */ 223 public void arm(Matcher target) { 224 this.target = checkArgNotNull(target, "target"); 225 } 226 227 /** 228 * Retrieves the innermost Matcher that is not a ProxyMatcher. 229 * 230 * @param matcher the matcher to unwrap 231 * @return the given instance if it is not a ProxyMatcher, otherwise the innermost non-proxy Matcher 232 */ 233 public static Matcher unwrap(Matcher matcher) { 234 if (matcher instanceof ProxyMatcher) { 235 ProxyMatcher proxyMatcher = (ProxyMatcher) matcher; 236 if (proxyMatcher.dirty) proxyMatcher.apply(); 237 return proxyMatcher.target; 238 } 239 return matcher; 240 } 241 242 public MatcherContext getSubContext(MatcherContext context) { 243 if (dirty) apply(); 244 return target.getSubContext(context); 245 } 246 247 // creates a shallow copy 248 private ProxyMatcher createClone() { 249 try { 250 return (ProxyMatcher) clone(); 251 } catch (CloneNotSupportedException e) { 252 throw new IllegalStateException(); 253 } 254 } 255 256}