${database.allClassCopyright}package ${glPackageBaseCommon};

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.dbflute.DBDef;
import org.seasar.dbflute.QLog;
import org.seasar.dbflute.XLog;
import org.seasar.dbflute.bhv.core.supplement.SequenceCacheKeyGenerator;
import org.seasar.dbflute.bhv.outsidesql.factory.OutsideSqlExecutorFactory;
import org.seasar.dbflute.cbean.sqlclause.SqlClauseCreator;
import org.seasar.dbflute.dbmeta.name.SqlNameFilter;
import org.seasar.dbflute.exception.IllegalDBFluteConfigAccessException;
import org.seasar.dbflute.jdbc.DataSourceHandler;
import org.seasar.dbflute.jdbc.NotClosingConnectionWrapper;
import org.seasar.dbflute.jdbc.PhysicalConnectionDigger;
import org.seasar.dbflute.jdbc.StatementConfig;
import org.seasar.dbflute.jdbc.ValueType;
import org.seasar.dbflute.s2dao.valuetype.TnValueTypes;
import org.seasar.dbflute.s2dao.valuetype.plugin.OracleAgent;
import org.seasar.dbflute.s2dao.valuetype.plugin.OracleDateType;
import org.seasar.dbflute.s2dao.valuetype.plugin.OracleArrayType;
import org.seasar.dbflute.s2dao.valuetype.plugin.OracleStructType;
import org.seasar.dbflute.twowaysql.DisplaySqlBuilder;
import org.seasar.dbflute.util.DfReflectionUtil;
#if ($database.isTargetContainerSeasar())
import org.seasar.extension.dbcp.ConnectionWrapper;
#end
#if ($database.isTargetContainerSpring())
import org.springframework.jdbc.datasource.DataSourceUtils;
#end

/**
 * @author ${database.classAuthor}
 */
public class ${glDBFluteConfig} {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    /** Log instance. */
    private static final Log _log = LogFactory.getLog(${glDBFluteConfig}.class);

#if ($database.isOutsideSqlPackageValid())
    /** The default package of outside SQL. The default value is '${database.outsideSqlPackage}'. */
    private static final String DEFAULT_OUTSIDE_SQL_PACKAGE = "${database.outsideSqlPackage}";
#else
    /** The default package of outside SQL. */
    private static final String DEFAULT_OUTSIDE_SQL_PACKAGE = null;
#end

#if ($database.isSqlLogRegistryValid())
    /** The default value of whether it uses SQL Log Registry. The default value is true. */
    private static final boolean DEFAULT_USE_SQL_LOG_REGISTRY = true;
#else
    /** The default value of whether it uses SQL Log Registry. The default value is false. */
    private static final boolean DEFAULT_USE_SQL_LOG_REGISTRY = false;
#end

    /** Singleton instance. */
    private static final ${glDBFluteConfig} _instance = new ${glDBFluteConfig}();

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    // -----------------------------------------------------
    //                                         Configuration
    //                                         -------------
    protected StatementConfig _defaultStatementConfig;
    protected boolean _queryLogLevelInfo;
    protected boolean _executeStatusLogLevelInfo;
    protected String _logDateFormat;
    protected String _logTimestampFormat;
    protected DataSourceHandler _dataSourceHandler;
    protected PhysicalConnectionDigger _physicalConnectionDigger;
    protected SequenceCacheKeyGenerator _sequenceCacheKeyGenerator;
    protected SqlClauseCreator _sqlClauseCreator;
    protected SqlNameFilter _tableSqlNameFilter;
    protected String _outsideSqlPackage = DEFAULT_OUTSIDE_SQL_PACKAGE;
    protected OutsideSqlExecutorFactory _outsideSqlExecutorFactory;
    protected boolean _useSqlLogRegistry = DEFAULT_USE_SQL_LOG_REGISTRY;
    protected boolean _disableSelectIndex;
    protected boolean _internalDebug;

    // -----------------------------------------------------
    //                                   Database Dependency
    //                                   -------------------
#if ($database.isAvailableDatabaseDependency())
#if ($database.isDatabasePostgreSQL())
    protected org.seasar.dbflute.dbway.WayOfPostgreSQL.OperandOfLikeSearch _fullTextSearchOperand
            = org.seasar.dbflute.dbway.WayOfPostgreSQL.OperandOfLikeSearch.FULL_TEXT_SEARCH;
#end
#end

    // -----------------------------------------------------
    //                                                  Lock
    //                                                  ----
    protected boolean _locked = true; // at first locked

