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.Action;
020import org.parboiled.ContextAware;
021import org.parboiled.MatcherContext;
022import org.parboiled.Rule;
023import org.parboiled.SkippableAction;
024import org.parboiled.errors.ActionError;
025import org.parboiled.errors.ActionException;
026import org.parboiled.matchervisitors.MatcherVisitor;
027
028import java.lang.reflect.Field;
029import java.util.ArrayList;
030import java.util.List;
031
032import static org.parboiled.common.Preconditions.checkArgNotNull;
033
034/**
035 * A {@link Matcher} that not actually matches input but runs a given parser {@link Action}.
036 */
037public class ActionMatcher extends AbstractMatcher {
038    public final Action action;
039    public final List<ContextAware> contextAwares = new ArrayList<ContextAware>();
040    public final boolean skipInPredicates;
041
042    public ActionMatcher(Action action) {
043        super(checkArgNotNull(action, "action").toString());
044        this.action = action;
045
046        skipInPredicates = action instanceof SkippableAction && ((SkippableAction) action).skipInPredicates();
047
048        // check whether the action is a synthetic class generated by parboiled transformation
049        // if so it will take care of context management itself and we can return immediately
050        if (action.getClass().isSynthetic()) return;
051
052        if (action instanceof ContextAware) {
053            contextAwares.add((ContextAware) action);
054        }
055        // in order to make anonymous inner classes and other member classes work seamlessly
056        // we collect the synthetic references to the outer parent classes and inform them of
057        // the current parsing context if they implement ContextAware
058        for (Field field : action.getClass().getDeclaredFields()) {
059            if (field.isSynthetic() && ContextAware.class.isAssignableFrom(field.getType())) {
060                field.setAccessible(true);
061                try {
062                    ContextAware contextAware = (ContextAware) field.get(action);
063                    if (contextAware != null) contextAwares.add(contextAware);
064                } catch (IllegalAccessException e) {
065                    // ignore
066                } finally {
067                    field.setAccessible(false);
068                }
069            }
070        }
071    }
072
073    @Override
074    public MatcherContext getSubContext(MatcherContext context) {
075        MatcherContext subContext = context.getBasicSubContext();
076        subContext.setMatcher(this);
077        if (context.getCurrentIndex() > context.getStartIndex()) {
078            // if we have already matched something we must be in a sequence at the second or later position
079            // the subcontext contains match data that the action might want to access, so we use the existing
080            // subcontext without reinitializing
081            return subContext;
082        } else {
083            return context.getSubContext(this);
084        }
085    }
086
087    @SuppressWarnings({"unchecked"})
088    public <V> boolean match(MatcherContext<V> context) {
089        if (skipInPredicates && context.inPredicate()) return true;
090
091        // actions need to run in the parent context
092        MatcherContext parentContext = context.getParent();
093        if (!contextAwares.isEmpty()) {
094            for (ContextAware contextAware : contextAwares) {
095                contextAware.setContext(parentContext);
096            }
097        }
098
099        try {
100            Object valueStackSnapshot = context.getValueStack().takeSnapshot();
101            if (!action.run(parentContext)) {
102                // failing actions are not allowed to change the ValueStack
103                context.getValueStack().restoreSnapshot(valueStackSnapshot);
104                return false;
105            }
106
107            // since we initialize the actions own context only partially in getSubContext(MatcherContext)
108            // (in order to be able to still access the previous subcontexts fields in action expressions)
109            // we need to make sure to not accidentally advance the current index of our parent with some old
110            // index from a previous subcontext, so we explicitly set the marker here
111            context.setCurrentIndex(parentContext.getCurrentIndex());
112            return true;
113        } catch (ActionException e) {
114            context.getParseErrors().add(new ActionError(context.getInputBuffer(), context.getCurrentIndex(),
115                    e.getMessage(), context.getPath(), e));
116            return false;
117        }
118    }
119
120    @Override
121    public Rule suppressNode() {
122        return this; // actions are already "suppressNode"
123    }
124
125    public <R> R accept(MatcherVisitor<R> visitor) {
126        checkArgNotNull(visitor, "visitor");
127        return visitor.visit(this);
128    }
129
130}