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}