    // ===================================================================================
    //                                                                         Constructor
    //                                                                         ===========
    /**
     * Constructor.
     */
    private ${glDBFluteConfig}() { // adjusts default settings
        _physicalConnectionDigger = new ImplementedPhysicalConnectionDigger();

        if (isCurrentDBDef(DBDef.Oracle)) {
            // date formatting has two points:
            //   o the DATE type of Oracle has seconds  
            //   o it uses a date literal of Oracle
            _logDateFormat = "timestamp $df:{yyyy-MM-dd HH:mm:ss}";
            _logTimestampFormat = "timestamp $df:{" + DisplaySqlBuilder.DEFAULT_TIMESTAMP_FORMAT + "}";
        }
#if ($database.isDatabaseOracle() && $database.isAvailableDatabaseNativeJDBC())

        // treats as uses Oracle date of database native JDBC to get best performances
        TnValueTypes.registerBasicValueType(DBDef.Oracle, java.util.Date.class, new ImplementedOracleDateType());
#end
    }

    // ===================================================================================
    //                                                                           Singleton
    //                                                                           =========
    /**
     * Get singleton instance.
     * @return Singleton instance. (NotNull)
     */
    public static ${glDBFluteConfig} getInstance() {
        return _instance;
    }

    // ===================================================================================
    //                                                            Default Statement Config
    //                                                            ========================
    public StatementConfig getDefaultStatementConfig() {
        return _defaultStatementConfig;
    }

