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.AbstractCollection;
027
028import org.apache.hadoop.classification.InterfaceAudience;
029import org.apache.hadoop.classification.InterfaceStability;
030import org.apache.hadoop.conf.Configurable;
031import org.apache.hadoop.conf.Configuration;
032
033/** A Writable wrapper for EnumSet. */
034@InterfaceAudience.Public
035@InterfaceStability.Stable
036public class EnumSetWritable<E extends Enum<E>> extends AbstractCollection<E>
037  implements Writable, Configurable  {
038
039  private EnumSet<E> value;
040
041  private transient Class<E> elementType;
042
043  private transient Configuration conf;
044  
045  EnumSetWritable() {
046  }
047
048  @Override
049  public Iterator<E> iterator() { return value.iterator(); }
050  @Override
051  public int size() { return value.size(); }
052  @Override
053  public boolean add(E e) {
054    if (value == null) {
055      value = EnumSet.of(e);
056      set(value, null);
057    }
058    return value.add(e);
059  }
060
061  /**
062   * Construct a new EnumSetWritable. If the <tt>value</tt> argument is null or
063   * its size is zero, the <tt>elementType</tt> argument must not be null. If
064   * the argument <tt>value</tt>'s size is bigger than zero, the argument
065   * <tt>elementType</tt> is not be used.
066   * 
067   * @param value
068   * @param elementType
069   */
070  public EnumSetWritable(EnumSet<E> value, Class<E> elementType) {
071    set(value, elementType);
072  }
073
074  /**
075   * Construct a new EnumSetWritable. Argument <tt>value</tt> should not be null
076   * or empty.
077   * 
078   * @param value
079   */
080  public EnumSetWritable(EnumSet<E> value) {
081    this(value, null);
082  }
083
084  /**
085   * reset the EnumSetWritable with specified
086   * <tt>value</value> and <tt>elementType</tt>. If the <tt>value</tt> argument
087   * is null or its size is zero, the <tt>elementType</tt> argument must not be
088   * null. If the argument <tt>value</tt>'s size is bigger than zero, the
089   * argument <tt>elementType</tt> is not be used.
090   * 
091   * @param value
092   * @param elementType
093   */
094  public void set(EnumSet<E> value, Class<E> elementType) {
095    if ((value == null || value.size() == 0)
096        && (this.elementType == null && elementType == null)) {
097      throw new IllegalArgumentException(
098          "The EnumSet argument is null, or is an empty set but with no elementType provided.");
099    }
100    this.value = value;
101    if (value != null && value.size() > 0) {
102      Iterator<E> iterator = value.iterator();
103      this.elementType = iterator.next().getDeclaringClass();
104    } else if (elementType != null) {
105      this.elementType = elementType;
106    }
107  }
108
109  /** Return the value of this EnumSetWritable. */
110  public EnumSet<E> get() {
111    return value;
112  }
113
114  @Override
115  @SuppressWarnings("unchecked")
116  public void readFields(DataInput in) throws IOException {
117    int length = in.readInt();
118    if (length == -1)
119      this.value = null;
120    else if (length == 0) {
121      this.elementType = (Class<E>) ObjectWritable.loadClass(conf,
122          WritableUtils.readString(in));
123      this.value = EnumSet.noneOf(this.elementType);
124    } else {
125      E first = (E) ObjectWritable.readObject(in, conf);
126      this.value = (EnumSet<E>) EnumSet.of(first);
127      for (int i = 1; i < length; i++)
128        this.value.add((E) ObjectWritable.readObject(in, conf));
129    }
130  }
131
132  @Override
133  public void write(DataOutput out) throws IOException {
134    if (this.value == null) {
135      out.writeInt(-1);
136      WritableUtils.writeString(out, this.elementType.getName());
137    } else {
138      Object[] array = this.value.toArray();
139      int length = array.length;
140      out.writeInt(length);
141      if (length == 0) {
142        if (this.elementType == null)
143          throw new UnsupportedOperationException(
144              "Unable to serialize empty EnumSet with no element type provided.");
145        WritableUtils.writeString(out, this.elementType.getName());
146      }
147      for (int i = 0; i < length; i++) {
148        ObjectWritable.writeObject(out, array[i], array[i].getClass(), conf);
149      }
150    }
151  }
152
153  /**
154   * Returns true if <code>o</code> is an EnumSetWritable with the same value,
155   * or both are null.
156   */
157  @Override
158  public boolean equals(Object o) {
159    if (o == null) {
160      throw new IllegalArgumentException("null argument passed in equal().");
161    }
162
163    if (!(o instanceof EnumSetWritable))
164      return false;
165
166    EnumSetWritable<?> other = (EnumSetWritable<?>) o;
167
168    if (this == o || (this.value == other.value))
169      return true;
170    if (this.value == null) // other.value must not be null if we reach here
171      return false;
172
173    return this.value.equals(other.value);
174  }
175
176  /**
177   * Returns the class of all the elements of the underlying EnumSetWriable. It
178   * may return null.
179   * 
180   * @return the element class
181   */
182  public Class<E> getElementType() {
183    return elementType;
184  }
185
186  @Override
187  public int hashCode() {
188    if (value == null)
189      return 0;
190    return (int) value.hashCode();
191  }
192
193  @Override
194  public String toString() {
195    if (value == null)
196      return "(null)";
197    return value.toString();
198  }
199
200  @Override
201  public Configuration getConf() {
202    return this.conf;
203  }
204
205  @Override
206  public void setConf(Configuration conf) {
207    this.conf = conf;
208  }
209
210  static {
211    WritableFactories.setFactory(EnumSetWritable.class, new WritableFactory() {
212      @SuppressWarnings("unchecked")
213      @Override
214      public Writable newInstance() {
215        return new EnumSetWritable();
216      }
217    });
218  }
219}