com.github.helenusdriver.driver.impl.RootClassInfoImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.github.helenusdriver.driver.impl.RootClassInfoImpl.java

Source

/*
 * Copyright (C) 2015-2015 The Helenus Driver Project Authors.
 *
 * 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.github.helenusdriver.driver.impl;

import java.lang.reflect.Array;
import java.lang.reflect.Modifier;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.Row;
import com.github.helenusdriver.driver.ObjectConversionException;
import com.github.helenusdriver.persistence.RootEntity;

/**
 * The <code>RootClassInfoImpl</code> class provides information about a
 * particular root element POJO class.
 *
 * @copyright 2015-2015 The Helenus Driver Project Authors
 *
 * @author  The Helenus Driver Project Authors
 * @version 1 - Jan 19, 2015 - paouelle - Creation
 *
 * @param <T> The type of POJO represented by this root class
 *
 * @since 1.0
 */
@lombok.ToString(callSuper = true, of = { "ntypes" })
@lombok.EqualsAndHashCode(callSuper = true)
public class RootClassInfoImpl<T> extends ClassInfoImpl<T> {
    /**
     * Finds the class info for all type classes for this root element class.
     *
     * @author paouelle
     *
     * @param <T> The type of POJO represented by this root element class
     *
     * @param  mgr the non-<code>null</code> statement manager
     * @param  clazz the root POJO class
     * @return the non-<code>null</code> map of class info for all types
     * @throws NullPointerException if <code>clazz</code> is <code>null</code>
     * @throws IllegalArgumentException if any of the type classes are invalid
     */
    private static <T> Map<Class<? extends T>, TypeClassInfoImpl<? extends T>> findTypeInfos(
            StatementManagerImpl mgr, Class<T> clazz) {
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null root POJO class");
        final RootEntity re = clazz.getAnnotation(RootEntity.class);
        final int hsize = re.types().length * 3 / 2;
        final Map<Class<? extends T>, TypeClassInfoImpl<? extends T>> types = new HashMap<>(hsize);
        final Set<String> names = new HashSet<>(hsize);

        for (final Class<?> type : re.types()) {
            org.apache.commons.lang3.Validate.isTrue(clazz.isAssignableFrom(type),
                    "type class '%s' must extends root element class: %s", type.getName(), clazz.getName());
            @SuppressWarnings("unchecked") // tested above!
            final TypeClassInfoImpl<? extends T> tcinfo = new TypeClassInfoImpl<>(mgr, clazz,
                    (Class<? extends T>) type);

            org.apache.commons.lang3.Validate.isTrue(types.put(tcinfo.getObjectClass(), tcinfo) == null,
                    "duplicate type element class '%s' defined for root element class '%s'", type.getSimpleName(),
                    clazz.getSimpleName());
            org.apache.commons.lang3.Validate.isTrue(names.add(tcinfo.getType()),
                    "duplicate type name '%s' defined by class '%s' for root element class '%s'", tcinfo.getType(),
                    type.getSimpleName(), clazz.getSimpleName());
        }
        return types;
    }

    /**
     * The <code>Context</code> class extends the {@link ClassInfoImpl.Context}.
     *
     * @copyright 2015-2015 The Helenus Driver Project Authors
     *
     * @author  The Helenus Driver Project Authors
     * @version 1 - Jan 19, 2015 - paouelle - Creation
     *
     * @since 1.0
     */
    public class Context extends ClassInfoImpl<T>.Context {
        /**
         * Holds the contexts for all defined types.
         *
         * @author paouelle
         */
        private final Map<Class<? extends T>, TypeClassInfoImpl<? extends T>.Context> contexts;

