/*
 * Copyright 2004-2013 the Seasar Foundation and the Others.
 *
 * 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 org.seasar.extension.jdbc.meta;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.seasar.extension.jdbc.ColumnMetaFactory;
import org.seasar.extension.jdbc.EntityMeta;
import org.seasar.extension.jdbc.JoinColumnMeta;
import org.seasar.extension.jdbc.PropertyMeta;
import org.seasar.extension.jdbc.PropertyMetaFactory;
import org.seasar.extension.jdbc.RelationshipType;
import org.seasar.extension.jdbc.ValueType;
import org.seasar.extension.jdbc.exception.BothMappedByAndJoinColumnRuntimeException;
import org.seasar.extension.jdbc.exception.IdGeneratorNotFoundRuntimeException;
import org.seasar.extension.jdbc.exception.JoinColumnNameAndReferencedColumnNameMandatoryRuntimeException;
import org.seasar.extension.jdbc.exception.LazyFetchSpecifiedRuntimeException;
import org.seasar.extension.jdbc.exception.MappedByMandatoryRuntimeException;
import org.seasar.extension.jdbc.exception.OneToManyNotGenericsRuntimeException;
import org.seasar.extension.jdbc.exception.OneToManyNotListRuntimeException;
import org.seasar.extension.jdbc.exception.RelationshipNotEntityRuntimeException;
import org.seasar.extension.jdbc.exception.TemporalTypeNotSpecifiedRuntimeException;
import org.seasar.extension.jdbc.exception.UnsupportedPropertyTypeRuntimeException;
import org.seasar.extension.jdbc.exception.UnsupportedRelationshipRuntimeException;
import org.seasar.extension.jdbc.exception.VersionPropertyNotNumberRuntimeException;
import org.seasar.extension.jdbc.id.IdentityIdGenerator;
import org.seasar.extension.jdbc.id.SequenceIdGenerator;
import org.seasar.extension.jdbc.id.TableIdGenerator;
import org.seasar.extension.jdbc.types.ValueTypes;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.convention.PersistenceConvention;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.ModifierUtil;
import org.seasar.framework.util.StringUtil;
import org.seasar.framework.util.tiger.CollectionsUtil;
import org.seasar.framework.util.tiger.ReflectionUtil;

/**
 * {@link PropertyMetaFactory}の実装クラスです。
 * 
 * @author higa
 * 
 */
@SequenceGenerator(name = "default")
@TableGenerator(name = "default")
public class PropertyMetaFactoryImpl implements PropertyMetaFactory {

    /**
     * デフォルトの{@link SequenceGenerator}です。
     */
    protected static final SequenceGenerator DEFAULT_SEQUENCE_GENERATOR = PropertyMetaFactoryImpl.class
            .getAnnotation(SequenceGenerator.class);

    /**
     * デフォルトの{@link TableGenerator}です。
     */
    protected static final TableGenerator DEFAULT_TABLE_GENERATOR = PropertyMetaFactoryImpl.class
            .getAnnotation(TableGenerator.class);

    /**
     * フィールドの型に対応する値タイプのマップです。
     */
    protected static final Map<Class<?>, ValueType> valueTypes = CollectionsUtil
            .newHashMap();
    static {
        valueTypes.put(boolean.class, ValueTypes.BOOLEAN);
        valueTypes.put(Boolean.class, ValueTypes.BOOLEAN);
        valueTypes.put(char.class, ValueTypes.CHARACTER);
        valueTypes.put(Character.class, ValueTypes.CHARACTER);
        valueTypes.put(byte.class, ValueTypes.BYTE);
        valueTypes.put(Byte.class, ValueTypes.BYTE);
        valueTypes.put(short.class, ValueTypes.SHORT);
        valueTypes.put(Short.class, ValueTypes.SHORT);
        valueTypes.put(int.class, ValueTypes.INTEGER);
        valueTypes.put(Integer.class, ValueTypes.INTEGER);
        valueTypes.put(long.class, ValueTypes.LONG);
        valueTypes.put(Long.class, ValueTypes.LONG);
        valueTypes.put(float.class, ValueTypes.FLOAT);
        valueTypes.put(Float.class, ValueTypes.FLOAT);
        valueTypes.put(double.class, ValueTypes.DOUBLE);
        valueTypes.put(Double.class, ValueTypes.DOUBLE);
        valueTypes.put(BigDecimal.class, ValueTypes.BIGDECIMAL);
        valueTypes.put(BigInteger.class, ValueTypes.BIGINTEGER);
        valueTypes.put(java.sql.Date.class, ValueTypes.SQLDATE);
        valueTypes.put(java.sql.Time.class, ValueTypes.TIME);
        valueTypes.put(java.sql.Timestamp.class, ValueTypes.TIMESTAMP);
    }

