/* * Copyright (C) 2009-2011 Mathias Doenitz * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * 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 com.github.fge.grappa.matchers.wrap; import com.github.fge.grappa.matchers.MatcherType; import com.github.fge.grappa.matchers.base.Matcher; import com.github.fge.grappa.rules.Rule; import com.github.fge.grappa.run.context.MatcherContext; import java.util.List; import java.util.Objects; /** * A {@link Matcher} that delegates all {@link Rule} and {@link Matcher} * interface methods to another {@link Matcher} * * <p>It can also hold a label and a leaf marker and lazily apply these to the * underlying {@link Matcher} once it is available.</p> */ // TODO: REMOVE!! It is THE pain point in generation today public final class ProxyMatcher implements Matcher, Cloneable { private Matcher target; private String label; private boolean dirty; @Override public MatcherType getType() { if (dirty) apply(); return target.getType(); } @Override public List<Matcher> getChildren() { if (dirty) apply(); return target.getChildren(); } public void setLabel(final String label) { this.label = label; updateDirtyFlag(); } /* * TODO: here in particular * * This is UGLY!! Builders to the rescue? */ private void updateDirtyFlag() { dirty = label != null; } @Override public <V> boolean match(final MatcherContext<V> context) { if (dirty) apply(); return target.match(context); } @Override public String getLabel() { if (dirty) apply(); return target.getLabel(); } @Override public boolean hasCustomLabel() { if (dirty) apply(); return target.hasCustomLabel(); } @Override public String toString() { if (target == null) return super.toString(); if (dirty) apply(); return target.toString(); } private void apply() { if (label != null) label(label); } @Override public Rule label(final String label) { if (target == null) { // if we have no target yet we need to save the label and "apply" it later if (this.label == null) { setLabel(label); return this; } // this proxy matcher is already waiting for its label application opportunity, // so we need to create another proxy level final ProxyMatcher anotherProxy = createClone(); anotherProxy.setLabel(label); anotherProxy.arm(this); return anotherProxy; } // we already have a target to which we can directly apply the label final Rule inner = unwrap(target); // since relabelling might change the instance we have to update it target = (Matcher) inner.label(label); setLabel(null); return target; } /** * Supplies this ProxyMatcher with its underlying delegate. * * @param target the Matcher to delegate to */ public void arm(final Matcher target) { this.target = Objects.requireNonNull(target, "target"); } /** * Retrieves the innermost Matcher that is not a ProxyMatcher. * * @param matcher the matcher to unwrap * @return the given instance if it is not a ProxyMatcher, otherwise the innermost non-proxy Matcher */ public static Matcher unwrap(final Matcher matcher) { if (matcher instanceof ProxyMatcher) { final ProxyMatcher proxyMatcher = (ProxyMatcher) matcher; if (proxyMatcher.dirty) proxyMatcher.apply(); return proxyMatcher.target == null ? proxyMatcher : proxyMatcher.target; } return matcher; } @Override public <V> MatcherContext<V> getSubContext(final MatcherContext<V> context) { if (dirty) apply(); return target.getSubContext(context); } // creates a shallow copy private ProxyMatcher createClone() { try { return (ProxyMatcher) clone(); } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); } } }