    public void setDefaultStatementConfig(StatementConfig defaultStatementConfig) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting defaultStatementConfig: " + defaultStatementConfig);
        }
        _defaultStatementConfig = defaultStatementConfig;
    }

    // ===================================================================================
    //                                                                Query Log Level Info
    //                                                                ====================
    public void setQueryLogLevelInfo(boolean queryLogLevelInfo) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting queryLogLevelInfo: " + queryLogLevelInfo);
        }
        QLog.unlock();
        QLog.setQueryLogLevelInfo(queryLogLevelInfo);
        QLog.lock();
    }

    // ===================================================================================
    //                                                       Execute Status Log Level Info
    //                                                       =============================
    public void setExecuteStatusLogLevelInfo(boolean executeStatusLogLevelInfo) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting executeStatusLogLevelInfo: " + executeStatusLogLevelInfo);
        }
        XLog.unlock();
        XLog.setExecuteStatusLogLevelInfo(executeStatusLogLevelInfo);
        XLog.lock();
    }

    // ===================================================================================
    //                                                                          Log Format
    //                                                                          ==========
    public String getLogDateFormat() {
        return _logDateFormat;
    }

    public void setLogDateFormat(String logDateFormat) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting logDateFormat: " + logDateFormat);
        }
        _logDateFormat = logDateFormat;
    }

    public String getLogTimestampFormat() {
        return _logTimestampFormat;
    }

    public void setLogTimestampFormat(String logTimestampFormat) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting logTimestampFormat: " + logTimestampFormat);
        }
        _logTimestampFormat = logTimestampFormat;
    }

    // [DBFlute-0.9.0]
    // ===================================================================================
    //                                                                  DataSource Handler
    //                                                                  ==================
    /**
     * @return The handler of data source. (Nullable)
     */
    public DataSourceHandler getDataSourceHandler() {
        return _dataSourceHandler;
    }

    /**
     * @param dataSourceHandler The handler of data source. (Nullable)
     */
    public void setDataSourceHandler(DataSourceHandler dataSourceHandler) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting dataSourceHandler: " + dataSourceHandler);
        }
        _dataSourceHandler = dataSourceHandler;
    }

    // [DBFlute-0.9.7.6]
    // ===================================================================================
    //                                                           PhysicalConnection Digger
    //                                                           =========================
    /**
     * @return The digger of physical connection. (NotNull: has a default instance)
     */
    public PhysicalConnectionDigger getPhysicalConnectionDigger() {
        return _physicalConnectionDigger;
    }

    /**
     * @param physicalConnectionDigger The digger of physical connection. (NotNull)
     */
    public void setPhysicalConnectionDigger(PhysicalConnectionDigger physicalConnectionDigger) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting physicalConnectionDigger: " + physicalConnectionDigger);
        }
        if (physicalConnectionDigger == null) {
            throw new IllegalArgumentException("The argument 'physicalConnectionDigger' should not be null.");
        }
        _physicalConnectionDigger = physicalConnectionDigger;
    }

    // [DBFlute-0.9.6.4]
    // ===================================================================================
    //                                                                      Sequence Cache
    //                                                                      ==============
    /**
     * @return The key generator of sequence cache. (Nullable)
     */
    public SequenceCacheKeyGenerator getSequenceCacheKeyGenerator() {
        return _sequenceCacheKeyGenerator;
    }

    /**
     * @param sequenceCacheKeyGenerator The key generator of sequence cache. (Nullable)
     */
    public void setSequenceCacheKeyGenerator(SequenceCacheKeyGenerator sequenceCacheKeyGenerator) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting sequenceCacheKeyGenerator: " + sequenceCacheKeyGenerator);
        }
        _sequenceCacheKeyGenerator = sequenceCacheKeyGenerator;
    }

    // [DBFlute-0.9.6.9]
    // ===================================================================================
    //                                                                   SqlClause Creator
    //                                                                   =================
    /**
     * @return The creator of SQL clause. (Nullable)
     */
    public SqlClauseCreator getSqlClauseCreator() {
        return _sqlClauseCreator;
    }

    /**
     * @param sqlClauseCreator The creator of SQL clause. (Nullable)
     */
    public void setSqlClauseCreator(SqlClauseCreator sqlClauseCreator) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting sqlClauseCreator: " + sqlClauseCreator);
        }
        _sqlClauseCreator = sqlClauseCreator;
    }

    // [DBFlute-0.9.7.6]
    // ===================================================================================
    //                                                                 TableSqlName Filter
    //                                                                 ===================
    /**
     * @return The SQL name filter for table. (Nullable)
     */
    public SqlNameFilter getTableSqlNameFilter() {
        return _tableSqlNameFilter;
    }

    /**
     * Set the SQL name filter for table. <br />
     * This setting should be called before container's initialization.
     * (its exact meaning is: before class loading of DBMeta for table)
     * @param tableSqlNameFilter The SQL name filter for table. (Nullable)
     */
    public void setTableSqlNameFilter(SqlNameFilter tableSqlNameFilter) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting tableSqlNameFilter: " + tableSqlNameFilter);
        }
        _tableSqlNameFilter = tableSqlNameFilter;
    }

    // ===================================================================================
    //                                                                  OutsideSql Package
    //                                                                  ==================
    /**
     * @return The package of outside SQL. (Nullable)
     */
    public String getOutsideSqlPackage() {
        return _outsideSqlPackage;
    }

    /**
     * @param outsideSqlPackage The package of outside SQL. (Nullable)
     */
    public void setOutsideSqlPackage(String outsideSqlPackage) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting outsideSqlPackage: " + outsideSqlPackage);
        }
        _outsideSqlPackage = outsideSqlPackage;
    }

    // [DBFlute-0.9.7.0]
    // ===================================================================================
    //                                                                 OutsideSql Executor
    //                                                                 ===================
    public OutsideSqlExecutorFactory getOutsideSqlExecutorFactory() {
        return _outsideSqlExecutorFactory;
    }

    public void setOutsideSqlExecutorFactory(OutsideSqlExecutorFactory outsideSqlExecutorFactory) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting outsideSqlExecutorFactory: " + outsideSqlExecutorFactory);
        }
        _outsideSqlExecutorFactory = outsideSqlExecutorFactory;
    }

    // [DBFlute-0.8.2]
    // ===================================================================================
    //                                                                    SQL Log Registry
    //                                                                    ================
    public boolean isUseSqlLogRegistry() {
        return _useSqlLogRegistry;
    }

    public void setUseSqlLogRegistry(boolean useSqlLogRegistry) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting useSqlLogRegistry: " + useSqlLogRegistry);
        }
        _useSqlLogRegistry = useSqlLogRegistry;
    }

    // [DBFlute-0.9.0]
    // ===================================================================================
    //                                                                        Select Index
    //                                                                        ============
    public boolean isDisableSelectIndex() {
        return _disableSelectIndex;
    }

    public void setDisableSelectIndex(boolean disableSelectIndex) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting disableSelectIndex: " + disableSelectIndex);
        }
        _disableSelectIndex = disableSelectIndex;
    }
    
    // ===================================================================================
    //                                                                 Database Dependency
    //                                                                 ===================
#if ($database.isAvailableDatabaseDependency())
#if ($database.isDatabasePostgreSQL())
    // -----------------------------------------------------
    //                              Full-Text Search Operand
    //                              ------------------------
    public org.seasar.dbflute.dbway.WayOfPostgreSQL.OperandOfLikeSearch getFullTextSearchOperand() {
        return _fullTextSearchOperand;
    }

    public void useDefaultFullTextSearchOperand() {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Using fullTextSearchOperand: Default");
        }
        _fullTextSearchOperand = org.seasar.dbflute.dbway.WayOfPostgreSQL.OperandOfLikeSearch.FULL_TEXT_SEARCH; 
    }

    public void useOldFullTextSearchOperand() {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Using fullTextSearchOperand: Old");
        }
        _fullTextSearchOperand = org.seasar.dbflute.dbway.WayOfPostgreSQL.OperandOfLikeSearch.OLD_FULL_TEXT_SEARCH; 
    }
