org.apache.hadoop.io.ObjectWritable.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.io.ObjectWritable.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.io;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.io.*;
import java.util.*;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.util.ProtoUtil;

import com.google.protobuf.Message;

/** A polymorphic Writable that writes an instance with it's class name.
 * Handles arrays, strings and primitive types without a Writable wrapper.
 */
@InterfaceAudience.Public
@InterfaceStability.Stable
public class ObjectWritable implements Writable, Configurable {

    private Class declaredClass;
    private Object instance;
    private Configuration conf;

    public ObjectWritable() {
    }

    public ObjectWritable(Object instance) {
        set(instance);
    }

    public ObjectWritable(Class declaredClass, Object instance) {
        this.declaredClass = declaredClass;
        this.instance = instance;
    }

    /** Return the instance, or null if none. */
    public Object get() {
        return instance;
    }

    /** Return the class this is meant to be. */
    public Class getDeclaredClass() {
        return declaredClass;
    }

    /** Reset the instance. */
    public void set(Object instance) {
        this.declaredClass = instance.getClass();
        this.instance = instance;
    }

    @Override
    public String toString() {
        return "OW[class=" + declaredClass + ",value=" + instance + "]";
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        readObject(in, this, this.conf);
    }

    @Override
    public void write(DataOutput out) throws IOException {
        writeObject(out, instance, declaredClass, conf);
    }

    private static final Map<String, Class<?>> PRIMITIVE_NAMES = new HashMap<String, Class<?>>();
    static {
        PRIMITIVE_NAMES.put("boolean", Boolean.TYPE);
        PRIMITIVE_NAMES.put("byte", Byte.TYPE);
        PRIMITIVE_NAMES.put("char", Character.TYPE);
        PRIMITIVE_NAMES.put("short", Short.TYPE);
        PRIMITIVE_NAMES.put("int", Integer.TYPE);
        PRIMITIVE_NAMES.put("long", Long.TYPE);
        PRIMITIVE_NAMES.put("float", Float.TYPE);
        PRIMITIVE_NAMES.put("double", Double.TYPE);
        PRIMITIVE_NAMES.put("void", Void.TYPE);
    }

    private static class NullInstance extends Configured implements Writable {
        private Class<?> declaredClass;

        public NullInstance() {
            super(null);
        }

        public NullInstance(Class declaredClass, Configuration conf) {
            super(conf);
            this.declaredClass = declaredClass;
        }

        @Override
        public void readFields(DataInput in) throws IOException {
            String className = UTF8.readString(in);
            declaredClass = PRIMITIVE_NAMES.get(className);
            if (declaredClass == null) {
                try {
                    declaredClass = getConf().getClassByName(className);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e.toString());
                }
            }
        }