        /**
         * Instantiates a new <code>Context</code> object.
         *
         * @author paouelle
         */
        @SuppressWarnings("synthetic-access")
        Context() {
            this.contexts = ctypes.values().stream()
                    .collect(Collectors.toMap(tcinfo -> tcinfo.getObjectClass(), tcinfo -> tcinfo.newContext()));
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getInitialObjects()
         */
        @Override
        @SuppressWarnings("unchecked")
        public T[] getInitialObjects() {
            return contexts.values().stream().flatMap(tc -> {
                final T[] ts = tc.getInitialObjects();

                return (ts != null) ? Arrays.stream(ts) : Stream.empty();
            }).toArray(sz -> (T[]) Array.newInstance(clazz, sz));
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#addSuffix(java.lang.String, java.lang.Object)
         */
        @Override
        public void addSuffix(String suffix, Object value) {
            super.addSuffix(suffix, value);
            contexts.values().forEach(tc -> tc.addSuffix(suffix, value));
        }
    }

    /**
     * The <code>POJOContext</code> class extends the
     * {@link ClassInfoImpl.POJOContext}.
     *
     * @copyright 2015-2015 The Helenus Driver Project Authors
     *
     * @author  The Helenus Driver Project Authors
     * @version 1 - Jan 19, 2015 - paouelle - Creation
     *
     * @since 1.0
     */
    public class POJOContext extends ClassInfoImpl<T>.POJOContext {
        /**
         * Holds the corresponding type context for the POJO.
         *
         * @author paouelle
         */
        private final TypeClassInfoImpl<? extends T>.POJOContext tcontext;

        /**
         * Instantiates a new <code>POJORootContext</code> object.
         *
         * @author paouelle
         *
         * @param  object the POJO object
         * @throws NullPointerException if <code>object</code> is <code>null</code>
         * @throws IllegalArgumentException if <code>object</code> is not of the
         *         appropriate class
         */
        @SuppressWarnings("synthetic-access")
        public POJOContext(T object) {
            super(object);
            final TypeClassInfoImpl<? extends T> tcinfo = ctypes.get(object.getClass());

            org.apache.commons.lang3.Validate.isTrue(tcinfo != null, "invalid POJO class '%s'; expecting one of %s",
                    object.getClass().getName(), ctypes.keySet());
            this.tcontext = tcinfo.newContextFromRoot(object);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getColumnValues(String tname) {
            return tcontext.getColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getPartitionKeyColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getPartitionKeyColumnValues(String tname) {
            return tcontext.getPartitionKeyColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getSuffixAndPartitionKeyColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getSuffixAndPartitionKeyColumnValues(String tname) {
            return tcontext.getSuffixAndPartitionKeyColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getPrimaryKeyColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getPrimaryKeyColumnValues(String tname) {
            return tcontext.getPrimaryKeyColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getSuffixKeyValues()
         */
        @Override
        public Map<String, Object> getSuffixKeyValues() {
            return tcontext.getSuffixKeyValues();
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getSuffixAndPrimaryKeyColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getSuffixAndPrimaryKeyColumnValues(String tname) {
            return tcontext.getSuffixAndPrimaryKeyColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getMandatoryAndPrimaryKeyColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getMandatoryAndPrimaryKeyColumnValues(String tname) {
            return tcontext.getMandatoryAndPrimaryKeyColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getNonPrimaryKeyColumnValues(java.lang.String)
         */
        @Override
        public Map<String, Object> getNonPrimaryKeyColumnValues(String tname) {
            return tcontext.getNonPrimaryKeyColumnValues(tname);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValue(java.lang.String, java.lang.CharSequence)
         */
        @Override
        public Object getColumnValue(String tname, CharSequence name) {
            return tcontext.getColumnValue(tname, name);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValues(java.lang.String, java.lang.Iterable)
         */
        @Override
        public Map<String, Object> getColumnValues(String tname, Iterable<CharSequence> names) {
            return tcontext.getColumnValues(tname, names);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValues(java.lang.String, java.lang.CharSequence[])
         */
        @Override
        public Map<String, Object> getColumnValues(String tname, CharSequence... names) {
            return tcontext.getColumnValues(tname, names);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getKeyspace()
         */
        @Override
        public String getKeyspace() {
            return tcontext.getKeyspace();
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#addSuffix(java.lang.String, java.lang.Object)
         */
        @Override
        public void addSuffix(String suffix, Object value) {
            tcontext.addSuffix(suffix, value);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getObject(com.datastax.driver.core.Row)
         */
        @Override
        public T getObject(Row row) {
            return tcontext.getObject(row);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getInitialObjects()
         */
        @Override
        public T[] getInitialObjects() {
            return tcontext.getInitialObjects();
        }
    }

    /**
     * Holds all the type element subclasses for this root element base class
     * keyed by their type class.
     *
     * @author paouelle
     */
    private final Map<Class<? extends T>, TypeClassInfoImpl<? extends T>> ctypes;

    /**
     * Holds all the type element subclasses for this root element base class
     * keyed by their type names.
     *
     * @author paouelle
     */
    private final Map<String, TypeClassInfoImpl<? extends T>> ntypes;

    /**
     * Instantiates a new <code>RootClassInfoImpl</code> object.
     *
     * @author paouelle
     *
     * @param  mgr the non-<code>null</code> statement manager
     * @param  clazz the root class of POJO for which to get a class info object for
     * @param  types the non-<code>null</code> map of all type class infos
     * @throws NullPointerException if <code>clazz</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>clazz</code> or any of its type
     *         classes don't represent valid POJO classes
     */
    private RootClassInfoImpl(StatementManagerImpl mgr, Class<T> clazz,
            Map<Class<? extends T>, TypeClassInfoImpl<? extends T>> types) {
        super(mgr, clazz, RootEntity.class);
        // first make sure the class is abstract
        org.apache.commons.lang3.Validate.isTrue(Modifier.isAbstract(clazz.getModifiers()),
                "root entity class '%s', must be abstract", clazz.getSimpleName());
        this.ctypes = types;
        this.ntypes = types.values().stream()
                .collect(Collectors.toMap(tcinfo -> tcinfo.getType(), tcinfo -> tcinfo));
        validateAndComplementSchema();
    }

    /**
     * Instantiates a new <code>RootClassInfoImpl</code> object.
     *
     * @author paouelle
     *
     * @param  mgr the non-<code>null</code> statement manager
     * @param  clazz the root class of POJO for which to get a class info object for
     * @throws NullPointerException if <code>clazz</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>clazz</code> or any of its type
     *         classes don't represent valid POJO classes
     */
    RootClassInfoImpl(StatementManagerImpl mgr, Class<T> clazz) {
        this(mgr, clazz, RootClassInfoImpl.findTypeInfos(mgr, clazz));
    }

    /**
     * Validates this root entity class and complement its schema with the
     * additional columns defined by the type entities.
     *
     * @author paouelle
     *
     * @throws IllegalArgumentException if the POJO class is improperly annotated
     */
    private void validateAndComplementSchema() {
        // check all tables
        tables().forEach(t -> {
            // check type key
            org.apache.commons.lang3.Validate.isTrue(t.getTypeKey().isPresent(),
                    "%s must annotate one field as a type key for table '%s'", clazz.getSimpleName(), t.getName());
            ctypes.values().forEach(tcinfo -> {
                tcinfo.getTableImpl(t.getName()).getNonPrimaryKeys().stream()
                        .forEach(c -> t.addNonPrimaryColumn(c));
            });
        });
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#objectClasses()
     */
    @Override
    public Stream<Class<? extends T>> objectClasses() {
        return Stream.concat(Stream.of(clazz), ctypes.values().stream().map(t -> t.getObjectClass()));
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#classInfos()
     */
    @Override
    public Stream<ClassInfoImpl<? extends T>> classInfos() {
        return Stream.concat(Stream.of(this), ctypes.values().stream());
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#newContext()
     */
    @Override
    public Context newContext() {
        return new Context();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#newContext(java.lang.Object)
     */
    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public POJOContext newContext(T object) {
        return new POJOContext(object);
    }

    /**
     * Gets the class info for the specified type entity defined from this root
     * entity.
     *
     * @author paouelle
     *
     * @param <S> the type of POJO for the type entity
     *
     * @param  clazz the POJO class of the type entity to retrieve its info
     * @return the corresponding type entity POJO class information or <code>null</code>
     *         if none defined for the given class
     */
    @SuppressWarnings("unchecked")
    public <S extends T> TypeClassInfoImpl<S> getType(Class<S> clazz) {
        return (TypeClassInfoImpl<S>) ctypes.get(clazz);
    }

    /**
     * Gets the class info for the specified type entity defined from this root
     * entity.
     *
     * @author paouelle
     *
     * @param  name the name of the type entity to retrieve its info
     * @return the corresponding type entity POJO class information or <code>null</code>
     *         if none defined for the given name
     */
    public TypeClassInfoImpl<? extends T> getType(String name) {
        return ntypes.get(name);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#getObject(com.datastax.driver.core.Row, java.util.Map)
     */
    @Override
    public T getObject(Row row, Map<String, Object> suffixes) {
        if (row == null) {
            return null;
        }
        // extract the type so we know which object we are creating
        for (final ColumnDefinitions.Definition coldef : row.getColumnDefinitions()) {
            // find the table for this column
            final TableInfoImpl<T> table = (TableInfoImpl<T>) getTable(coldef.getTable());

            if (table != null) {
                // find the field in the table for this column
                final FieldInfoImpl<T> field = table.getColumn(coldef.getName());

                if ((field != null) && field.isTypeKey()) { // get the POJO type
                    final String type = Objects.toString(field.decodeValue(row), null);
                    final TypeClassInfoImpl<? extends T> tcinfo = ntypes.get(type);

                    if (tcinfo == null) {
                        throw new ObjectConversionException(clazz, row, "unknown POJO type: " + type);
                    }
                    return tcinfo.getObject(row, type, suffixes);
                }
            }
        }
        throw new ObjectConversionException(clazz, row, "missing POJO type column");
    }
}