/** * Copyright 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.data; import com.google.common.base.Preconditions; import com.google.wave.api.ElementType; import com.google.wave.api.Range; import java.util.Map; import java.util.Map.Entry; /** * A interface encapsulating iterating through a document. * * <p> * At some point we could consider turning this into an actual Iterable/Iterator * thing * */ public interface DocumentHitIterator { /** * @returns the next range yielded by this Iterator or null if we're done. */ Range next(); /** * Notify the iterator that the underlying document has changed; a change with * size delta has been applied at where. */ void shift(int where, int delta); /** * Singleshot returns a single range. */ class Singleshot implements DocumentHitIterator { private final Range range; private boolean called; public Singleshot(Range range) { this.called = false; this.range = range; } @Override public Range next() { if (called) { return null; } called = true; return this.range; } @Override public void shift(int where, int delta) { } } /** * TextMatcher yields all ranges in the document matching the searched for * string. */ class TextMatcher implements DocumentHitIterator { private final ApiView apiView; private final String searchFor; private int from; private int hitsLeft; public TextMatcher(ApiView apiView, String searchFor, int maxHits) { Preconditions.checkNotNull(apiView, "Api view must not be null"); Preconditions.checkNotNull(searchFor, "The string to search for must not be null"); this.apiView = apiView; this.searchFor = searchFor; this.from = -1; this.hitsLeft = maxHits; } @Override public Range next() { if (hitsLeft == 0) { return null; } hitsLeft--; String searchIn = apiView.apiContents(); int next = searchIn.indexOf(searchFor, from + 1); if (next == -1) { return null; } from = next; return new Range(from, from + searchFor.length()); } @Override public void shift(int where, int delta) { if (from != -1) { if (where - 1 <= from) { from += delta; } } } } /** * ElementMatcher yields all ranges in the document matching the searched for * element type. */ class ElementMatcher implements DocumentHitIterator { private int hitsLeft; private int index; private final ApiView apiView; private final ElementType elementType; private final Map<String, String> restrictions; public ElementMatcher( ApiView apiView, ElementType elementType, Map<String, String> restrictions, int maxRes) { Preconditions.checkNotNull(apiView, "Api view must not be null"); Preconditions.checkNotNull(elementType, "The type of element to search for must not be null"); Preconditions.checkNotNull(restrictions, "The search restricitions must not be null"); this.elementType = elementType; this.apiView = apiView; this.index = -1; this.hitsLeft = maxRes; this.restrictions = restrictions; } @Override public Range next() { if (hitsLeft == 0) { return null; } hitsLeft--; for (ApiView.ElementInfo elementInfo : apiView.getElements()) { if (elementInfo.element.getType().equals(elementType) && elementInfo.apiPosition > index) { boolean allMatched = true; for (Entry<String, String> entry : restrictions.entrySet()) { if (!entry.getValue().equals(elementInfo.element.getProperty(entry.getKey()))) { allMatched = false; break; } } if (!allMatched) { continue; } index = elementInfo.apiPosition; return new Range(index, index + 1); } } return null; } @Override public void shift(int where, int delta) { } } }