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

Java tutorial

Introduction

Here is the source code for com.github.helenusdriver.driver.impl.TableInfoImpl.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.Field;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.commons.lang3.tuple.Pair;

import com.github.helenusdriver.commons.lang3.reflect.ReflectionUtils;
import com.github.helenusdriver.driver.ColumnPersistenceException;
import com.github.helenusdriver.driver.info.ClassInfo;
import com.github.helenusdriver.driver.info.FieldInfo;
import com.github.helenusdriver.driver.info.TableInfo;
import com.github.helenusdriver.persistence.Column;
import com.github.helenusdriver.persistence.DataType;
import com.github.helenusdriver.persistence.Table;

/**
 * The <code>TableInfo</code> class caches all the table and its field
 * information needed by the class {@link ClassInfoImpl}.
 * <p>
 * <i>Note:</i> A fake instance of this class will be created with no table
 * annotations for user-defined type entities. By design, the
 * {@link FieldInfoImpl} class will not allow any type of keys but only columns.
 *
 * @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 class
 *
 * @since 1.0
 */
@lombok.ToString(exclude = { "cinfo" })
public class TableInfoImpl<T> implements TableInfo<T> {
    /**
     * Cleanups the specified table name according to Cassandra's guidelines.
     *
     * @author paouelle
     *
     * @param  name the table name to clean up
     * @return the corresponding cleaned up table name
     */
    static final String cleanName(String name) {
        // replaces all non-alphanumeric or underscores with underscores
        // to comply with Cassandra
        return name.replaceAll("[^a-zA-Z0-9_]", "_").toLowerCase();
    }

    /**
     * Holds the class for the POJO.
     *
     * @author vasu
     */
    private final Class<T> clazz;

    /**
     * Holds the class info for the POJO.
     *
     * @author paouelle
     */
    private final ClassInfoImpl<T> cinfo;

    /**
     * Holds the table annotation.
     *
     * @author vasu
     */
    private final Table table;

    /**
     * Holds the cleaned up table name.
     *
     * @author paouelle
     */
    private final String name;

    /**
     * Holds a map of all fields annotated as columns keyed by the field name and
     * its declaring class.
     *
     * @author vasu
     */
    private final Map<Pair<String, Class<?>>, FieldInfoImpl<T>> fields = new LinkedHashMap<>(25);

    /**
     * Holds a map of all fields annotated as columns keyed by the column name.
     *
     * @author vasu
     */
    private final Map<String, FieldInfoImpl<T>> columns = new LinkedHashMap<>(25);

    /**
     * Holds a map of all primary key column fields keyed by the column name.
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> primaryKeyColumns = new LinkedHashMap<>(12);

    /**
     * Holds a map of all index column fields keyed by the column name.
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> indexColumns = new LinkedHashMap<>(12);

    /**
     * Holds a map of all partition key column fields keyed by the column name.
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> partitionKeyColumns = new LinkedHashMap<>(12);

    /**
     * Holds a map of all clustering key column fields keyed by the column name.
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> clusteringKeyColumns = new LinkedHashMap<>(12);

    /**
     * Holds the key column field.
     *
     * @author paouelle
     */
    private final MutableObject<FieldInfoImpl<T>> typeKeyColumn = new MutableObject<>();

    /**
     * Holds a map of all final primary key values keyed by the column
     * name.
     *
     * @author paouelle
     */
    private final Map<String, Object> finalPrimaryKeyValues = new LinkedHashMap<>(12);

    /**
     * Holds a map of all mandatory, type key, and primary key column fields keyed
     * by the column name.
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> mandatoryAndPrimaryKeyColumns = new LinkedHashMap<>(12);

    /**
     * Holds a map of all non primary key column fields keyed by the
     * column name.
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> nonPrimaryKeyColumns = new LinkedHashMap<>(12);

    /**
     * Holds the fields which are defined as multi-keys for the table (may be
     * empty if none defined).
     *
     * @author paouelle
     */
    private final Map<String, FieldInfoImpl<T>> multiKeyColumns = new LinkedHashMap<>(2);

    /**
     * Holds the fields which are defined as multi-keys for the table (may be
     * empty if none defined).
     *
     * @author paouelle
     */
    private final List<FieldInfoImpl<T>> multiKeyColumnsList = new ArrayList<>(2);

    /**
     * Instantiates a new <code>TableInfo</code> object.
     *
     * @author paouelle
     *
     * @param  mgr the non-<code>null</code> statement manager
     * @param  cinfo the non-<code>null</code> class info for the POJO
     * @param  table the table annotation or <code>null</code> if there is no table
     * @throws IllegalArgumentException if unable to find getter or setter
     *         methods for fields of if improperly annotated
     */
    public TableInfoImpl(StatementManagerImpl mgr, ClassInfoImpl<T> cinfo, Table table) {
        this.clazz = cinfo.getObjectClass();
        this.cinfo = cinfo;
        this.table = table;
        this.name = (table != null) ? TableInfoImpl.cleanName(table.name()) : null;
        findColumnFields(mgr);
    }

    /**
     * Instantiates a new <code>TableInfo</code> object.
     *
     * @author paouelle
     *
     * @param  mgr the non-<code>null</code> statement manager
     * @param  cinfo the non-<code>null</code> class info for the POJO
     * @param  name the non-<code>null</code> table name
     * @throws IllegalArgumentException if unable to find getter or setter
     *         methods for fields of if improperly annotated
     */
    public TableInfoImpl(StatementManagerImpl mgr, ClassInfoImpl<T> cinfo, String name) {
        this.clazz = cinfo.getObjectClass();
        this.cinfo = cinfo;
        this.table = null;
        this.name = name;
        findColumnFields(mgr);
    }

