/* Copyright (c) 2009 Google Inc. * * 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.google.wave.api; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Map.Entry; /** * An iterator over blip content, that consists of text or element. * * Please note that this iterator does not support {@code remove} operation at * the moment. * * @param <T> the generic type of the target entity to match. */ public abstract class BlipIterator<T> implements Iterator<Range> { /** The blip to be iterated. */ protected final Blip blip; /** The target to be matched. */ protected final T target; /** The maximum number of iterations allowed. */ private final int maxHits; /** The range size of a match. */ private final int rangeSize; /** The number of allowed iterations left. */ private int hitsLeft; /** The current position of the iterator. */ protected int position; /** * Constructor. * * @param blip the blip to be iterated. * @param target the target to be matched. * @param maxHits the maximum number of iterations allowed. * @param rangeSize the size of a matching range, for example, 1 for element * iteration, or the length of the {@code target} for text/string * iteration. */ protected BlipIterator(Blip blip, T target, int maxHits, int rangeSize) { this.blip = blip; this.target = target; this.maxHits = maxHits; this.rangeSize = rangeSize; reset(); } @Override public boolean hasNext() { return hitsLeft != 0 && getNextIndex() != -1; } @Override public Range next() { if (hitsLeft == 0) { throw new NoSuchElementException(); } int index = getNextIndex(); if (index == -1) { throw new NoSuchElementException(); } hitsLeft--; position = index; return new Range(position, position + rangeSize); } /** * {@code remove} is not supported. * * @throws UnsupportedOperationException this operation is not supported yet. * it will throw {@link UnsupportedOperationException} on all invocations. */ @Override public void remove() { throw new UnsupportedOperationException(); } /** * Shifts the iterator cursor. * * @param shiftAmount the shift amount. */ void shift(int shiftAmount) { this.position += shiftAmount; } /** * Shifts the iterator cursor to beginning, to reset iteration. */ void reset() { this.position = -1; this.hitsLeft = maxHits; } /** * Returns the index of the next match. * * @return the index of the next match, or -1 if there is no more match. */ protected abstract int getNextIndex(); /** * A blip iterator that allows a single iteration over a given range. */ static final class SingleshotIterator extends BlipIterator<Void> { /** The starting index of the range. */ private final int start; /** * Constructor. * * @param blip the blip to iterate. * @param start the start index of the range to iterate. * @param end the end index of the range to iterate. */ public SingleshotIterator(Blip blip, int start, int end) { super(blip, null, 1, end - start); int length = blip.getContent().length(); this.start = start; } @Override protected int getNextIndex() { return start; } } /** * A blip iterator that allows iteration over text/string content. */ static final class TextIterator extends BlipIterator<String> { /** * Constructor. * * @param blip the blip to be iterated. * @param target the string to be matched. * @param maxHits the maximum number of iterations allowed. */ public TextIterator(Blip blip, String target, int maxHits) { super(blip, target, maxHits, target.length()); } @Override protected int getNextIndex() { return blip.getContent().indexOf(target, position + 1); } } /** * A blip iterator that allows iteration over element content, such as, * gadget, form element, image, and so on. */ static final class ElementIterator extends BlipIterator<ElementType> { /** A map of restrictions that would be applied during iteration. */ private final Map<String, String> restrictions; /** * Constructor. * * @param blip the blip to be iterated. * @param target the string to be matched. * @param restrictions a map of restrictions. * @param maxHits the maximum number of iterations allowed. */ public ElementIterator(Blip blip, ElementType target, Map<String, String> restrictions, int maxHits) { super(blip, target, maxHits, 1); this.restrictions = restrictions; } @Override protected int getNextIndex() { int index = -1; for (Entry<Integer, Element> entry : blip.getElements().tailMap(position + 1).entrySet()) { if (match(entry.getValue())) { index = entry.getKey(); break; } } return index; } /** * Checks whether the given {@code element} is a match or not, by checking * the type and the properties. * * @param element the element to check. * @return {@code true} if the element type matches the target type, and * the all filters/restrictions are satisfied. */ private boolean match(Element element) { if (element.getType() != target) { return false; } if (restrictions == null) { return true; } for (Entry<String, String> entry : restrictions.entrySet()) { if (!entry.getValue().equals(element.getProperty(entry.getKey()))) { return false; } } return true; } } }