| // 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; |
| } |
| } |