    /**
     * Finds and record all fields annotated as columns for this table.
     *
     * @author paouelle
     *
     * @param  mgr the non-<code>null</code> statement manager
     * @throws IllegalArgumentException if unable to find a getter or setter
     *         method for the field of if improperly annotated
     */
    private void findColumnFields(StatementManagerImpl mgr) {
        // make sure to walk up the class hierarchy
        FieldInfoImpl<T> lastPartitionKey = null;
        FieldInfoImpl<T> lastClusteringKey = null;

        for (final Field f : ReflectionUtils.getAllFieldsAnnotatedWith(clazz, Column.class, true)) {
            final Pair<String, Class<?>> pf = Pair.of(f.getName(), f.getDeclaringClass());
            FieldInfoImpl<T> field = fields.get(pf);

            if (field == null) {
                field = new FieldInfoImpl<>(this, f);
                if (!field.isColumn()) {
                    // not annotated as a column for this table so skip it
                    continue;
                }
                fields.put(pf, field);
            }
            final FieldInfoImpl<T> oldc = columns.put(field.getColumnName(), field);

            if (oldc != null) {
                throw new IllegalArgumentException(clazz.getSimpleName()
                        + " cannot annotate more than one field with the same column name '" + field.getColumnName()
                        + ((table != null) ? "' for table '" + table.name() : "") + "': found '"
                        + oldc.getDeclaringClass().getSimpleName() + "." + oldc.getName() + "' and '"
                        + field.getDeclaringClass().getSimpleName() + "." + field.getName() + "'");
            }
            if (field.isTypeKey()) { // by design will be false if no table is defined
                final FieldInfoImpl<T> oldk = typeKeyColumn.getValue();

                if (oldk != null) {
                    throw new IllegalArgumentException(clazz.getSimpleName()
                            + " cannot annotate more than one field as a type key for table '" + table.name()
                            + "': found '" + oldk.getDeclaringClass().getSimpleName() + "." + oldk.getName()
                            + "' and '" + field.getDeclaringClass().getSimpleName() + "." + field.getName() + "'");
                }
                typeKeyColumn.setValue(field);
                mandatoryAndPrimaryKeyColumns.put(field.getColumnName(), field);
                if (field.isIndex()) {
                    indexColumns.put(field.getColumnName(), field);
                }
            }
            if (field.isPartitionKey()) {
                lastPartitionKey = field;
                mandatoryAndPrimaryKeyColumns.put(field.getColumnName(), field);
                primaryKeyColumns.put(field.getColumnName(), field);
                partitionKeyColumns.put(field.getColumnName(), field);
                if (field.isFinal()) {
                    finalPrimaryKeyValues.put(field.getColumnName(), field.getFinalValue());
                }
                if (field.isMultiKey()) {
                    multiKeyColumns.put(field.getColumnName(), field);
                    multiKeyColumnsList.add(field);
                }
            } else if (field.isClusteringKey()) {
                lastClusteringKey = field;
                mandatoryAndPrimaryKeyColumns.put(field.getColumnName(), field);
                primaryKeyColumns.put(field.getColumnName(), field);
                clusteringKeyColumns.put(field.getColumnName(), field);
                if (field.isFinal()) {
                    finalPrimaryKeyValues.put(field.getColumnName(), field.getFinalValue());
                }
                if (field.isMultiKey()) {
                    multiKeyColumns.put(field.getColumnName(), field);
                    multiKeyColumnsList.add(field);
                }
            } else if (!field.isTypeKey()) {
                if (field.isIndex()) {
                    indexColumns.put(field.getColumnName(), field);
                }
                if (field.isMandatory()) {
                    mandatoryAndPrimaryKeyColumns.put(field.getColumnName(), field);
                    nonPrimaryKeyColumns.put(field.getColumnName(), field);
                } else {
                    nonPrimaryKeyColumns.put(field.getColumnName(), field);
                }
            }
        }
        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(!partitionKeyColumns.isEmpty(),
                    "%s must annotate one field as a partition primary key for table '%s'", clazz.getSimpleName(),
                    table.name());
        }
        // filters out columns if need be
        mgr.filter(this);
        // finalize table keys
        reorderPrimaryKeys();
        if (lastPartitionKey != null) {
            lastPartitionKey.setLast();
        }
        if (lastClusteringKey != null) {
            lastClusteringKey.setLast();
        }
    }

    /**
     * Re-order primary keys based on @Table annotation specifications.
     *
     * @author paouelle
     */
    private void reorderPrimaryKeys() {
        if ((table == null) || ((table.partition().length == 0) && (table.clustering().length == 0))) {
            return; // nothing to do so keep original order
        }
        // clone keys map so we can modify the original ones
        final Map<String, FieldInfoImpl<T>> partition = new LinkedHashMap<>(partitionKeyColumns);
        final Map<String, FieldInfoImpl<T>> clustering = new LinkedHashMap<>(clusteringKeyColumns);

        primaryKeyColumns.clear();
        partitionKeyColumns.clear();
        clusteringKeyColumns.clear();
        // start with partition keys specified in @Table
        for (final String columnName : table.partition()) {
            final FieldInfoImpl<T> field = partition.remove(columnName);

            org.apache.commons.lang3.Validate.isTrue(field != null,
                    "@Table partition key '%s' not found in %s for table '%s'", columnName, clazz.getSimpleName(),
                    table.name());
            primaryKeyColumns.put(columnName, field);
            partitionKeyColumns.put(columnName, field);
        }
        // add remaining partition keys
        primaryKeyColumns.putAll(partition);
        partitionKeyColumns.putAll(partition);
        // now deal with clustering keys specified in @Table
        for (final String columnName : table.clustering()) {
            final FieldInfoImpl<T> field = clustering.remove(columnName);

            org.apache.commons.lang3.Validate.isTrue(field != null,
                    "@Table clustering key '%s' not found in %s for table '%s'", columnName, clazz.getSimpleName(),
                    table.name());
            primaryKeyColumns.put(columnName, field);
            clusteringKeyColumns.put(columnName, field);
        }
        // add remaining clustering keys
        primaryKeyColumns.putAll(clustering);
        clusteringKeyColumns.putAll(clustering);
    }

    /**
     * Validates if a column is defined by the POJO in this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  ignoreNonColumnNames <code>true</code> to not generate an exception if
     *         the specified name cannot be interpreted as a column name;
     *         <code>false</code> to generate an exception
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO
     */
    private void validateColumn(Object name, boolean ignoreNonColumnNames) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateColumn(n); // recurse to validate
            }
            return;
        }
        final String n;

        if (name instanceof Utils.FCall) {
            for (final Object parm : ((Utils.FCall) name)) {
                validateColumn(parm, true);
            }
            return;
        }
        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            if (ignoreNonColumnNames) { // ignore it
                return;
            }
            if (table == null) {
                throw new IllegalArgumentException("unexpected column name '" + name + "'");
            }
            throw new IllegalArgumentException(
                    "unexpected column name '" + name + "' in table '" + table.name() + "'");
        }
        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(columns.containsKey(n),
                    "%s doesn't define column '%s' in table '%s'", clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(columns.containsKey(n), "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n, table.name());
        }
    }

    /**
     * Adds the specified non-primary column field to this table.
     * <p>
     * This method will be called by the root entity when it is adding non-primary
     * column fields defined only in its type POJO classes to complement its
     * schema definition.
     * <p>
     * <i>Note:</i> The provided column will not be added to this table if a
     * compatible column with the same name already exist in this table.
     *
     * @author paouelle
     *
     * @param  col the non-<code>null</code> non-primary column field to add to
     *         this table
     * @throws IllegalArgumentException if the column being added is not compatible
     *         to one already defined with the same column name in this table
     */
    void addNonPrimaryColumn(FieldInfoImpl<? extends T> col) {
        org.apache.commons.lang3.Validate.isTrue(cinfo instanceof RootClassInfoImpl,
                "should not have been called for class '%s'", cinfo.getClass());
        final FieldInfoImpl<T> old = columns.get(col.getColumnName());

        if (old != null) {
            // already defined so skip it but not before making sure it is compatible
            if (old.getDeclaringClass().equals(col.getDeclaringClass())) {
                // same exact field so bail!!!
                return;
            }
            // check data type
            org.apache.commons.lang3.Validate.isTrue(old.getDataType().getType() == col.getDataType().getType(),
                    "incompatible type columns '%s.%s' of type '%s' and '%s.%s' of type '%s' in table '%s'",
                    old.getDeclaringClass().getSimpleName(), old.getName(), old.getDataType().getType(),
                    col.getDeclaringClass().getSimpleName(), col.getName(), col.getDataType().getType(), name);
            return;
        }
        final FieldInfoImpl<T> rcol = new FieldInfoImpl<>((RootClassInfoImpl<T>) cinfo, this, col);

        fields.put(Pair.of(rcol.getName(), rcol.getDeclaringClass()), rcol);
        columns.put(rcol.getColumnName(), rcol);
        if (rcol.isIndex()) {
            indexColumns.put(rcol.getColumnName(), rcol);
        }
        if (rcol.isMandatory()) {
            mandatoryAndPrimaryKeyColumns.put(rcol.getColumnName(), rcol);
            nonPrimaryKeyColumns.put(rcol.getColumnName(), rcol);
        } else {
            nonPrimaryKeyColumns.put(rcol.getColumnName(), rcol);
        }
    }

    /**
     * Retrieves all columns and their values from the given POJO .
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all column/value pairs for the POJO
     * @throws IllegalArgumentException if a mandatory column is missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getColumnValues(T object) {
        final Map<String, Object> values = new LinkedHashMap<>(columns.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : columns.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getValue(object);

            if (value == null) {
                if (table != null) {
                    org.apache.commons.lang3.Validate.isTrue(!field.isMandatory(),
                            "missing mandatory column '%s' from table '%s'", name, table.name());
                    org.apache.commons.lang3.Validate.isTrue(!field.isPartitionKey() && !field.isClusteringKey(),
                            "missing primary key column '%s' from table '%s'", name, table.name());
                    org.apache.commons.lang3.Validate.isTrue(!field.isTypeKey(),
                            "missing type key column '%s' from table '%s'", name, table.name());
                } else {
                    org.apache.commons.lang3.Validate.isTrue(!field.isMandatory(), "missing mandatory column '%s'",
                            name);
                }
            }
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves all partition key columns and their values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all partition key column/value pairs
     *         for the POJO
     * @throws IllegalArgumentException if a column is missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getPartitionKeyColumnValues(T object) {
        if (table == null) {
            return Collections.emptyMap();
        }
        final Map<String, Object> values = new LinkedHashMap<>(primaryKeyColumns.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : partitionKeyColumns.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getValue(object);

            org.apache.commons.lang3.Validate.isTrue(value != null,
                    "missing partition key column '%s' from table '%s'", name, table.name());
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves all final primary keys and their values.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> map of all final primary key column/value
     *         pairs
     */
    Map<String, Object> getFinalPrimaryKeyValues() {
        return finalPrimaryKeyValues;
    }

    /**
     * Retrieves all suffix and partition key columns and their values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all suffix key and partition key
     *         column/value pairs for the POJO
     * @throws IllegalArgumentException if a column or a suffix key is missing
     *         from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getSuffixAndPartitionKeyColumnValues(T object) {
        if (table == null) {
            return Collections.emptyMap();
        }
        final Map<String, FieldInfoImpl<T>> skeys = cinfo.getSuffixKeys();
        final Map<String, FieldInfoImpl<T>> keys = new LinkedHashMap<>(primaryKeyColumns.size() + skeys.size());

        // start with suffix keys
        keys.putAll(skeys);
        // now add partition keys (overriding suffixes if names clashes!!!)
        keys.putAll(partitionKeyColumns);
        final Map<String, Object> values = new LinkedHashMap<>(keys.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : keys.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getValue(object);

            org.apache.commons.lang3.Validate.isTrue(value != null,
                    "missing suffix or partition key column '%s' from table '%s'", name, table.name());
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves all primary key columns and their values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all primary key column/value pairs
     *         for the POJO
     * @throws IllegalArgumentException if a column is missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getPrimaryKeyColumnValues(T object) {
        if (table == null) {
            return Collections.emptyMap();
        }
        final Map<String, Object> values = new LinkedHashMap<>(primaryKeyColumns.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : primaryKeyColumns.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getValue(object);

            org.apache.commons.lang3.Validate.isTrue(value != null,
                    "missing primary key column '%s' from table '%s'", name, table.name());
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves all suffix and primary key columns and their values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all suffix key and primary key
     *         column/value pairs for the POJO
     * @throws IllegalArgumentException if a column or a suffix key is missing
     *         from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getSuffixAndPrimaryKeyColumnValues(T object) {
        if (table == null) {
            return Collections.emptyMap();
        }
        final Map<String, FieldInfoImpl<T>> skeys = cinfo.getSuffixKeys();
        final Map<String, FieldInfoImpl<T>> keys = new LinkedHashMap<>(primaryKeyColumns.size() + skeys.size());

        // start with suffix keys
        keys.putAll(skeys);
        // now add primary keys (overriding suffixes if names clashes!!!)
        keys.putAll(primaryKeyColumns);
        final Map<String, Object> values = new LinkedHashMap<>(keys.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : keys.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getValue(object);

            org.apache.commons.lang3.Validate.isTrue(value != null,
                    "missing suffix or primary key column '%s' from table '%s'; null value", name, table.name());
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves all mandatory and primary key columns and their
     * values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all mandatory and primary key
     *         column/value pairs for the POJO
     * @throws IllegalArgumentException if a column is missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getMandatoryAndPrimaryKeyColumnValues(T object) {
        final Map<String, Object> values = new LinkedHashMap<>(mandatoryAndPrimaryKeyColumns.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : mandatoryAndPrimaryKeyColumns.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getValue(object);

            if (value == null) {
                if (table != null) {
                    org.apache.commons.lang3.Validate.isTrue(!field.isMandatory(),
                            "missing mandatory column '%s' from table '%s'", name, table.name());
                    org.apache.commons.lang3.Validate.isTrue(!field.isPartitionKey() && !field.isClusteringKey(),
                            "missing primary key column '%s' from table '%s'", table.name(), name);
                    org.apache.commons.lang3.Validate.isTrue(!field.isTypeKey(),
                            "missing type key column '%s' from table '%s'", table.name(), name);
                } else {
                    org.apache.commons.lang3.Validate.isTrue(!field.isMandatory(), "missing mandatory column '%s'",
                            name);
                }
            }
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves all non primary key columns and their values from the POJO.
     * <p>
     * <i>Note:</i> The returned values should not be encoded.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @return a non-<code>null</code> map of all non primary key column/value
     *         pairs for the POJO
     * @throws IllegalArgumentException if a mandatory column is missing from
     *         the POJO
     */
    Map<String, Object> getNonPrimaryKeyColumnValues(T object) {
        final Map<String, Object> values = new LinkedHashMap<>(nonPrimaryKeyColumns.size());

        for (final Map.Entry<String, FieldInfoImpl<T>> e : nonPrimaryKeyColumns.entrySet()) {
            final String name = e.getKey();
            final FieldInfoImpl<T> field = e.getValue();
            final Object value = field.getNonEncodedValue(object);

            if (table != null) {
                org.apache.commons.lang3.Validate.isTrue(!(field.isMandatory() && (value == null)),
                        "missing mandatory column '%s' from table '%s'", name, table.name());
            } else {
                org.apache.commons.lang3.Validate.isTrue(!(field.isMandatory() && (value == null)),
                        "missing mandatory column '%s'", name);
            }
            values.put(name, value);
        }
        return values;
    }

    /**
     * Retrieves the specified column value from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @param  name the name of the column to retrieve
     * @return the column value for the POJO
     * @throws IllegalArgumentException if the column name is not defined by the
     *         POJO or is mandatory and missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Object getColumnValue(T object, CharSequence name) {
        final String n;

        if (name instanceof Utils.CNameSequence) {
            n = ((Utils.CNameSequence) name).getName();
        } else {
            n = name.toString();
        }
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        final Object value = field.getValue(object);

        if (value == null) {
            if (table != null) {
                org.apache.commons.lang3.Validate.isTrue(!field.isMandatory(),
                        "missing mandatory column '%s' in table '%s'", n, table.name());
                org.apache.commons.lang3.Validate.isTrue(!field.isPartitionKey() && !field.isClusteringKey(),
                        "missing primary key column '%s' in table '%s'", n, table.name());
                org.apache.commons.lang3.Validate.isTrue(!field.isTypeKey(),
                        "missing type key column '%s' in table '%s'", n, table.name());
            } else {
                org.apache.commons.lang3.Validate.isTrue(!field.isMandatory(), "missing mandatory column '%s'", n);
            }
        }
        return value;
    }

    /**
     * Retrieves the specified columns and their values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @param  names the names of the columns to retrieve
     * @return a non-<code>null</code> map of all requested column/value pairs
     *         for the POJO
     * @throws IllegalArgumentException if any of the column names are not defined
     *         by the POJO or is mandatory and missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getColumnValues(T object, Iterable<CharSequence> names) {
        final Map<String, Object> values = new LinkedHashMap<>(columns.size());

        for (final CharSequence name : names) {
            final String n;

            if (name instanceof Utils.CNameSequence) {
                n = ((Utils.CNameSequence) name).getName();
            } else {
                n = name.toString();
            }
            values.put(n, getColumnValue(object, n));
        }
        return values;
    }

    /**
     * Retrieves the specified columns and their values from the POJO.
     *
     * @author paouelle
     *
     * @param  object the non-<code>null</code> POJO object
     * @param  names the names of the columns to retrieve
     * @return a non-<code>null</code> map of all requested column/value pairs
     *         for the POJO
     * @throws IllegalArgumentException if any of the column names are not defined
     *         by the POJO or is mandatory and missing from the POJO
     * @throws ColumnPersistenceException if unable to persist a column's value
     */
    Map<String, Object> getColumnValues(T object, CharSequence... names) {
        final Map<String, Object> values = new LinkedHashMap<>(columns.size());

        for (final CharSequence name : names) {
            final String n;

            if (name instanceof Utils.CNameSequence) {
                n = ((Utils.CNameSequence) name).getName();
            } else {
                n = name.toString();
            }
            values.put(n, getColumnValue(object, n));
        }
        return values;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.info.TableInfo#getObjectClass()
     */
    @Override
    public Class<T> getObjectClass() {
        return clazz;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.info.TableInfo#getClassInfo()
     */
    @Override
    public ClassInfo<T> getClassInfo() {
        return cinfo;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.info.TableInfo#getTable()
     */
    @Override
    public Table getTable() {
        return table;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.info.TableInfo#getName()
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Checks if a column is defined in this table.
     *
     * @author paouelle
     *
     * @param  name the name of the column to check if it is defined in this table
     * @return <code>true</code> if the specified column is defined in this table;
     *         <code>false</code> otherwise
     */
    public boolean hasColumn(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                if (!hasColumn(n)) { // recurse to validate
                    return false;
                }
            }
            return true;
        }
        if (name instanceof Utils.FCall) {
            for (final Object parm : ((Utils.FCall) name)) {
                if (!hasColumn(parm)) {
                    return false;
                }
            }
            return true;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            return false;
        }
        return columns.containsKey(n);
    }

    /**
     * Checks if a column is defined as a primary key in this table.
     *
     * @author paouelle
     *
     * @param  name the name of the column to check if it is defined as a primary
     *         key in this table
     * @return <code>true</code> if the specified column is defined as a primary
     *         key in this table; <code>false</code> otherwise
     */
    public boolean hasPrimaryKey(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (table == null) {
            return false;
        }
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                if (!hasPrimaryKey(n)) { // recurse to validate
                    return false;
                }
            }
            return true;
        }
        if (name instanceof Utils.FCall) {
            for (final Object parm : ((Utils.FCall) name)) {
                if (!hasPrimaryKey(parm)) {
                    return false;
                }
            }
            return true;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            return false;
        }
        return primaryKeyColumns.containsKey(n);
    }

    /**
     * Checks if a column is defined as the last partition key in this table.
     *
     * @author paouelle
     *
     * @param  name the name of the column to check if it is defined as the last
     *         partition key in this table
     * @return <code>true</code> if the specified column is defined as the last
     *         partition key in this table; <code>false</code> otherwise
     */
    public boolean isLastPartitionKey(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (table == null) {
            return false;
        }
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                if (!isLastPartitionKey(n)) { // recurse to validate
                    return false;
                }
            }
            return true;
        }
        if (name instanceof Utils.FCall) {
            for (final Object parm : ((Utils.FCall) name)) {
                if (!isLastPartitionKey(parm)) {
                    return false;
                }
            }
            return true;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            return false;
        }
        final FieldInfoImpl<?> f = partitionKeyColumns.get(n);

        return (f != null) ? f.isLast() : false;
    }

    /**
     * Checks if a column is defined as the last clustering key in this table.
     *
     * @author paouelle
     *
     * @param  name the name of the column to check if it is defined as the last
     *         clustering key in this table
     * @return <code>true</code> if the specified column is defined as the last
     *         clustering key in this table; <code>false</code> otherwise
     */
    public boolean isLastClusteringKey(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (table == null) {
            return false;
        }
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                if (!isLastPartitionKey(n)) { // recurse to validate
                    return false;
                }
            }
            return true;
        }
        if (name instanceof Utils.FCall) {
            for (final Object parm : ((Utils.FCall) name)) {
                if (!isLastPartitionKey(parm)) {
                    return false;
                }
            }
            return true;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            return false;
        }
        final FieldInfoImpl<?> f = clusteringKeyColumns.get(n);

        return (f != null) ? f.isLast() : false;
    }

    /**
     * Checks if a column is defined as a multi-key in this table.
     *
     * @author paouelle
     *
     * @param  name the name of the column to check if it is defined as a multi-key
     *         in this table
     * @return <code>true</code> if the specified column is defined as a multi-key
     *         in this table; <code>false</code> otherwise
     */
    public boolean isMultiKey(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (table == null) {
            return false;
        }
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                if (!isMultiKey(n)) { // recurse to validate
                    return false;
                }
            }
            return true;
        }
        if (name instanceof Utils.FCall) {
            for (final Object parm : ((Utils.FCall) name)) {
                if (!isMultiKey(parm)) {
                    return false;
                }
            }
            return true;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            return false;
        }
        final FieldInfoImpl<?> f = multiKeyColumns.get(n);

        return (f != null);
    }

    /**
     * Gets a column field for the POJO in this table given its field name.
     *
     * @author paouelle
     *
     * @param  f the field to retrieve its column from this table
     * @return the corresponding column field or <code>null</code> if not defined
     */
    public FieldInfoImpl<T> getColumnByField(Field f) {
        return fields.get(Pair.of(f.getName(), f.getDeclaringClass()));
    }

    /**
     * Gets a column field for the POJO in this table given its column name.
     *
     * @author paouelle
     *
     * @param  name the name of the column to retrieve in this table
     * @return the corresponding column field or <code>null</code> if not defined
     */
    public FieldInfoImpl<T> getColumn(CharSequence name) {
        return (name != null) ? columns.get(name.toString()) : null;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.info.TableInfo#columns()
     */
    @Override
    public Iterator<FieldInfo<T>> columns() {
        final Iterator<FieldInfoImpl<T>> iterator = columns.values().iterator();

        return new Iterator<FieldInfo<T>>() {
            private FieldInfoImpl<T> current = null;

            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }

            @Override
            public FieldInfo<T> next() {
                this.current = iterator.next();
                return current;
            }

            @SuppressWarnings("synthetic-access")
            @Override
            public void remove() {
                iterator.remove();
                final String cname = current.getColumnName();

                // make sure the remove the field from all internal maps too
                if (table != null) {
                    primaryKeyColumns.remove(cname);
                    partitionKeyColumns.remove(cname);
                    finalPrimaryKeyValues.remove(cname);
                    clusteringKeyColumns.remove(cname);
                    final FieldInfoImpl<T> old = typeKeyColumn.getValue();

                    if ((old != null) && old.getColumnName().equals(cname)) {
                        typeKeyColumn.setValue(null);
                    }
                    indexColumns.remove(cname);
                }
                mandatoryAndPrimaryKeyColumns.remove(cname);
                nonPrimaryKeyColumns.remove(cname);
                fields.remove(Pair.of(current.getName(), current.getDeclaringClass()));
                this.current = null; // clear the current pointer since it was removed!
            }
        };
    }

    /**
     * Gets the set of column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all column fields
     */
    public Collection<FieldInfoImpl<T>> getColumns() {
        return columns.values();
    }

    /**
     * Gets the set of non-primary key column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all non-primary key column fields
     */
    public Collection<FieldInfoImpl<T>> getNonPrimaryKeys() {
        return nonPrimaryKeyColumns.values();
    }

    /**
     * Gets the set of partition key column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all partition key column fields
     */
    public Collection<FieldInfoImpl<T>> getPartitionKeys() {
        return partitionKeyColumns.values();
    }

    /**
     * Gets the set of suffix and partition column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all suffix and partition column fields
     */
    public Collection<FieldInfoImpl<T>> getSuffixAndPartitionKeys() {
        final Map<String, FieldInfoImpl<T>> skeys = cinfo.getSuffixKeys();
        final Map<String, FieldInfoImpl<T>> keys = new LinkedHashMap<>(primaryKeyColumns.size() + skeys.size());

        // start with suffix keys
        keys.putAll(skeys);
        // now add partition keys (overriding suffixes if names clashes!!!)
        keys.putAll(partitionKeyColumns);
        return keys.values();
    }

    /**
     * Gets the set of clustering key column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all clustering key column fields
     */
    public Collection<FieldInfoImpl<T>> getClusteringKeys() {
        return clusteringKeyColumns.values();
    }

    /**
     * Gets the type key column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return an optional type key column field
     */
    public Optional<FieldInfoImpl<T>> getTypeKey() {
        return Optional.ofNullable(typeKeyColumn.getValue());
    }

    /**
     * Gets the set of primary column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all primary column fields
     */
    public Collection<FieldInfoImpl<T>> getPrimaryKeys() {
        return primaryKeyColumns.values();
    }

    /**
     * Gets the multi-keys for the POJO in this table if one is defined.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all multi-key column fields
     */
    public List<FieldInfoImpl<T>> getMultiKeys() {
        return multiKeyColumnsList;
    }

    /**
     * Gets the set of suffix and primary column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all suffix and primary column fields
     */
    public Collection<FieldInfoImpl<T>> getSuffixAndPrimaryKeys() {
        final Map<String, FieldInfoImpl<T>> skeys = cinfo.getSuffixKeys();
        final Map<String, FieldInfoImpl<T>> keys = new LinkedHashMap<>(primaryKeyColumns.size() + skeys.size());

        // start with suffix keys
        keys.putAll(skeys);
        // now add primary keys (overriding suffixes if names clashes!!!)
        keys.putAll(primaryKeyColumns);
        return keys.values();
    }

    /**
     * Gets the a primary column fields for the POJO in this table given its
     * column name.
     *
     * @author paouelle
     *
     * @param  name the name of the primary column to retrieve in this table
     * @return the corresponding primary column field or <code>null</code> if
     *         not defined
     */
    public FieldInfoImpl<T> getPrimaryKey(CharSequence name) {
        return (name != null) ? primaryKeyColumns.get(name.toString()) : null;
    }

    /**
     * Gets the set of index column fields for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all index column fields
     */
    public Collection<FieldInfoImpl<T>> getIndexes() {
        return indexColumns.values();
    }

    /**
     * Gets the set of mandatory and primary column fields for the POJO in this
     * table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all mandatory and primary column
     *         fields
     */
    public Collection<FieldInfoImpl<T>> getMandatoryAndPrimaryKeys() {
        return mandatoryAndPrimaryKeyColumns.values();
    }

    /**
     * Gets the set of mandatory and primary columns for the POJO in this table.
     *
     * @author paouelle
     *
     * @return a non-<code>null</code> set of all mandatory and primary column
     *         names
     */
    public Set<String> getMandatoryAndPrimaryKeyColumns() {
        return mandatoryAndPrimaryKeyColumns.keySet();
    }

    /**
     * Validates if a column is defined by the POJO in this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO
     */
    public void validateColumn(Object name) {
        validateColumn(name, false);
    }

    /**
     * Validates if specified columns are defined by the POJO in this table.
     *
     * @author paouelle
     *
     * @param  names the names of the column to validate
     * @throws NullPointerException if any of the column names are <code>null</code>
     * @throws IllegalArgumentException if any of the column name are not
     *         defined by the POJO
     */
    public void validateColumns(Iterable<Object> names) {
        for (final Object name : names) {
            validateColumn(name);
        }
    }

    /**
     * Validates if specified columns are defined by the POJO in this table.
     *
     * @author paouelle
     *
     * @param  names the names of the column to validate
     * @throws NullPointerException if any of the column names are <code>null</code>
     * @throws IllegalArgumentException if any of the column name are not
     *         defined by the POJO
     */
    public void validateColumns(Object... names) {
        for (final Object name : names) {
            validateColumn(name);
        }
    }

    /**
     * Validates if a column is not defined as either a mandatory or a primary
     * key column by the POJO in this table.
     * <p>
     * <i>Note:</i> Only the column names passed as a {@link String} are
     * validated.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is defined
     *         by the POJO as a mandatory or a primary key column
     */
    public void validateNotMandatoryOrPrimaryKeyColumn(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateNotMandatoryOrPrimaryKeyColumn(n); // recurse to validate
            }
            return;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            throw new IllegalArgumentException("unexpected column name: " + name);
        }
        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(!mandatoryAndPrimaryKeyColumns.containsKey(n),
                    "%s defines mandatory or primary key column '%s' in table '%s'", clazz.getSimpleName(), n,
                    table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(!mandatoryAndPrimaryKeyColumns.containsKey(n),
                    "%s defines mandatory column '%s'", clazz.getSimpleName(), n);
        }
    }

    /**
     * Validates if a column is defined as either a primary key or an index column
     * by the POJO in this table.
     * <p>
     * <i>Note:</i> Only the column names passed as a {@link String} are
     * validated.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO or is not a primary key or an index column
     */
    public void validatePrimaryKeyOrIndexColumn(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validatePrimaryKeyOrIndexColumn(n); // recurse to validate
            }
            return;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            throw new IllegalArgumentException("unexpected column name: " + name);
        }
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
            org.apache.commons.lang3.Validate.isTrue(
                    field.isPartitionKey() || field.isClusteringKey() || field.isIndex(),
                    "%s doesn't define primary key or index column '%s' in table '%s'", clazz.getSimpleName(), n,
                    table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
    }

    /**
     * Validates if a column is defined as either a suffix key, a primary key, or
     * an index column, by the POJO in this table.
     * <p>
     * <i>Note:</i> Only the column names passed as a {@link String} are
     * validated.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO or is not a primary key or an index column
     */
    public void validateSuffixKeyOrPrimaryKeyOrIndexColumn(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validatePrimaryKeyOrIndexColumn(n); // recurse to validate
            }
            return;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            throw new IllegalArgumentException("unexpected column name: " + name);
        }
        FieldInfoImpl<T> field = columns.get(n);

        if (field == null) {
            // check suffixes
            field = (FieldInfoImpl<T>) cinfo.getSuffixKey(n);
        }
        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null,
                    "%s doesn't define column or suffix key '%s' in table '%s'", clazz.getSimpleName(), n,
                    table.name());
            org.apache.commons.lang3.Validate.isTrue(
                    field.isPartitionKey() || field.isClusteringKey() || field.isSuffixKey() || field.isIndex(),
                    "%s doesn't define suffix key, primary key, or index column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
    }

    /**
     * Validates if a column is defined as a counter by the POJO in this table.
     * <p>
     * <i>Note:</i> Only the column names passed as a {@link String} are
     * validated.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO or is not a counter column
     */
    public void validateCounterColumn(Object name) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateCounterColumn(n); // recurse to validate
            }
            return;
        }
        final String n;

        if (name instanceof Utils.CName) {
            n = ((Utils.CName) name).getColumnName();
        } else if (name instanceof CharSequence) {
            n = name.toString();
        } else {
            throw new IllegalArgumentException("unexpected column name: " + name);
        }
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
            org.apache.commons.lang3.Validate.isTrue(field.isCounter(),
                    "%s doesn't define counter column '%s' in table '%s'", clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
    }

    /**
     * Validates if a column is defined by the POJO and its potential value in
     * this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  value the value to validate for the column
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO or if the specified value is not of the
     *         right type or is <code>null</code> when the column is mandatory
     */
    public void validateColumnAndValue(CharSequence name, Object value) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateColumnAndValue(n, value); // recurse to validate
            }
            return;
        }
        final String n = name.toString();
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        field.validateValue(value);
    }

    /**
     * Validates if a column or suffix is defined by the POJO as a column or as a
     * suffix and its potential value in this table.
     *
     * @author paouelle
     *
     * @param  name the column or suffix name to validate
     * @param  value the value to validate for the column
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column or suffix is not
     *         defined by the POJO or if the specified value is not of the
     *         right type or is <code>null</code> when the column is mandatory
     */
    public void validateSuffixOrColumnAndValue(CharSequence name, Object value) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name or suffix key");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateSuffixOrColumnAndValue(n, value); // recurse to validate
            }
            return;
        }
        final String n = name.toString();
        FieldInfoImpl<T> field = columns.get(n);

        if (field == null) {
            // check suffixes
            field = (FieldInfoImpl<T>) cinfo.getSuffixKey(n);
        }
        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null,
                    "%s doesn't define column or suffix key '%s' in table '%s'", clazz.getSimpleName(), n,
                    table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        field.validateValue(value);
    }

    /**
     * Validates if a column is defined by the POJO and its potential values in
     * this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  values the values to validate for the column
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO or if any of the specified values is not of the
     *         right type or is <code>null</code> when the column is mandatory
     */
    public void validateColumnAndValues(CharSequence name, Collection<?> values) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateColumnAndValues(n, values); // recurse to validate
            }
            return;
        }
        final String n = name.toString();
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        for (final Object value : values) {
            field.validateValue(value);
        }
    }

    /**
     * Validates if a column is defined as the given collection data type by the
     * POJO and its potential element value in this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  type the collection data type of the column to validate
     * @param  value the element value to be validated for the collection
     * @throws NullPointerException if <code>name</code> or <code>type</code> is
     *         <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO as the provided data type or if the specified value is
     *         not of the right element type or is <code>null</code> when the
     *         column is mandatory
     */
    public void validateCollectionColumnAndValue(CharSequence name, DataType type, Object value) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateCollectionColumnAndValue(n, type, value); // recurse to validate
            }
            return;
        }
        org.apache.commons.lang3.Validate.notNull(type, "invalid null column type");
        org.apache.commons.lang3.Validate.isTrue(type.isCollection(), "%s is not a collection type", name);
        final String n = name.toString();
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        field.validateCollectionValue(type, value);
    }

    /**
     * Validates if a column is defined as the given collection data type by the
     * POJO and its potential element value in this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  type the collection data type of the column to validate
     * @param  values the list of element values to be validated for the collection
     * @throws NullPointerException if <code>name</code> or <code>type</code> is
     *         <code>null</code>
     * @throws IllegalArgumentException if the specified column is not
     *         defined by the POJO as the specified data type or if any of the
     *         specified values are not of the right element type or are
     *         <code>null</code> when the column is mandatory
     */
    public void validateCollectionColumnAndValues(CharSequence name, DataType type, Iterable<?> values) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateCollectionColumnAndValues(n, type, values); // recurse to validate
            }
            return;
        }
        org.apache.commons.lang3.Validate.notNull(values, "invalid null list of values");
        org.apache.commons.lang3.Validate.isTrue(type.isCollection(), "%s is not a collection type", name);
        final String n = name.toString();
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        for (final Object value : values) {
            field.validateCollectionValue(type, value);
        }
    }

    /**
     * Validates if a column is defined as a map by the POJO and its potential
     * mapping key/value in this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  key the mapping key to be validated for the map
     * @param  value the mapping value to be validated for the map
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO as a map or if the specified key/value are not of
     *         the right mapping types or the value is <code>null</code> when the
     *         column is mandatory
     */
    public void validateMapColumnAndKeyValue(CharSequence name, Object key, Object value) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateMapColumnAndKeyValue(n, key, value); // recurse to validate
            }
            return;
        }
        final String n = name.toString();
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        field.validateMapKeyValue(key, value);
    }

    /**
     * Validates if a column is defined as a map by the POJO and its potential
     * mapping key/value in this table.
     *
     * @author paouelle
     *
     * @param  name the column name to validate
     * @param  mappings the mappings to be validated for the map
     * @throws NullPointerException if <code>name</code> is <code>null</code>
     * @throws IllegalArgumentException if the specified column is not defined
     *         by the POJO as a map or if the specified key/value are not of
     *         the right mapping types or the value is <code>null</code> when the
     *         column is mandatory
     */
    public void validateMapColumnAndKeyValues(CharSequence name, Map<?, ?> mappings) {
        org.apache.commons.lang3.Validate.notNull(name, "invalid null column name");
        if (name instanceof Utils.CNameSequence) {
            for (final String n : ((Utils.CNameSequence) name).getNames()) {
                validateMapColumnAndKeyValues(n, mappings); // recurse to validate
            }
            return;
        }
        org.apache.commons.lang3.Validate.notNull(mappings, "invalid null collection of mappings");
        final String n = name.toString();
        final FieldInfoImpl<T> field = columns.get(n);

        if (table != null) {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s' in table '%s'",
                    clazz.getSimpleName(), n, table.name());
        } else {
            org.apache.commons.lang3.Validate.isTrue(field != null, "%s doesn't define column '%s'",
                    clazz.getSimpleName(), n);
        }
        for (final Map.Entry<?, ?> e : mappings.entrySet()) {
            field.validateMapKeyValue(e.getKey(), e.getValue());
        }
    }
}