#end
#end

    // ===================================================================================
    //                                                                      Internal Debug
    //                                                                      ==============
    public boolean isInternalDebug() {
        return _internalDebug;
    }

    public void setInternalDebug(boolean internalDebug) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Setting internalDebug: " + internalDebug);
        }
        _internalDebug = internalDebug;
    }

    // ===================================================================================
    //                                                                          Value Type
    //                                                                          ==========
    /**
     * Register the basic value type. <br />
     * This setting is shared per DBMS in the same class loader. 
     * @param keyType The type as key. (NotNull)
     * @param valueType The basic value type. (NotNull)
     */
    public void registerBasicValueType(Class<?> keyType, ValueType valueType) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Registering basic valueType: " + keyType + " = " + valueType);
        }
        TnValueTypes.registerBasicValueType(currentDBDef(), keyType, valueType);
    }

    public void removeBasicValueType(Class<?> keyType) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Removing basic valueType: " + keyType);
        }
        TnValueTypes.removeBasicValueType(currentDBDef(), keyType);
    }

    /**
     * Register the plug-in value type. <br />
     * This setting is shared per DBMS in the same class loader.
     * @param keyName The name as key. (NotNull)
     * @param valueType The plug-in value type. (NotNull)
     */
    public void registerPluginValueType(String keyName, ValueType valueType) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Registering plug-in valueType: " + keyName + " = " + valueType);
        }
        TnValueTypes.registerPluginValueType(currentDBDef(), keyName, valueType);
    }

    public void removePluginValueType(String keyName) {
        assertNotLocked();
        if (_log.isInfoEnabled()) {
            _log.info("...Removing plug-in valueType: keyName=" + keyName);
        }
        TnValueTypes.removePluginValueType(currentDBDef(), keyName);
    }

    // ===================================================================================
    //                                                                  Configuration Lock
    //                                                                  ==================
    public boolean isLocked() {
        return _locked;
    }

    public void lock() {
        if (_log.isInfoEnabled()) {
            _log.info("...Locking the configuration of DBFlute");
        }
        _locked = true;
    }

    public void unlock() {
        if (_log.isInfoEnabled()) {
            _log.info("...Unlocking the configuration of DBFlute");
        }
        _locked = false;
    }

    protected void assertNotLocked() {
        if (!isLocked()) {
            return;
        }
        String msg = "The configuration of DBFlute is locked.";
        throw new IllegalDBFluteConfigAccessException(msg);
    }

    // ===================================================================================
    //                                                                       Assist Helper
    //                                                                       =============
    protected DBDef currentDBDef() {
        return ${glDBCurrent}.getInstance().currentDBDef();
    }

    protected boolean isCurrentDBDef(DBDef currentDBDef) {
        return ${glDBCurrent}.getInstance().isCurrentDBDef(currentDBDef);
    }

    // ===================================================================================
    //                                                                   Implemented Class
    //                                                                   =================
    // -----------------------------------------------------
    //                                                Spring
    //                                                ------
    protected static class SpringDBCPDataSourceHandler implements DataSourceHandler {

        public Connection getConnection(DataSource ds) throws SQLException {
            final Connection conn = getConnectionFromUtils(ds);
            if (isConnectionTransactional(conn, ds)) {
                return new NotClosingConnectionWrapper(conn);
            } else {
                return conn;
            }
        }

        public Connection getConnectionFromUtils(DataSource ds) {
#if ($database.isTargetContainerSpring())
            return DataSourceUtils.getConnection(ds);
#else
            throw new UnsupportedOperationException("This method is only for Spring Framework.");
#end
        }

        public boolean isConnectionTransactional(Connection conn, DataSource ds) {
#if ($database.isTargetContainerSpring())
            return DataSourceUtils.isConnectionTransactional(conn, ds);
#else
            throw new UnsupportedOperationException("This method is only for Spring Framework.");
#end
        }

        @Override
        public String toString() {
            return "SpringDBCPDataSourceHandler(for Spring and Commons-DBCP)";
        }
    }

    // -----------------------------------------------------
    //                                                Oracle
    //                                                ------
    public static class ImplementedOracleAgent implements OracleAgent {

        public Object toOracleDate(Timestamp timestamp) {
#if ($database.isAvailableOracleNativeJDBC())
            return new oracle.sql.DATE(timestamp);
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public Object toOracleArray(Connection conn, String arrayTypeName, Object arrayValue) throws SQLException {
#if ($database.isAvailableOracleNativeJDBC())
            oracle.sql.ArrayDescriptor desc = oracle.sql.ArrayDescriptor.createDescriptor(arrayTypeName, conn);
            return new oracle.sql.ARRAY(desc, conn, arrayValue);
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public Object toStandardArray(Object oracleArray) throws SQLException {
#if ($database.isAvailableOracleNativeJDBC())
            return ((oracle.sql.ARRAY)oracleArray).getArray();
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public boolean isOracleArray(Object obj) {
#if ($database.isAvailableOracleNativeJDBC())
            return obj instanceof oracle.sql.ARRAY;
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public Object toOracleStruct(Connection conn, String structTypeName, Object[] attrs) throws SQLException {
#if ($database.isAvailableOracleNativeJDBC())
            oracle.sql.StructDescriptor desc = new oracle.sql.StructDescriptor(structTypeName, conn);
            return new ImplementedOracleStructWrapper(new oracle.sql.STRUCT(desc, conn, attrs));
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public Object[] toStandardStructAttributes(Object oracleStruct) throws SQLException {
#if ($database.isAvailableOracleNativeJDBC())
            return ((oracle.sql.STRUCT)oracleStruct).getAttributes();
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public boolean isOracleStruct(Object obj) {
#if ($database.isAvailableOracleNativeJDBC())
            return obj instanceof oracle.sql.STRUCT;
#else
            throw new UnsupportedOperationException("This method is only for Oracle.");
#end
        }

        public PhysicalConnectionDigger getPhysicalConnectionDigger() {
            return ${glDBFluteConfig}.getInstance().getPhysicalConnectionDigger();
        }
    }
#if ($database.isAvailableOracleNativeJDBC())

    public static class ImplementedOracleStructWrapper implements oracle.sql.ORAData {
        protected oracle.sql.STRUCT _struct;
        public ImplementedOracleStructWrapper(oracle.sql.STRUCT struct) {
            _struct = struct;
        }
        public oracle.sql.Datum toDatum(Connection conn) throws SQLException {
            return _struct;
        }
    }
#end

    public static class ImplementedOracleDateType extends OracleDateType {

        @Override
        protected OracleAgent createOracleAgent() {
            return new ImplementedOracleAgent();
        }
    }

    public static class ImplementedOracleArrayType extends OracleArrayType {

        public ImplementedOracleArrayType(String arrayTypeName, Class<?> elementType) {
            super(arrayTypeName, elementType);
        }

        @Override
        protected OracleAgent createOracleAgent() {
            return new ImplementedOracleAgent();
        }
    }

    public static class ImplementedOracleStructType extends OracleStructType {

        public ImplementedOracleStructType(String structTypeName, Class<?> entityType) {
            super(structTypeName, entityType);
        }

        @Override
        protected OracleAgent createOracleAgent() {
            return new ImplementedOracleAgent();
        }
    }

    // -----------------------------------------------------
    //                                   Physical Connection
    //                                   -------------------
    public static class ImplementedPhysicalConnectionDigger implements PhysicalConnectionDigger {

        public Connection digUp(Connection conn) throws SQLException {
            if (conn instanceof NotClosingConnectionWrapper) {
                conn = ((NotClosingConnectionWrapper) conn).getActualConnection();
            }
            conn = resolveS2DBCP(conn);
            conn = resolveCommonsDBCP(conn);
            return conn;
        }

        protected Connection resolveS2DBCP(Connection conn) {
#if ($database.isTargetContainerSeasar())
            if (conn instanceof ConnectionWrapper) {
                conn = ((ConnectionWrapper)conn).getPhysicalConnection();
            }
#end
            return conn;
        }

        protected Connection resolveCommonsDBCP(Connection conn) {
            if ("org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper".equals(conn.getClass().getName())) {
                conn = getFieldConnection(conn, "delegate");
            }
            if ("org.apache.commons.dbcp.PoolableConnection".equals(conn.getClass().getName())) {
                conn = getFieldConnection(conn, "_conn");
            }
            return conn;
        }

        protected Connection getFieldConnection(Connection conn, String fieldName) {
            Field field = DfReflectionUtil.getWholeField(conn.getClass(), fieldName);
            return (Connection) DfReflectionUtil.getValueForcedly(field, conn);
        }
    } 
}
