All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.parser.AvroParser Maven / Gradle / Ivy

Go to download

A general programming library in Java/Android. It's easy to learn and simple to use with concise and powerful APIs.

There is a newer version: 5.3.16
Show newest version
/*
 * Copyright (C) 2015 HaiYang Li
 *
 * 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 com.landawn.abacus.parser;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.avro.Schema;
import org.apache.avro.Schema.Field;
import org.apache.avro.file.DataFileStream;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.generic.GenericData.Record;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificDatumWriter;
import org.apache.avro.specific.SpecificRecord;

import com.landawn.abacus.annotation.SuppressFBWarnings;
import com.landawn.abacus.exception.UncheckedIOException;
import com.landawn.abacus.parser.ParserUtil.BeanInfo;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.ByteArrayOutputStream;
import com.landawn.abacus.util.ClassUtil;
import com.landawn.abacus.util.IOUtil;
import com.landawn.abacus.util.Maps;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Objectory;
import com.landawn.abacus.util.Strings;

/**
 * The content is encoded with Base64 if the target output is String or Writer, otherwise the content is NOT encoded with Base64 if the target output is File or OutputStream.
 * So content must be encoded with Base64 if the specified input is String or Reader, otherwise the content must NOT be encoded with Base64 if the specified input is File or InputStream.
 * The reason not to encoded the content with Base64 for File/OutputStream is to provide higher performance solution.
 *
 * @author Haiyang Li
 * @since 0.8
 */
public final class AvroParser extends AbstractParser {

    /**
     *
     * @param obj
     * @param config
     * @return
     */
    @Override
    public String serialize(Object obj, AvroSerializationConfig config) {
        final ByteArrayOutputStream os = Objectory.createByteArrayOutputStream();

        try {
            serialize(obj, config, os);

            return Strings.base64Encode(os.toByteArray());
        } finally {
            Objectory.recycle(os);
        }
    }

    /**
     *
     * @param obj
     * @param config
     * @param output
     */
    @Override
    public void serialize(Object obj, AvroSerializationConfig config, File output) {
        OutputStream os = null;

        try {
            createNewFileIfNotExists(output);

            os = IOUtil.newFileOutputStream(output);

            serialize(obj, config, os);

            os.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            IOUtil.close(os);
        }
    }

    /**
     *
     * @param obj
     * @param config
     * @param output
     */
    @SuppressFBWarnings
    @SuppressWarnings("resource")
    @Override
    public void serialize(Object obj, AvroSerializationConfig config, OutputStream output) {
        final Type type = N.typeOf(obj.getClass());

        if (obj instanceof SpecificRecord specificRecord) {
            final DatumWriter datumWriter = new SpecificDatumWriter<>((Class) specificRecord.getClass());
            final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter);

            try {
                dataFileWriter.create(specificRecord.getSchema(), output);
                dataFileWriter.append(specificRecord);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            } finally {
                IOUtil.close(dataFileWriter);
            }
        } else if (type.isCollection() && ((Collection) obj).size() > 0 && ((Collection) obj).iterator().next() instanceof SpecificRecord) {
            final Collection c = (Collection) obj;
            final SpecificRecord specificRecord = c.iterator().next();
            final DatumWriter datumWriter = new SpecificDatumWriter<>((Class) specificRecord.getClass());
            final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter);

            try {
                dataFileWriter.create(specificRecord.getSchema(), output);

                for (SpecificRecord e : c) {
                    dataFileWriter.append(e);
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            } finally {
                IOUtil.close(dataFileWriter);
            }
        } else if (type.isObjectArray() && ((Object[]) obj).length > 0 && ((Object[]) obj)[0] instanceof SpecificRecord) {
            final Object[] a = (Object[]) obj;
            final SpecificRecord specificRecord = (SpecificRecord) a[0];
            final DatumWriter datumWriter = new SpecificDatumWriter<>((Class) specificRecord.getClass());
            final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter);

            try {
                dataFileWriter.create(specificRecord.getSchema(), output);

                for (Object e : a) {
                    dataFileWriter.append((SpecificRecord) e);
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            } finally {
                IOUtil.close(dataFileWriter);
            }
        } else {
            if (config == null || config.getSchema() == null) {
                throw new IllegalArgumentException("Schema is not specified");
            }

            final Schema schema = config.getSchema();
            final DatumWriter datumWriter = new GenericDatumWriter<>(schema);
            final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter);

