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.lang.reflect.Array;
025    import java.util.HashMap;
026    import java.util.Map;
027    
028    import org.apache.hadoop.HadoopIllegalArgumentException;
029    import org.apache.hadoop.classification.InterfaceAudience;
030    import 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
042    public 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