| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_connectivity.hxx" |
| |
| #include "MacabStatement.hxx" |
| #include "MacabConnection.hxx" |
| #include "MacabAddressBook.hxx" |
| #include "MacabDriver.hxx" |
| #include "MacabResultSet.hxx" |
| #include "MacabResultSetMetaData.hxx" |
| #include "macabcondition.hxx" |
| #include "macaborder.hxx" |
| #include "TConnection.hxx" |
| #include <connectivity/dbexception.hxx> |
| #include "resource/sharedresources.hxx" |
| #include "resource/macab_res.hrc" |
| |
| #if OSL_DEBUG_LEVEL > 0 |
| # define OUtoCStr( x ) ( ::rtl::OUStringToOString ( (x), RTL_TEXTENCODING_ASCII_US).getStr()) |
| #else /* OSL_DEBUG_LEVEL */ |
| # define OUtoCStr( x ) ("dummy") |
| #endif /* OSL_DEBUG_LEVEL */ |
| |
| using namespace connectivity::macab; |
| using namespace com::sun::star::uno; |
| using namespace com::sun::star::lang; |
| using namespace com::sun::star::beans; |
| using namespace com::sun::star::sdbc; |
| using namespace com::sun::star::sdbcx; |
| using namespace com::sun::star::container; |
| using namespace com::sun::star::io; |
| using namespace com::sun::star::util; |
| |
| namespace connectivity |
| { |
| namespace macab |
| { |
| void impl_throwError(sal_uInt16 _nErrorId) |
| { |
| ::connectivity::SharedResources aResources; |
| const ::rtl::OUString sError( aResources.getResourceString(_nErrorId) ); |
| ::dbtools::throwGenericSQLException(sError,NULL); |
| } |
| } |
| } |
| |
| IMPLEMENT_SERVICE_INFO(MacabStatement, "com.sun.star.sdbc.drivers.MacabStatement", "com.sun.star.sdbc.Statement"); |
| //------------------------------------------------------------------------------ |
| MacabCommonStatement::MacabCommonStatement(MacabConnection* _pConnection ) |
| : MacabCommonStatement_BASE(m_aMutex), |
| OPropertySetHelper(MacabCommonStatement_BASE::rBHelper), |
| m_aParser(_pConnection->getDriver()->getMSFactory()), |
| m_aSQLIterator(_pConnection, _pConnection->createCatalog()->getTables(), m_aParser, NULL ), |
| m_pParseTree(NULL), |
| m_pConnection(_pConnection), |
| rBHelper(MacabCommonStatement_BASE::rBHelper) |
| { |
| m_pConnection->acquire(); |
| } |
| // ----------------------------------------------------------------------------- |
| MacabCommonStatement::~MacabCommonStatement() |
| { |
| } |
| // ----------------------------------------------------------------------------- |
| void MacabCommonStatement::disposing() |
| { |
| MacabCommonStatement_BASE::disposing(); |
| } |
| // ----------------------------------------------------------------------------- |
| void MacabCommonStatement::resetParameters() const throw(::com::sun::star::sdbc::SQLException) |
| { |
| impl_throwError(STR_PARA_ONLY_PREPARED); |
| } |
| // ----------------------------------------------------------------------------- |
| void MacabCommonStatement::getNextParameter(::rtl::OUString &) const throw(::com::sun::star::sdbc::SQLException) |
| { |
| impl_throwError(STR_PARA_ONLY_PREPARED); |
| } |
| // ----------------------------------------------------------------------------- |
| MacabCondition *MacabCommonStatement::analyseWhereClause(const OSQLParseNode *pParseNode) const throw(SQLException) |
| { |
| if (pParseNode->count() == 3) |
| { |
| const OSQLParseNode *pLeft = pParseNode->getChild(0), |
| *pMiddle = pParseNode->getChild(1), |
| *pRight = pParseNode->getChild(2); |
| |
| // WHERE ( ... ) ? |
| if (SQL_ISPUNCTUATION(pLeft, "(") && SQL_ISPUNCTUATION(pRight, ")")) |
| { |
| return analyseWhereClause(pMiddle); |
| } |
| else if (SQL_ISRULE(pParseNode, comparison_predicate)) |
| { |
| if (pLeft->isToken() && pRight->isToken()) |
| { |
| switch (pMiddle->getNodeType()) |
| { |
| case SQL_NODE_EQUAL: |
| // WHERE 0 = 1 |
| return new MacabConditionConstant(pLeft->getTokenValue() == pRight->getTokenValue()); |
| |
| case SQL_NODE_NOTEQUAL: |
| // WHERE 0 <> 1 |
| // (might not be correct SQL... don't care, handling anyway) |
| return new MacabConditionConstant(pLeft->getTokenValue() != pRight->getTokenValue()); |
| |
| default: |
| break; |
| } |
| } |
| else if (SQL_ISRULE(pLeft, column_ref)) |
| { |
| ::rtl::OUString sColumnName, |
| sTableRange; |
| |
| m_aSQLIterator.getColumnRange(pLeft, sColumnName, sTableRange); |
| |
| if (pRight->isToken() || SQL_ISRULE(pRight, parameter)) |
| { |
| ::rtl::OUString sMatchString; |
| |
| if (pRight->isToken()) // WHERE Name = 'Doe' |
| sMatchString = pRight->getTokenValue(); |
| else if (SQL_ISRULE(pRight, parameter)) // WHERE Name = ? |
| getNextParameter(sMatchString); |
| |
| switch (pMiddle->getNodeType()) |
| { |
| case SQL_NODE_EQUAL: |
| // WHERE Name = 'Smith' |
| return new MacabConditionEqual(m_pHeader, sColumnName, sMatchString); |
| |
| case SQL_NODE_NOTEQUAL: |
| // WHERE Name <> 'Jones' |
| return new MacabConditionDifferent(m_pHeader, sColumnName, sMatchString); |
| |
| default: |
| break; |
| } |
| } |
| } |
| } |
| else if (SQL_ISRULE(pParseNode, search_condition)) |
| { |
| if (SQL_ISTOKEN(pMiddle, OR)) |
| { |
| // WHERE Name = 'Smith' OR Name = 'Jones' |
| return new MacabConditionOr( |
| analyseWhereClause(pLeft), |
| analyseWhereClause(pRight)); |
| } |
| } |
| else if (SQL_ISRULE(pParseNode, boolean_term)) |
| { |
| if (SQL_ISTOKEN(pMiddle, AND)) |
| { |
| // WHERE Name = 'Smith' AND "Given Name" = 'Peter' |
| return new MacabConditionAnd( |
| analyseWhereClause(pLeft), |
| analyseWhereClause(pRight)); |
| } |
| } |
| } |
| else if (SQL_ISRULE(pParseNode, test_for_null) || SQL_ISRULE(pParseNode, like_predicate)) |
| { |
| const OSQLParseNode *pLeft = pParseNode->getChild(0); |
| const OSQLParseNode* pPart2 = pParseNode->getChild(1); |
| const OSQLParseNode *pMiddleLeft = pPart2->getChild(0), |
| *pMiddleRight = pPart2->getChild(1), |
| *pRight = pPart2->getChild(2); |
| |
| if (SQL_ISRULE(pParseNode, test_for_null)) |
| { |
| if (SQL_ISRULE(pLeft, column_ref) && |
| SQL_ISTOKEN(pMiddleLeft, IS) && |
| SQL_ISTOKEN(pRight, NULL)) |
| { |
| ::rtl::OUString sColumnName, |
| sTableRange; |
| |
| m_aSQLIterator.getColumnRange(pLeft, sColumnName, sTableRange); |
| |
| if (SQL_ISTOKEN(pMiddleRight, NOT)) |
| { |
| // WHERE "Mobile Phone" IS NOT NULL |
| return new MacabConditionNotNull(m_pHeader, sColumnName); |
| } |
| else |
| { |
| // WHERE "Mobile Phone" IS NULL |
| return new MacabConditionNull(m_pHeader, sColumnName); |
| } |
| } |
| } |
| else if (SQL_ISRULE(pParseNode, like_predicate)) |
| { |
| if (SQL_ISRULE(pLeft, column_ref)) |
| { |
| ::rtl::OUString sColumnName, |
| sTableRange; |
| |
| m_aSQLIterator.getColumnRange(pLeft, sColumnName, sTableRange); |
| |
| if (pMiddleRight->isToken() || SQL_ISRULE(pMiddleRight, parameter)) |
| { |
| ::rtl::OUString sMatchString; |
| |
| if (pMiddleRight->isToken()) // WHERE Name LIKE 'Sm%' |
| sMatchString = pMiddleRight->getTokenValue(); |
| else if (SQL_ISRULE(pMiddleRight, parameter)) // WHERE Name LIKE ? |
| getNextParameter(sMatchString); |
| |
| return new MacabConditionSimilar(m_pHeader, sColumnName, sMatchString); |
| } |
| } |
| } |
| } |
| impl_throwError(STR_QUERY_TOO_COMPLEX); |
| // Unreachable: |
| OSL_ASSERT(false); |
| return 0; |
| } |
| // ----------------------------------------------------------------------------- |
| MacabOrder *MacabCommonStatement::analyseOrderByClause(const OSQLParseNode *pParseNode) const throw(SQLException) |
| { |
| if (SQL_ISRULE(pParseNode, ordering_spec_commalist)) |
| { |
| MacabComplexOrder *list = new MacabComplexOrder(); |
| sal_uInt32 n = pParseNode->count(); |
| |
| // Iterate through the ordering columns |
| for (sal_uInt32 i = 0; i < n; i++) |
| { |
| list->addOrder |
| (analyseOrderByClause(pParseNode->getChild(i))); |
| } |
| |
| return list; |
| } |
| else if (SQL_ISRULE(pParseNode, ordering_spec)) |
| { |
| if (pParseNode->count() == 2) |
| { |
| OSQLParseNode* pColumnRef = pParseNode->getChild(0); |
| OSQLParseNode* pAscendingDescending = pParseNode->getChild(1); |
| |
| if (SQL_ISRULE(pColumnRef, column_ref)) |
| { |
| if (pColumnRef->count() == 3) |
| pColumnRef = pColumnRef->getChild(2); |
| |
| if (pColumnRef->count() == 1) |
| { |
| ::rtl::OUString sColumnName = |
| pColumnRef->getChild(0)->getTokenValue(); |
| sal_Bool bAscending = |
| SQL_ISTOKEN(pAscendingDescending, DESC)? |
| sal_False: |
| sal_True; |
| |
| return new MacabSimpleOrder(m_pHeader, sColumnName, bAscending); |
| } |
| } |
| } |
| } |
| impl_throwError(STR_QUERY_TOO_COMPLEX); |
| // Unreachable: |
| OSL_ASSERT(false); |
| return 0; |
| } |
| //------------------------------------------------------------------------------ |
| ::rtl::OUString MacabCommonStatement::getTableName() const |
| { |
| const OSQLTables& xTabs = m_aSQLIterator.getTables(); |
| |
| if( xTabs.empty() ) |
| return ::rtl::OUString(); |
| |
| // can only deal with one table at a time |
| if(xTabs.size() > 1 || m_aSQLIterator.hasErrors() ) |
| return ::rtl::OUString(); |
| |
| return xTabs.begin()->first; |
| } |
| //------------------------------------------------------------------------------ |
| void MacabCommonStatement::setMacabFields(MacabResultSet *pResult) const throw(SQLException) |
| { |
| ::vos::ORef<connectivity::OSQLColumns> xColumns; // selected columns |
| MacabResultSetMetaData *pMeta; // meta information - holds the list of AddressBook fields |
| |
| xColumns = m_aSQLIterator.getSelectColumns(); |
| if (!xColumns.isValid()) |
| { |
| ::connectivity::SharedResources aResources; |
| const ::rtl::OUString sError( aResources.getResourceString( |
| STR_INVALID_COLUMN_SELECTION |
| ) ); |
| ::dbtools::throwGenericSQLException(sError,NULL); |
| } |
| pMeta = static_cast<MacabResultSetMetaData *>(pResult->getMetaData().get()); |
| pMeta->setMacabFields(xColumns); |
| } |
| // ------------------------------------------------------------------------- |
| void MacabCommonStatement::selectRecords(MacabResultSet *pResult) const throw(SQLException) |
| { |
| const OSQLParseNode *pParseNode; |
| MacabCondition *pCondition; |
| |
| pParseNode = m_aSQLIterator.getWhereTree(); |
| if (pParseNode != NULL) |
| { |
| if (SQL_ISRULE(pParseNode, where_clause)) |
| { |
| // Since we don't support parameters, don't reset them. If we ever |
| // support them, uncomment this line and fix resetParameters. |
| //resetParameters(); |
| pParseNode = pParseNode->getChild(1); |
| pCondition = analyseWhereClause(pParseNode); |
| if (pCondition->isAlwaysTrue()) |
| pResult->allMacabRecords(); |
| else if (!pCondition->isAlwaysFalse()) |
| pResult->someMacabRecords(pCondition); |
| delete pCondition; |
| return; |
| } |
| } |
| |
| // no WHERE clause: get all rows |
| pResult->allMacabRecords(); |
| } |
| // ------------------------------------------------------------------------- |
| void MacabCommonStatement::sortRecords(MacabResultSet *pResult) const throw(SQLException) |
| { |
| const OSQLParseNode *pParseNode; |
| MacabOrder *pOrder; |
| |
| pParseNode = m_aSQLIterator.getOrderTree(); |
| if (pParseNode != NULL) |
| { |
| if (SQL_ISRULE(pParseNode, opt_order_by_clause)) |
| { |
| pParseNode = pParseNode->getChild(2); |
| pOrder = analyseOrderByClause(pParseNode); |
| pResult->sortMacabRecords(pOrder); |
| delete pOrder; |
| } |
| } |
| } |
| //----------------------------------------------------------------------------- |
| Any SAL_CALL MacabCommonStatement::queryInterface( const Type & rType ) throw(RuntimeException) |
| { |
| Any aRet = MacabCommonStatement_BASE::queryInterface(rType); |
| if (!aRet.hasValue()) |
| aRet = OPropertySetHelper::queryInterface(rType); |
| return aRet; |
| } |
| // ------------------------------------------------------------------------- |
| Sequence< Type > SAL_CALL MacabCommonStatement::getTypes( ) throw(RuntimeException) |
| { |
| ::cppu::OTypeCollection aTypes( ::getCppuType( (const Reference< XMultiPropertySet > *)0 ), |
| ::getCppuType( (const Reference< XFastPropertySet > *)0 ), |
| ::getCppuType( (const Reference< XPropertySet > *)0 )); |
| |
| return comphelper::concatSequences(aTypes.getTypes(),MacabCommonStatement_BASE::getTypes()); |
| } |
| // ------------------------------------------------------------------------- |
| void SAL_CALL MacabCommonStatement::cancel( ) throw(RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| // cancel the current sql statement |
| } |
| // ------------------------------------------------------------------------- |
| void SAL_CALL MacabCommonStatement::close( ) throw(SQLException, RuntimeException) |
| { |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| } |
| dispose(); |
| } |
| // ------------------------------------------------------------------------- |
| sal_Bool SAL_CALL MacabCommonStatement::execute( |
| const ::rtl::OUString& sql ) throw(SQLException, RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| Reference< XResultSet > xRS = executeQuery(sql); |
| |
| return xRS.is(); |
| } |
| // ------------------------------------------------------------------------- |
| Reference< XResultSet > SAL_CALL MacabCommonStatement::executeQuery( |
| const ::rtl::OUString& sql ) throw(SQLException, RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| OSL_TRACE("Mac OS Address book - SQL Request: %s", OUtoCStr(sql)); |
| |
| MacabResultSet* pResult = new MacabResultSet(this); |
| Reference< XResultSet > xRS = pResult; |
| ::rtl::OUString aErr; |
| |
| m_pParseTree = m_aParser.parseTree(aErr, sql); |
| if (m_pParseTree == NULL) |
| throw SQLException(aErr, *this, aErr, 0, Any()); |
| |
| m_aSQLIterator.setParseTree(m_pParseTree); |
| m_aSQLIterator.traverseAll(); |
| switch (m_aSQLIterator.getStatementType()) |
| { |
| case SQL_STATEMENT_SELECT: |
| { |
| ::rtl::OUString sTableName = getTableName(); // FROM which table ? |
| if (sTableName.getLength() != 0) // a match |
| { |
| MacabRecords *aRecords; |
| aRecords = m_pConnection->getAddressBook()->getMacabRecords(sTableName); |
| |
| // In case, somehow, we don't have anything with the name m_sTableName |
| if(aRecords == NULL) |
| { |
| impl_throwError(STR_NO_TABLE); |
| } |
| else |
| { |
| m_pHeader = aRecords->getHeader(); |
| |
| pResult->setTableName(sTableName); |
| |
| setMacabFields(pResult); // SELECT which columns ? |
| selectRecords(pResult); // WHERE which condition ? |
| sortRecords(pResult); // ORDER BY which columns ? |
| } |
| // To be continued: DISTINCT |
| // etc... |
| } |
| } |
| break; |
| |
| default: |
| // To be continued: UPDATE |
| // DELETE |
| // etc... |
| impl_throwError(STR_QUERY_TOO_COMPLEX); |
| } |
| |
| m_xResultSet = Reference<XResultSet>(pResult); |
| return xRS; |
| } |
| // ------------------------------------------------------------------------- |
| Reference< XConnection > SAL_CALL MacabCommonStatement::getConnection( ) throw(SQLException, RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| // just return our connection here |
| return (Reference< XConnection >) m_pConnection; |
| } |
| // ------------------------------------------------------------------------- |
| sal_Int32 SAL_CALL MacabCommonStatement::executeUpdate( const ::rtl::OUString& ) throw(SQLException, RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| // the return values gives information about how many rows are affected by executing the sql statement |
| return 0; |
| } |
| // ------------------------------------------------------------------------- |
| Any SAL_CALL MacabCommonStatement::getWarnings( ) throw(SQLException, RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| return makeAny(m_aLastWarning); |
| } |
| // ------------------------------------------------------------------------- |
| void SAL_CALL MacabCommonStatement::clearWarnings( ) throw(SQLException, RuntimeException) |
| { |
| ::osl::MutexGuard aGuard( m_aMutex ); |
| checkDisposed(MacabCommonStatement_BASE::rBHelper.bDisposed); |
| |
| m_aLastWarning = SQLWarning(); |
| } |
| // ------------------------------------------------------------------------- |
| ::cppu::IPropertyArrayHelper* MacabCommonStatement::createArrayHelper( ) const |
| { |
| // this properties are defined by the service statement |
| // they must be in alphabetic order |
| Sequence< Property > aProps(10); |
| Property* pProperties = aProps.getArray(); |
| sal_Int32 nPos = 0; |
| DECL_PROP0(CURSORNAME, ::rtl::OUString); |
| DECL_BOOL_PROP0(ESCAPEPROCESSING); |
| DECL_PROP0(FETCHDIRECTION,sal_Int32); |
| DECL_PROP0(FETCHSIZE, sal_Int32); |
| DECL_PROP0(MAXFIELDSIZE,sal_Int32); |
| DECL_PROP0(MAXROWS, sal_Int32); |
| DECL_PROP0(QUERYTIMEOUT,sal_Int32); |
| DECL_PROP0(RESULTSETCONCURRENCY,sal_Int32); |
| DECL_PROP0(RESULTSETTYPE,sal_Int32); |
| DECL_BOOL_PROP0(USEBOOKMARKS); |
| |
| return new ::cppu::OPropertyArrayHelper(aProps); |
| } |
| // ------------------------------------------------------------------------- |
| ::cppu::IPropertyArrayHelper & MacabCommonStatement::getInfoHelper() |
| { |
| return *const_cast<MacabCommonStatement*>(this)->getArrayHelper(); |
| } |
| // ------------------------------------------------------------------------- |
| sal_Bool MacabCommonStatement::convertFastPropertyValue( |
| Any &, |
| Any &, |
| sal_Int32, |
| const Any&) throw (::com::sun::star::lang::IllegalArgumentException) |
| { |
| sal_Bool bConverted = sal_False; |
| // here we have to try to convert |
| return bConverted; |
| } |
| // ------------------------------------------------------------------------- |
| void MacabCommonStatement::setFastPropertyValue_NoBroadcast(sal_Int32 nHandle,const Any&) throw (Exception) |
| { |
| // set the value to whatever is necessary |
| switch (nHandle) |
| { |
| case PROPERTY_ID_QUERYTIMEOUT: |
| case PROPERTY_ID_MAXFIELDSIZE: |
| case PROPERTY_ID_MAXROWS: |
| case PROPERTY_ID_CURSORNAME: |
| case PROPERTY_ID_RESULTSETCONCURRENCY: |
| case PROPERTY_ID_RESULTSETTYPE: |
| case PROPERTY_ID_FETCHDIRECTION: |
| case PROPERTY_ID_FETCHSIZE: |
| case PROPERTY_ID_ESCAPEPROCESSING: |
| case PROPERTY_ID_USEBOOKMARKS: |
| default: |
| ; |
| } |
| } |
| // ------------------------------------------------------------------------- |
| void MacabCommonStatement::getFastPropertyValue(Any&,sal_Int32 nHandle) const |
| { |
| switch (nHandle) |
| { |
| case PROPERTY_ID_QUERYTIMEOUT: |
| case PROPERTY_ID_MAXFIELDSIZE: |
| case PROPERTY_ID_MAXROWS: |
| case PROPERTY_ID_CURSORNAME: |
| case PROPERTY_ID_RESULTSETCONCURRENCY: |
| case PROPERTY_ID_RESULTSETTYPE: |
| case PROPERTY_ID_FETCHDIRECTION: |
| case PROPERTY_ID_FETCHSIZE: |
| case PROPERTY_ID_ESCAPEPROCESSING: |
| case PROPERTY_ID_USEBOOKMARKS: |
| default: |
| ; |
| } |
| } |
| // ----------------------------------------------------------------------------- |
| void SAL_CALL MacabCommonStatement::acquire() throw() |
| { |
| MacabCommonStatement_BASE::acquire(); |
| } |
| // ----------------------------------------------------------------------------- |
| void SAL_CALL MacabCommonStatement::release() throw() |
| { |
| MacabCommonStatement_BASE::release(); |
| } |
| // ----------------------------------------------------------------------------- |
| Reference< ::com::sun::star::beans::XPropertySetInfo > SAL_CALL MacabCommonStatement::getPropertySetInfo( ) throw(RuntimeException) |
| { |
| return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); |
| } |
| // ----------------------------------------------------------------------------- |
| MacabStatement::MacabStatement(MacabConnection* _pConnection) |
| : MacabStatement_BASE(_pConnection) |
| { |
| } |