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.Collection;
027    import java.util.AbstractCollection;
028    
029    import org.apache.hadoop.classification.InterfaceAudience;
030    import org.apache.hadoop.classification.InterfaceStability;
031    import org.apache.hadoop.conf.Configurable;
032    import org.apache.hadoop.conf.Configuration;
033    
034    /** A Writable wrapper for EnumSet. */
035    @InterfaceAudience.Public
036    @InterfaceStability.Stable
037    public 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    }