            try {
                dataFileWriter.create(schema, output);

                if (obj instanceof GenericRecord) {
                    dataFileWriter.append((GenericRecord) obj);
                } else if (type.isBean() || type.isMap()) {
                    dataFileWriter.append(toGenericRecord(obj, schema));
                } else if (type.isCollection()) {
                    boolean isMapOrBean = false;
                    final Collection c = (Collection) obj;

                    for (Object e : c) {
                        if (e != null && (e instanceof Map || ClassUtil.isBeanClass(e.getClass()) || e instanceof GenericRecord)) {
                            isMapOrBean = true;
                            break;
                        }
                    }

                    if (isMapOrBean) {
                        for (Object e : c) {
                            dataFileWriter.append(toGenericRecord(e, schema));
                        }
                    } else {
                        dataFileWriter.append(toGenericRecord(obj, schema));
                    }
                } else if (type.isObjectArray()) {
                    boolean isMapOrBean = false;
                    final Object[] a = (Object[]) obj;

                    for (Object e : a) {
                        if (e != null && (e instanceof Map || ClassUtil.isBeanClass(e.getClass()) || e instanceof GenericRecord)) {
                            isMapOrBean = true;
                            break;
                        }
                    }

                    if (isMapOrBean) {
                        for (Object e : a) {
                            dataFileWriter.append(toGenericRecord(e, schema));
                        }
                    } else {
                        dataFileWriter.append(toGenericRecord(obj, schema));
                    }
                } else if (type.isPrimitiveArray()) {
                    dataFileWriter.append(toGenericRecord(obj, schema));
                } else {
                    throw new IllegalArgumentException("Unsupprted type: " + type.name()); //NOSONAR
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            } finally {
                IOUtil.close(dataFileWriter);
            }
        }
    }

    /**
     * 
     *
     * @param obj 
     * @param config 
     * @param output 
     * @throws UnsupportedOperationException 
     */
    @Override
    public void serialize(Object obj, AvroSerializationConfig config, Writer output) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    /**
     * To generic record.
     *
     * @param obj
     * @param schema
     * @return
     */
    private GenericRecord toGenericRecord(final Object obj, final Schema schema) {
        if (obj instanceof GenericRecord) {
            return (GenericRecord) obj;
        }

        final Class cls = obj.getClass();
        final Type type = N.typeOf(cls);

        if (type.isBean()) {
            return toGenericRecord(Maps.bean2Map(obj), schema);
        } else if (type.isMap()) {
            final Map m = (Map) obj;
            final Record localRecord = new Record(schema);

            for (Map.Entry entry : m.entrySet()) {
                localRecord.put(entry.getKey(), entry.getValue());
            }

            return localRecord;
        } else if (type.isCollection()) {
            final Collection c = (Collection) obj;
            final Record localRecord = new Record(schema);

            int index = 0;
            for (Object e : c) {
                localRecord.put(index++, e);
            }

            return localRecord;
        } else if (type.isObjectArray()) {
            final Object[] a = (Object[]) obj;
            final Record localRecord = new Record(schema);

            int index = 0;
            for (Object e : a) {
                localRecord.put(index++, e);
            }

            return localRecord;
        } else if (type.isPrimitiveArray()) {
            return toGenericRecord(type.array2Collection(obj, List.class), schema);
        } else {
            throw new IllegalArgumentException("Unsupprted type: " + type.name());
        }
    }

    /**
     * 
     *
     * @param  
     * @param source 
     * @param config 
     * @param targetClass 
     * @return 
     */
    @Override
    public  T deserialize(String source, AvroDeserializationConfig config, Class targetClass) {
        return deserialize(new ByteArrayInputStream(Strings.base64Decode(source)), config, targetClass);
    }

    /**
     * 
     *
     * @param  
     * @param source 
     * @param config 
     * @param targetClass 
     * @return 
     */
    @Override
    public  T deserialize(File source, AvroDeserializationConfig config, Class targetClass) {
        InputStream is = null;

        try {
            is = IOUtil.newFileInputStream(source);

            return deserialize(is, config, targetClass);
        } finally {
            IOUtil.close(is);
        }
    }

    /**
     * 
     *
     * @param  
     * @param source 
     * @param config 
     * @param targetClass 
     * @return 
     */
    @Override
    public  T deserialize(InputStream source, AvroDeserializationConfig config, Class targetClass) {
        final Type targetType = N.typeOf(targetClass);
        final Type eleType = config == null ? null : config.getElementType();

        if (SpecificRecord.class.isAssignableFrom(targetClass)) {
            final DatumReader datumReader = new SpecificDatumReader<>((Class) targetClass);
            T bean = null;

            try (DataFileStream dataFileReader = new DataFileStream<>(source, datumReader)) {

                if (dataFileReader.hasNext()) {
                    bean = dataFileReader.next();
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }

            return bean;
        } else if ((targetType.isCollection() || targetType.isObjectArray()) && ((eleType != null && SpecificRecord.class.isAssignableFrom(eleType.clazz()))
                || (targetType.isObjectArray() && SpecificRecord.class.isAssignableFrom(targetClass.getComponentType())))) {
            final Class eleClass = eleType != null && SpecificRecord.class.isAssignableFrom(eleType.clazz()) ? eleType.clazz()
                    : (Class) targetClass.getComponentType();
            @SuppressWarnings("rawtypes")
            final Collection c = targetType.isCollection() ? ((Collection) N.newCollection((Class) targetClass))
                    : new ArrayList<>();
            final DatumReader datumReader = new SpecificDatumReader<>(eleClass);

            try (DataFileStream dataFileReader = new DataFileStream<>(source, datumReader)) {

                while (dataFileReader.hasNext()) {
                    c.add(dataFileReader.next());
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }

            return (T) (targetType.isCollection() ? c : c.toArray((Object[]) N.newArray(targetClass.getComponentType(), c.size())));
        } else {
            if (config == null || config.getSchema() == null) {
                throw new IllegalArgumentException("Schema is not specified");
            }

            final Schema schema = config.getSchema();
            final DatumReader datumReader = new GenericDatumReader<>(schema);

            try (DataFileStream dataFileReader = new DataFileStream<>(source, datumReader)) {
                if (targetClass.isAssignableFrom(GenericRecord.class)) {
                    return (T) (dataFileReader.hasNext() ? dataFileReader.next() : null);
                } else if (targetType.isBean() || targetType.isMap()) {
                    return dataFileReader.hasNext() ? fromGenericRecord(dataFileReader.next(), targetClass) : null;
                } else if (targetType.isCollection() || targetType.isObjectArray()) {
                    if (eleType != null && (eleType.isBean() || eleType.isMap() || GenericRecord.class.isAssignableFrom(eleType.clazz()))) {
                        @SuppressWarnings("rawtypes")
                        final Collection c = targetType.isCollection() ? ((Collection) N.newCollection((Class) targetClass))
                                : new ArrayList<>();

                        while (dataFileReader.hasNext()) {
                            c.add(fromGenericRecord(dataFileReader.next(), eleType.clazz()));
                        }

                        return (T) (targetType.isCollection() ? c : c.toArray((Object[]) N.newArray(targetClass.getComponentType(), c.size())));
                    } else if (targetType.isObjectArray() && (targetType.getElementType().isBean() || targetType.getElementType().isMap()
                            || GenericRecord.class.isAssignableFrom(targetClass.getComponentType()))) {
                        @SuppressWarnings("rawtypes")
                        final Collection c = targetType.isCollection() ? ((Collection) N.newCollection((Class) targetClass))
                                : new ArrayList<>();

                        while (dataFileReader.hasNext()) {
                            c.add(fromGenericRecord(dataFileReader.next(), targetClass.getComponentType()));
                        }

                        return (T) (targetType.isCollection() ? c : c.toArray((Object[]) N.newArray(targetClass.getComponentType(), c.size())));
                    } else {
                        return dataFileReader.hasNext() ? fromGenericRecord(dataFileReader.next(), targetClass) : null;
                    }
                } else if (targetType.isPrimitiveArray()) {
                    return dataFileReader.hasNext() ? fromGenericRecord(dataFileReader.next(), targetClass) : null;
                } else {
                    throw new IllegalArgumentException("Unsupprted type: " + targetType.name());
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
    }

    /**
     * 
     *
     * @param  
     * @param source 
     * @param config 
     * @param targetClass 
     * @return 
     * @throws UnsupportedOperationException 
     */
    @Override
    public  T deserialize(Reader source, AvroDeserializationConfig config, Class targetClass) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    /**
     * From generic record.
     * @param source
     * @param targetClass
     *
     * @param 
     * @return
     */
    private  T fromGenericRecord(final GenericRecord source, final Class targetClass) {
        if (targetClass.isAssignableFrom(source.getClass())) {
            return (T) source;
        }

        final Type type = N.typeOf(targetClass);

        if (type.isBean()) {
            final BeanInfo entitInfo = ParserUtil.getBeanInfo(targetClass);
            final Object result = entitInfo.createBeanResult();
            Object propValue = null;

            for (Field field : source.getSchema().getFields()) {
                propValue = source.get(field.name());

                if (propValue != null) {
                    entitInfo.setPropValue(result, field.name(), propValue);
                }
            }
            return entitInfo.finishBeanResult(result);
        } else if (type.isMap()) {
            @SuppressWarnings("rawtypes")
            final Map m = N.newMap((Class) targetClass);
            Object propValue = null;

            for (Field field : source.getSchema().getFields()) {
                propValue = source.get(field.name());

                if (propValue != null) {
                    m.put(field.name(), propValue);
                }
            }

            return (T) m;
        } else if (type.isCollection() || type.isObjectArray()) {
            @SuppressWarnings("rawtypes")
            final Collection c = type.isCollection() ? (Collection) N.newCollection((Class) targetClass) : new ArrayList<>();
            Object propValue = null;

            for (Field field : source.getSchema().getFields()) {
                propValue = source.get(field.pos());

                if (propValue != null) {
                    c.add(propValue);
                }
            }

            return (T) (type.isCollection() ? c : c.toArray((Object[]) N.newArray(targetClass.getComponentType(), c.size())));
        } else if (type.isPrimitiveArray()) {
            final Collection c = new ArrayList<>();
            Object propValue = null;

            for (Field field : source.getSchema().getFields()) {
                propValue = source.get(field.pos());

                if (propValue != null) {
                    c.add(propValue);
                }
            }

            return (T) type.collection2Array(c);
        } else {
            throw new IllegalArgumentException("Unsupprted type: " + type.name());
        }
    }
}