001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.io;
020
021import java.io.DataInput;
022import java.io.DataOutput;
023import java.io.IOException;
024import java.util.EnumSet;
025import java.util.Iterator;
026import java.util.Collection;
027import java.util.AbstractCollection;
028
029import org.apache.hadoop.classification.InterfaceAudience;
030import org.apache.hadoop.classification.InterfaceStability;
031import org.apache.hadoop.conf.Configurable;
032import org.apache.hadoop.conf.Configuration;
033
034/** A Writable wrapper for EnumSet. */
035@InterfaceAudience.Public
036@InterfaceStability.Stable
037public class EnumSetWritable<E extends Enum<E>> extends AbstractCollection<E>
038  implements Writable, Configurable  {
039
040  private EnumSet<E> value;
041
042  private transient Class<E> elementType;
043
044  private transient Configuration conf;
045  
046  EnumSetWritable() {
047  }
048
049  public Iterator<E> iterator() { return value.iterator(); }
050  public int size() { return value.size(); }
051  public boolean add(E e) {
052    if (value == null) {
053      value = EnumSet.of(e);
054      set(value, null);
055    }
056    return value.add(e);
057  }
058
059  /**
060   * Construct a new EnumSetWritable. If the <tt>value</tt> argument is null or
061   * its size is zero, the <tt>elementType</tt> argument must not be null. If
062   * the argument <tt>value</tt>'s size is bigger than zero, the argument
063   * <tt>elementType</tt> is not be used.
064   * 
065   * @param value
066   * @param elementType
067   */
068  public EnumSetWritable(EnumSet<E> value, Class<E> elementType) {
069    set(value, elementType);
070  }
071
072  /**
073   * Construct a new EnumSetWritable. Argument <tt>value</tt> should not be null
074   * or empty.
075   * 
076   * @param value
077   */
078  public EnumSetWritable(EnumSet<E> value) {
079    this(value, null);
080  }
081
082  /**
083   * reset the EnumSetWritable with specified
084   * <tt>value</value> and <tt>elementType</tt>. If the <tt>value</tt> argument
085   * is null or its size is zero, the <tt>elementType</tt> argument must not be
086   * null. If the argument <tt>value</tt>'s size is bigger than zero, the
087   * argument <tt>elementType</tt> is not be used.
088   * 
089   * @param value
090   * @param elementType
091   */
092  public void set(EnumSet<E> value, Class<E> elementType) {
093    if ((value == null || value.size() == 0)
094        && (this.elementType == null && elementType == null)) {
095      throw new IllegalArgumentException(
096          "The EnumSet argument is null, or is an empty set but with no elementType provided.");
097    }
098    this.value = value;
099    if (value != null && value.size() > 0) {
100      Iterator<E> iterator = value.iterator();
101      this.elementType = iterator.next().getDeclaringClass();
102    } else if (elementType != null) {
103      this.elementType = elementType;
104    }
105  }
106
107  /** Return the value of this EnumSetWritable. */
108  public EnumSet<E> get() {
109    return value;
110  }
111
112  /** {@inheritDoc} */
113  @SuppressWarnings("unchecked")
114  public void readFields(DataInput in) throws IOException {
115    int length = in.readInt();
116    if (length == -1)
117      this.value = null;
118    else if (length == 0) {
119      this.elementType = (Class<E>) ObjectWritable.loadClass(conf,
120          WritableUtils.readString(in));
121      this.value = EnumSet.noneOf(this.elementType);
122    } else {
123      E first = (E) ObjectWritable.readObject(in, conf);
124      this.value = (EnumSet<E>) EnumSet.of(first);
125      for (int i = 1; i < length; i++)
126        this.value.add((E) ObjectWritable.readObject(in, conf));
127    }
128  }
129
130  /** {@inheritDoc} */
131  public void write(DataOutput out) throws IOException {
132    if (this.value == null) {
133      out.writeInt(-1);
134      WritableUtils.writeString(out, this.elementType.getName());
135    } else {
136      Object[] array = this.value.toArray();
137      int length = array.length;
138      out.writeInt(length);
139      if (length == 0) {
140        if (this.elementType == null)
141          throw new UnsupportedOperationException(
142              "Unable to serialize empty EnumSet with no element type provided.");
143        WritableUtils.writeString(out, this.elementType.getName());
144      }
145      for (int i = 0; i < length; i++) {
146        ObjectWritable.writeObject(out, array[i], array[i].getClass(), conf);
147      }
148    }
149  }
150
151  /**
152   * Returns true if <code>o</code> is an EnumSetWritable with the same value,
153   * or both are null.
154   */
155  public boolean equals(Object o) {
156    if (o == null) {
157      throw new IllegalArgumentException("null argument passed in equal().");
158    }
159
160    if (!(o instanceof EnumSetWritable))
161      return false;
162
163    EnumSetWritable<?> other = (EnumSetWritable<?>) o;
164
165    if (this == o || (this.value == other.value))
166      return true;
167    if (this.value == null) // other.value must not be null if we reach here
168      return false;
169
170    return this.value.equals(other.value);
171  }
172
173  /**
174   * Returns the class of all the elements of the underlying EnumSetWriable. It
175   * may return null.
176   * 
177   * @return the element class
178   */
179  public Class<E> getElementType() {
180    return elementType;
181  }
182
183  /** {@inheritDoc} */
184  public int hashCode() {
185    if (value == null)
186      return 0;
187    return (int) value.hashCode();
188  }
189
190  /** {@inheritDoc} */
191  public String toString() {
192    if (value == null)
193      return "(null)";
194    return value.toString();
195  }
196
197  /** {@inheritDoc} */
198  @Override
199  public Configuration getConf() {
200    return this.conf;
201  }
202
203  /** {@inheritDoc} */
204  @Override
205  public void setConf(Configuration conf) {
206    this.conf = conf;
207  }
208
209  static {
210    WritableFactories.setFactory(EnumSetWritable.class, new WritableFactory() {
211      @SuppressWarnings("unchecked")
212      @Override
213      public Writable newInstance() {
214        return new EnumSetWritable();
215      }
216    });
217  }
218}