co.cask.cdap.internal.io.ReflectionReader.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.internal.io.ReflectionReader.java

Source

/*
 * Copyright  2015 Cask Data, Inc.
 *
 * Licensed 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 co.cask.cdap.internal.io;

import co.cask.cdap.api.data.schema.Schema;
import co.cask.cdap.common.lang.Instantiator;
import co.cask.cdap.common.lang.InstantiatorFactory;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
import com.google.common.reflect.TypeToken;

import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.UUID;

/**
 * Reflection based reader.
 *
 * @param <FROM> type of object to read from
 * @param <TO> type of object to read
 */
public abstract class ReflectionReader<FROM, TO> {

    private final Map<Class<?>, Instantiator<?>> creators;
    private final InstantiatorFactory creatorFactory;
    private final FieldAccessorFactory fieldAccessorFactory;
    protected final Schema schema;
    protected final TypeToken<TO> type;

    protected ReflectionReader(Schema schema, TypeToken<TO> type) {
        this.creatorFactory = new InstantiatorFactory(true);
        this.creators = Maps.newIdentityHashMap();
        this.fieldAccessorFactory = new ReflectionFieldAccessorFactory();
        this.schema = schema;
        this.type = type;
    }

    /**
     * Read from the source to create the target type with target schema.
     *
     * @param source the source from which to read the object
     * @param sourceSchema the write schema of the object
     * @return the object of given type and schema
     * @throws IOException if there was some exception reading the object
     */
    public TO read(FROM source, Schema sourceSchema) throws IOException {
        return read(source, sourceSchema, schema, type);
    }

    @SuppressWarnings("unchecked")
    protected TO read(FROM source, Schema sourceSchema, Schema targetSchema, TypeToken<?> targetTypeToken)
            throws IOException {
        if (sourceSchema.getType() != Schema.Type.UNION && targetSchema.getType() == Schema.Type.UNION) {
            // Try every target schemas
            for (Schema schema : targetSchema.getUnionSchemas()) {
                try {
                    return (TO) doRead(source, sourceSchema, schema, targetTypeToken);
                } catch (IOException e) {
                    // Continue;
                }
            }
            throw new IOException(
                    String.format("No matching schema to resolve %s to %s", sourceSchema, targetSchema));
        }
        return (TO) doRead(source, sourceSchema, targetSchema, targetTypeToken);
    }

    protected abstract Object readNull(FROM source) throws IOException;

    protected abstract boolean readBool(FROM source) throws IOException;

    protected abstract int readInt(FROM source) throws IOException;

    protected abstract long readLong(FROM source) throws IOException;

    protected abstract float readFloat(FROM source) throws IOException;

    protected abstract double readDouble(FROM source) throws IOException;

    protected abstract String readString(FROM source) throws IOException;

    protected abstract ByteBuffer readBytes(FROM source) throws IOException;

    protected abstract Object readEnum(FROM source, Schema sourceSchema, Schema targetSchema,
            TypeToken<?> targetTypeToken) throws IOException;

    protected abstract Object readArray(FROM source, Schema sourceSchema, Schema targetSchema,
            TypeToken<?> targetTypeToken) throws IOException;

    protected abstract Object readMap(FROM source, Schema sourceSchema, Schema targetSchema,
            TypeToken<?> targetTypeToken) throws IOException;

    protected abstract Object readUnion(FROM source, Schema sourceSchema, Schema targetSchema,
            TypeToken<?> targetTypeToken) throws IOException;

    protected abstract Object readRecord(FROM source, Schema sourceSchema, Schema targetSchema,
            TypeToken<?> targetTypeToken) throws IOException;

