/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ #include "model.hxx" #include "model_helper.hxx" #include "unohelper.hxx" #include "binding.hxx" #include "submission.hxx" #include "mip.hxx" #include "evaluationcontext.hxx" #include "xmlhelper.hxx" #include "datatyperepository.hxx" #include "NameContainer.hxx" #include #include #include #include #include #include #include // UNO classes #include #include #include #include #include #include #include #include #include #include #include using com::sun::star::lang::XMultiServiceFactory; using com::sun::star::lang::XUnoTunnel; using com::sun::star::beans::XPropertySet; using com::sun::star::beans::PropertyValue; using com::sun::star::beans::PropertyVetoException; using com::sun::star::beans::UnknownPropertyException; using com::sun::star::util::VetoException; using com::sun::star::lang::WrappedTargetException; using com::sun::star::lang::IllegalArgumentException; using com::sun::star::ucb::XSimpleFileAccess3; using com::sun::star::ucb::SimpleFileAccess; using com::sun::star::io::XInputStream; using namespace com::sun::star::uno; using namespace com::sun::star::xml::dom; using namespace xforms; #if OSL_DEBUG_LEVEL > 1 #define DBG_INVARIANT_TYPE(TYPE) class DBG_##TYPE { const TYPE* mpT; void check() { mpT->dbg_assertInvariant(); } public: DBG_##TYPE(const TYPE* pT) : mpT(pT) { check(); } ~DBG_##TYPE() { check(); } } _DBG_##TYPE(this); #define DBG_INVARIANT() DBG_INVARIANT_TYPE(Model) #else #define DBG_INVARIANT() #endif // // The Model // void Model::ensureAtLeastOneInstance() { if( ! mpInstances->hasItems() ) { // create a default instance newInstance( OUString(), OUString(), true ); } } /** Model default constructor; create empty model */ Model::Model() : msID(), mpBindings( NULL ), mpSubmissions( NULL ), mpInstances( new InstanceCollection ), mxNamespaces( new NameContainer() ), mxBindings( mpBindings ), mxSubmissions( mpSubmissions ), mxInstances( mpInstances ), mbInitialized( false ), mbExternalData( true ) { initializePropertySet(); // initialize bindings collections // (not in initializer list to avoid use of incomplete 'this') mpBindings = new BindingCollection( this ); mxBindings = mpBindings; mpSubmissions = new SubmissionCollection( this ); mxSubmissions = mpSubmissions; // invariant only holds after construction DBG_INVARIANT(); } Model::~Model() throw() { // give up bindings & submissions; the mxBindings/mxSubmissions // references will then delete them mpBindings = NULL; mpSubmissions = NULL; } static Model* lcl_getModel( const Reference& xTunnel ) { Model* pModel = NULL; if( xTunnel.is() ) pModel = reinterpret_cast( xTunnel->getSomething( Model::getUnoTunnelID() ) ); return pModel; } Model* Model::getModel( const Reference& xModel ) { return lcl_getModel( Reference( xModel, UNO_QUERY ) ); } EvaluationContext Model::getEvaluationContext() { // the default context is the top-level element node. A default // node (instanceData' is inserted when there is no default node Reference xInstance = getDefaultInstance(); Reference xElement( xInstance->getDocumentElement(), UNO_QUERY ); // no element found? Then insert default element 'instanceData' if( ! xElement.is() ) { xElement = Reference( xInstance->createElement( "instanceData" ), UNO_QUERY_THROW ); xInstance->appendChild( xElement ); } OSL_ENSURE( xElement.is() && xElement->getNodeType() == NodeType_ELEMENT_NODE, "no element in evaluation context" ); return EvaluationContext( xElement, this, mxNamespaces, 0, 1 ); } Model::IntSequence_t Model::getUnoTunnelID() { static cppu::OImplementationId aImplementationId; return aImplementationId.getImplementationId(); } Model::XDocument_t Model::getForeignSchema() const { return mxForeignSchema; } void Model::setForeignSchema( const XDocument_t& rDocument ) { mxForeignSchema = rDocument; } OUString Model::getSchemaRef() const { return msSchemaRef; } void Model::setSchemaRef( const OUString& rSchemaRef ) { msSchemaRef = rSchemaRef; } Model::XNameContainer_t Model::getNamespaces() const { return mxNamespaces; } void Model::setNamespaces( const XNameContainer_t& rNamespaces ) { if( rNamespaces.is() ) mxNamespaces = rNamespaces; } bool Model::getExternalData() const { return mbExternalData; } void Model::setExternalData( bool _bData ) { mbExternalData = _bData; } #if OSL_DEBUG_LEVEL > 1 void Model::dbg_assertInvariant() const { OSL_ENSURE( mpInstances != NULL, "no instances found" ); OSL_ENSURE( mxInstances.is(), "No instance container!" ); OSL_ENSURE( mpBindings != NULL, "no bindings element" ); OSL_ENSURE( mxBindings.is(), "No Bindings container" ); OSL_ENSURE( mpSubmissions != NULL, "no submissions element" ); OSL_ENSURE( mxSubmissions.is(), "No Submission container" ); } #endif // // MIP management // void Model::addMIP( void* pTag, const XNode_t& xNode, const MIP& rMIP ) { OSL_ENSURE( pTag != NULL, "empty tag?" ); OSL_ENSURE( xNode.is(), "no node" ); MIPs_t::value_type aValue( xNode, ::std::pair( pTag, rMIP ) ); maMIPs.insert( aValue ); } void Model::removeMIPs( void* pTag ) { OSL_ENSURE( pTag != NULL, "empty tag?" ); for( MIPs_t::iterator aIter = maMIPs.begin(); aIter != maMIPs.end(); ) { if( aIter->second.first == pTag ) { MIPs_t::iterator next( aIter ); ++next; maMIPs.erase( aIter ); aIter = next; } else ++aIter; } } MIP Model::queryMIP( const XNode_t& xNode ) const { // travel up inheritance chain and inherit MIPs MIP aRet; for( XNode_t xCurrent = xNode; xCurrent.is(); xCurrent = xCurrent->getParentNode() ) { // iterate over all MIPs for this node, and join MIPs MIP aMIP; MIPs_t::const_iterator aEnd = maMIPs.upper_bound( xCurrent ); MIPs_t::const_iterator aIter = maMIPs.lower_bound( xCurrent ); for( ; aIter != aEnd; ++aIter ) aMIP.join( aIter->second.second ); // inherit from current node (or set if we are at the start node) if( xCurrent == xNode ) aRet = aMIP; else aRet.inherit( aMIP ); } return aRet; } void Model::rebind() { OSL_ENSURE( mpBindings != NULL, "bindings?" ); // iterate over all bindings and call update sal_Int32 nCount = mpBindings->countItems(); for( sal_Int32 i = 0; i < nCount; i++ ) { Binding* pBind = Binding::getBinding( mpBindings->Collection::getItem( i ) ); OSL_ENSURE( pBind != NULL, "binding?" ); pBind->update(); } } void Model::deferNotifications( bool bDefer ) { // iterate over all bindings and defer notifications sal_Int32 nCount = mpBindings->countItems(); for( sal_Int32 i = 0; i < nCount; i++ ) { Binding* pBind = Binding::getBinding( mpBindings->Collection::getItem( i ) ); OSL_ENSURE( pBind != NULL, "binding?" ); pBind->deferNotifications( bDefer ); } } bool Model::setSimpleContent( const XNode_t& xConstNode, const OUString& sValue ) { OSL_ENSURE( xConstNode.is(), "need node to set data" ); bool bRet = false; if( xConstNode.is() ) { // non-const node reference so we can assign children (if necessary) XNode_t xNode( xConstNode ); switch( xNode->getNodeType() ) { case NodeType_ELEMENT_NODE: { // find first text node child Reference xChild; for( xChild = xNode->getFirstChild(); xChild.is() && xChild->getNodeType() != NodeType_TEXT_NODE; xChild = xChild->getNextSibling() ) ; // empty loop; only find first text node child // create text node, if none is found if( ! xChild.is() ) { xChild = Reference( xNode->getOwnerDocument()->createTextNode( OUString() ), UNO_QUERY_THROW ); xNode->appendChild( xChild ); } xNode = xChild; OSL_ENSURE( xNode.is() && xNode->getNodeType() == NodeType_TEXT_NODE, "text node creation failed?" ); } // no break; continue as with text node: case NodeType_TEXT_NODE: case NodeType_ATTRIBUTE_NODE: { // set the node value (defer notifications) if( xNode->getNodeValue() != sValue ) { deferNotifications( true ); xNode->setNodeValue( sValue ); deferNotifications( false ); } bRet = true; } break; default: { OSL_FAIL( "bound to unknown node type?" ); } break; } } return bRet; } void Model::loadInstance( sal_Int32 nInstance ) { Sequence aSequence = mpInstances->getItem( nInstance ); // find URL from instance OUString sURL; bool bOnce = false; getInstanceData( aSequence, NULL, NULL, &sURL, &bOnce ); // if we have a URL, load the document and set it into the instance if( !sURL.isEmpty() ) { try { Reference xInput = Reference( SimpleFileAccess::create( ::comphelper::getProcessComponentContext() ) )->openFileRead( sURL ); if( xInput.is() ) { Reference xInstance = getDocumentBuilder()->parse( xInput ); if( xInstance.is() ) { OUString sEmpty; setInstanceData( aSequence, NULL, &xInstance, bOnce ? &sEmpty : &sURL, NULL); mpInstances->setItem( nInstance, aSequence ); } } } catch( const Exception& ) { // couldn't load the instance -> ignore! } } } void Model::loadInstances() { // iterate over instance array to get PropertyValue-Sequence const sal_Int32 nInstances = mpInstances->countItems(); for( sal_Int32 nInstance = 0; nInstance < nInstances; nInstance++ ) { loadInstance( nInstance ); } } bool Model::isInitialized() const { return mbInitialized; } bool Model::isValid() const { bool bValid = true; sal_Int32 nCount = mpBindings->countItems(); for( sal_Int32 i = 0; bValid && i < nCount; i++ ) { Binding* pBind = Binding::getBinding( mpBindings->Collection::getItem( i ) ); OSL_ENSURE( pBind != NULL, "binding?" ); bValid = pBind->isValid(); } return bValid; } // // implement xforms::XModel // OUString Model::getID() throw( RuntimeException ) { DBG_INVARIANT(); return msID; } void Model::setID( const OUString& sID ) throw( RuntimeException ) { DBG_INVARIANT(); msID = sID; } void Model::initialize() throw( RuntimeException ) { DBG_ASSERT( ! mbInitialized, "model already initialized" ); // load instances loadInstances(); // let's pretend we're initialized and rebind all bindings mbInitialized = true; rebind(); } void Model::rebuild() throw( RuntimeException ) { if( ! mbInitialized ) initialize(); else rebind(); } void Model::recalculate() throw( RuntimeException ) { rebind(); } void Model::revalidate() throw( RuntimeException ) { // do nothing. We don't validate anyways! } void Model::refresh() throw( RuntimeException ) { rebind(); } void SAL_CALL Model::submitWithInteraction( const OUString& sID, const XInteractionHandler_t& _rxHandler ) throw( VetoException, WrappedTargetException, RuntimeException ) { DBG_INVARIANT(); if( mpSubmissions->hasItem( sID ) ) { Submission* pSubmission = Submission::getSubmission( mpSubmissions->getItem( sID ) ); OSL_ENSURE( pSubmission != NULL, "no submission?" ); OSL_ENSURE( pSubmission->getModel() == Reference( this ), "wrong model" ); // submit. All exceptions are allowed to leave. pSubmission->submitWithInteraction( _rxHandler ); } } void Model::submit( const OUString& sID ) throw( VetoException, WrappedTargetException, RuntimeException ) { submitWithInteraction( sID, NULL ); } Model::XDataTypeRepository_t SAL_CALL Model::getDataTypeRepository( ) throw( RuntimeException ) { if ( !mxDataTypes.is() ) mxDataTypes = new ODataTypeRepository; return mxDataTypes; } // // instance management // Model::XSet_t Model::getInstances() throw( RuntimeException ) { return mxInstances; } Model::XDocument_t Model::getInstanceDocument( const OUString& rName ) throw( RuntimeException ) { ensureAtLeastOneInstance(); Reference aInstance; sal_Int32 nInstance = lcl_findInstance( mpInstances, rName ); if( nInstance != -1 ) getInstanceData( mpInstances->getItem( nInstance ), NULL, &aInstance, NULL, NULL ); return aInstance; } Model::XDocument_t SAL_CALL Model::getDefaultInstance() throw( RuntimeException ) { ensureAtLeastOneInstance(); DBG_ASSERT( mpInstances->countItems() > 0, "no instance?" ); Reference aInstance; getInstanceData( mpInstances->getItem( 0 ), NULL, &aInstance, NULL, NULL ); return aInstance; } // // bindings management // Model::XPropertySet_t SAL_CALL Model::createBinding() throw( RuntimeException ) { DBG_INVARIANT(); return new Binding(); } Model::XPropertySet_t Model::cloneBinding( const XPropertySet_t& xBinding ) throw( RuntimeException ) { DBG_INVARIANT(); XPropertySet_t xNewBinding = createBinding(); copy( xBinding, xNewBinding ); return xNewBinding; } Model::XPropertySet_t Model::getBinding( const OUString& sId ) throw( RuntimeException ) { DBG_INVARIANT(); return mpBindings->hasItem( sId ) ? mpBindings->getItem( sId ) : NULL; } Model::XSet_t Model::getBindings() throw( RuntimeException ) { DBG_INVARIANT(); return mxBindings; } // // submission management // Model::XSubmission_t Model::createSubmission() throw( RuntimeException ) { DBG_INVARIANT(); return new Submission(); } Model::XSubmission_t Model::cloneSubmission(const XPropertySet_t& xSubmission) throw( RuntimeException ) { DBG_INVARIANT(); XSubmission_t xNewSubmission = createSubmission(); XPropertySet_t xAsPropertySet( xNewSubmission.get() ); copy( xSubmission.get(), xAsPropertySet ); return xNewSubmission; } Model::XSubmission_t Model::getSubmission( const OUString& sId ) throw( RuntimeException ) { DBG_INVARIANT(); XSubmission_t xSubmission; if ( mpSubmissions->hasItem( sId ) ) xSubmission = xSubmission.query( mpSubmissions->getItem( sId ) ); return xSubmission; } Model::XSet_t Model::getSubmissions() throw( RuntimeException ) { DBG_INVARIANT(); return mxSubmissions; } // // implement XPropertySet & friends // #define HANDLE_ID 0 #define HANDLE_ForeignSchema 3 #define HANDLE_SchemaRef 4 #define HANDLE_Namespaces 5 #define HANDLE_ExternalData 6 #define REGISTER_PROPERTY( property, type ) \ registerProperty( PROPERTY( property, type ), \ new DirectPropertyAccessor< Model, type >( this, &Model::set##property, &Model::get##property ) ); #define REGISTER_PROPERTY_API( property, type ) \ registerProperty( PROPERTY( property, type ), \ new APIPropertyAccessor< Model, type >( this, &Model::set##property, &Model::get##property ) ); #define REGISTER_BOOL_PROPERTY( property ) \ registerProperty( PROPERTY( property, sal_Bool ), \ new BooleanPropertyAccessor< Model, bool >( this, &Model::set##property, &Model::get##property ) ); void Model::initializePropertySet() { REGISTER_PROPERTY_API ( ID, OUString ); REGISTER_PROPERTY ( ForeignSchema, XDocument_t ); REGISTER_PROPERTY ( SchemaRef, OUString ); REGISTER_PROPERTY ( Namespaces, XNameContainer_t ); REGISTER_BOOL_PROPERTY( ExternalData ); } void Model::update() throw( RuntimeException ) { rebuild(); } sal_Int64 Model::getSomething( const IntSequence_t& xId ) throw( RuntimeException ) { return reinterpret_cast( ( xId == getUnoTunnelID() ) ? this : NULL ); } Sequence Model::getImplementationId() throw( RuntimeException ) { return getUnoTunnelID(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */