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.lang.reflect.Array;
025import java.util.HashMap;
026import java.util.Map;
027
028import org.apache.hadoop.HadoopIllegalArgumentException;
029import org.apache.hadoop.classification.InterfaceAudience;
030import org.apache.hadoop.classification.InterfaceStability;
031
032/**
033 * This is a wrapper class.  It wraps a Writable implementation around
034 * an array of primitives (e.g., int[], long[], etc.), with optimized 
035 * wire format, and without creating new objects per element.
036 * 
037 * This is a wrapper class only; it does not make a copy of the 
038 * underlying array.
039 */
040@InterfaceAudience.Public
041@InterfaceStability.Stable
042public class ArrayPrimitiveWritable implements Writable {
043  
044  //componentType is determined from the component type of the value array 
045  //during a "set" operation.  It must be primitive.
046  private Class<?> componentType = null; 
047  //declaredComponentType need not be declared, but if you do (by using the
048  //ArrayPrimitiveWritable(Class<?>) constructor), it will provide typechecking
049  //for all "set" operations.
050  private Class<?> declaredComponentType = null;
051  private int length;
052  private Object value; //must be an array of <componentType>[length]
053  
054  private static final Map<String, Class<?>> PRIMITIVE_NAMES = 
055    new HashMap<String, Class<?>>(16);
056  static {
057    PRIMITIVE_NAMES.put(boolean.class.getName(), boolean.class);
058    PRIMITIVE_NAMES.put(byte.class.getName(), byte.class);
059    PRIMITIVE_NAMES.put(char.class.getName(), char.class);
060    PRIMITIVE_NAMES.put(short.class.getName(), short.class);
061    PRIMITIVE_NAMES.put(int.class.getName(), int.class);
062    PRIMITIVE_NAMES.put(long.class.getName(), long.class);
063    PRIMITIVE_NAMES.put(float.class.getName(), float.class);
064    PRIMITIVE_NAMES.put(double.class.getName(), double.class);
065  }
066  
067  private static Class<?> getPrimitiveClass(String className) {
068    return PRIMITIVE_NAMES.get(className);
069  }
070  
071  private static void checkPrimitive(Class<?> componentType) {
072    if (componentType == null) { 
073      throw new HadoopIllegalArgumentException("null component type not allowed"); 
074    }
075    if (! PRIMITIVE_NAMES.containsKey(componentType.getName())) {
076      throw new HadoopIllegalArgumentException("input array component type "
077          + componentType.getName() + " is not a candidate primitive type");
078    }
079  }
080  
081  private void checkDeclaredComponentType(Class<?> componentType) {
082    if ((declaredComponentType != null) 
083        && (componentType != declaredComponentType)) {
084      throw new HadoopIllegalArgumentException("input array component type "
085          + componentType.getName() + " does not match declared type "
086          + declaredComponentType.getName());     
087    }
088  }
089  
090  private static void checkArray(Object value) {
091    if (value == null) { 
092      throw new HadoopIllegalArgumentException("null value not allowed"); 
093    }
094    if (! value.getClass().isArray()) {
095      throw new HadoopIllegalArgumentException("non-array value of class "
096          + value.getClass() + " not allowed");             
097    }
098  }
099  
100  /**
101   * Construct an empty instance, for use during Writable read
102   */
103  public ArrayPrimitiveWritable() {
104    //empty constructor
105  }
106  
107  /**
108   * Construct an instance of known type but no value yet
109   * for use with type-specific wrapper classes
110   */
111  public ArrayPrimitiveWritable(Class<?> componentType) {
112    checkPrimitive(componentType);
113    this.declaredComponentType = componentType;
114  }
115  
116  /**
117   * Wrap an existing array of primitives
118   * @param value - array of primitives
119   */
120  public ArrayPrimitiveWritable(Object value) {
121    set(value);
122  }
123  
124  /**
125   * Get the original array.  
126   * Client must cast it back to type componentType[]
127   * (or may use type-specific wrapper classes).
128   * @return - original array as Object
129   */
130  public Object get() { return value; }
131  
132  public Class<?> getComponentType() { return componentType; }
133  
134  public Class<?> getDeclaredComponentType() { return declaredComponentType; }
135  
136  public boolean isDeclaredComponentType(Class<?> componentType) {
137    return componentType == declaredComponentType;
138  }
139  
140  public void set(Object value) {
141    checkArray(value);
142    Class<?> componentType = value.getClass().getComponentType();
143    checkPrimitive(componentType);
144    checkDeclaredComponentType(componentType);
145    this.componentType = componentType;
146    this.value = value;
147    this.length = Array.getLength(value);
148  }
149  
150  /**
151   * Do not use this class.
152   * This is an internal class, purely for ObjectWritable to use as
153   * a label class for transparent conversions of arrays of primitives
154   * during wire protocol reads and writes.
155   */
156  static class Internal extends ArrayPrimitiveWritable {
157    Internal() {             //use for reads
158      super(); 
159    }
160    
161    Internal(Object value) { //use for writes
162      super(value);
163    }
164  } //end Internal subclass declaration
165
166  /* 
167   * @see org.apache.hadoop.io.Writable#write(java.io.DataOutput)
168   */
169  @Override
170  @SuppressWarnings("deprecation")
171  public void write(DataOutput out) throws IOException {
172    // write componentType 
173    UTF8.writeString(out, componentType.getName());      
174    // write length
175    out.writeInt(length);
176
177    // do the inner loop.  Walk the decision tree only once.
178    if (componentType == Boolean.TYPE) {          // boolean
179      writeBooleanArray(out);
180    } else if (componentType == Character.TYPE) { // char
181      writeCharArray(out);
182    } else if (componentType == Byte.TYPE) {      // byte
183      writeByteArray(out);
184    } else if (componentType == Short.TYPE) {     // short
185      writeShortArray(out);
186    } else if (componentType == Integer.TYPE) {   // int
187      writeIntArray(out);
188    } else if (componentType == Long.TYPE) {      // long
189      writeLongArray(out);
190    } else if (componentType == Float.TYPE) {     // float
191      writeFloatArray(out);
192    } else if (componentType == Double.TYPE) {    // double
193      writeDoubleArray(out);
194    } else {
195      throw new IOException("Component type " + componentType.toString()
196          + " is set as the output type, but no encoding is implemented for this type.");
197    }
198  }
199
200  /* 
201   * @see org.apache.hadoop.io.Writable#readFields(java.io.DataInput)
202   */
203  @Override
204  public void readFields(DataInput in) throws IOException {
205    
206    // read and set the component type of the array
207    @SuppressWarnings("deprecation")
208    String className = UTF8.readString(in);
209    Class<?> componentType = getPrimitiveClass(className);
210    if (componentType == null) {
211      throw new IOException("encoded array component type "
212          + className + " is not a candidate primitive type");
213    }
214    checkDeclaredComponentType(componentType);
215    this.componentType = componentType;
216  
217    // read and set the length of the array
218    int length = in.readInt();
219    if (length < 0) {
220      throw new IOException("encoded array length is negative " + length);
221    }
222    this.length = length;
223    
224    // construct and read in the array
225    value = Array.newInstance(componentType, length);
226
227    // do the inner loop.  Walk the decision tree only once.
228    if (componentType == Boolean.TYPE) {             // boolean
229      readBooleanArray(in);
230    } else if (componentType == Character.TYPE) {    // char
231      readCharArray(in);
232    } else if (componentType == Byte.TYPE) {         // byte
233      readByteArray(in);
234    } else if (componentType == Short.TYPE) {        // short
235      readShortArray(in);
236    } else if (componentType == Integer.TYPE) {      // int
237      readIntArray(in);
238    } else if (componentType == Long.TYPE) {         // long
239      readLongArray(in);
240    } else if (componentType == Float.TYPE) {        // float
241      readFloatArray(in);
242    } else if (componentType == Double.TYPE) {       // double
243      readDoubleArray(in);
244    } else {
245      throw new IOException("Encoded type " + className
246          + " converted to valid component type " + componentType.toString()
247          + " but no encoding is implemented for this type.");
248    }
249  }
250  
251  //For efficient implementation, there's no way around
252  //the following massive code duplication.
253  
254  private void writeBooleanArray(DataOutput out) throws IOException {
255    boolean[] v = (boolean[]) value;
256    for (int i = 0; i < length; i++)
257      out.writeBoolean(v[i]);
258  }
259  
260  private void writeCharArray(DataOutput out) throws IOException {
261    char[] v = (char[]) value;
262    for (int i = 0; i < length; i++)
263      out.writeChar(v[i]);
264  }
265  
266  private void writeByteArray(DataOutput out) throws IOException {
267    out.write((byte[]) value, 0, length);
268  }
269  
270  private void writeShortArray(DataOutput out) throws IOException {
271    short[] v = (short[]) value;
272    for (int i = 0; i < length; i++)
273      out.writeShort(v[i]);
274  }
275  
276  private void writeIntArray(DataOutput out) throws IOException {
277    int[] v = (int[]) value;
278    for (int i = 0; i < length; i++)
279      out.writeInt(v[i]);
280  }
281  
282  private void writeLongArray(DataOutput out) throws IOException {
283    long[] v = (long[]) value;
284    for (int i = 0; i < length; i++)
285      out.writeLong(v[i]);
286  }
287  
288  private void writeFloatArray(DataOutput out) throws IOException {
289    float[] v = (float[]) value;
290    for (int i = 0; i < length; i++)
291      out.writeFloat(v[i]);
292  }
293  
294  private void writeDoubleArray(DataOutput out) throws IOException {
295    double[] v = (double[]) value;
296    for (int i = 0; i < length; i++)
297      out.writeDouble(v[i]);
298  }
299
300  private void readBooleanArray(DataInput in) throws IOException {
301    boolean[] v = (boolean[]) value;
302    for (int i = 0; i < length; i++)
303      v[i] = in.readBoolean(); 
304  }
305  
306  private void readCharArray(DataInput in) throws IOException {
307    char[] v = (char[]) value;
308    for (int i = 0; i < length; i++)
309      v[i] = in.readChar(); 
310  }
311  
312  private void readByteArray(DataInput in) throws IOException {
313    in.readFully((byte[]) value, 0, length);
314  }
315  
316  private void readShortArray(DataInput in) throws IOException {
317    short[] v = (short[]) value;
318    for (int i = 0; i < length; i++)
319      v[i] = in.readShort(); 
320  }
321  
322  private void readIntArray(DataInput in) throws IOException {
323    int[] v = (int[]) value;
324    for (int i = 0; i < length; i++)
325      v[i] = in.readInt(); 
326  }
327  
328  private void readLongArray(DataInput in) throws IOException {
329    long[] v = (long[]) value;
330    for (int i = 0; i < length; i++)
331      v[i] = in.readLong(); 
332  }
333  
334  private void readFloatArray(DataInput in) throws IOException {
335    float[] v = (float[]) value;
336    for (int i = 0; i < length; i++)
337      v[i] = in.readFloat(); 
338  }
339  
340  private void readDoubleArray(DataInput in) throws IOException {
341    double[] v = (double[]) value;
342    for (int i = 0; i < length; i++)
343      v[i] = in.readDouble(); 
344  }
345}
346