/** * (c) Copyright 2012 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.kiji.mapreduce.avro; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.avro.hadoop.util.AvroCharSequenceComparator; import org.kiji.annotations.ApiAudience; import org.kiji.annotations.ApiStability; /** * A reader for Avro "map" data. * * <p>Avro "map" types are implemented in Java as {@link java.util.Map}, where the keys * are {@link java.lang.CharSequence}. Since <code>CharSequence</code> does not refine * the general contracts of <code>equals()</code> and <code>hashCode()</code> methods, * this unfortunately means that testing for the existence of a key in an Avro map is not * possible. In particular, if you test for the existence of a <code>String</code> key * <code>"foo"</code> in a map deserialized using Avro, its underyling * <code>HashMap</code> implementation will not match your <code>"foo"</code> hash code to * its underlying <code>Utf8("foo")</code>'s hash code.</p> * * <p>A possible workaround would be to always use <code>Utf8</code> objects whenever * working with Avro maps. However, this is not possible when writing framework code like * Kiji. Clients must be free to use any <code>CharSequence</code> implementation of * their choosing.</p> * * <p>Until this issue is resolved in Avro, use this class to read from Avro "map" * types. This is a read-only map. Calling a method that would mutate the map will throw * an <code>UnsupportedOperationException</code>.</p> * * <p>Note that the AvroMapReader wraps the state of the Avro "map" at the time of the * reader's creation. If the underlying Avro "map" is mutated and you want the reader to * reflect its new contents, you should call {@link AvroMapReader#reload()} to reload * the map. Otherwise the <code>AvroMapReader</code> will reflect the state of the wrapped * Avro "map" at the time of the Reader's creation or the most recent call to * <code>reload()</code>.</p> * * @param <V> The map's value type. */ @ApiAudience.Public @ApiStability.Evolving public final class AvroMapReader<V> implements Map<CharSequence, V> { /** The wrapped Avro map to read. */ private final Map<CharSequence, V> mMap; /** * A map that mirrors the data in the wrapped Avro map. * * <p>This map has a key comparator that correctly compares CharSequences, so comparing a * String with a Utf8 works as expected.</p> */ private Map<CharSequence, V> mMirror; /** * Wraps an Avro map. * * @param map The Avro map to read from. */ private AvroMapReader(Map<CharSequence, V> map) { mMap = map; reload(); } /** * Creates a new Avro map reader wrapping a map generated by the Avro framework. * The map will be loaded with a copy of the current state of the <code>map</code> * parameter; you must call {@link #reload()} on the <code>AvroMapReader</code> * after later mutations to the <code>map</code> if you want them to be * reflected by the reader. * * @param map The map obtained from the Avro framework that should be wrapped. * @param <V> The type of the values contained in the map. * @return A new Avro map reader wrapping the specified map. */ public static <V> AvroMapReader<V> create(Map<CharSequence, V> map) { return new AvroMapReader<V>(map); } /** * Updates <code>AvroMapReader</code> to reflect any mutations (additions, * deletions or updates) that have been made to the the wrapped Avro map * used to create it. * * <p>Call this method whenever you want the <code>AvroMapReader</code> * to use the latest state of the Avro map.</p> */ public void reload() { if (null == mMirror) { mMirror = new TreeMap<CharSequence, V>(AvroCharSequenceComparator.INSTANCE); } else { mMirror.clear(); } mMirror.putAll(mMap); } /** {@inheritDoc} */ @Override public void clear() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean containsKey(Object key) { return mMirror.containsKey(key); } /** {@inheritDoc} */ @Override public boolean containsValue(Object value) { return mMirror.containsValue(value); } /** {@inheritDoc} */ @Override public Set<Map.Entry<CharSequence, V>> entrySet() { return mMirror.entrySet(); } /** {@inheritDoc} */ @Override public boolean equals(Object o) { return mMirror.equals(o); } /** {@inheritDoc} */ @Override public V get(Object key) { return mMirror.get(key); } /** {@inheritDoc} */ @Override public int hashCode() { return mMirror.hashCode(); } /** {@inheritDoc} */ @Override public boolean isEmpty() { return mMirror.isEmpty(); } /** {@inheritDoc} */ @Override public Set<CharSequence> keySet() { return mMirror.keySet(); } /** {@inheritDoc} */ @Override public V put(CharSequence key, V value) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public void putAll(Map<? extends CharSequence, ? extends V> m) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public V remove(Object key) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public int size() { return mMirror.size(); } /** {@inheritDoc} */ @Override public Collection<V> values() { return mMirror.values(); } }