001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package eu.cqse.check.framework.util.tokens;
018
019/**
020 * Matches a nested structure (e.g. nested parentheses).
021 */
022public class SkipNestedPattern extends TokenPatternBase {
023
024        /** Matches the token that increases the nesting level. */
025        private final TokenPatternBase openMatcher;
026
027        /** Matches the token that decreases the nesting level. */
028        private final TokenPatternBase closeMatcher;
029
030        /**
031         * If this is <code>true</code>, this pattern matches, even if no nested
032         * structure is found at the current position. In this case, an empty match is
033         * returned.
034         */
035        private final boolean optional;
036
037        /** Constructor. */
038        public SkipNestedPattern(TokenPatternBase openMatcher, TokenPatternBase closeMatcher, boolean optional) {
039                this.openMatcher = openMatcher;
040                this.closeMatcher = closeMatcher;
041                this.optional = optional;
042        }
043
044        /** {@inheritDoc} */
045        @Override
046        protected TokenPatternMatch matchesLocally(TokenStream stream) {
047                int beforeMatch = stream.getPosition();
048                TokenPatternMatch parentMatch = createMatch(stream);
049
050                TokenPatternMatch openMatch = openMatcher.matches(stream);
051                if (openMatch == null) {
052                        if (optional) {
053                                stream.setPosition(beforeMatch);
054                                return createMatch(stream);
055                        }
056                        return null;
057                }
058                parentMatch.mergeFrom(openMatch);
059
060                int level = 1;
061                while (level > 0) {
062                        if (stream.isExhausted()) {
063                                if (optional) {
064                                        stream.setPosition(beforeMatch);
065                                        return createMatch(stream);
066                                }
067                                return null;
068                        }
069
070                        int beforeAlternative = stream.getPosition();
071                        TokenPatternMatch alternativeSubMatch = closeMatcher.matches(stream);
072                        if (alternativeSubMatch != null) {
073                                parentMatch.mergeFrom(alternativeSubMatch);
074                                level -= 1;
075                                continue;
076                        }
077                        stream.setPosition(beforeAlternative);
078
079                        alternativeSubMatch = openMatcher.matches(stream);
080                        if (alternativeSubMatch != null) {
081                                parentMatch.mergeFrom(alternativeSubMatch);
082                                level += 1;
083                                continue;
084                        }
085                        stream.setPosition(beforeAlternative);
086
087                        stream.next();
088                }
089
090                return parentMatch;
091        }
092
093        /** {@inheritDoc} */
094        @Override
095        public String toString() {
096                return "SkipNested (OPEN: " + openMatcher.toString() + " CLOSE: " + closeMatcher.toString() + " optional: "
097                                + openMatcher + ")";
098        }
099
100}