/**
* 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();
}
}