blob: f0d154f8337def9e26752d4ddb019fcc62770e03 [file] [log] [blame]
// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.enterprise.adaptor;
import static java.util.AbstractMap.SimpleImmutableEntry;
import static java.util.Map.Entry;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Allows storing multiple metadata values to a single key.
* <p>
* Null keys are invalid as arguments. Null values are
* invalid as arguments.
* <p>
* This class is mutable and not thread-safe.
*/
public class Metadata implements Iterable<Entry<String, String>> {
private Map<String, Set<String>> mappings
= new TreeMap<String, Set<String>>();
/** Create empty instance. */
public Metadata() {
}
/** Duplicate. */
public Metadata(Iterable<Entry<String, String>> m) {
for (Entry<String, String> e : m) {
add(e.getKey(), e.getValue());
}
}
/** Make v be only value associated with key. */
public void set(String k, String v) {
if (null == k) {
throw new NullPointerException();
}
if (null == v) {
throw new NullPointerException();
}
TreeSet<String> single = new TreeSet<String>();
single.add(v);
mappings.put(k, single);
}
/** Throws NullPointerException if a null is found. */
private static void assureNoNulls(Set<String> items) {
// Can't use items.contains(null) because Set is permitted to throw NPE in
// such a case.
for (String s : items) {
if (s == null) {
throw new NullPointerException();
}
}
}
/** Make copy of v be the values associated with key. */
public void set(String k, Set<String> v) {
if (null == k) {
throw new NullPointerException();
}
if (null == v) {
throw new NullPointerException();
}
assureNoNulls(v);
if (v.isEmpty()) {
mappings.remove(k);
} else {
v = new TreeSet<String>(v);
mappings.put(k, v);
}
}
/** Increases values mapped to k with v. */
public void add(String k, String v) {
if (null == k) {
throw new NullPointerException();
}
if (null == v) {
throw new NullPointerException();
}
Set<String> found = mappings.get(k);
if (null == found) {
set(k, v);
} else {
found.add(v);
}
}
/** Replaces entries inside of this metadata with provided ones. */
public void set(Iterable<Entry<String, String>> it) {
mappings.clear();
for (Entry<String, String> e : it) {
add(e.getKey(), e.getValue());
}
}
/** Gives unmodifiable reference to inserted values for key, empty if none. */
public Set<String> getAllValues(String key) {
Set<String> found = mappings.get(key);
if (null == found) {
found = Collections.emptySet();
}
return Collections.unmodifiableSet(found);
}
/** One of the inserted values, or null if none. */
public String getOneValue(String key) {
Set<String> found = mappings.get(key);
String first = null;
if (null != found) {
if (found.isEmpty()) {
throw new AssertionError();
}
first = found.iterator().next();
}
return first;
}
/** Get modifiable set of all keys with at least one value. */
public Set<String> getKeys() {
return mappings.keySet();
}
/**
* Provides every key and value in immutable entries sorted
* alphabetically, first by key, and secondly by value.
* <p>
* Behaviour is undefined if backing Metadata instance is modified
* during iteration.
* <p>
* remove() is unsupported on returned iterator.
*/
public Iterator<Entry<String, String>> iterator() {
return new EntriesIterator();
}
/** Loops through keys and for each key all values. */
private class EntriesIterator implements Iterator<Entry<String, String>> {
private Iterator<Entry<String, Set<String>>> byKey
= mappings.entrySet().iterator();
private String currentKey;
private Iterator<String> currentValues
= Collections.<String>emptyList().iterator();
@Override
public boolean hasNext() {
if (currentValues.hasNext()) {
return true;
}
if (!byKey.hasNext()) {
return false;
}
Entry<String, Set<String>> currentEntry = byKey.next();
currentKey = currentEntry.getKey();
currentValues = currentEntry.getValue().iterator();
return hasNext();
}
@Override
public Entry<String, String> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String k = currentKey, v = currentValues.next();
return new SimpleImmutableEntry<String, String>(k, v);
}
/** Not supported. */
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/** True if exactly the same key-values are represented. */
@Override
public boolean equals(Object o) {
if (!(o instanceof Metadata)) {
return false;
}
if (this == o) {
return true;
}
Metadata other = (Metadata) o;
return mappings.equals(other.mappings);
}
@Override
public int hashCode() {
return mappings.hashCode();
}
/** True with 0 entries. */
public boolean isEmpty() {
return mappings.isEmpty();
}
/** Contains every key and value pair; useful for debugging. */
public String toString() {
String sep = ", ";
StringBuilder builder = new StringBuilder();
for (Entry<String, String> e : this) {
builder.append(sep);
builder.append(e.getKey()).append("=").append(e.getValue());
}
String body = "";
if (0 != builder.length()) {
body = builder.substring(sep.length());
}
return "[" + body + "]";
}
/** Does not allow any mutating operations. */
private static class ReadableMetadata extends Metadata {
@Override
public void set(String k, String v) {
throw new UnsupportedOperationException();
}
@Override
public void set(String k, Set<String> v) {
throw new UnsupportedOperationException();
}
@Override
public void add(String k, String v) {
throw new UnsupportedOperationException();
}
@Override
public void set(Iterable<Entry<String, String>> it) {
throw new UnsupportedOperationException();
}
@Override
public Set<String> getKeys() {
return Collections.unmodifiableSet(super.getKeys());
}
};
/** Get a reference to an unmodifiable view of this object. */
public Metadata unmodifiableView() {
Metadata unmodifiable = new ReadableMetadata();
// Extra precaution against mappings use, but not against moding
// sets that are values inside it.
unmodifiable.mappings = Collections.unmodifiableMap(this.mappings);
return unmodifiable;
}
}