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    
019    package org.apache.hadoop.io;
020    
021    import java.io.DataInput;
022    import java.io.DataOutput;
023    import java.io.IOException;
024    import java.util.EnumSet;
025    import java.util.Iterator;
026    import java.util.AbstractCollection;
027    
028    import org.apache.hadoop.classification.InterfaceAudience;
029    import org.apache.hadoop.classification.InterfaceStability;
030    import org.apache.hadoop.conf.Configurable;
031    import org.apache.hadoop.conf.Configuration;
032    
033    /** A Writable wrapper for EnumSet. */
034    @InterfaceAudience.Public
035    @InterfaceStability.Stable
036    public 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    }