    /**
     * カラムメタデータファクトリです。
     */
    protected ColumnMetaFactory columnMetaFactory;

    /**
     * 永続化層の命名規約です。
     */
    protected PersistenceConvention persistenceConvention;

    public PropertyMeta createPropertyMeta(Field field, EntityMeta entityMeta) {
        PropertyMeta propertyMeta = new PropertyMeta();
        doField(propertyMeta, field, entityMeta);
        doName(propertyMeta, field, entityMeta);
        doTransient(propertyMeta, field, entityMeta);
        if (!propertyMeta.isTransient()) {
            Object relationshipAnnotation = getRelationshipAnnotation(field);
            if (relationshipAnnotation == null) {
                doColumnMeta(propertyMeta, field, entityMeta);
                doId(propertyMeta, field, entityMeta);
                doFetchType(propertyMeta, field, entityMeta);
                doTemporal(propertyMeta, field, entityMeta);
                doEnum(propertyMeta, field, entityMeta);
                doVersion(propertyMeta, field, entityMeta);
                doLob(propertyMeta, field, entityMeta);
                doValueType(propertyMeta, entityMeta);
            } else {
                doRelationship(propertyMeta, field, entityMeta,
                        relationshipAnnotation);
            }
        }
        doCustomize(propertyMeta, field, entityMeta);
        return propertyMeta;
    }

    /**
     * フィールドを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doField(PropertyMeta propertyMeta, Field field,
            @SuppressWarnings("unused") EntityMeta entityMeta) {
        propertyMeta.setField(field);
    }

    /**
     * 名前を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doName(PropertyMeta propertyMeta, Field field,
            @SuppressWarnings("unused") EntityMeta entityMeta) {
        propertyMeta.setName(persistenceConvention
                .fromFieldNameToPropertyName(field.getName()));
    }

    /**
     * カラムメタデータを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doColumnMeta(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta) {
        propertyMeta.setColumnMeta(columnMetaFactory.createColumnMeta(field,
                entityMeta, propertyMeta));
    }

    /**
     * 識別子メタデータを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doId(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta) {
        propertyMeta.setId(field.getAnnotation(Id.class) != null);
        GeneratedValue generatedValue = field
                .getAnnotation(GeneratedValue.class);
        if (generatedValue == null) {
            return;
        }
        GenerationType generationType = generatedValue.strategy();
        propertyMeta.setGenerationType(generationType);
        switch (generationType) {
        case AUTO:
            doIdentityIdGenerator(propertyMeta, entityMeta);
            doSequenceIdGenerator(propertyMeta, generatedValue, entityMeta);
            doTableIdGenerator(propertyMeta, generatedValue, entityMeta);
            break;
        case IDENTITY:
            doIdentityIdGenerator(propertyMeta, entityMeta);
            break;
        case SEQUENCE:
            if (!doSequenceIdGenerator(propertyMeta, generatedValue, entityMeta)) {
                throw new IdGeneratorNotFoundRuntimeException(entityMeta
                        .getName(), propertyMeta.getName(), generatedValue
                        .generator());
            }
            break;
        case TABLE:
            if (!doTableIdGenerator(propertyMeta, generatedValue, entityMeta)) {
                throw new IdGeneratorNotFoundRuntimeException(entityMeta
                        .getName(), propertyMeta.getName(), generatedValue
                        .generator());
            }
            break;
        }
    }

    /**
     * {@link GenerationType#IDENTITY}方式で識別子の値を自動生成するIDジェネレータを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param entityMeta
     *            エンティティのメタデータ
     */
    protected void doIdentityIdGenerator(PropertyMeta propertyMeta,
            EntityMeta entityMeta) {
        propertyMeta.setIdentityIdGenerator(new IdentityIdGenerator(entityMeta,
                propertyMeta));
    }

    /**
     * {@link GenerationType#SEQUENCE}方式で識別子の値を自動生成するIDジェネレータを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param generatedValue
     *            識別子に付けられた{@link GeneratedValue}アノテーション
     * @param entityMeta
     *            エンティティのメタデータ
     * @return {@link GenerationType#SEQUENCE}方式で識別子の値を自動生成するIDジェネレータが存在した場合に
     *         <code>true</code>
     */
    protected boolean doSequenceIdGenerator(PropertyMeta propertyMeta,
            GeneratedValue generatedValue, EntityMeta entityMeta) {
        String name = generatedValue.generator();
        SequenceGenerator sequenceGenerator;
        if (StringUtil.isEmpty(name)) {
            sequenceGenerator = DEFAULT_SEQUENCE_GENERATOR;
        } else {
            sequenceGenerator = propertyMeta.getField().getAnnotation(
                    SequenceGenerator.class);
            if (sequenceGenerator == null
                    || !name.equals(sequenceGenerator.name())) {
                sequenceGenerator = entityMeta.getEntityClass().getAnnotation(
                        SequenceGenerator.class);
                if (sequenceGenerator == null
                        || !name.equals(sequenceGenerator.name())) {
                    return false;
                }
            }
        }
        propertyMeta.setSequenceIdGenerator(new SequenceIdGenerator(entityMeta,
                propertyMeta, sequenceGenerator));
        return true;
    }

    /**
     * {@link GenerationType#TABLE}方式で識別子の値を自動生成するIDジェネレータを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param generatedValue
     *            識別子に付けられた{@link GeneratedValue}アノテーション
     * @param entityMeta
     *            エンティティのメタデータ
     * @return {@link GenerationType#TABLE}方式で識別子の値を自動生成するIDジェネレータが存在した場合に
     *         <code>true</code>
     */
    protected boolean doTableIdGenerator(PropertyMeta propertyMeta,
            GeneratedValue generatedValue, EntityMeta entityMeta) {
        String name = generatedValue.generator();
        TableGenerator tableGenerator;
        if (StringUtil.isEmpty(name)) {
            tableGenerator = DEFAULT_TABLE_GENERATOR;
        } else {
            tableGenerator = propertyMeta.getField().getAnnotation(
                    TableGenerator.class);
            if (tableGenerator == null || !name.equals(tableGenerator.name())) {
                tableGenerator = entityMeta.getEntityClass().getAnnotation(
                        TableGenerator.class);
                if (tableGenerator == null
                        || !name.equals(tableGenerator.name())) {
                    return false;
                }
            }
        }
        propertyMeta.setTableIdGenerator(new TableIdGenerator(entityMeta,
                propertyMeta, tableGenerator));
        return true;
    }

    /**
     * フェッチタイプを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doFetchType(final PropertyMeta propertyMeta,
            final Field field, final EntityMeta entityMeta) {
        final Basic basic = field.getAnnotation(Basic.class);
        if (basic == null) {
            propertyMeta.setFetchType(FetchType.EAGER);
            return;
        }
        if (propertyMeta.isId() && basic.fetch() == FetchType.LAZY) {
            throw new LazyFetchSpecifiedRuntimeException(entityMeta.getName(),
                    propertyMeta.getName());
        }
        propertyMeta.setFetchType(basic.fetch());
    }

    /**
     * 時制の種別を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doTemporal(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta) {
        if (propertyMeta.getPropertyClass() != java.util.Date.class
                && propertyMeta.getPropertyClass() != Calendar.class) {
            return;
        }
        Temporal temporal = field.getAnnotation(Temporal.class);
        if (temporal == null) {
            throw new TemporalTypeNotSpecifiedRuntimeException(entityMeta
                    .getName(), propertyMeta.getName());
        }
        propertyMeta.setTemporalType(temporal.value());
    }

    /**
     * enumの種別を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doEnum(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta) {
        if (!propertyMeta.getPropertyClass().isEnum()) {
            return;
        }
        Enumerated enumerated = field.getAnnotation(Enumerated.class);
        if (enumerated == null) {
            return;
        }
        propertyMeta.setEnumType(enumerated.value());
    }

    /**
     * バージョンチェック用かどうかを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doVersion(PropertyMeta propertyMeta, Field field,
            @SuppressWarnings("unused") EntityMeta entityMeta) {
        if (field.getAnnotation(Version.class) == null) {
            return;
        }
        Class<?> clazz = ClassUtil.getWrapperClassIfPrimitive(field.getType());
        if (clazz != Integer.class && clazz != Long.class && clazz != int.class
                && clazz != long.class) {
            throw new VersionPropertyNotNumberRuntimeException(entityMeta
                    .getName(), propertyMeta.getName());
        }
        propertyMeta.setVersion(true);
    }

    /**
     * 一時的かどうかを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doTransient(PropertyMeta propertyMeta, Field field,
            @SuppressWarnings("unused") EntityMeta entityMeta) {
        propertyMeta.setTransient(field.getAnnotation(Transient.class) != null
                || ModifierUtil.isTransient(field));
    }

    /**
     * <code>LOB</code>かどうかを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doLob(PropertyMeta propertyMeta, Field field,
            @SuppressWarnings("unused") EntityMeta entityMeta) {
        propertyMeta.setLob(field.getAnnotation(Lob.class) != null);
    }

    /**
     * {@link ValueType}を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param entityMeta
     *            エンティティメタデータ
     */
    @SuppressWarnings("unchecked")
    protected void doValueType(final PropertyMeta propertyMeta,
            final EntityMeta entityMeta) {
        final Class<?> propertyClass = propertyMeta.getPropertyClass();
        final ValueType valueType = valueTypes.get(propertyClass);
        if (valueType != null) {
            propertyMeta.setValueType(valueType);
            return;
        }

        if (propertyClass == String.class) {
            if (propertyMeta.isLob()) {
                propertyMeta.setValueType(ValueTypes.CLOB);
            } else {
                propertyMeta.setValueType(ValueTypes.STRING);
            }
            return;
        }

        if (propertyClass == byte[].class) {
            if (propertyMeta.isLob()) {
                propertyMeta.setValueType(ValueTypes.BLOB);
            } else {
                propertyMeta.setValueType(ValueTypes.BYTE_ARRAY);
            }
            return;
        }

        if (propertyClass == Date.class) {
            switch (propertyMeta.getTemporalType()) {
            case DATE:
                propertyMeta.setValueType(ValueTypes.DATE_SQLDATE);
                return;
            case TIME:
                propertyMeta.setValueType(ValueTypes.DATE_TIME);
                return;
            case TIMESTAMP:
                propertyMeta.setValueType(ValueTypes.DATE_TIMESTAMP);
                return;
            }
        }

        if (propertyClass == Calendar.class) {
            switch (propertyMeta.getTemporalType()) {
            case DATE:
                propertyMeta.setValueType(ValueTypes.CALENDAR_SQLDATE);
                return;
            case TIME:
                propertyMeta.setValueType(ValueTypes.CALENDAR_TIME);
                return;
            case TIMESTAMP:
                propertyMeta.setValueType(ValueTypes.CALENDAR_TIMESTAMP);
                return;
            }
        }

        if (propertyClass.isEnum()) {
            if (propertyMeta.getEnumType() == null) {
                propertyMeta.setValueType(ValueTypes
                        .getValueType(propertyClass));
                return;
            }
            switch (propertyMeta.getEnumType()) {
            case ORDINAL:
                propertyMeta.setValueType(ValueTypes
                        .getEnumOrdinalValueType(propertyClass));
                return;
            case STRING:
                propertyMeta.setValueType(ValueTypes
                        .getEnumStringValueType(propertyClass));
                return;
            }
        }

        final ValueType userDefinedValueType = ValueTypes
                .createUserDefineValueType(propertyClass);
        if (userDefinedValueType != null) {
            propertyMeta.setValueType(userDefinedValueType);
            return;
        }

        if (Serializable.class.isAssignableFrom(propertyClass)) {
            if (propertyMeta.isLob()) {
                propertyMeta.setValueType(ValueTypes.SERIALIZABLE_BLOB);
            } else {
                propertyMeta.setValueType(ValueTypes.SERIALIZABLE_BYTE_ARRAY);
            }
            return;
        }

        throw new UnsupportedPropertyTypeRuntimeException(entityMeta.getName(),
                propertyMeta.getName(), propertyMeta.getPropertyClass());
    }

