/** * 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 org.waveprotocol.wave.client.common.util; import static org.waveprotocol.wave.client.common.util.JsoStringMap.escape; import static org.waveprotocol.wave.client.common.util.JsoStringMap.unescape; import com.google.common.annotations.VisibleForTesting; import org.waveprotocol.wave.model.util.ReadableStringSet; import org.waveprotocol.wave.model.util.StringSet; import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; import java.util.Set; /** * An implementation of StringSet based on JavaScript objects. * * @author ohler@google.com (Christian Ohler) */ public class JsoStringSet implements StringSet { @VisibleForTesting public final JsoView backend; private static final Object AN_OBJECT = new Object(); /** * Exposed for JsoStringMap * @param backend */ JsoStringSet(JsoView backend) { this.backend = backend; } public static JsoStringSet create() { return new JsoStringSet(JsoStringMap.createBackend()); } @Override public void add(String s) { backend.setObject(escape(s), AN_OBJECT); } @Override public void clear() { backend.clear(); } @Override public boolean contains(String s) { return backend.containsKey(escape(s)); } @Override public void each(final Proc callback) { backend.each(new ProcV<Object>() { @Override public void apply(String key, Object item) { callback.apply(unescape(key)); } }); } @Override public void filter(final StringPredicate filter) { backend.each(new ProcV<Object>() { @Override public void apply(String key, Object item) { if (filter.apply(unescape(key))) { // entry stays } else { backend.remove(key); } } }); } @Override public boolean isEmpty() { return backend.isEmpty(); } @Override public String someElement() { return backend.firstKey(); } @Override public void remove(String s) { backend.remove(escape(s)); } private static class False extends RuntimeException { private False() { super("Preallocated exception without a meaningful stacktrace"); } @Override public Throwable fillInStackTrace() { // don't fill in the stack trace, which is slow (especially on client) return this; } } private static final False FALSE = new False(); @Override public boolean isSubsetOf(final Set<String> set) { try { each(new Proc() { @Override public void apply(String element) { if (!set.contains(element)) { throw FALSE; } } }); return true; } catch (False e) { assert e == FALSE; return false; } } @Override public void addAll(ReadableStringSet set) { set.each(new Proc() { public void apply(String element) { add(element); } }); } @Override public void removeAll(ReadableStringSet set) { set.each(new Proc() { public void apply(String element) { remove(element); } }); } @Override public boolean isSubsetOf(final ReadableStringSet other) { try { each(new Proc() { @Override public void apply(String element) { if (!other.contains(element)) { throw FALSE; } } }); } catch (RuntimeException e) { if (e != FALSE) { throw e; } return false; } return true; } @Override public int countEntries() { return backend.countEntries(); } @Override public String toString() { final StringBuilder b = new StringBuilder("{"); each(new Proc() { @Override public void apply(String element) { if (b.length() > 1) { b.append(","); } try { b.append("'" + element.replaceAll("\\\\", "\\\\").replaceAll("'", "\\'") + "'"); } catch (RuntimeException e) { b.append("REGEX DEATH - " + element); } } }); b.append("}"); return b.toString(); } }