        @Override
        public void write(DataOutput out) throws IOException {
            UTF8.writeString(out, declaredClass.getName());
        }
    }

    /** Write a {@link Writable}, {@link String}, primitive type, or an array of
     * the preceding. */
    public static void writeObject(DataOutput out, Object instance, Class declaredClass, Configuration conf)
            throws IOException {
        writeObject(out, instance, declaredClass, conf, false);
    }

    /** 
     * Write a {@link Writable}, {@link String}, primitive type, or an array of
     * the preceding.  
     * 
     * @param allowCompactArrays - set true for RPC and internal or intra-cluster
     * usages.  Set false for inter-cluster, File, and other persisted output 
     * usages, to preserve the ability to interchange files with other clusters 
     * that may not be running the same version of software.  Sometime in ~2013 
     * we can consider removing this parameter and always using the compact format.
     */
    public static void writeObject(DataOutput out, Object instance, Class declaredClass, Configuration conf,
            boolean allowCompactArrays) throws IOException {

        if (instance == null) { // null
            instance = new NullInstance(declaredClass, conf);
            declaredClass = Writable.class;
        }

        // Special case: must come before writing out the declaredClass.
        // If this is an eligible array of primitives,
        // wrap it in an ArrayPrimitiveWritable$Internal wrapper class.
        if (allowCompactArrays && declaredClass.isArray()
                && instance.getClass().getName().equals(declaredClass.getName())
                && instance.getClass().getComponentType().isPrimitive()) {
            instance = new ArrayPrimitiveWritable.Internal(instance);
            declaredClass = ArrayPrimitiveWritable.Internal.class;
        }

        UTF8.writeString(out, declaredClass.getName()); // always write declared

        if (declaredClass.isArray()) { // non-primitive or non-compact array
            int length = Array.getLength(instance);
            out.writeInt(length);
            for (int i = 0; i < length; i++) {
                writeObject(out, Array.get(instance, i), declaredClass.getComponentType(), conf,
                        allowCompactArrays);
            }

        } else if (declaredClass == ArrayPrimitiveWritable.Internal.class) {
            ((ArrayPrimitiveWritable.Internal) instance).write(out);

        } else if (declaredClass == String.class) { // String
            UTF8.writeString(out, (String) instance);

        } else if (declaredClass.isPrimitive()) { // primitive type

            if (declaredClass == Boolean.TYPE) { // boolean
                out.writeBoolean(((Boolean) instance).booleanValue());
            } else if (declaredClass == Character.TYPE) { // char
                out.writeChar(((Character) instance).charValue());
            } else if (declaredClass == Byte.TYPE) { // byte
                out.writeByte(((Byte) instance).byteValue());
            } else if (declaredClass == Short.TYPE) { // short
                out.writeShort(((Short) instance).shortValue());
            } else if (declaredClass == Integer.TYPE) { // int
                out.writeInt(((Integer) instance).intValue());
            } else if (declaredClass == Long.TYPE) { // long
                out.writeLong(((Long) instance).longValue());
            } else if (declaredClass == Float.TYPE) { // float
                out.writeFloat(((Float) instance).floatValue());
            } else if (declaredClass == Double.TYPE) { // double
                out.writeDouble(((Double) instance).doubleValue());
            } else if (declaredClass == Void.TYPE) { // void
            } else {
                throw new IllegalArgumentException("Not a primitive: " + declaredClass);
            }
        } else if (declaredClass.isEnum()) { // enum
            UTF8.writeString(out, ((Enum) instance).name());
        } else if (Writable.class.isAssignableFrom(declaredClass)) { // Writable
            UTF8.writeString(out, instance.getClass().getName());
            ((Writable) instance).write(out);

        } else if (Message.class.isAssignableFrom(declaredClass)) {
            ((Message) instance).writeDelimitedTo(DataOutputOutputStream.constructOutputStream(out));
        } else {
            throw new IOException("Can't write: " + instance + " as " + declaredClass);
        }
    }

    /** Read a {@link Writable}, {@link String}, primitive type, or an array of
     * the preceding. */
    public static Object readObject(DataInput in, Configuration conf) throws IOException {
        return readObject(in, null, conf);
    }

    /** Read a {@link Writable}, {@link String}, primitive type, or an array of
     * the preceding. */
    @SuppressWarnings("unchecked")
    public static Object readObject(DataInput in, ObjectWritable objectWritable, Configuration conf)
            throws IOException {
        String className = UTF8.readString(in);
        Class<?> declaredClass = PRIMITIVE_NAMES.get(className);
        if (declaredClass == null) {
            declaredClass = loadClass(conf, className);
        }

        Object instance;

        if (declaredClass.isPrimitive()) { // primitive types

            if (declaredClass == Boolean.TYPE) { // boolean
                instance = Boolean.valueOf(in.readBoolean());
            } else if (declaredClass == Character.TYPE) { // char
                instance = Character.valueOf(in.readChar());
            } else if (declaredClass == Byte.TYPE) { // byte
                instance = Byte.valueOf(in.readByte());
            } else if (declaredClass == Short.TYPE) { // short
                instance = Short.valueOf(in.readShort());
            } else if (declaredClass == Integer.TYPE) { // int
                instance = Integer.valueOf(in.readInt());
            } else if (declaredClass == Long.TYPE) { // long
                instance = Long.valueOf(in.readLong());
            } else if (declaredClass == Float.TYPE) { // float
                instance = Float.valueOf(in.readFloat());
            } else if (declaredClass == Double.TYPE) { // double
                instance = Double.valueOf(in.readDouble());
            } else if (declaredClass == Void.TYPE) { // void
                instance = null;
            } else {
                throw new IllegalArgumentException("Not a primitive: " + declaredClass);
            }

        } else if (declaredClass.isArray()) { // array
            int length = in.readInt();
            instance = Array.newInstance(declaredClass.getComponentType(), length);
            for (int i = 0; i < length; i++) {
                Array.set(instance, i, readObject(in, conf));
            }

        } else if (declaredClass == ArrayPrimitiveWritable.Internal.class) {
            // Read and unwrap ArrayPrimitiveWritable$Internal array.
            // Always allow the read, even if write is disabled by allowCompactArrays.
            ArrayPrimitiveWritable.Internal temp = new ArrayPrimitiveWritable.Internal();
            temp.readFields(in);
            instance = temp.get();
            declaredClass = instance.getClass();

        } else if (declaredClass == String.class) { // String
            instance = UTF8.readString(in);
        } else if (declaredClass.isEnum()) { // enum
            instance = Enum.valueOf((Class<? extends Enum>) declaredClass, UTF8.readString(in));
        } else if (Message.class.isAssignableFrom(declaredClass)) {
            instance = tryInstantiateProtobuf(declaredClass, in);
        } else { // Writable
            Class instanceClass = null;
            String str = UTF8.readString(in);
            instanceClass = loadClass(conf, str);

            Writable writable = WritableFactories.newInstance(instanceClass, conf);
            writable.readFields(in);
            instance = writable;

            if (instanceClass == NullInstance.class) { // null
                declaredClass = ((NullInstance) instance).declaredClass;
                instance = null;
            }
        }

        if (objectWritable != null) { // store values
            objectWritable.declaredClass = declaredClass;
            objectWritable.instance = instance;
        }

        return instance;

    }

    /**
     * Try to instantiate a protocol buffer of the given message class
     * from the given input stream.
     * 
     * @param protoClass the class of the generated protocol buffer
     * @param dataIn the input stream to read from
     * @return the instantiated Message instance
     * @throws IOException if an IO problem occurs
     */
    private static Message tryInstantiateProtobuf(Class<?> protoClass, DataInput dataIn) throws IOException {

        try {
            if (dataIn instanceof InputStream) {
                // We can use the built-in parseDelimitedFrom and not have to re-copy
                // the data
                Method parseMethod = getStaticProtobufMethod(protoClass, "parseDelimitedFrom", InputStream.class);
                return (Message) parseMethod.invoke(null, (InputStream) dataIn);
            } else {
                // Have to read it into a buffer first, since protobuf doesn't deal
                // with the DataInput interface directly.

                // Read the size delimiter that writeDelimitedTo writes
                int size = ProtoUtil.readRawVarint32(dataIn);
                if (size < 0) {
                    throw new IOException("Invalid size: " + size);
                }

                byte[] data = new byte[size];
                dataIn.readFully(data);
                Method parseMethod = getStaticProtobufMethod(protoClass, "parseFrom", byte[].class);
                return (Message) parseMethod.invoke(null, data);
            }
        } catch (InvocationTargetException e) {

            if (e.getCause() instanceof IOException) {
                throw (IOException) e.getCause();
            } else {
                throw new IOException(e.getCause());
            }
        } catch (IllegalAccessException iae) {
            throw new AssertionError("Could not access parse method in " + protoClass);
        }
    }

    static Method getStaticProtobufMethod(Class<?> declaredClass, String method, Class<?>... args) {

        try {
            return declaredClass.getMethod(method, args);
        } catch (Exception e) {
            // This is a bug in Hadoop - protobufs should all have this static method
            throw new AssertionError("Protocol buffer class " + declaredClass
                    + " does not have an accessible parseFrom(InputStream) method!");
        }
    }

    /**
     * Find and load the class with given name <tt>className</tt> by first finding
     * it in the specified <tt>conf</tt>. If the specified <tt>conf</tt> is null,
     * try load it directly.
     */
    public static Class<?> loadClass(Configuration conf, String className) {
        Class<?> declaredClass = null;
        try {
            if (conf != null)
                declaredClass = conf.getClassByName(className);
            else
                declaredClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("readObject can't find class " + className, e);
        }
        return declaredClass;
    }

    @Override
    public void setConf(Configuration conf) {
        this.conf = conf;
    }

    @Override
    public Configuration getConf() {
        return this.conf;
    }

}