    protected Object doRead(FROM source, Schema sourceSchema, Schema targetSchema, TypeToken<?> targetTypeToken)
            throws IOException {

        Schema.Type sourceType = sourceSchema.getType();
        Schema.Type targetType = targetSchema.getType();

        switch (sourceType) {
        case NULL:
            check(sourceType == targetType, "Fails to resolve %s to %s", sourceType, targetType);
            return readNull(source);
        case BYTES:
            check(sourceType == targetType, "Fails to resolve %s to %s", sourceType, targetType);
            ByteBuffer buffer = readBytes(source);

            if (targetTypeToken.getRawType().equals(byte[].class)) {
                if (buffer.hasArray()) {
                    byte[] array = buffer.array();
                    if (buffer.remaining() == array.length) {
                        return array;
                    }
                    byte[] bytes = new byte[buffer.remaining()];
                    System.arraycopy(array, buffer.arrayOffset() + buffer.position(), bytes, 0, buffer.remaining());
                    return bytes;
                } else {
                    byte[] bytes = new byte[buffer.remaining()];
                    buffer.get(bytes);
                    return bytes;
                }
            } else if (targetTypeToken.getRawType().equals(UUID.class) && buffer.remaining() == Longs.BYTES * 2) {
                return new UUID(buffer.getLong(), buffer.getLong());
            }
            return buffer;
        case ENUM:
            check(sourceType == targetType, "Fails to resolve %s to %s", sourceType, targetType);
            return readEnum(source, sourceSchema, targetSchema, targetTypeToken);
        case ARRAY:
            check(sourceType == targetType, "Fails to resolve %s to %s", sourceType, targetType);
            return readArray(source, sourceSchema, targetSchema, targetTypeToken);
        case MAP:
            check(sourceType == targetType, "Fails to resolve %s to %s", sourceType, targetType);
            return readMap(source, sourceSchema, targetSchema, targetTypeToken);
        case RECORD:
            check(sourceType == targetType, "Fails to resolve %s to %s", sourceType, targetType);
            return readRecord(source, sourceSchema, targetSchema, targetTypeToken);
        case UNION:
            return readUnion(source, sourceSchema, targetSchema, targetTypeToken);
        }
        // For simple type other than NULL and BYTES
        if (sourceType.isSimpleType()) {
            return resolveType(source, sourceType, targetType, targetTypeToken);
        }
        throw new IOException(String.format("Fails to resolve %s to %s", sourceSchema, targetSchema));
    }

    private Object resolveType(FROM source, Schema.Type sourceType, Schema.Type targetType,
            TypeToken<?> targetTypeToken) throws IOException {
        switch (sourceType) {
        case BOOLEAN:
            switch (targetType) {
            case BOOLEAN:
                return readBool(source);
            case STRING:
                return String.valueOf(readBool(source));
            }
            break;
        case INT:
            switch (targetType) {
            case INT:
                Class<?> targetClass = targetTypeToken.getRawType();
                int value = readInt(source);
                if (targetClass.equals(byte.class) || targetClass.equals(Byte.class)) {
                    return (byte) value;
                }
                if (targetClass.equals(char.class) || targetClass.equals(Character.class)) {
                    return (char) value;
                }
                if (targetClass.equals(short.class) || targetClass.equals(Short.class)) {
                    return (short) value;
                }
                return value;
            case LONG:
                return (long) readInt(source);
            case FLOAT:
                return (float) readInt(source);
            case DOUBLE:
                return (double) readInt(source);
            case STRING:
                return String.valueOf(readInt(source));
            }
            break;
        case LONG:
            switch (targetType) {
            case LONG:
                return readLong(source);
            case FLOAT:
                return (float) readLong(source);
            case DOUBLE:
                return (double) readLong(source);
            case STRING:
                return String.valueOf(readLong(source));
            }
            break;
        case FLOAT:
            switch (targetType) {
            case FLOAT:
                return readFloat(source);
            case DOUBLE:
                return (double) readFloat(source);
            case STRING:
                return String.valueOf(readFloat(source));
            }
            break;
        case DOUBLE:
            switch (targetType) {
            case DOUBLE:
                return readDouble(source);
            case STRING:
                return String.valueOf(readDouble(source));
            }
            break;
        case STRING:
            switch (targetType) {
            case STRING:
                String str = readString(source);
                Class<?> targetClass = targetTypeToken.getRawType();
                if (targetClass.equals(URI.class)) {
                    return URI.create(str);
                } else if (targetClass.equals(URL.class)) {
                    return new URL(str);
                }
                return str;
            }
            break;
        }

        throw new IOException("Fail to resolve type " + sourceType + " to type " + targetType);
    }

    protected void check(boolean condition, String message, Object... objs) throws IOException {
        if (!condition) {
            throw new IOException(String.format(message, objs));
        }
    }

    protected IOException propagate(Throwable t) throws IOException {
        if (t instanceof IOException) {
            throw (IOException) t;
        }
        throw new IOException(t);
    }

    protected Object create(TypeToken<?> type) {
        Class<?> rawType = type.getRawType();
        Instantiator<?> creator = creators.get(rawType);
        if (creator == null) {
            creator = creatorFactory.get(type);
            creators.put(rawType, creator);
        }
        return creator.create();
    }

    protected FieldAccessor getFieldAccessor(TypeToken<?> targetTypeToken, String fieldName) {
        return fieldAccessorFactory.getFieldAccessor(targetTypeToken, fieldName);
    }
}