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.shallowparser;
018
019import java.util.Iterator;
020import java.util.List;
021import java.util.NoSuchElementException;
022
023import org.conqat.lib.commons.assertion.CCSMAssert;
024
025import eu.cqse.check.framework.scanner.ETokenType;
026import eu.cqse.check.framework.scanner.IToken;
027
028/**
029 * Iterator over a token list that keeps track of nesting.
030 */
031public class NestingAwareTokenIterator implements Iterator<IToken> {
032
033        /** The tokens over which this iterator iterates. */
034        private final List<IToken> tokens;
035
036        /**
037         * The current index into the token list, i.e. the token that will be returned
038         * by the next call to the iterator's {@link #next()} method.
039         */
040        private int currentPosition;
041
042        /** The token types that open an new nesting level. */
043        private final List<ETokenType> openingTypes;
044
045        /** The token types that close a nesting level. */
046        private final List<ETokenType> closingTypes;
047
048        /**
049         * <code>true</code> if the last token returned by {@link #next()} was a token
050         * from {@link #closingTypes}. This information is used to report correct
051         * nesting depth for closing tokens.
052         */
053        private boolean isClosingToken = false;
054
055        /**
056         * Records the number of open nesting levels per entry in {@link #openingTypes}.
057         */
058        private final int[] nestingDepths;
059
060        /**
061         * Constructor.
062         * 
063         * @param tokens
064         *            the stream over which to iterate.
065         * @param startPosition
066         *            the index of the first token that should be returned by the
067         *            iterator.
068         * @param openingTypes
069         *            the list of tokens that open an new nesting level.
070         * @param closingTypes
071         *            the list of tokens that close a nesting level. Each entry must
072         *            correspond to the entry of openingTypes at the same position.
073         */
074        public NestingAwareTokenIterator(List<IToken> tokens, int startPosition, List<ETokenType> openingTypes,
075                        List<ETokenType> closingTypes) {
076                CCSMAssert.isTrue(openingTypes.size() == closingTypes.size(), "Open and close tokens must have the same size");
077                this.tokens = tokens;
078                this.currentPosition = startPosition;
079                this.openingTypes = openingTypes;
080                this.closingTypes = closingTypes;
081                this.nestingDepths = new int[openingTypes.size()];
082        }
083
084        /** {@inheritDoc} */
085        @Override
086        public boolean hasNext() {
087                return currentPosition < tokens.size();
088        }
089
090        /** {@inheritDoc} */
091        @Override
092        public IToken next() {
093                if (currentPosition >= tokens.size()) {
094                        throw new NoSuchElementException("The iterator is exhausted");
095                }
096                IToken token = tokens.get(currentPosition);
097                currentPosition++;
098                updateNestingInfo(token);
099                return token;
100        }
101
102        /**
103         * Updates the {@link #nestingDepths} array based on the type of the given
104         * token.
105         */
106        private void updateNestingInfo(IToken token) {
107                isClosingToken = false;
108                ETokenType type = token.getType();
109                int openIndex = openingTypes.indexOf(type);
110                if (openIndex != -1) {
111                        nestingDepths[openIndex]++;
112                }
113                int closeIndex = closingTypes.indexOf(type);
114                if (closeIndex != -1 && nestingDepths[closeIndex] > 0) {
115                        nestingDepths[closeIndex]--;
116                        isClosingToken = true;
117                }
118        }
119
120        /**
121         * Returns <code>true</code> if the last token returned by {@link #next()} is a
122         * top-level token. Opening/closing tokens are never top-level. In case
123         * opening/closing tokens are not balanced, returns <code>true</code> for
124         * superfluous closing tokens and tokens thereafter.
125         */
126        public boolean isTopLevel() {
127                return getNestingDepth() <= 0;
128        }
129
130        /**
131         * Returns the depth of the current nesting. Opening/closing tokens are reported
132         * as the nesting level they introduce. In case opening/closing tokens are not
133         * balanced, returns negative numbers for superfluous closing tokens and tokens
134         * thereafter.
135         */
136        public int getNestingDepth() {
137                int sum = 0;
138                for (int value : nestingDepths) {
139                        sum += value;
140                }
141                if (isClosingToken) {
142                        sum += 1;
143                }
144                return sum;
145        }
146
147        /**
148         * Returns the index of the current token, i.e. the token returned by the last
149         * call to {@link #next()}. The result of this method is thus only valid if you
150         * called {@link #next()} at least once.
151         */
152        public int getCurrentIndex() {
153                return currentPosition - 1;
154        }
155
156        /** {@inheritDoc} */
157        @Override
158        public void remove() {
159                throw new UnsupportedOperationException("This iterator does not support removal of elements");
160        }
161}