    /**
     * フィールドに関連のアノテーションが指定されていればそれを返します。
     * 
     * @param field
     *            フィールド
     * @return 関連のアノテーションまたは<code>null</code>
     */
    protected Object getRelationshipAnnotation(final Field field) {
        final OneToOne oneToOne = field.getAnnotation(OneToOne.class);
        if (oneToOne != null) {
            return oneToOne;
        }
        final OneToMany oneToMany = field.getAnnotation(OneToMany.class);
        if (oneToMany != null) {
            return oneToMany;
        }
        final ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);
        if (manyToOne != null) {
            return manyToOne;
        }
        final ManyToMany manyToMany = field.getAnnotation(ManyToMany.class);
        if (manyToMany != null) {
            return manyToMany;
        }
        return null;
    }

    /**
     * 関連を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     * @param annotation
     *            関連のアノテーション
     */
    protected void doRelationship(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta, Object annotation) {
        doJoinColumn(propertyMeta, field, entityMeta);
        if (OneToOne.class.isInstance(annotation)) {
            doOneToOne(propertyMeta, field, entityMeta, OneToOne.class
                    .cast(annotation));
        } else if (OneToMany.class.isInstance(annotation)) {
            doOneToMany(propertyMeta, field, entityMeta, OneToMany.class
                    .cast(annotation));
        } else if (ManyToOne.class.isInstance(annotation)) {
            doManyToOne(propertyMeta, field, entityMeta, ManyToOne.class
                    .cast(annotation));
        } else {
            throw new UnsupportedRelationshipRuntimeException(entityMeta
                    .getName(), propertyMeta.getName());
        }
    }

    /**
     * JoinColumnを処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    protected void doJoinColumn(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta) {
        JoinColumn joinColumn = field.getAnnotation(JoinColumn.class);
        if (joinColumn != null) {
            JoinColumnMeta meta = new JoinColumnMeta(joinColumn.name(),
                    joinColumn.referencedColumnName());
            propertyMeta.addJoinColumnMeta(meta);
        } else {
            JoinColumns joinColumns = field.getAnnotation(JoinColumns.class);
            if (joinColumns != null) {
                JoinColumn[] array = joinColumns.value();
                for (int i = 0; i < array.length; i++) {
                    JoinColumn jc = array[i];
                    JoinColumnMeta meta = new JoinColumnMeta(jc.name(), jc
                            .referencedColumnName());
                    if (i > 0
                            && (meta.getName() == null || meta
                                    .getReferencedColumnName() == null)) {
                        throw new JoinColumnNameAndReferencedColumnNameMandatoryRuntimeException(
                                entityMeta.getName(), propertyMeta.getName(),
                                i + 1);
                    }
                    propertyMeta.addJoinColumnMeta(meta);
                }
            }
        }
    }

    /**
     * 一対一の関連を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     * @param oneToOne
     *            一対一関連
     */
    protected void doOneToOne(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta, OneToOne oneToOne) {
        propertyMeta.setRelationshipType(RelationshipType.ONE_TO_ONE);
        Class<?> relationshipClass = field.getType();
        if (relationshipClass.getAnnotation(Entity.class) == null) {
            throw new RelationshipNotEntityRuntimeException(entityMeta
                    .getName(), propertyMeta.getName(), relationshipClass);
        }
        propertyMeta.setRelationshipClass(relationshipClass);
        String mappedBy = oneToOne.mappedBy();
        if (!StringUtil.isEmpty(mappedBy)) {
            if (propertyMeta.getJoinColumnMetaList().size() > 0) {
                throw new BothMappedByAndJoinColumnRuntimeException(entityMeta
                        .getName(), propertyMeta.getName());
            }
            propertyMeta.setMappedBy(mappedBy);
        }
    }

    /**
     * 一対多の関連を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     * @param oneToMany
     *            一対多関連
     */
    protected void doOneToMany(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta, OneToMany oneToMany) {
        propertyMeta.setRelationshipType(RelationshipType.ONE_TO_MANY);
        if (!List.class.isAssignableFrom(field.getType())) {
            throw new OneToManyNotListRuntimeException(entityMeta.getName(),
                    propertyMeta.getName());
        }
        Class<?> relationshipClass = ReflectionUtil.getElementTypeOfList(field
                .getGenericType());
        if (relationshipClass == null) {
            throw new OneToManyNotGenericsRuntimeException(
                    entityMeta.getName(), propertyMeta.getName());
        }
        if (relationshipClass.getAnnotation(Entity.class) == null) {
            throw new RelationshipNotEntityRuntimeException(entityMeta
                    .getName(), propertyMeta.getName(), relationshipClass);
        }
        propertyMeta.setRelationshipClass(relationshipClass);
        String mappedBy = oneToMany.mappedBy();
        if (!StringUtil.isEmpty(mappedBy)) {
            if (propertyMeta.getJoinColumnMetaList().size() > 0) {
                throw new BothMappedByAndJoinColumnRuntimeException(entityMeta
                        .getName(), propertyMeta.getName());
            }
            propertyMeta.setMappedBy(mappedBy);
        } else {
            throw new MappedByMandatoryRuntimeException(entityMeta.getName(),
                    propertyMeta.getName());
        }
    }

    /**
     * 多対一の関連を処理します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     * @param manyToOne
     *            多対一関連
     */
    protected void doManyToOne(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta,
            @SuppressWarnings("unused") ManyToOne manyToOne) {
        propertyMeta.setRelationshipType(RelationshipType.MANY_TO_ONE);
        Class<?> relationshipClass = field.getType();
        if (relationshipClass.getAnnotation(Entity.class) == null) {
            throw new RelationshipNotEntityRuntimeException(entityMeta
                    .getName(), propertyMeta.getName(), relationshipClass);
        }
        propertyMeta.setRelationshipClass(relationshipClass);
    }

    /**
     * 関連用のクラスを返します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     * @return 関連用のクラス
     * @throws OneToManyNotGenericsRuntimeException
     *             一対多の関連がジェネリクスのリストではない場合。
     */
    protected Class<?> getRelationshipClass(PropertyMeta propertyMeta,
            Field field, EntityMeta entityMeta)
            throws OneToManyNotGenericsRuntimeException {
        Class<?> clazz = field.getType();
        if (List.class.isAssignableFrom(clazz)) {
            clazz = ReflectionUtil.getElementTypeOfList(field.getGenericType());
            if (clazz == null) {
                throw new OneToManyNotGenericsRuntimeException(entityMeta
                        .getName(), propertyMeta.getName());
            }
        }
        return clazz;
    }

    /**
     * カスタマイズします。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @param field
     *            フィールド
     * @param entityMeta
     *            エンティティメタデータ
     */
    @SuppressWarnings("unused")
    protected void doCustomize(PropertyMeta propertyMeta, Field field,
            EntityMeta entityMeta) {
    }

    /**
     * カラムメタデータファクトリを返します。
     * 
     * @return カラムメタデータファクトリ
     */
    public ColumnMetaFactory getColumnMetaFactory() {
        return columnMetaFactory;
    }

    /**
     * カラムメタデータファクトリを設定します。
     * 
     * @param columnMetaFactory
     *            カラムメタデータファクトリ
     */
    @Binding(bindingType = BindingType.MUST)
    public void setColumnMetaFactory(ColumnMetaFactory columnMetaFactory) {
        this.columnMetaFactory = columnMetaFactory;
    }

    /**
     * 永続化層の命名規約を返します。
     * 
     * @return 永続化層の命名規約
     */
    public PersistenceConvention getPersistenceConvention() {
        return persistenceConvention;
    }

    /**
     * 永続化層の命名規約を設定します。
     * 
     * @param persistenceConvention
     *            永続化層の命名規約
     */
    public void setPersistenceConvention(
            PersistenceConvention persistenceConvention) {
        this.persistenceConvention = persistenceConvention;
    }
}