diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /configmgr/source | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
66 files changed, 14516 insertions, 0 deletions
diff --git a/configmgr/source/access.cxx b/configmgr/source/access.cxx new file mode 100644 index 000000000..865d38300 --- /dev/null +++ b/configmgr/source/access.cxx @@ -0,0 +1,2212 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <cstdlib> +#include <utility> +#include <vector> + +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyChangeEvent.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/beans/XExactName.hpp> +#include <com/sun/star/beans/XHierarchicalPropertySet.hpp> +#include <com/sun/star/beans/XHierarchicalPropertySetInfo.hpp> +#include <com/sun/star/beans/XMultiHierarchicalPropertySet.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XPropertiesChangeListener.hpp> +#include <com/sun/star/beans/XProperty.hpp> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/XVetoableChangeListener.hpp> +#include <com/sun/star/container/ContainerEvent.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <com/sun/star/container/XElementAccess.hpp> +#include <com/sun/star/container/XHierarchicalName.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XHierarchicalNameReplace.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/XWeak.hpp> +#include <com/sun/star/util/ElementChange.hpp> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/lok.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <cppu/unotype.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/interlck.h> +#include <osl/mutex.hxx> +#include <rtl/character.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> + +#include "access.hxx" +#include "broadcaster.hxx" +#include "childaccess.hxx" +#include "components.hxx" +#include "data.hxx" +#include "groupnode.hxx" +#include "localizedpropertynode.hxx" +#include "localizedvaluenode.hxx" +#include "lock.hxx" +#include "modifications.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "propertynode.hxx" +#include "rootaccess.hxx" +#include "setnode.hxx" +#include "type.hxx" + +namespace configmgr { + +namespace { + +// Conservatively forbid what is either not an XML Char (including lone +// surrogates, even though they should not appear in well-formed UNO OUString +// instances anyway), or is a slash (as it causes problems in path syntax): +bool isValidName(OUString const & name, bool setMember) { + for (sal_Int32 i = 0; i != name.getLength();) { + sal_uInt32 c = name.iterateCodePoints(&i); + if ((c < 0x20 && !(c == 0x09 || c == 0x0A || c == 0x0D)) + || rtl::isSurrogate(c) || c == 0xFFFE || c == 0xFFFF + || (!setMember && c == '/')) + { + return false; + } + } + return !name.isEmpty(); +} + +} + +oslInterlockedCount Access::acquireCounting() { + return osl_atomic_increment(&m_refCount); +} + +void Access::releaseNondeleting() { + osl_atomic_decrement(&m_refCount); +} + +bool Access::isValue() { + rtl::Reference< Node > p(getNode()); + switch (p->kind()) { + case Node::KIND_PROPERTY: + case Node::KIND_LOCALIZED_VALUE: + return true; + case Node::KIND_LOCALIZED_PROPERTY: + return !Components::allLocales(getRootAccess()->getLocale()); + default: + return false; + } +} + +void Access::markChildAsModified(rtl::Reference< ChildAccess > const & child) { + assert(child.is() && child->getParentAccess() == this); + modifiedChildren_[child->getNameInternal()] = ModifiedChild(child, true); + for (rtl::Reference< Access > p(this);;) { + rtl::Reference< Access > parent(p->getParentAccess()); + if (!parent.is()) { + break; + } + assert(dynamic_cast< ChildAccess * >(p.get()) != nullptr); + parent->modifiedChildren_.emplace( + p->getNameInternal(), + ModifiedChild(static_cast< ChildAccess * >(p.get()), false)); + p = parent; + } +} + +void Access::releaseChild(OUString const & name) { + cachedChildren_.erase(name); +} + +void Access::initBroadcaster( + Modifications::Node const & modifications, Broadcaster * broadcaster) +{ + initBroadcasterAndChanges(modifications, broadcaster, nullptr); +} + +css::uno::Sequence< css::uno::Type > Access::getTypes() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + std::vector< css::uno::Type > types { cppu::UnoType< css::uno::XInterface >::get(), + cppu::UnoType< css::uno::XWeak >::get(), + cppu::UnoType< css::lang::XTypeProvider >::get(), + cppu::UnoType< css::lang::XServiceInfo >::get(), + cppu::UnoType< css::lang::XComponent >::get(), + cppu::UnoType< css::container::XContainer >::get(), + cppu::UnoType< css::beans::XExactName >::get(), + cppu::UnoType< css::container::XHierarchicalName >::get(), + cppu::UnoType< css::container::XNamed >::get(), + cppu::UnoType< css::beans::XProperty >::get(), + cppu::UnoType< css::container::XElementAccess >::get(), + cppu::UnoType< css::container::XNameAccess >::get() + }; + if (getNode()->kind() == Node::KIND_GROUP) { + types.push_back(cppu::UnoType< css::beans::XPropertySetInfo >::get()); + types.push_back(cppu::UnoType< css::beans::XPropertySet >::get()); + types.push_back(cppu::UnoType< css::beans::XMultiPropertySet >::get()); + types.push_back( + cppu::UnoType< css::beans::XHierarchicalPropertySet >::get()); + types.push_back( + cppu::UnoType< css::beans::XMultiHierarchicalPropertySet >::get()); + types.push_back( + cppu::UnoType< css::beans::XHierarchicalPropertySetInfo >::get()); + } + if (getRootAccess()->isUpdate()) { + types.push_back(cppu::UnoType< css::container::XNameReplace >::get()); + types.push_back( + cppu::UnoType< css::container::XHierarchicalNameReplace >::get()); + if (getNode()->kind() != Node::KIND_GROUP || + static_cast< GroupNode * >(getNode().get())->isExtensible()) + { + types.push_back( + cppu::UnoType< css::container::XNameContainer >::get()); + } + if (getNode()->kind() == Node::KIND_SET) { + types.push_back( + cppu::UnoType< css::lang::XSingleServiceFactory >::get()); + } + } else { + types.push_back( + cppu::UnoType< css::container::XHierarchicalNameAccess >::get()); + } + addTypes(&types); + return comphelper::containerToSequence(types); +} + +css::uno::Sequence< sal_Int8 > Access::getImplementationId() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return css::uno::Sequence< sal_Int8 >(); +} + +OUString Access::getImplementationName() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return "org.openoffice-configmgr::Access"; +} + +sal_Bool Access::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > Access::getSupportedServiceNames() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + std::vector<OUString> services; + services.emplace_back("com.sun.star.configuration.ConfigurationAccess"); + if (getRootAccess()->isUpdate()) { + services.emplace_back("com.sun.star.configuration.ConfigurationUpdateAccess"); + } + services.emplace_back("com.sun.star.configuration.HierarchyAccess"); + services.emplace_back("com.sun.star.configuration.HierarchyElement"); + if (getNode()->kind() == Node::KIND_GROUP) { + services.emplace_back("com.sun.star.configuration.GroupAccess"); + services.emplace_back("com.sun.star.configuration.PropertyHierarchy"); + if (getRootAccess()->isUpdate()) { + services.emplace_back("com.sun.star.configuration.GroupUpdate"); + } + } else { + services.emplace_back("com.sun.star.configuration.SetAccess"); + services.emplace_back("com.sun.star.configuration.SimpleSetAccess"); + if (getRootAccess()->isUpdate()) { + services.emplace_back("com.sun.star.configuration.SetUpdate"); + services.emplace_back("com.sun.star.configuration.SimpleSetUpdate"); + } + } + addSupportedServiceNames(&services); + return comphelper::containerToSequence(services); +} + +void Access::dispose() { + assert(thisIs(IS_ANY)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + if (getParentAccess().is()) { + throw css::uno::RuntimeException( + "configmgr dispose inappropriate Access", + static_cast< cppu::OWeakObject * >(this)); + } + if (disposed_) { + return; + } + initDisposeBroadcaster(&bc); + clearListeners(); + disposed_ = true; + } + bc.send(); +} + +void Access::addEventListener( + css::uno::Reference< css::lang::XEventListener > const & xListener) +{ + assert(thisIs(IS_ANY)); + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + if (!xListener.is()) { + throw css::uno::RuntimeException( + "null listener", static_cast< cppu::OWeakObject * >(this)); + } + if (!disposed_) { + disposeListeners_.insert(xListener); + return; + } + } + try { + xListener->disposing( + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } catch (css::lang::DisposedException &) {} +} + +void Access::removeEventListener( + css::uno::Reference< css::lang::XEventListener > const & aListener) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + DisposeListeners::iterator i(disposeListeners_.find(aListener)); + if (i != disposeListeners_.end()) { + disposeListeners_.erase(i); + } +} + +css::uno::Type Access::getElementType() { + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + rtl::Reference< Node > p(getNode()); + switch (p->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + return mapType( + static_cast< LocalizedPropertyNode * >(p.get())->getStaticType()); + case Node::KIND_GROUP: + //TODO: Should a specific type be returned for a non-extensible group + // with homogeneous members or for an extensible group that currently + // has only homogeneous members? + return cppu::UnoType<void>::get(); + case Node::KIND_SET: + return cppu::UnoType<void>::get(); //TODO: correct? + default: + assert(false); + throw css::uno::RuntimeException( + "this cannot happen", static_cast< cppu::OWeakObject * >(this)); + } +} + +sal_Bool Access::hasElements() { + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return !getAllChildren().empty(); //TODO: optimize +} + +bool Access::getByNameFast(const OUString & name, css::uno::Any & value) +{ + bool bGotValue = false; + rtl::Reference< ChildAccess > child; + + if (getNode()->kind() != Node::KIND_LOCALIZED_PROPERTY) + { // try to get it directly + ModifiedChildren::iterator i(modifiedChildren_.find(name)); + if (i != modifiedChildren_.end()) + { + child = getModifiedChild(i); + if (child.is()) + { + value = child->asValue(); + bGotValue = true; + } + } + else + { + rtl::Reference< Node > node(getNode()->getMember(name)); + if (!node.is()) + return false; + bGotValue = ChildAccess::asSimpleValue(node, value, components_); + } + } + + if (!bGotValue) + { + child = getChild(name); + if (!child.is()) + return false; + value = child->asValue(); + } + return true; +} + +css::uno::Any Access::getByName(OUString const & aName) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + css::uno::Any value; + if (!getByNameFast(aName, value)) + throw css::container::NoSuchElementException( + aName, static_cast< cppu::OWeakObject * >(this)); + return value; +} + +css::uno::Sequence< OUString > Access::getElementNames() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + std::vector< rtl::Reference< ChildAccess > > children(getAllChildren()); + css::uno::Sequence<OUString> names(children.size()); + OUString* pArray = names.getArray(); + for (auto const& child : children) + { + *pArray++ = child->getNameInternal(); + } + return names; +} + +sal_Bool Access::hasByName(OUString const & aName) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return getChild(aName).is(); +} + +css::uno::Any Access::getByHierarchicalName(OUString const & aName) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + rtl::Reference< ChildAccess > child(getSubChild(aName)); + if (!child.is()) { + throw css::container::NoSuchElementException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + return child->asValue(); +} + +sal_Bool Access::hasByHierarchicalName(OUString const & aName) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return getSubChild(aName).is(); +} + +void Access::replaceByHierarchicalName( + OUString const & aName, css::uno::Any const & aElement) +{ + //TODO: Actually support sets and combine with replaceByName: + assert(thisIs(IS_UPDATE)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + rtl::Reference< ChildAccess > child(getSubChild(aName)); + if (!child.is()) { + throw css::container::NoSuchElementException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + child->checkFinalized(); + rtl::Reference< Node > parent(child->getParentNode()); + assert(parent.is()); + Modifications localMods; + switch (parent->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + child->setProperty(aElement, &localMods); + break; + case Node::KIND_SET: + throw css::lang::IllegalArgumentException( + ("configmgr::Access::replaceByHierarchicalName does not" + " currently support set members"), + static_cast< cppu::OWeakObject * >(this), 0); + case Node::KIND_ROOT: + throw css::lang::IllegalArgumentException( + ("configmgr::Access::replaceByHierarchicalName does not allow" + " changing component " + aName), + static_cast< cppu::OWeakObject * >(this), 0); + default: + assert(false); // this cannot happen + break; + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +void Access::addContainerListener( + css::uno::Reference< css::container::XContainerListener > const & xListener) +{ + assert(thisIs(IS_ANY)); + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + if (!xListener.is()) { + throw css::uno::RuntimeException( + "null listener", static_cast< cppu::OWeakObject * >(this)); + } + if (!disposed_) { + containerListeners_.insert(xListener); + return; + } + } + try { + xListener->disposing( + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } catch (css::lang::DisposedException &) {} +} + +void Access::removeContainerListener( + css::uno::Reference< css::container::XContainerListener > const & xListener) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + ContainerListeners::iterator i(containerListeners_.find(xListener)); + if (i != containerListeners_.end()) { + containerListeners_.erase(i); + } +} + +OUString Access::getExactName(OUString const & aApproximateName) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return aApproximateName; +} + +css::uno::Sequence< css::beans::Property > Access::getProperties() +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + std::vector< rtl::Reference< ChildAccess > > children(getAllChildren()); + std::vector< css::beans::Property > properties; + properties.reserve(children.size()); + for (auto const& child : children) + { + properties.push_back(child->asProperty()); + } + return comphelper::containerToSequence(properties); +} + +css::beans::Property Access::getPropertyByName(OUString const & aName) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + rtl::Reference< ChildAccess > child(getChild(aName)); + if (!child.is()) { + throw css::beans::UnknownPropertyException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + return child->asProperty(); +} + +sal_Bool Access::hasPropertyByName(OUString const & Name) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + return getChild(Name).is(); +} + +OUString Access::getHierarchicalName() { + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + // For backwards compatibility, return an absolute path representation where + // available: + OUString rootPath; + rtl::Reference< RootAccess > root(getRootAccess()); + if (root.is()) { + rootPath = root->getAbsolutePathRepresentation(); + } + OUString rel(getRelativePathRepresentation()); + OUStringBuffer path(rootPath); + if (!rootPath.isEmpty() && rootPath != "/" && !rel.isEmpty()) { + path.append('/'); + } + path.append(rel); + return path.makeStringAndClear(); +} + +OUString Access::composeHierarchicalName( + OUString const & aRelativeName) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + if (aRelativeName.isEmpty() || aRelativeName[0] == '/') { + throw css::lang::IllegalArgumentException( + "configmgr composeHierarchicalName inappropriate relative name", + static_cast< cppu::OWeakObject * >(this), -1); + } + OUStringBuffer path(getRelativePathRepresentation()); + if (!path.isEmpty()) { + path.append('/'); + } + path.append(aRelativeName); + return path.makeStringAndClear(); +} + +OUString Access::getName() { + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return getNameInternal(); +} + +void Access::setName(OUString const & aName) +{ + assert(thisIs(IS_ANY)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + checkFinalized(); + Modifications localMods; + switch (getNode()->kind()) { + case Node::KIND_GROUP: + case Node::KIND_SET: + { + rtl::Reference< Access > parent(getParentAccess()); + if (parent.is()) { + rtl::Reference< Node > node(getNode()); + if (! node->getTemplateName().isEmpty()) { + rtl::Reference< ChildAccess > other( + parent->getChild(aName)); + if (other.get() == this) { + break; + } + if (node->getMandatory() == Data::NO_LAYER && + !(other.is() && other->isFinalized())) + { + if (!isValidName(aName, true)) { + throw css::uno::RuntimeException( + "invalid element name " + aName); + } + rtl::Reference< RootAccess > root(getRootAccess()); + rtl::Reference< ChildAccess > childAccess( + static_cast< ChildAccess * >(this)); + localMods.add(getRelativePath()); + // unbind() modifies the parent chain that + // markChildAsModified() walks, so order is + // important: + parent->markChildAsModified(childAccess); + //TODO: must not throw + childAccess->unbind(); // must not throw + if (other.is()) { + other->unbind(); // must not throw + } + childAccess->bind(root, parent, aName); + // must not throw + parent->markChildAsModified(childAccess); + //TODO: must not throw + localMods.add(getRelativePath()); + break; + } + } + } + } + [[fallthrough]]; + case Node::KIND_LOCALIZED_PROPERTY: + // renaming a property could only work for an extension property, + // but a localized property is never an extension property + throw css::uno::RuntimeException( + "configmgr setName inappropriate node", + static_cast< cppu::OWeakObject * >(this)); + default: + assert(false); // this cannot happen + break; + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +css::beans::Property Access::getAsProperty() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return asProperty(); +} + +css::uno::Reference< css::beans::XPropertySetInfo > Access::getPropertySetInfo() +{ + assert(thisIs(IS_GROUP)); + return this; +} + +void Access::setPropertyValue( + OUString const & aPropertyName, css::uno::Any const & aValue) +{ + assert(thisIs(IS_GROUP)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + if (!getRootAccess()->isUpdate()) { + throw css::uno::RuntimeException( + "configmgr setPropertyValue on non-update access", + static_cast< cppu::OWeakObject * >(this)); + } + Modifications localMods; + if (!setChildProperty(aPropertyName, aValue, &localMods)) { + throw css::beans::UnknownPropertyException( + aPropertyName, static_cast< cppu::OWeakObject * >(this)); + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +css::uno::Any Access::getPropertyValue(OUString const & PropertyName) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + + css::uno::Any value; + if (!getByNameFast(PropertyName, value)) + throw css::beans::UnknownPropertyException( + PropertyName, static_cast< cppu::OWeakObject * >(this)); + return value; +} + +void Access::addPropertyChangeListener( + OUString const & aPropertyName, + css::uno::Reference< css::beans::XPropertyChangeListener > const & + xListener) +{ + assert(thisIs(IS_GROUP)); + { + osl::MutexGuard g(*lock_); + if (!xListener.is()) { + throw css::uno::RuntimeException( + "null listener", static_cast< cppu::OWeakObject * >(this)); + } + checkKnownProperty(aPropertyName); + if (!disposed_) { + propertyChangeListeners_[aPropertyName].insert(xListener); + return; + } + } + try { + xListener->disposing( + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } catch (css::lang::DisposedException &) {} +} + +void Access::removePropertyChangeListener( + OUString const & aPropertyName, + css::uno::Reference< css::beans::XPropertyChangeListener > const & + aListener) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + checkKnownProperty(aPropertyName); + PropertyChangeListeners::iterator i( + propertyChangeListeners_.find(aPropertyName)); + if (i != propertyChangeListeners_.end()) { + PropertyChangeListenersElement::iterator j(i->second.find(aListener)); + if (j != i->second.end()) { + i->second.erase(j); + if (i->second.empty()) { + propertyChangeListeners_.erase(i); + } + } + } +} + +void Access::addVetoableChangeListener( + OUString const & PropertyName, + css::uno::Reference< css::beans::XVetoableChangeListener > const & + aListener) +{ + assert(thisIs(IS_GROUP)); + { + osl::MutexGuard g(*lock_); + if (!aListener.is()) { + throw css::uno::RuntimeException( + "null listener", static_cast< cppu::OWeakObject * >(this)); + } + checkKnownProperty(PropertyName); + if (!disposed_) { + vetoableChangeListeners_[PropertyName].insert(aListener); + //TODO: actually call vetoableChangeListeners_ + return; + } + } + try { + aListener->disposing( + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } catch (css::lang::DisposedException &) {} +} + +void Access::removeVetoableChangeListener( + OUString const & PropertyName, + css::uno::Reference< css::beans::XVetoableChangeListener > const & + aListener) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + checkKnownProperty(PropertyName); + VetoableChangeListeners::iterator i( + vetoableChangeListeners_.find(PropertyName)); + if (i != vetoableChangeListeners_.end()) { + VetoableChangeListenersElement::iterator j(i->second.find(aListener)); + if (j != i->second.end()) { + i->second.erase(j); + if (i->second.empty()) { + vetoableChangeListeners_.erase(i); + } + } + } +} + +void Access::setPropertyValues( + css::uno::Sequence< OUString > const & aPropertyNames, + css::uno::Sequence< css::uno::Any > const & aValues) +{ + assert(thisIs(IS_GROUP)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + if (!getRootAccess()->isUpdate()) { + throw css::uno::RuntimeException( + "configmgr setPropertyValues on non-update access", + static_cast< cppu::OWeakObject * >(this)); + } + if (aPropertyNames.getLength() != aValues.getLength()) { + throw css::lang::IllegalArgumentException( + ("configmgr setPropertyValues: aPropertyNames/aValues of" + " different length"), + static_cast< cppu::OWeakObject * >(this), -1); + } + Modifications localMods; + for (sal_Int32 i = 0; i < aPropertyNames.getLength(); ++i) { + if (!setChildProperty(aPropertyNames[i], aValues[i], &localMods)) { + throw css::lang::IllegalArgumentException( + "configmgr setPropertyValues inappropriate property name", + static_cast< cppu::OWeakObject * >(this), -1); + } + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +css::uno::Sequence< css::uno::Any > Access::getPropertyValues( + css::uno::Sequence< OUString > const & aPropertyNames) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + css::uno::Sequence< css::uno::Any > vals(aPropertyNames.getLength()); + auto aValsRange = asNonConstRange(vals); + for (sal_Int32 i = 0; i < aPropertyNames.getLength(); ++i) + { + if (!getByNameFast(aPropertyNames[i], aValsRange[i])) + throw css::uno::RuntimeException( + "configmgr getPropertyValues inappropriate property name", + static_cast< cppu::OWeakObject * >(this)); + } + + return vals; +} + +void Access::addPropertiesChangeListener( + css::uno::Sequence< OUString > const &, + css::uno::Reference< css::beans::XPropertiesChangeListener > const & + xListener) +{ + assert(thisIs(IS_GROUP)); + { + osl::MutexGuard g(*lock_); + if (!xListener.is()) { + throw css::uno::RuntimeException( + "null listener", static_cast< cppu::OWeakObject * >(this)); + } + if (!disposed_) { + propertiesChangeListeners_.insert(xListener); + return; + } + } + try { + xListener->disposing( + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } catch (css::lang::DisposedException &) {} +} + +void Access::removePropertiesChangeListener( + css::uno::Reference< css::beans::XPropertiesChangeListener > const & + xListener) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + PropertiesChangeListeners::iterator i( + propertiesChangeListeners_.find(xListener)); + if (i != propertiesChangeListeners_.end()) { + propertiesChangeListeners_.erase(i); + } +} + +void Access::firePropertiesChangeEvent( + css::uno::Sequence< OUString > const & aPropertyNames, + css::uno::Reference< css::beans::XPropertiesChangeListener > const & + xListener) +{ + assert(thisIs(IS_GROUP)); + css::uno::Sequence< css::beans::PropertyChangeEvent > events( + aPropertyNames.getLength()); + auto aEventsRange = asNonConstRange(events); + for (sal_Int32 i = 0; i < events.getLength(); ++i) { + aEventsRange[i].Source = static_cast< cppu::OWeakObject * >(this); + aEventsRange[i].PropertyName = aPropertyNames[i]; + aEventsRange[i].Further = false; + aEventsRange[i].PropertyHandle = -1; + } + xListener->propertiesChange(events); +} + +css::uno::Reference< css::beans::XHierarchicalPropertySetInfo > +Access::getHierarchicalPropertySetInfo() { + assert(thisIs(IS_GROUP)); + return this; +} + +void Access::setHierarchicalPropertyValue( + OUString const & aHierarchicalPropertyName, + css::uno::Any const & aValue) +{ + assert(thisIs(IS_GROUP)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + if (!getRootAccess()->isUpdate()) { + throw css::uno::RuntimeException( + "configmgr setHierarchicalPropertyName on non-update access", + static_cast< cppu::OWeakObject * >(this)); + } + rtl::Reference< ChildAccess > child( + getSubChild(aHierarchicalPropertyName)); + if (!child.is()) { + throw css::beans::UnknownPropertyException( + aHierarchicalPropertyName, + static_cast< cppu::OWeakObject * >(this)); + } + child->checkFinalized(); + Modifications localMods; + child->setProperty(aValue, &localMods); + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +css::uno::Any Access::getHierarchicalPropertyValue( + OUString const & aHierarchicalPropertyName) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + rtl::Reference< ChildAccess > child(getSubChild(aHierarchicalPropertyName)); + if (!child.is()) { + throw css::beans::UnknownPropertyException( + aHierarchicalPropertyName, + static_cast< cppu::OWeakObject * >(this)); + } + return child->asValue(); +} + +void Access::setHierarchicalPropertyValues( + css::uno::Sequence< OUString > const & aHierarchicalPropertyNames, + css::uno::Sequence< css::uno::Any > const & Values) +{ + assert(thisIs(IS_GROUP)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + if (!getRootAccess()->isUpdate()) { + throw css::uno::RuntimeException( + "configmgr setPropertyValues on non-update access", + static_cast< cppu::OWeakObject * >(this)); + } + if (aHierarchicalPropertyNames.getLength() != Values.getLength()) { + throw css::lang::IllegalArgumentException( + ("configmgr setHierarchicalPropertyValues:" + " aHierarchicalPropertyNames/Values of different length"), + static_cast< cppu::OWeakObject * >(this), -1); + } + Modifications localMods; + for (sal_Int32 i = 0; i < aHierarchicalPropertyNames.getLength(); ++i) { + rtl::Reference< ChildAccess > child( + getSubChild(aHierarchicalPropertyNames[i])); + if (!child.is()) { + throw css::lang::IllegalArgumentException( + ("configmgr setHierarchicalPropertyValues inappropriate" + " property name"), + static_cast< cppu::OWeakObject * >(this), -1); + } + child->checkFinalized(); + child->setProperty(Values[i], &localMods); + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +css::uno::Sequence< css::uno::Any > Access::getHierarchicalPropertyValues( + css::uno::Sequence< OUString > const & aHierarchicalPropertyNames) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + css::uno::Sequence< css::uno::Any > vals( + aHierarchicalPropertyNames.getLength()); + auto aValsRange = asNonConstRange(vals); + for (sal_Int32 i = 0; i < aHierarchicalPropertyNames.getLength(); ++i) { + rtl::Reference< ChildAccess > child( + getSubChild(aHierarchicalPropertyNames[i])); + if (!child.is()) { + throw css::lang::IllegalArgumentException( + ("configmgr getHierarchicalPropertyValues inappropriate" + " hierarchical property name"), + static_cast< cppu::OWeakObject * >(this), -1); + } + aValsRange[i] = child->asValue(); + } + return vals; +} + +css::beans::Property Access::getPropertyByHierarchicalName( + OUString const & aHierarchicalName) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + rtl::Reference< ChildAccess > child(getSubChild(aHierarchicalName)); + if (!child.is()) { + throw css::beans::UnknownPropertyException( + aHierarchicalName, static_cast< cppu::OWeakObject * >(this)); + } + return child->asProperty(); +} + +sal_Bool Access::hasPropertyByHierarchicalName( + OUString const & aHierarchicalName) +{ + assert(thisIs(IS_GROUP)); + osl::MutexGuard g(*lock_); + return getSubChild(aHierarchicalName).is(); +} + +void Access::replaceByName( + OUString const & aName, css::uno::Any const & aElement) +{ + assert(thisIs(IS_UPDATE)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + rtl::Reference< ChildAccess > child(getChild(aName)); + if (!child.is()) { + throw css::container::NoSuchElementException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + child->checkFinalized(); + Modifications localMods; + switch (getNode()->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + child->setProperty(aElement, &localMods); + break; + case Node::KIND_SET: + { + rtl::Reference< ChildAccess > freeAcc( + getFreeSetMember(aElement)); + rtl::Reference< RootAccess > root(getRootAccess()); + localMods.add(child->getRelativePath()); + child->unbind(); // must not throw + freeAcc->bind(root, this, aName); // must not throw + markChildAsModified(freeAcc); //TODO: must not throw + } + break; + default: + assert(false); // this cannot happen + break; + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +void Access::insertByName( + OUString const & aName, css::uno::Any const & aElement) +{ + assert(thisIs(IS_EXTENSIBLE|IS_UPDATE)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + checkFinalized(); + if (getChild(aName).is()) { + throw css::container::ElementExistException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + Modifications localMods; + switch (getNode()->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + if (!isValidName(aName, false)) { + throw css::lang::IllegalArgumentException( + aName, static_cast<cppu::OWeakObject *>(this), 0); + } + insertLocalizedValueChild(aName, aElement, &localMods); + break; + case Node::KIND_GROUP: + { + if (!isValidName(aName, false)) { + throw css::lang::IllegalArgumentException( + aName, static_cast<cppu::OWeakObject *>(this), 0); + } + checkValue(aElement, TYPE_ANY, true); + rtl::Reference child( + new ChildAccess( + components_, getRootAccess(), this, aName, + new PropertyNode( + Data::NO_LAYER, TYPE_ANY, true, aElement, true))); + markChildAsModified(child); + localMods.add(child->getRelativePath()); + } + break; + case Node::KIND_SET: + { + if (!isValidName(aName, true)) { + throw css::lang::IllegalArgumentException( + aName, static_cast<cppu::OWeakObject *>(this), 0); + } + rtl::Reference< ChildAccess > freeAcc( + getFreeSetMember(aElement)); + freeAcc->bind(getRootAccess(), this, aName); // must not throw + markChildAsModified(freeAcc); //TODO: must not throw + localMods.add(freeAcc->getRelativePath()); + } + break; + default: + assert(false); // this cannot happen + break; + } + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +void Access::removeByName(OUString const & aName) +{ + assert(thisIs(IS_EXTENSIBLE|IS_UPDATE)); + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + rtl::Reference< ChildAccess > child(getChild(aName)); + if (!child.is() || child->isFinalized() || + child->getNode()->getMandatory() != Data::NO_LAYER) + { + throw css::container::NoSuchElementException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + if (getNode()->kind() == Node::KIND_GROUP) { + rtl::Reference< Node > p(child->getNode()); + if (p->kind() != Node::KIND_PROPERTY || + !static_cast< PropertyNode * >(p.get())->isExtension()) + { + throw css::container::NoSuchElementException( + aName, static_cast< cppu::OWeakObject * >(this)); + } + } + Modifications localMods; + localMods.add(child->getRelativePath()); + // unbind() modifies the parent chain that markChildAsModified() walks, + // so order is important: + markChildAsModified(child); //TODO: must not throw + child->unbind(); + getNotificationRoot()->initBroadcaster(localMods.getRoot(), &bc); + } + bc.send(); +} + +css::uno::Reference< css::uno::XInterface > Access::createInstance() +{ + assert(thisIs(IS_SET|IS_UPDATE)); + OUString tmplName( + static_cast< SetNode * >(getNode().get())->getDefaultTemplateName()); + rtl::Reference< Node > tmpl( + components_.getTemplate(tmplName)); + if (!tmpl.is()) { + throw css::uno::Exception( + "unknown template " + tmplName, + static_cast< cppu::OWeakObject * >(this)); + } + rtl::Reference< Node > node(tmpl->clone(true)); + node->setLayer(Data::NO_LAYER); + return static_cast< cppu::OWeakObject * >( + new ChildAccess(components_, getRootAccess(), node)); +} + +css::uno::Reference< css::uno::XInterface > Access::createInstanceWithArguments( + css::uno::Sequence< css::uno::Any > const & aArguments) +{ + assert(thisIs(IS_SET|IS_UPDATE)); + if (aArguments.hasElements()) { + throw css::uno::Exception( + ("configuration SimpleSetUpdate createInstanceWithArguments" + " must not specify any arguments"), + static_cast< cppu::OWeakObject * >(this)); + } + return createInstance(); +} + +Access::Access(Components & components): + components_(components), disposed_(false), lock_( lock() ) +{ +} + +Access::~Access() {} + +void Access::initDisposeBroadcaster(Broadcaster * broadcaster) { + assert(broadcaster != nullptr); + for (auto const& disposeListener : disposeListeners_) + { + broadcaster->addDisposeNotification( + disposeListener, + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } + for (auto const& containerListener : containerListeners_) + { + broadcaster->addDisposeNotification( + containerListener, + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } + for (auto const& propertyChangeListener : propertyChangeListeners_) + { + for (auto const& propertyChangeListenerElement : propertyChangeListener.second) + { + broadcaster->addDisposeNotification( + propertyChangeListenerElement, + css::lang::EventObject( + static_cast< cppu::OWeakObject * >(this))); + } + } + for (auto const& vetoableChangeListener : vetoableChangeListeners_) + { + for (auto const& vetoableChangeListenerElement : vetoableChangeListener.second) + { + broadcaster->addDisposeNotification( + vetoableChangeListenerElement, + css::lang::EventObject( + static_cast< cppu::OWeakObject * >(this))); + } + } + for (auto const& propertiesChangeListener : propertiesChangeListeners_) + { + broadcaster->addDisposeNotification( + propertiesChangeListener, + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } + //TODO: iterate over children w/ listeners (incl. unmodified ones): + for (ModifiedChildren::iterator i(modifiedChildren_.begin()); + i != modifiedChildren_.end(); ++i) + { + rtl::Reference< ChildAccess > child(getModifiedChild(i)); + if (child.is()) { + child->initDisposeBroadcaster(broadcaster); + } + } +} + +void Access::clearListeners() noexcept { + disposeListeners_.clear(); + containerListeners_.clear(); + propertyChangeListeners_.clear(); + vetoableChangeListeners_.clear(); + propertiesChangeListeners_.clear(); + //TODO: iterate over children w/ listeners (incl. unmodified ones): + for (ModifiedChildren::iterator i(modifiedChildren_.begin()); + i != modifiedChildren_.end(); ++i) + { + rtl::Reference< ChildAccess > child(getModifiedChild(i)); + if (child.is()) { + child->clearListeners(); + } + } +} + +css::uno::Any Access::queryInterface(css::uno::Type const & aType) +{ + css::uno::Any res(OWeakObject::queryInterface(aType)); + if (res.hasValue()) { + return res; + } + res = cppu::queryInterface( + aType, static_cast< css::lang::XTypeProvider * >(this), + static_cast< css::lang::XServiceInfo * >(this), + static_cast< css::lang::XComponent * >(this), + static_cast< css::container::XHierarchicalNameAccess * >(this), + static_cast< css::container::XContainer * >(this), + static_cast< css::beans::XExactName * >(this), + static_cast< css::container::XHierarchicalName * >(this), + static_cast< css::container::XNamed * >(this), + static_cast< css::beans::XProperty * >(this), + static_cast< css::container::XElementAccess * >(this), + static_cast< css::container::XNameAccess * >(this)); + if (res.hasValue()) { + return res; + } + if (getNode()->kind() == Node::KIND_GROUP) { + res = cppu::queryInterface( + aType, static_cast< css::beans::XPropertySetInfo * >(this), + static_cast< css::beans::XPropertySet * >(this), + static_cast< css::beans::XMultiPropertySet * >(this), + static_cast< css::beans::XHierarchicalPropertySet * >(this), + static_cast< css::beans::XMultiHierarchicalPropertySet * >(this), + static_cast< css::beans::XHierarchicalPropertySetInfo * >(this)); + if (res.hasValue()) { + return res; + } + } + if (getRootAccess()->isUpdate()) { + res = cppu::queryInterface( + aType, static_cast< css::container::XNameReplace * >(this), + static_cast< css::container::XHierarchicalNameReplace * >(this)); + if (res.hasValue()) { + return res; + } + if (getNode()->kind() != Node::KIND_GROUP || + static_cast< GroupNode * >(getNode().get())->isExtensible()) + { + res = cppu::queryInterface( + aType, static_cast< css::container::XNameContainer * >(this)); + if (res.hasValue()) { + return res; + } + } + if (getNode()->kind() == Node::KIND_SET) { + res = cppu::queryInterface( + aType, static_cast< css::lang::XSingleServiceFactory * >(this)); + } + } + return res; +} + + +void Access::checkLocalizedPropertyAccess() { + if (getNode()->kind() == Node::KIND_LOCALIZED_PROPERTY && + !Components::allLocales(getRootAccess()->getLocale())) + { + throw css::uno::RuntimeException( + "configmgr Access to specialized LocalizedPropertyNode", + static_cast< cppu::OWeakObject * >(this)); + } +} + +rtl::Reference< Node > Access::getParentNode() { + rtl::Reference< Access > parent(getParentAccess()); + return parent.is() ? parent->getNode() : rtl::Reference< Node >(); +} + +rtl::Reference< ChildAccess > Access::getChild(OUString const & name) { + OUString locale; + if (getNode()->kind() == Node::KIND_LOCALIZED_PROPERTY + && name.startsWith("*", &locale)) + { + if (locale.startsWith("*")) { + SAL_WARN( + "configmgr", + ("access best-matching localized property value via" + " \"*<locale>\" with <locale> \"") + << locale << "\" recursively starting with \"*\""); + return getChild(locale); + } + SAL_WARN_IF( + locale.isEmpty(), "configmgr", + ("access best-matching localized property value via \"*<locale>\"" + " with empty <locale>; falling back to defaults")); + + // Since the locale given to us is the one used at initialization, + // here we override it with the actual current-user's language to + // support per-view localization in LOK. + if (comphelper::LibreOfficeKit::isActive()) + locale = comphelper::LibreOfficeKit::getLanguageTag().getBcp47(); + + if (!locale.isEmpty()) { + // Try exact match first, avoiding all fallback overhead. + rtl::Reference<ChildAccess> directChild(getChild(locale)); + if (directChild.is()) + return directChild; + + // Find the best match using the LanguageTag fallback mechanism, + // excluding the original tag. + std::vector<OUString> aFallbacks = LanguageTag(locale).getFallbackStrings(false); + for (const OUString& rFallback : aFallbacks) + { + rtl::Reference<ChildAccess> child(getChild(rFallback)); + if (child.is()) + return child; + } + + // As a workaround for broken xcu data that does not use shortest + // xml:lang attributes, look for the first entry with the same first + // segment as the requested language tag before falling back to + // defaults (see fdo#33638): + if (aFallbacks.size() > 0) + locale = aFallbacks[aFallbacks.size() - 1]; + assert( + !locale.isEmpty() && locale.indexOf('-') == -1 && + locale.indexOf('_') == -1); + + std::vector< rtl::Reference< ChildAccess > > children( + getAllChildren()); + for (auto const& child : children) + { + const OUString & name2(child->getNameInternal()); + if (name2.startsWith(locale) && + (name2.getLength() == locale.getLength() || + name2[locale.getLength()] == '-' || + name2[locale.getLength()] == '_')) + { + return child; + } + } + } + // Defaults are the "en-US" locale, the "en" locale, the empty string locale, the first child (if + // any, and if the property is non-nillable), or a null ChildAccess, in that order: + rtl::Reference< ChildAccess > child(getChild("en-US")); + if (child.is()) { + return child; + } + child = getChild("en"); + if (child.is()) { + return child; + } + child = getChild(""); + if (child.is()) { + return child; + } + if (!static_cast<LocalizedPropertyNode *>(getNode().get())->isNillable()) { + std::vector< rtl::Reference< ChildAccess > > children(getAllChildren()); + if (!children.empty()) { + return children.front(); + } + } + return rtl::Reference< ChildAccess >(); + } + ModifiedChildren::iterator i(modifiedChildren_.find(name)); + return i == modifiedChildren_.end() + ? getUnmodifiedChild(name) : getModifiedChild(i); +} + +std::vector< rtl::Reference< ChildAccess > > Access::getAllChildren() { + std::vector< rtl::Reference< ChildAccess > > vec; + NodeMap const & members = getNode()->getMembers(); + for (auto const& member : members) + { + if (modifiedChildren_.find(member.first) == modifiedChildren_.end()) { + vec.push_back(getUnmodifiedChild(member.first)); + assert(vec.back().is()); + } + } + for (ModifiedChildren::iterator i(modifiedChildren_.begin()); + i != modifiedChildren_.end(); ++i) + { + rtl::Reference< ChildAccess > child(getModifiedChild(i)); + if (child.is()) { + vec.push_back(child); + } + } + return vec; +} + +void Access::checkValue(css::uno::Any const & value, Type type, bool nillable) { + bool ok; + switch (type) { + case TYPE_ERROR: + ok = false; + break; + case TYPE_ANY: + switch (getDynamicType(value)) { + case TYPE_ERROR: + ok = false; + break; + case TYPE_NIL: + ok = nillable; + break; + default: + ok = true; + break; + case TYPE_ANY: + for (;;) std::abort(); // cannot happen + } + break; + default: + ok = value.hasValue() ? value.isExtractableTo(mapType(type)) : nillable; + break; + case TYPE_NIL: + for (;;) std::abort(); // cannot happen + } + if (!ok) { + throw css::lang::IllegalArgumentException( + "configmgr inappropriate property value", + static_cast< cppu::OWeakObject * >(this), -1); + } +} + +void Access::insertLocalizedValueChild( + OUString const & name, css::uno::Any const & value, + Modifications * localModifications) +{ + assert(localModifications != nullptr); + LocalizedPropertyNode * locprop = static_cast< LocalizedPropertyNode * >( + getNode().get()); + checkValue(value, locprop->getStaticType(), locprop->isNillable()); + rtl::Reference child( + new ChildAccess( + components_, getRootAccess(), this, name, + new LocalizedValueNode(Data::NO_LAYER, value))); + markChildAsModified(child); + localModifications->add(child->getRelativePath()); +} + +void Access::reportChildChanges( + std::vector< css::util::ElementChange > * changes) +{ + assert(changes != nullptr); + for (ModifiedChildren::iterator i(modifiedChildren_.begin()); + i != modifiedChildren_.end(); ++i) + { + rtl::Reference< ChildAccess > child(getModifiedChild(i)); + if (child.is()) { + child->reportChildChanges(changes); + changes->push_back(css::util::ElementChange()); + //TODO: changed value and/or inserted node + } else { + changes->push_back(css::util::ElementChange()); //TODO: removed node + } + } +} + +void Access::commitChildChanges( + bool valid, Modifications * globalModifications) +{ + assert(globalModifications != nullptr); + while (!modifiedChildren_.empty()) { + bool childValid = valid; + ModifiedChildren::iterator i(modifiedChildren_.begin()); + rtl::Reference< ChildAccess > child(getModifiedChild(i)); + if (child.is()) { + childValid = childValid && !child->isFinalized(); + child->commitChanges(childValid, globalModifications); + //TODO: currently, this is called here for directly inserted + // children as well as for children whose sub-children were + // modified (and should never be called for directly removed + // children); clarify what exactly should happen here for + // directly inserted children + } + NodeMap & members = getNode()->getMembers(); + NodeMap::iterator j(members.find(i->first)); + if (child.is()) { + // Inserted: + if (j != members.end()) { + childValid = childValid && + j->second->getFinalized() == Data::NO_LAYER; + if (childValid) { + child->getNode()->setMandatory(j->second->getMandatory()); + } + } + if (childValid) { + members[i->first] = child->getNode(); + } + } else { + // Removed: + childValid = childValid && j != members.end() && + j->second->getFinalized() == Data::NO_LAYER && + j->second->getMandatory() == Data::NO_LAYER; + if (childValid) { + members.erase(j); + } + } + if (childValid && i->second.directlyModified) { + std::vector<OUString> path(getAbsolutePath()); + path.push_back(i->first); + components_.addModification(path); + globalModifications->add(path); + } + i->second.child->committed(); + modifiedChildren_.erase(i); + } +} + +void Access::initBroadcasterAndChanges( + Modifications::Node const & modifications, Broadcaster * broadcaster, + std::vector< css::util::ElementChange > * allChanges) +{ + assert(broadcaster != nullptr); + std::vector< css::beans::PropertyChangeEvent > propChanges; + bool collectPropChanges = !propertiesChangeListeners_.empty(); + for (const auto & i : modifications.children) + { + rtl::Reference< ChildAccess > child(getChild(i.first)); + if (child.is()) { + switch (child->getNode()->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + if (!i.second.children.empty()) { + if (Components::allLocales(getRootAccess()->getLocale())) { + child->initBroadcasterAndChanges( + i.second, broadcaster, allChanges); + //TODO: if allChanges==0, recurse only into children + // w/ listeners + } else { + //TODO: filter child mods that are irrelevant for + // locale: + for (auto const& containerListener : containerListeners_) + { + broadcaster-> + addContainerElementReplacedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >( + this), + css::uno::Any(i.first), + css::uno::Any(), css::uno::Any())); + //TODO: non-void Element, ReplacedElement + } + PropertyChangeListeners::iterator j( + propertyChangeListeners_.find(i.first)); + if (j != propertyChangeListeners_.end()) { + for (auto const& propertyChangeListenerElement : j->second) + { + broadcaster->addPropertyChangeNotification( + propertyChangeListenerElement, + css::beans::PropertyChangeEvent( + static_cast< cppu::OWeakObject * >( + this), + i.first, false, -1, css::uno::Any(), + css::uno::Any())); + } + } + j = propertyChangeListeners_.find(""); + if (j != propertyChangeListeners_.end()) { + for (auto const& propertyChangeListenerElement : j->second) + { + broadcaster->addPropertyChangeNotification( + propertyChangeListenerElement, + css::beans::PropertyChangeEvent( + static_cast< cppu::OWeakObject * >( + this), + i.first, false, -1, css::uno::Any(), + css::uno::Any())); + } + } + if (allChanges != nullptr) { + allChanges->push_back( + css::util::ElementChange( + css::uno::Any( + child->getRelativePathRepresentation()), + css::uno::Any(), css::uno::Any())); + //TODO: non-void Element, ReplacedElement + } + if (collectPropChanges) { + propChanges.emplace_back( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any()); + } + } + } + // else: spurious Modifications::Node not representing a change + break; + case Node::KIND_LOCALIZED_VALUE: + assert(Components::allLocales(getRootAccess()->getLocale())); + for (auto const& containerListener : containerListeners_) + { + broadcaster->addContainerElementReplacedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >(this), + css::uno::Any(i.first), child->asValue(), + css::uno::Any())); + //TODO: distinguish add/modify; non-void ReplacedElement + } + if (allChanges != nullptr) { + allChanges->push_back( + css::util::ElementChange( + css::uno::Any( + child->getRelativePathRepresentation()), + child->asValue(), css::uno::Any())); + //TODO: non-void ReplacedElement + } + assert(!collectPropChanges); + break; + case Node::KIND_PROPERTY: + { + for (auto const& containerListener : containerListeners_) + { + broadcaster->addContainerElementReplacedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >(this), + css::uno::Any(i.first), child->asValue(), + css::uno::Any())); + //TODO: distinguish add/remove/modify; non-void + // ReplacedElement + } + PropertyChangeListeners::iterator j( + propertyChangeListeners_.find(i.first)); + if (j != propertyChangeListeners_.end()) { + for (auto const& propertyChangeListenerElement : j->second) + { + broadcaster->addPropertyChangeNotification( + propertyChangeListenerElement, + css::beans::PropertyChangeEvent( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any())); + } + } + j = propertyChangeListeners_.find(""); + if (j != propertyChangeListeners_.end()) { + for (auto const& propertyChangeListenerElement : j->second) + { + broadcaster->addPropertyChangeNotification( + propertyChangeListenerElement, + css::beans::PropertyChangeEvent( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any())); + } + } + if (allChanges != nullptr) { + allChanges->push_back( + css::util::ElementChange( + css::uno::Any( + child->getRelativePathRepresentation()), + child->asValue(), css::uno::Any())); + //TODO: non-void ReplacedElement + } + if (collectPropChanges) { + propChanges.emplace_back( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any()); + } + } + break; + case Node::KIND_GROUP: + case Node::KIND_SET: + if (i.second.children.empty()) { + if (!child->getNode()->getTemplateName().isEmpty()) { + for (auto const& containerListener : containerListeners_) + { + broadcaster-> + addContainerElementInsertedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >( + this), + css::uno::Any(i.first), + child->asValue(), css::uno::Any())); + } + if (allChanges != nullptr) { + allChanges->push_back( + css::util::ElementChange( + css::uno::Any( + child->getRelativePathRepresentation()), + css::uno::Any(), css::uno::Any())); + //TODO: non-void Element, ReplacedElement + } + } + // else: spurious Modifications::Node not representing a + // change + } else { + child->initBroadcasterAndChanges( + i.second, broadcaster, allChanges); + //TODO: if allChanges==0, recurse only into children w/ + // listeners + } + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } + } else { + switch (getNode()->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + // Removed localized property value: + assert(Components::allLocales(getRootAccess()->getLocale())); + for (auto const& containerListener : containerListeners_) + { + broadcaster->addContainerElementRemovedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >(this), + css::uno::Any(i.first), css::uno::Any(), + css::uno::Any())); + //TODO: non-void ReplacedElement + } + if (allChanges != nullptr) { + OUStringBuffer path(getRelativePathRepresentation()); + if (!path.isEmpty()) { + path.append('/'); + } + path.append(Data::createSegment(u"*", i.first)); + allChanges->push_back( + css::util::ElementChange( + css::uno::Any(path.makeStringAndClear()), + css::uno::Any(), css::uno::Any())); + //TODO: non-void ReplacedElement + } + assert(!collectPropChanges); + break; + case Node::KIND_GROUP: + { + // Removed (non-localized) extension property: + for (auto const& containerListener : containerListeners_) + { + broadcaster->addContainerElementRemovedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >(this), + css::uno::Any(i.first), css::uno::Any(), + css::uno::Any())); + //TODO: non-void ReplacedElement + } + PropertyChangeListeners::iterator j( + propertyChangeListeners_.find(i.first)); + if (j != propertyChangeListeners_.end()) { + for (auto const& propertyChangeListenerElement : j->second) + { + broadcaster->addPropertyChangeNotification( + propertyChangeListenerElement, + css::beans::PropertyChangeEvent( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any())); + } + } + j = propertyChangeListeners_.find(""); + if (j != propertyChangeListeners_.end()) { + for (auto const& propertyChangeListenerElement : j->second) + { + broadcaster->addPropertyChangeNotification( + propertyChangeListenerElement, + css::beans::PropertyChangeEvent( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any())); + } + } + if (allChanges != nullptr) { + OUStringBuffer path( + getRelativePathRepresentation()); + if (!path.isEmpty()) { + path.append('/'); + } + path.append(i.first); + allChanges->push_back( + css::util::ElementChange( + css::uno::Any(path.makeStringAndClear()), + css::uno::Any(), css::uno::Any())); + //TODO: non-void ReplacedElement + } + if (collectPropChanges) { + propChanges.emplace_back( + static_cast< cppu::OWeakObject * >(this), + i.first, false, -1, css::uno::Any(), + css::uno::Any()); + } + } + break; + case Node::KIND_SET: + // Removed set member: + if (i.second.children.empty()) { + for (auto const& containerListener : containerListeners_) + { + broadcaster->addContainerElementRemovedNotification( + containerListener, + css::container::ContainerEvent( + static_cast< cppu::OWeakObject * >(this), + css::uno::Any(i.first), + css::uno::Any(), css::uno::Any())); + //TODO: non-void ReplacedElement + } + if (allChanges != nullptr) { + OUStringBuffer path( + getRelativePathRepresentation()); + if (!path.isEmpty()) { + path.append('/'); + } + path.append(Data::createSegment(u"*", i.first)); + allChanges->push_back( + css::util::ElementChange( + css::uno::Any(path.makeStringAndClear()), + css::uno::Any(), css::uno::Any())); + //TODO: non-void ReplacedElement + } + } + // else: spurious Modifications::Node not representing a change + break; + default: + assert(false); // this cannot happen + break; + } + } + } + if (!propChanges.empty()) { + css::uno::Sequence< css::beans::PropertyChangeEvent > seq( + comphelper::containerToSequence(propChanges)); + for (auto const& propertyChangeListener : propertiesChangeListeners_) + { + broadcaster->addPropertiesChangeNotification(propertyChangeListener, seq); + } + } +} + + +Access::ModifiedChild::ModifiedChild(): + directlyModified(false) +{} + +Access::ModifiedChild::ModifiedChild( + rtl::Reference< ChildAccess > theChild, bool theDirectlyModified): + child(std::move(theChild)), directlyModified(theDirectlyModified) +{} + +rtl::Reference< ChildAccess > Access::getModifiedChild( + ModifiedChildren::iterator const & childIterator) +{ + return (childIterator->second.child->getParentAccess() == this && + (childIterator->second.child->getNameInternal() == + childIterator->first)) + ? childIterator->second.child : rtl::Reference< ChildAccess >(); +} + +rtl::Reference< ChildAccess > Access::createUnmodifiedChild( + const OUString &name, const rtl::Reference< Node > &node) +{ + rtl::Reference child( + new ChildAccess(components_, getRootAccess(), this, name, node)); + cachedChildren_[name] = child.get(); + return child; +} + +rtl::Reference< ChildAccess > Access::getUnmodifiedChild( + OUString const & name) +{ + assert(modifiedChildren_.find(name) == modifiedChildren_.end()); + rtl::Reference< Node > node(getNode()->getMember(name)); + if (!node.is()) { + return rtl::Reference< ChildAccess >(); + } + WeakChildMap::iterator i(cachedChildren_.find(name)); + if (i != cachedChildren_.end()) { + rtl::Reference< ChildAccess > child; + if (i->second->acquireCounting() > 1) { + child.set(i->second); // must not throw + } + i->second->releaseNondeleting(); + if (child.is()) { + child->setNode(node); + return child; + } + } + return createUnmodifiedChild(name,node); +} + +rtl::Reference< ChildAccess > Access::getSubChild(OUString const & path) { + sal_Int32 i = 0; + // For backwards compatibility, allow absolute paths where meaningful: + if( path.startsWith("/") ) { + ++i; + if (!getRootAccess().is()) { + return rtl::Reference< ChildAccess >(); + } + std::vector<OUString> abs(getAbsolutePath()); + for (auto const& elem : abs) + { + OUString name1; + bool setElement1; + OUString templateName1; + i = Data::parseSegment( + path, i, &name1, &setElement1, &templateName1); + if (i == -1 || (i != path.getLength() && path[i] != '/')) { + return rtl::Reference< ChildAccess >(); + } + OUString name2; + bool setElement2; + OUString templateName2; + Data::parseSegment(elem, 0, &name2, &setElement2, &templateName2); + if (name1 != name2 || setElement1 != setElement2 || + (setElement1 && + !Data::equalTemplateNames(templateName1, templateName2))) + { + return rtl::Reference< ChildAccess >(); + } + if (i != path.getLength()) { + ++i; + } + } + } + for (rtl::Reference< Access > parent(this);;) { + OUString name; + bool setElement; + OUString templateName; + i = Data::parseSegment(path, i, &name, &setElement, &templateName); + if (i == -1 || (i != path.getLength() && path[i] != '/')) { + return rtl::Reference< ChildAccess >(); + } + rtl::Reference< ChildAccess > child(parent->getChild(name)); + if (!child.is()) { + return rtl::Reference< ChildAccess >(); + } + if (setElement) { + rtl::Reference< Node > p(parent->getNode()); + switch (p->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + if (!Components::allLocales(getRootAccess()->getLocale()) || + !templateName.isEmpty()) + { + return rtl::Reference< ChildAccess >(); + } + break; + case Node::KIND_SET: + if (!templateName.isEmpty() && + !static_cast< SetNode * >(p.get())->isValidTemplate( + templateName)) + { + return rtl::Reference< ChildAccess >(); + } + break; + default: + return rtl::Reference< ChildAccess >(); + } + } + // For backwards compatibility, ignore a final slash after non-value + // nodes: + if (child->isValue()) { + return i == path.getLength() + ? child : rtl::Reference< ChildAccess >(); + } else if (i >= path.getLength() - 1) { + return child; + } + ++i; + parent = child.get(); + } +} + +bool Access::setChildProperty( + OUString const & name, css::uno::Any const & value, + Modifications * localModifications) +{ + assert(localModifications != nullptr); + rtl::Reference< ChildAccess > child(getChild(name)); + if (!child.is()) { + return false; + } + child->checkFinalized(); + child->setProperty(value, localModifications); + return true; +} + +css::beans::Property Access::asProperty() { + css::uno::Type type; + bool nillable; + bool removable; + rtl::Reference< Node > p(getNode()); + switch (p->kind()) { + case Node::KIND_PROPERTY: + { + PropertyNode * prop = static_cast< PropertyNode * >(p.get()); + type = mapType(prop->getStaticType()); + nillable = prop->isNillable(); + removable = prop->isExtension(); + } + break; + case Node::KIND_LOCALIZED_PROPERTY: + { + LocalizedPropertyNode * locprop = + static_cast< LocalizedPropertyNode *>(p.get()); + if (Components::allLocales(getRootAccess()->getLocale())) { + type = cppu::UnoType< css::uno::XInterface >::get(); + //TODO: correct? + removable = false; + } else { + type = mapType(locprop->getStaticType()); + removable = false; //TODO ??? + } + nillable = locprop->isNillable(); + } + break; + case Node::KIND_LOCALIZED_VALUE: + { + LocalizedPropertyNode * locprop = + static_cast< LocalizedPropertyNode * >(getParentNode().get()); + type = mapType(locprop->getStaticType()); + nillable = locprop->isNillable(); + removable = false; //TODO ??? + } + break; + default: + type = cppu::UnoType< css::uno::XInterface >::get(); //TODO: correct? + nillable = false; + rtl::Reference< Node > parent(getParentNode()); + removable = parent.is() && parent->kind() == Node::KIND_SET; + break; + } + return css::beans::Property( + getNameInternal(), -1, type, + (css::beans::PropertyAttribute::BOUND | //TODO: correct for group/set? + css::beans::PropertyAttribute::CONSTRAINED | + (nillable ? css::beans::PropertyAttribute::MAYBEVOID : 0) | + (getRootAccess()->isUpdate() && removable + ? css::beans::PropertyAttribute::REMOVABLE : 0) | + (!getRootAccess()->isUpdate() || p->getFinalized() != Data::NO_LAYER + ? css::beans::PropertyAttribute::READONLY : 0))); //TODO: MAYBEDEFAULT +} + +void Access::checkFinalized() { + if (isFinalized()) { + throw css::lang::IllegalArgumentException( + "configmgr modification of finalized item", + static_cast< cppu::OWeakObject * >(this), -1); + } +} + +void Access::checkKnownProperty(OUString const & descriptor) { + if (descriptor.isEmpty()) { + return; + } + rtl::Reference< ChildAccess > child(getChild(descriptor)); + if (child.is()) { + switch (child->getNode()->kind()) { + case Node::KIND_PROPERTY: + return; + case Node::KIND_LOCALIZED_PROPERTY: + if (!Components::allLocales(getRootAccess()->getLocale())) { + return; + } + break; + case Node::KIND_LOCALIZED_VALUE: + if (Components::allLocales(getRootAccess()->getLocale())) { + return; + } + break; + default: + break; + } + } + throw css::beans::UnknownPropertyException( + descriptor, static_cast< cppu::OWeakObject * >(this)); +} + +rtl::Reference< ChildAccess > Access::getFreeSetMember( + css::uno::Any const & value) +{ + rtl::Reference< ChildAccess > freeAcc = comphelper::getFromUnoTunnel<ChildAccess>(value); + if (!freeAcc.is() || freeAcc->getParentAccess().is() || + (freeAcc->isInTransaction() && + freeAcc->getRootAccess() != getRootAccess())) + { + throw css::lang::IllegalArgumentException( + "configmgr inappropriate set element", + static_cast< cppu::OWeakObject * >(this), 1); + } + assert(dynamic_cast< SetNode * >(getNode().get()) != nullptr); + if (!static_cast< SetNode * >(getNode().get())->isValidTemplate( + freeAcc->getNode()->getTemplateName())) + { + throw css::lang::IllegalArgumentException( + "configmgr inappropriate set element", + static_cast< cppu::OWeakObject * >(this), 1); + } + return freeAcc; +} + +rtl::Reference< Access > Access::getNotificationRoot() { + for (rtl::Reference< Access > p(this);;) { + rtl::Reference< Access > parent(p->getParentAccess()); + if (!parent.is()) { + return p; + } + p = parent; + } +} + +#if !defined NDEBUG +bool Access::thisIs(int what) { + osl::MutexGuard g(*lock_); + rtl::Reference< Node > p(getNode()); + Node::Kind k(p->kind()); + return (k != Node::KIND_PROPERTY && k != Node::KIND_LOCALIZED_VALUE && + ((what & IS_GROUP) == 0 || k == Node::KIND_GROUP) && + ((what & IS_SET) == 0 || k == Node::KIND_SET) && + ((what & IS_EXTENSIBLE) == 0 || k != Node::KIND_GROUP || + static_cast< GroupNode * >(p.get())->isExtensible()) && + ((what & IS_GROUP_MEMBER) == 0 || + getParentNode()->kind() == Node::KIND_GROUP)) || + ((what & IS_SET_MEMBER) == 0 || + getParentNode()->kind() == Node::KIND_SET) || + ((what & IS_UPDATE) == 0 || getRootAccess()->isUpdate()); +} +#endif + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/access.hxx b/configmgr/source/access.hxx new file mode 100644 index 000000000..51e43d5bc --- /dev/null +++ b/configmgr/source/access.hxx @@ -0,0 +1,450 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> +#include <vector> +#include "config_map.hxx" + +#include <com/sun/star/beans/XExactName.hpp> +#include <com/sun/star/beans/XHierarchicalPropertySet.hpp> +#include <com/sun/star/beans/XHierarchicalPropertySetInfo.hpp> +#include <com/sun/star/beans/XMultiHierarchicalPropertySet.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XProperty.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/container/XHierarchicalName.hpp> +#include <com/sun/star/container/XHierarchicalNameReplace.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/interlck.h> +#include <rtl/ref.hxx> +#include <sal/types.h> + +#include "modifications.hxx" +#include "type.hxx" + +namespace com::sun::star { + namespace beans { + class XHierarchicalPropertySetInfo; + class XPropertiesChangeListener; + class XPropertyChangeListener; + class XVetoableChangeListener; + struct Property; + } + namespace container { class XContainerListener; } + namespace lang { class XEventListener; } + namespace uno { + class Any; + class Type; + class XInterface; + } + namespace util { struct ElementChange; } +} + +namespace configmgr { + +class Broadcaster; +class ChildAccess; +class Components; +class Node; +class RootAccess; + +class Access: + public cppu::OWeakObject, public css::lang::XTypeProvider, + public css::lang::XServiceInfo, + public css::lang::XComponent, + public css::container::XHierarchicalNameReplace, + public css::container::XContainer, + public css::beans::XExactName, + public css::beans::XPropertySetInfo, + public css::container::XHierarchicalName, + public css::container::XNamed, + public css::beans::XProperty, + public css::beans::XPropertySet, + public css::beans::XMultiPropertySet, + public css::beans::XHierarchicalPropertySet, + public css::beans::XMultiHierarchicalPropertySet, + public css::beans::XHierarchicalPropertySetInfo, + public css::container::XNameContainer, + public css::lang::XSingleServiceFactory +{ +public: + oslInterlockedCount acquireCounting(); + + void releaseNondeleting(); + + bool isValue(); + + void markChildAsModified(rtl::Reference< ChildAccess > const & child); + void releaseChild(OUString const & name); + + virtual std::vector<OUString> getAbsolutePath() = 0; + virtual std::vector<OUString> getRelativePath() = 0; + + virtual OUString getRelativePathRepresentation() = 0; + virtual rtl::Reference< Node > getNode() = 0; + + virtual bool isFinalized() = 0; + + virtual void initBroadcaster( + Modifications::Node const & modifications, Broadcaster * broadcaster); + + using OWeakObject::acquire; + using OWeakObject::release; + + virtual css::uno::Sequence< css::uno::Type > SAL_CALL + getTypes() override; + + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + virtual void SAL_CALL dispose() override; + + virtual void SAL_CALL addEventListener( + css::uno::Reference< css::lang::XEventListener > + const & xListener) override; + + virtual void SAL_CALL removeEventListener( + css::uno::Reference< css::lang::XEventListener > + const & aListener) override; + + virtual css::uno::Type SAL_CALL getElementType() override; + + virtual sal_Bool SAL_CALL hasElements() override; + + virtual css::uno::Any SAL_CALL getByName( + OUString const & aName) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName(OUString const & aName) override; + + virtual css::uno::Any SAL_CALL getByHierarchicalName( + OUString const & aName) override; + + virtual sal_Bool SAL_CALL hasByHierarchicalName(OUString const & aName) override; + + virtual void SAL_CALL replaceByHierarchicalName( + OUString const & aName, css::uno::Any const & aElement) override; + + virtual void SAL_CALL addContainerListener( + css::uno::Reference< + css::container::XContainerListener > const & xListener) override; + + virtual void SAL_CALL removeContainerListener( + css::uno::Reference< + css::container::XContainerListener > const & xListener) override; + + virtual OUString SAL_CALL getExactName( + OUString const & aApproximateName) override; + + virtual css::uno::Sequence< css::beans::Property > + SAL_CALL getProperties() override; + + virtual css::beans::Property SAL_CALL getPropertyByName( + OUString const & aName) override; + + virtual sal_Bool SAL_CALL hasPropertyByName(OUString const & Name) override; + + virtual OUString SAL_CALL getHierarchicalName() override; + + virtual OUString SAL_CALL composeHierarchicalName( + OUString const & aRelativeName) override; + + virtual OUString SAL_CALL getName() override; + + virtual void SAL_CALL setName(OUString const & aName) override; + + virtual css::beans::Property SAL_CALL getAsProperty() override; + + virtual + css::uno::Reference< css::beans::XPropertySetInfo > + SAL_CALL getPropertySetInfo() override; + + virtual void SAL_CALL setPropertyValue( + OUString const & aPropertyName, + css::uno::Any const & aValue) override; + + virtual css::uno::Any SAL_CALL getPropertyValue( + OUString const & PropertyName) override; + + virtual void SAL_CALL addPropertyChangeListener( + OUString const & aPropertyName, + css::uno::Reference< + css::beans::XPropertyChangeListener > const & xListener) override; + + virtual void SAL_CALL removePropertyChangeListener( + OUString const & aPropertyName, + css::uno::Reference< + css::beans::XPropertyChangeListener > const & aListener) override; + + virtual void SAL_CALL addVetoableChangeListener( + OUString const & PropertyName, + css::uno::Reference< + css::beans::XVetoableChangeListener > const & aListener) override; + + virtual void SAL_CALL removeVetoableChangeListener( + OUString const & PropertyName, + css::uno::Reference< + css::beans::XVetoableChangeListener > const & aListener) override; + + virtual void SAL_CALL setPropertyValues( + css::uno::Sequence< OUString > const & aPropertyNames, + css::uno::Sequence< css::uno::Any > const & + aValues) override; + + virtual css::uno::Sequence< css::uno::Any > SAL_CALL + getPropertyValues( + css::uno::Sequence< OUString > const & aPropertyNames) override; + + virtual void SAL_CALL addPropertiesChangeListener( + css::uno::Sequence< OUString > const & aPropertyNames, + css::uno::Reference< + css::beans::XPropertiesChangeListener > const & + xListener) override; + + virtual void SAL_CALL removePropertiesChangeListener( + css::uno::Reference< + css::beans::XPropertiesChangeListener > const & + xListener) override; + + virtual void SAL_CALL firePropertiesChangeEvent( + css::uno::Sequence< OUString > const & aPropertyNames, + css::uno::Reference< + css::beans::XPropertiesChangeListener > const & + xListener) override; + + virtual + css::uno::Reference< + css::beans::XHierarchicalPropertySetInfo > SAL_CALL + getHierarchicalPropertySetInfo() override; + + virtual void SAL_CALL setHierarchicalPropertyValue( + OUString const & aHierarchicalPropertyName, + css::uno::Any const & aValue) override; + + virtual css::uno::Any SAL_CALL getHierarchicalPropertyValue( + OUString const & aHierarchicalPropertyName) override; + + virtual void SAL_CALL setHierarchicalPropertyValues( + css::uno::Sequence< OUString > const & + aHierarchicalPropertyNames, + css::uno::Sequence< css::uno::Any > const & + Values) override; + + virtual css::uno::Sequence< css::uno::Any > SAL_CALL + getHierarchicalPropertyValues( + css::uno::Sequence< OUString > const & + aHierarchicalPropertyNames) override; + + virtual css::beans::Property SAL_CALL + getPropertyByHierarchicalName(OUString const & aHierarchicalName) override; + + virtual sal_Bool SAL_CALL hasPropertyByHierarchicalName( + OUString const & aHierarchicalName) override; + + virtual void SAL_CALL replaceByName( + OUString const & aName, css::uno::Any const & aElement) override; + + virtual void SAL_CALL insertByName( + OUString const & aName, css::uno::Any const & aElement) override; + + virtual void SAL_CALL removeByName(OUString const & aName) override; + + virtual css::uno::Reference< css::uno::XInterface > + SAL_CALL createInstance() override; + + virtual css::uno::Reference< css::uno::XInterface > + SAL_CALL createInstanceWithArguments( + css::uno::Sequence< css::uno::Any > const & + aArguments) override; + +protected: + explicit Access(Components & components); + + virtual ~Access() override; + + virtual const OUString & getNameInternal() = 0; + virtual rtl::Reference< RootAccess > getRootAccess() = 0; + virtual rtl::Reference< Access > getParentAccess() = 0; + + virtual void addTypes(std::vector< css::uno::Type > * types) + const = 0; + + virtual void addSupportedServiceNames( + std::vector<OUString> * services) = 0; + + virtual void initDisposeBroadcaster(Broadcaster * broadcaster); + virtual void clearListeners() noexcept; + + virtual css::uno::Any SAL_CALL queryInterface( + css::uno::Type const & aType) override; + + Components & getComponents() const { return components_;} + + void checkLocalizedPropertyAccess(); + + rtl::Reference< Node > getParentNode(); + rtl::Reference< ChildAccess > getChild(OUString const & name); + std::vector< rtl::Reference< ChildAccess > > getAllChildren(); + + void checkValue( + css::uno::Any const & value, Type type, bool nillable); + + void insertLocalizedValueChild( + OUString const & name, css::uno::Any const & value, + Modifications * localModifications); + + void reportChildChanges( + std::vector< css::util::ElementChange > * changes); + + void commitChildChanges(bool valid, Modifications * globalModifications); + + void initBroadcasterAndChanges( + Modifications::Node const & modifications, Broadcaster * broadcaster, + std::vector< css::util::ElementChange > * changes); + + bool isDisposed() const { return disposed_;} + +private: + Access(const Access&) = delete; + Access& operator=(const Access&) = delete; + + struct ModifiedChild { + rtl::Reference< ChildAccess > child; + bool directlyModified; + + ModifiedChild(); + + ModifiedChild( + rtl::Reference< ChildAccess > theChild, + bool theDirectlyModified); + }; + + typedef config_map< ModifiedChild > ModifiedChildren; + + rtl::Reference< ChildAccess > getModifiedChild( + ModifiedChildren::iterator const & childIterator); + + rtl::Reference< ChildAccess > getUnmodifiedChild( + OUString const & name); + + rtl::Reference< ChildAccess > getSubChild(OUString const & path); + + bool setChildProperty( + OUString const & name, css::uno::Any const & value, + Modifications * localModifications); + + css::beans::Property asProperty(); + + bool getByNameFast(const OUString & name, css::uno::Any & value); + rtl::Reference< ChildAccess > createUnmodifiedChild(const OUString &name, + const rtl::Reference< Node > &node); + + void checkFinalized(); + + void checkKnownProperty(OUString const & descriptor); + + rtl::Reference< ChildAccess > getFreeSetMember( css::uno::Any const & value); + + rtl::Reference< Access > getNotificationRoot(); + + typedef config_map< ChildAccess * > WeakChildMap; + + typedef + std::multiset< + css::uno::Reference< + css::lang::XEventListener > > + DisposeListeners; + + typedef + std::multiset< + css::uno::Reference< + css::container::XContainerListener > > + ContainerListeners; + + typedef + std::multiset< + css::uno::Reference< + css::beans::XPropertyChangeListener > > + PropertyChangeListenersElement; + + typedef config_map< PropertyChangeListenersElement > + PropertyChangeListeners; + + typedef + std::multiset< + css::uno::Reference< + css::beans::XVetoableChangeListener > > + VetoableChangeListenersElement; + + typedef config_map< VetoableChangeListenersElement > + VetoableChangeListeners; + + typedef + std::multiset< + css::uno::Reference< + css::beans::XPropertiesChangeListener > > + PropertiesChangeListeners; + + Components & components_; + ModifiedChildren modifiedChildren_; + WeakChildMap cachedChildren_; + DisposeListeners disposeListeners_; + ContainerListeners containerListeners_; + PropertyChangeListeners propertyChangeListeners_; + VetoableChangeListeners vetoableChangeListeners_; + PropertiesChangeListeners propertiesChangeListeners_; + bool disposed_; + + std::shared_ptr<osl::Mutex> lock_; + +#if !defined NDEBUG +protected: + enum { + IS_ANY = 0, IS_GROUP = 0x01, IS_SET = 0x02, IS_EXTENSIBLE = 0x04, + IS_GROUP_MEMBER = 0x08, IS_SET_MEMBER = 0x10, IS_UPDATE = 0x20 }; + bool thisIs(int what); +#endif +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/additions.hxx b/configmgr/source/additions.hxx new file mode 100644 index 000000000..9f50f1878 --- /dev/null +++ b/configmgr/source/additions.hxx @@ -0,0 +1,34 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ustring.hxx> + +#include <vector> + +namespace configmgr +{ +// Additions is a list of configuration node paths +typedef std::vector<std::vector<OUString>> Additions; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/broadcaster.cxx b/configmgr/source/broadcaster.cxx new file mode 100644 index 000000000..f1830ee81 --- /dev/null +++ b/configmgr/source/broadcaster.cxx @@ -0,0 +1,230 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/beans/XPropertiesChangeListener.hpp> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/XChangesListener.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <utility> + +#include "broadcaster.hxx" + +namespace configmgr { + +namespace { + +void appendMessage( + OUStringBuffer & buffer, css::uno::Exception const & exception) +{ + buffer.append("; "); + buffer.append(exception.Message); +} + +} + +void Broadcaster::addDisposeNotification( + css::uno::Reference< css::lang::XEventListener > const & listener, + css::lang::EventObject const & event) +{ + disposeNotifications_.emplace_back(listener, event); +} + +void Broadcaster::addContainerElementReplacedNotification( + css::uno::Reference< css::container::XContainerListener > const & listener, + css::container::ContainerEvent const & event) +{ + containerElementReplacedNotifications_.emplace_back(listener, event); +} + +void Broadcaster::addContainerElementInsertedNotification( + css::uno::Reference< css::container::XContainerListener > const & listener, + css::container::ContainerEvent const & event) +{ + containerElementInsertedNotifications_.emplace_back(listener, event); +} + +void Broadcaster::addContainerElementRemovedNotification( + css::uno::Reference< css::container::XContainerListener > const & listener, + css::container::ContainerEvent const & event) +{ + containerElementRemovedNotifications_.emplace_back(listener, event); +} + +void Broadcaster::addPropertyChangeNotification( + css::uno::Reference< css::beans::XPropertyChangeListener > const & listener, + css::beans::PropertyChangeEvent const & event) +{ + propertyChangeNotifications_.emplace_back(listener, event); +} + +void Broadcaster::addPropertiesChangeNotification( + css::uno::Reference< css::beans::XPropertiesChangeListener > const & + listener, + css::uno::Sequence< css::beans::PropertyChangeEvent > const & event) +{ + propertiesChangeNotifications_.emplace_back(listener, event); +} + +void Broadcaster::addChangesNotification( + css::uno::Reference< css::util::XChangesListener > const & listener, + css::util::ChangesEvent const & event) +{ + changesNotifications_.emplace_back(listener, event); +} + +void Broadcaster::send() { + css::uno::Any exception; + OUStringBuffer messages; + for (auto& rNotification : disposeNotifications_) { + try { + rNotification.listener->disposing(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + for (auto& rNotification : containerElementInsertedNotifications_) + { + try { + rNotification.listener->elementInserted(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + for (auto& rNotification : containerElementRemovedNotifications_) + { + try { + rNotification.listener->elementRemoved(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + for (auto& rNotification : containerElementReplacedNotifications_) + { + try { + rNotification.listener->elementReplaced(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + for (auto& rNotification : propertyChangeNotifications_) + { + try { + rNotification.listener->propertyChange(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + for (auto& rNotification : propertiesChangeNotifications_) + { + try { + rNotification.listener->propertiesChange(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + for (auto& rNotification : changesNotifications_) { + try { + rNotification.listener->changesOccurred(rNotification.event); + } catch (css::lang::DisposedException &) { + } catch (css::uno::Exception & e) { + exception = cppu::getCaughtException(); + appendMessage(messages, e); + } + } + if (exception.hasValue()) { + throw css::lang::WrappedTargetRuntimeException( + ("configmgr exceptions during listener notification" + + messages.makeStringAndClear()), + css::uno::Reference< css::uno::XInterface >(), + exception); + } +} + +Broadcaster::DisposeNotification::DisposeNotification( + css::uno::Reference< css::lang::XEventListener > const & theListener, + css::lang::EventObject theEvent): + listener(theListener), event(std::move(theEvent)) +{ + assert(theListener.is()); +} + +Broadcaster::ContainerNotification::ContainerNotification( + css::uno::Reference< css::container::XContainerListener > const & + theListener, + css::container::ContainerEvent theEvent): + listener(theListener), event(std::move(theEvent)) +{ + assert(theListener.is()); +} + +Broadcaster::PropertyChangeNotification::PropertyChangeNotification( + css::uno::Reference< css::beans::XPropertyChangeListener > const & + theListener, + css::beans::PropertyChangeEvent theEvent): + listener(theListener), event(std::move(theEvent)) +{ + assert(theListener.is()); +} + +Broadcaster::PropertiesChangeNotification::PropertiesChangeNotification( + css::uno::Reference< css::beans::XPropertiesChangeListener > const & + theListener, + css::uno::Sequence< css::beans::PropertyChangeEvent > const & theEvent): + listener(theListener), event(theEvent) +{ + assert(theListener.is()); +} + +Broadcaster::ChangesNotification::ChangesNotification( + css::uno::Reference< css::util::XChangesListener > const & theListener, + css::util::ChangesEvent theEvent): + listener(theListener), event(std::move(theEvent)) +{ + assert(theListener.is()); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/broadcaster.hxx b/configmgr/source/broadcaster.hxx new file mode 100644 index 000000000..d0c461e73 --- /dev/null +++ b/configmgr/source/broadcaster.hxx @@ -0,0 +1,139 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vector> + +#include <com/sun/star/beans/PropertyChangeEvent.hpp> +#include <com/sun/star/container/ContainerEvent.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/util/ChangesEvent.hpp> + +namespace com::sun::star { + namespace beans { + class XPropertiesChangeListener; + class XPropertyChangeListener; + } + namespace container { class XContainerListener; } + namespace lang { class XEventListener; } + namespace util { class XChangesListener; } +} + +namespace configmgr { + +class Broadcaster { +public: + Broadcaster() {} + + void addDisposeNotification( + css::uno::Reference< css::lang::XEventListener > const & listener, + css::lang::EventObject const & event); + + void addContainerElementInsertedNotification( + css::uno::Reference< css::container::XContainerListener > const & listener, + css::container::ContainerEvent const & event); + + void addContainerElementRemovedNotification( + css::uno::Reference< css::container::XContainerListener > const & listener, + css::container::ContainerEvent const & event); + + void addContainerElementReplacedNotification( + css::uno::Reference< css::container::XContainerListener > const & listener, + css::container::ContainerEvent const & event); + + void addPropertyChangeNotification( + css::uno::Reference< css::beans::XPropertyChangeListener > const & listener, + css::beans::PropertyChangeEvent const & event); + + void addPropertiesChangeNotification( + css::uno::Reference< css::beans::XPropertiesChangeListener > const & listener, + css::uno::Sequence< css::beans::PropertyChangeEvent > const & event); + + void addChangesNotification( + css::uno::Reference< css::util::XChangesListener > const & listener, + css::util::ChangesEvent const & event); + + void send(); + +private: + Broadcaster(const Broadcaster&) = delete; + Broadcaster& operator=(const Broadcaster&) = delete; + + struct DisposeNotification { + css::uno::Reference< css::lang::XEventListener > listener; + css::lang::EventObject event; + + DisposeNotification( + css::uno::Reference< css::lang::XEventListener > const & theListener, + css::lang::EventObject theEvent); + }; + + struct ContainerNotification { + css::uno::Reference< css::container::XContainerListener > listener; + css::container::ContainerEvent event; + + ContainerNotification( + css::uno::Reference< css::container::XContainerListener > const & theListener, + css::container::ContainerEvent theEvent); + }; + + struct PropertyChangeNotification { + css::uno::Reference< css::beans::XPropertyChangeListener > listener; + css::beans::PropertyChangeEvent event; + + PropertyChangeNotification( + css::uno::Reference< css::beans::XPropertyChangeListener > const & theListener, + css::beans::PropertyChangeEvent theEvent); + }; + + struct PropertiesChangeNotification { + css::uno::Reference< css::beans::XPropertiesChangeListener > listener; + css::uno::Sequence< css::beans::PropertyChangeEvent > event; + + PropertiesChangeNotification( + css::uno::Reference< css::beans::XPropertiesChangeListener > const & theListener, + css::uno::Sequence< css::beans::PropertyChangeEvent > const & theEvent); + }; + + struct ChangesNotification { + css::uno::Reference< css::util::XChangesListener > listener; + css::util::ChangesEvent event; + + ChangesNotification( + css::uno::Reference< css::util::XChangesListener > const & theListener, + css::util::ChangesEvent theEvent); + }; + + std::vector< DisposeNotification > disposeNotifications_; + std::vector< ContainerNotification > containerElementInsertedNotifications_; + std::vector< ContainerNotification > containerElementRemovedNotifications_; + std::vector< ContainerNotification > containerElementReplacedNotifications_; + std::vector< PropertyChangeNotification > propertyChangeNotifications_; + std::vector< PropertiesChangeNotification > propertiesChangeNotifications_; + std::vector< ChangesNotification > changesNotifications_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/childaccess.cxx b/configmgr/source/childaccess.cxx new file mode 100644 index 000000000..60a11006b --- /dev/null +++ b/configmgr/source/childaccess.cxx @@ -0,0 +1,350 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <utility> +#include <vector> + +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppu/unotype.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/weak.hxx> +#include <comphelper/servicehelper.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include "access.hxx" +#include "childaccess.hxx" +#include "components.hxx" +#include "data.hxx" +#include "localizedpropertynode.hxx" +#include "localizedvaluenode.hxx" +#include "lock.hxx" +#include "modifications.hxx" +#include "node.hxx" +#include "propertynode.hxx" +#include "rootaccess.hxx" +#include "type.hxx" + +namespace configmgr { + +css::uno::Sequence< sal_Int8 > const & ChildAccess::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theChildAccessUnoTunnelId; + return theChildAccessUnoTunnelId.getSeq(); +} + +ChildAccess::ChildAccess( + Components & components, rtl::Reference< RootAccess > const & root, + rtl::Reference< Access > const & parent, OUString name, + rtl::Reference< Node > const & node): + Access(components), root_(root), parent_(parent), name_(std::move(name)), node_(node), + inTransaction_(false), + lock_( lock() ) +{ + assert(root.is() && parent.is() && node.is()); +} + +ChildAccess::ChildAccess( + Components & components, rtl::Reference< RootAccess > const & root, + rtl::Reference< Node > const & node): + Access(components), root_(root), node_(node), inTransaction_(false), + lock_( lock() ) +{ + assert(root.is() && node.is()); +} + +std::vector<OUString> ChildAccess::getAbsolutePath() { + rtl::Reference< Access > parent(getParentAccess()); + assert(parent.is()); + std::vector<OUString> path(parent->getAbsolutePath()); + path.push_back(name_); + return path; +} + +std::vector<OUString> ChildAccess::getRelativePath() { + std::vector<OUString> path; + rtl::Reference< Access > parent(getParentAccess()); + if (parent.is()) { + path = parent->getRelativePath(); + } + path.push_back(name_); + return path; +} + +OUString ChildAccess::getRelativePathRepresentation() { + OUStringBuffer path(128); + rtl::Reference< Access > parent(getParentAccess()); + if (parent.is()) { + path.append(parent->getRelativePathRepresentation()); + if (!path.isEmpty()) { + path.append('/'); + } + } + path.append(Data::createSegment(node_->getTemplateName(), name_)); + return path.makeStringAndClear(); +} + +rtl::Reference< Node > ChildAccess::getNode() { + return node_; +} + +bool ChildAccess::isFinalized() { + return node_->getFinalized() != Data::NO_LAYER || + (parent_.is() && parent_->isFinalized()); +} + +const OUString & ChildAccess::getNameInternal() { + return name_; +} + +rtl::Reference< RootAccess > ChildAccess::getRootAccess() { + return root_; +} + +rtl::Reference< Access > ChildAccess::getParentAccess() { + return parent_; +} + +void ChildAccess::acquire() noexcept { + Access::acquire(); +} + +void ChildAccess::release() noexcept { + Access::release(); +} + +css::uno::Reference< css::uno::XInterface > ChildAccess::getParent() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return static_cast< cppu::OWeakObject * >(parent_.get()); +} + +void ChildAccess::setParent(css::uno::Reference< css::uno::XInterface > const &) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + throw css::lang::NoSupportException( + "setParent", static_cast< cppu::OWeakObject * >(this)); +} + +sal_Int64 ChildAccess::getSomething( + css::uno::Sequence< sal_Int8 > const & aIdentifier) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return comphelper::getSomethingImpl(aIdentifier, this); +} + +void ChildAccess::bind( + rtl::Reference< RootAccess > const & root, + rtl::Reference< Access > const & parent, OUString const & name) + noexcept +{ + assert(!parent_.is() && root.is() && parent.is() && !name.isEmpty()); + root_ = root; + parent_ = parent; + name_ = name; +} + +void ChildAccess::unbind() noexcept { + assert(parent_.is()); + parent_->releaseChild(name_); + parent_.clear(); + inTransaction_ = true; +} + +void ChildAccess::committed() { + inTransaction_ = false; +} + +void ChildAccess::setNode(rtl::Reference< Node > const & node) { + node_ = node; +} + +void ChildAccess::setProperty( + css::uno::Any const & value, Modifications * localModifications) +{ + assert(localModifications != nullptr); + Type type = TYPE_ERROR; + bool isNillable = false; + switch (node_->kind()) { + case Node::KIND_PROPERTY: + { + PropertyNode * prop = static_cast< PropertyNode * >(node_.get()); + type = prop->getStaticType(); + isNillable = prop->isNillable(); + } + break; + case Node::KIND_LOCALIZED_PROPERTY: + { + OUString locale(getRootAccess()->getLocale()); + if (!Components::allLocales(locale)) { + rtl::Reference< ChildAccess > child(getChild(locale)); + if (child.is()) { + child->setProperty(value, localModifications); + } else { + insertLocalizedValueChild( + locale, value, localModifications); + } + return; + } + } + break; + case Node::KIND_LOCALIZED_VALUE: + { + LocalizedPropertyNode * locprop = + static_cast< LocalizedPropertyNode * >(getParentNode().get()); + type = locprop->getStaticType(); + isNillable = locprop->isNillable(); + } + break; + default: + break; + } + checkValue(value, type, isNillable); + getParentAccess()->markChildAsModified(this); + changedValue_.emplace(value); + localModifications->add(getRelativePath()); +} + + +css::uno::Any ChildAccess::asValue() +{ + if (changedValue_) + { + return *changedValue_; + } + css::uno::Any value; + if (!asSimpleValue(node_, value, getComponents())) + { + if (node_->kind() == Node::KIND_LOCALIZED_PROPERTY) + { + OUString locale(getRootAccess()->getLocale()); + if (!Components::allLocales(locale)) { + rtl::Reference< ChildAccess > child(getChild("*" + locale)); + // As a last resort, return a nil value even though it may be + // illegal for the given property: + return child.is() ? child->asValue() : css::uno::Any(); + } + } + value <<= css::uno::Reference< css::uno::XInterface >( + static_cast< cppu::OWeakObject * >(this)); + } + return value; +} + +/// Can we quickly extract a simple value into value ? if so returns true +bool ChildAccess::asSimpleValue(const rtl::Reference< Node > &rNode, + css::uno::Any &value, + Components &components) +{ + switch (rNode->kind()) { + case Node::KIND_PROPERTY: + value = static_cast< PropertyNode * >(rNode.get())->getValue(components); + return true; + case Node::KIND_LOCALIZED_VALUE: + value = static_cast< LocalizedValueNode * >(rNode.get())->getValue(); + return true; + default: + return false; + } +} + +void ChildAccess::commitChanges(bool valid, Modifications * globalModifications) +{ + assert(globalModifications != nullptr); + commitChildChanges(valid, globalModifications); + if (valid && changedValue_) + { + std::vector<OUString> path(getAbsolutePath()); + getComponents().addModification(path); + globalModifications->add(path); + switch (node_->kind()) { + case Node::KIND_PROPERTY: + static_cast< PropertyNode * >(node_.get())->setValue( + Data::NO_LAYER, *changedValue_); + break; + case Node::KIND_LOCALIZED_VALUE: + static_cast< LocalizedValueNode * >(node_.get())->setValue( + Data::NO_LAYER, *changedValue_); + break; + default: + assert(false); // this cannot happen + break; + } + } + changedValue_.reset(); +} + +ChildAccess::~ChildAccess() { + osl::MutexGuard g(*lock_); + if (parent_.is()) { + parent_->releaseChild(name_); + } +} + +void ChildAccess::addTypes(std::vector< css::uno::Type > * types) const { + assert(types != nullptr); + types->push_back(cppu::UnoType< css::container::XChild >::get()); + types->push_back(cppu::UnoType< css::lang::XUnoTunnel >::get()); +} + +void ChildAccess::addSupportedServiceNames( + std::vector<OUString> * services) +{ + assert(services != nullptr); + services->push_back( + getParentNode()->kind() == Node::KIND_GROUP + ? OUString("com.sun.star.configuration.GroupElement") + : OUString("com.sun.star.configuration.SetElement")); +} + +css::uno::Any ChildAccess::queryInterface(css::uno::Type const & aType) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + css::uno::Any res(Access::queryInterface(aType)); + return res.hasValue() + ? res + : cppu::queryInterface( + aType, static_cast< css::container::XChild * >(this), + static_cast< css::lang::XUnoTunnel * >(this)); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/childaccess.hxx b/configmgr/source/childaccess.hxx new file mode 100644 index 000000000..3b7a5a541 --- /dev/null +++ b/configmgr/source/childaccess.hxx @@ -0,0 +1,137 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <memory> +#include <optional> +#include <vector> + +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ref.hxx> +#include <sal/types.h> + +#include "access.hxx" + +namespace com::sun::star::uno { + class Any; + class Type; + class XInterface; +} + +namespace configmgr { + +class Components; +class Modifications; +class Node; +class RootAccess; + +class ChildAccess: + public Access, public css::container::XChild, + public css::lang::XUnoTunnel +{ +public: + static css::uno::Sequence< sal_Int8 > const & getUnoTunnelId(); + + ChildAccess( + Components & components, rtl::Reference< RootAccess > const & root, + rtl::Reference< Access > const & parent, OUString name, + rtl::Reference< Node > const & node); + + ChildAccess( + Components & components, rtl::Reference< RootAccess > const & root, + rtl::Reference< Node > const & node); + + virtual std::vector<OUString> getAbsolutePath() override; + virtual std::vector<OUString> getRelativePath() override; + + virtual OUString getRelativePathRepresentation() override; + virtual rtl::Reference< Node > getNode() override; + + virtual bool isFinalized() override; + + virtual const OUString & getNameInternal() override; + + virtual rtl::Reference< RootAccess > getRootAccess() override; + virtual rtl::Reference< Access > getParentAccess() override; + + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + virtual css::uno::Reference< css::uno::XInterface > + SAL_CALL getParent() override; + + virtual void SAL_CALL setParent( + css::uno::Reference< css::uno::XInterface > const &) override; + + virtual sal_Int64 SAL_CALL getSomething( + css::uno::Sequence< sal_Int8 > const & aIdentifier) override; + + void bind( + rtl::Reference< RootAccess > const & root, + rtl::Reference< Access > const & parent, OUString const & name) + noexcept; + + void unbind() noexcept; + + bool isInTransaction() const { return inTransaction_; } + void committed(); + void setNode(rtl::Reference< Node > const & node); + + void setProperty( + css::uno::Any const & value, + Modifications * localModifications); + + css::uno::Any asValue(); + static bool asSimpleValue(const rtl::Reference< Node > &rNode, + css::uno::Any &value, + Components &components); + + void commitChanges(bool valid, Modifications * globalModifications); + +private: + virtual ~ChildAccess() override; + + virtual void addTypes( + std::vector< css::uno::Type > * types) const override; + + virtual void addSupportedServiceNames( + std::vector<OUString> * services) override; + + virtual css::uno::Any SAL_CALL queryInterface( + css::uno::Type const & aType) override; + + rtl::Reference< RootAccess > root_; + rtl::Reference< Access > parent_; // null if free node + OUString name_; + rtl::Reference< Node > node_; + std::optional< css::uno::Any > changedValue_; + bool inTransaction_; + // to determine if a free node can be inserted underneath some root + std::shared_ptr<osl::Mutex> lock_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/components.cxx b/configmgr/source/components.cxx new file mode 100644 index 000000000..0693ec26b --- /dev/null +++ b/configmgr/source/components.cxx @@ -0,0 +1,895 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <chrono> +#include <utility> +#include <vector> +#include <set> + +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <config_dconf.h> +#include <config_folders.h> +#include <osl/conditn.hxx> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <salhelper/thread.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/backupfilehelper.hxx> +#include <o3tl/string_view.hxx> + +#include "additions.hxx" +#include "components.hxx" +#include "data.hxx" +#include "lock.hxx" +#include "modifications.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "parsemanager.hxx" +#include "partial.hxx" +#include "rootaccess.hxx" +#include "writemodfile.hxx" +#include "xcdparser.hxx" +#include "xcuparser.hxx" +#include "xcsparser.hxx" + +#if ENABLE_DCONF +#include "dconf.hxx" +#endif + +#if defined(_WIN32) +#include "winreg.hxx" +#endif + +namespace configmgr { + +namespace { + +struct UnresolvedVectorItem { + OUString name; + rtl::Reference< ParseManager > manager; + + UnresolvedVectorItem( + OUString theName, + rtl::Reference< ParseManager > theManager): + name(std::move(theName)), manager(std::move(theManager)) {} +}; + +typedef std::vector< UnresolvedVectorItem > UnresolvedVector; + +void parseXcsFile( + OUString const & url, int layer, Data & data, Partial const * partial, + Modifications * modifications, Additions * additions) +{ + assert(partial == nullptr && modifications == nullptr && additions == nullptr); + (void) partial; (void) modifications; (void) additions; + bool ok = rtl::Reference< ParseManager >( + new ParseManager(url, new XcsParser(layer, data)))->parse(nullptr); + assert(ok); + (void) ok; // avoid warnings +} + +void parseXcuFile( + OUString const & url, int layer, Data & data, Partial const * partial, + Modifications * modifications, Additions * additions) +{ + bool ok = rtl::Reference< ParseManager >( + new ParseManager( + url, + new XcuParser(layer, data, partial, modifications, additions)))-> + parse(nullptr); + assert(ok); + (void) ok; // avoid warnings +} + +OUString expand(OUString const & str) { + OUString s(str); + rtl::Bootstrap::expandMacros(s); //TODO: detect failure + return s; +} + +bool canRemoveFromLayer(int layer, rtl::Reference< Node > const & node) { + assert(node.is()); + if (node->getLayer() > layer && node->getLayer() < Data::NO_LAYER) { + return false; + } + switch (node->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + for (auto const& member : node->getMembers()) + { + if (!canRemoveFromLayer(layer, member.second)) { + return false; + } + } + return true; + case Node::KIND_SET: + return node->getMembers().empty(); + default: // Node::KIND_PROPERTY, Node::KIND_LOCALIZED_VALUE + return true; + } +} + +} + +class Components::WriteThread: public salhelper::Thread { +public: + WriteThread( + rtl::Reference< WriteThread > * reference, Components & components, + OUString url, Data const & data); + + void flush() { delay_.set(); } + +private: + virtual ~WriteThread() override {} + + virtual void execute() override; + + rtl::Reference< WriteThread > * reference_; + Components & components_; + OUString url_; + Data const & data_; + osl::Condition delay_; + std::shared_ptr<osl::Mutex> lock_; +}; + +Components::WriteThread::WriteThread( + rtl::Reference< WriteThread > * reference, Components & components, + OUString url, Data const & data): + Thread("configmgrWriter"), reference_(reference), components_(components), + url_(std::move(url)), data_(data), + lock_( lock() ) +{ + assert(reference != nullptr); +} + +void Components::WriteThread::execute() { + delay_.wait(std::chrono::seconds(1)); // must not throw; result_error is harmless and ignored + osl::MutexGuard g(*lock_); // must not throw + try { + try { + writeModFile(components_, url_, data_); + } catch (css::uno::RuntimeException &) { + // Ignore write errors, instead of aborting: + TOOLS_WARN_EXCEPTION("configmgr", "error writing modifications"); + } + } catch (...) { + reference_->clear(); + throw; + } + reference_->clear(); +} + +Components & Components::getSingleton( + css::uno::Reference< css::uno::XComponentContext > const & context) +{ + assert(context.is()); + static Components singleton(context); + return singleton; +} + +bool Components::allLocales(std::u16string_view locale) { + return locale == u"*"; +} + +rtl::Reference< Node > Components::resolvePathRepresentation( + OUString const & pathRepresentation, + OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer) + const +{ + return data_.resolvePathRepresentation( + pathRepresentation, canonicRepresentation, path, finalizedLayer); +} + +rtl::Reference< Node > Components::getTemplate(OUString const & fullName) const +{ + return data_.getTemplate(Data::NO_LAYER, fullName); +} + +void Components::addRootAccess(rtl::Reference< RootAccess > const & access) { + roots_.insert(access.get()); +} + +void Components::removeRootAccess(RootAccess * access) { + roots_.erase(access); +} + +void Components::initGlobalBroadcaster( + Modifications const & modifications, + rtl::Reference< RootAccess > const & exclude, Broadcaster * broadcaster) +{ + //TODO: Iterate only over roots w/ listeners: + for (auto const& elemRoot : roots_) + { + rtl::Reference< RootAccess > root; + if (elemRoot->acquireCounting() > 1) { + root.set(elemRoot); // must not throw + } + elemRoot->releaseNondeleting(); + if (root.is()) { + if (root != exclude) { + std::vector<OUString> path(root->getAbsolutePath()); + Modifications::Node const * mods = &modifications.getRoot(); + for (auto const& pathElem : path) + { + Modifications::Node::Children::const_iterator k( + mods->children.find(pathElem)); + if (k == mods->children.end()) { + mods = nullptr; + break; + } + mods = &k->second; + } + //TODO: If the complete tree of which root is a part is deleted, + // or replaced, mods will be null, but some of the listeners + // from within root should probably fire nonetheless: + if (mods != nullptr) { + root->initBroadcaster(*mods, broadcaster); + } + } + } + } +} + +void Components::addModification(std::vector<OUString> const & path) { + data_.modifications.add(path); +} + +void Components::writeModifications() { + + if (data_.modifications.empty()) + return; + + switch (modificationTarget_) { + case ModificationTarget::None: + break; + case ModificationTarget::File: + if (!writeThread_.is()) { + writeThread_ = new WriteThread( + &writeThread_, *this, modificationFileUrl_, data_); + writeThread_->launch(); + } + break; + case ModificationTarget::Dconf: +#if ENABLE_DCONF + dconf::writeModifications(*this, data_); +#endif + break; + } +} + +void Components::flushModifications() { + rtl::Reference< WriteThread > thread; + { + osl::MutexGuard g(*lock_); + thread = writeThread_; + } + if (thread.is()) { + thread->flush(); + thread->join(); + } +} + +void Components::insertExtensionXcsFile( + bool shared, OUString const & fileUri) +{ + int layer = getExtensionLayer(shared); + try { + parseXcsFile(fileUri, layer, data_, nullptr, nullptr, nullptr); + } catch (css::container::NoSuchElementException & e) { + throw css::uno::RuntimeException( + "insertExtensionXcsFile does not exist: " + e.Message); + } +} + +void Components::insertExtensionXcuFile( + bool shared, OUString const & fileUri, Modifications * modifications) +{ + assert(modifications != nullptr); + int layer = getExtensionLayer(shared) + 1; + Additions * adds = data_.addExtensionXcuAdditions(fileUri, layer); + try { + parseXcuFile(fileUri, layer, data_, nullptr, modifications, adds); + } catch (css::container::NoSuchElementException & e) { + data_.removeExtensionXcuAdditions(fileUri); + throw css::uno::RuntimeException( + "insertExtensionXcuFile does not exist: " + e.Message); + } +} + +void Components::removeExtensionXcuFile( + OUString const & fileUri, Modifications * modifications) +{ + //TODO: Ideally, exactly the data coming from the specified xcu file would + // be removed. However, not enough information is recorded in the in-memory + // data structures to do so. So, as a workaround, all those set elements + // that were freshly added by the xcu and have afterwards been left + // unchanged or have only had their properties changed in the user layer are + // removed (and nothing else). The heuristic to determine + // whether a node has been left unchanged is to check the layer ID (as + // usual) and additionally to check that the node does not recursively + // contain any non-empty sets (multiple extension xcu files are merged into + // one layer, so checking layer ID alone is not enough). Since + // item->additions records all additions of set members in textual order, + // the latter check works well when iterating through item->additions in + // reverse order. + assert(modifications != nullptr); + rtl::Reference< Data::ExtensionXcu > item( + data_.removeExtensionXcuAdditions(fileUri)); + if (!item.is()) + return; + + for (Additions::reverse_iterator i(item->additions.rbegin()); + i != item->additions.rend(); ++i) + { + rtl::Reference< Node > parent; + NodeMap const * map = &data_.getComponents(); + rtl::Reference< Node > node; + for (auto const& j : *i) + { + parent = node; + node = map->findNode(Data::NO_LAYER, j); + if (!node.is()) { + break; + } + map = &node->getMembers(); + } + if (node.is()) { + assert(parent.is()); + if (parent->kind() == Node::KIND_SET) { + assert( + node->kind() == Node::KIND_GROUP || + node->kind() == Node::KIND_SET); + if (canRemoveFromLayer(item->layer, node)) { + parent->getMembers().erase(i->back()); + data_.modifications.remove(*i); + modifications->add(*i); + } + } + } + } + writeModifications(); +} + +void Components::insertModificationXcuFile( + OUString const & fileUri, + std::set< OUString > const & includedPaths, + std::set< OUString > const & excludedPaths, + Modifications * modifications) +{ + assert(modifications != nullptr); + Partial part(includedPaths, excludedPaths); + try { + parseFileLeniently( + &parseXcuFile, fileUri, Data::NO_LAYER, &part, modifications, nullptr); + } catch (const css::container::NoSuchElementException &) { + TOOLS_WARN_EXCEPTION( + "configmgr", + "error inserting non-existing \"" << fileUri << "\""); + } +} + +css::beans::Optional< css::uno::Any > Components::getExternalValue( + std::u16string_view descriptor) +{ + size_t i = descriptor.find(' '); + if (i == 0 || i == std::u16string_view::npos) { + throw css::uno::RuntimeException( + OUString::Concat("bad external value descriptor ") + descriptor); + } + //TODO: Do not make calls with mutex locked: + OUString name(descriptor.substr(0, i)); + ExternalServices::iterator j(externalServices_.find(name)); + if (j == externalServices_.end()) { + css::uno::Reference< css::uno::XInterface > service; + try { + service = context_->getServiceManager()->createInstanceWithContext( + name, context_); + } catch (const css::uno::RuntimeException &) { + // Assuming these exceptions are real errors: + throw; + } catch (const css::uno::Exception &) { + // Assuming these exceptions indicate that the service is not + // installed: + TOOLS_WARN_EXCEPTION( + "configmgr", + "createInstance(" << name << ") failed"); + } + css::uno::Reference< css::beans::XPropertySet > propset; + if (service.is()) { + propset.set( service, css::uno::UNO_QUERY_THROW); + } + j = externalServices_.emplace(name, propset).first; + } + css::beans::Optional< css::uno::Any > value; + if (j->second.is()) { + try { + if (!(j->second->getPropertyValue(OUString(descriptor.substr(i + 1))) >>= + value)) + { + throw css::uno::RuntimeException( + OUString::Concat("cannot obtain external value through ") + descriptor); + } + } catch (css::beans::UnknownPropertyException & e) { + throw css::uno::RuntimeException( + "unknown external value descriptor ID: " + e.Message); + } catch (css::lang::WrappedTargetException & e) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "cannot obtain external value: " + e.Message, + nullptr, anyEx ); + } + } + return value; +} + +Components::Components( + css::uno::Reference< css::uno::XComponentContext > const & context): + context_(context), sharedExtensionLayer_(-1), userExtensionLayer_(-1), + modificationTarget_(ModificationTarget::None) +{ + assert(context.is()); + lock_ = lock(); + OUString conf(expand("${CONFIGURATION_LAYERS}")); + int layer = 0; + for (sal_Int32 i = 0;;) { + while (i != conf.getLength() && conf[i] == ' ') { + ++i; + } + if (i == conf.getLength()) { + break; + } + if (modificationTarget_ != ModificationTarget::None) { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: modification target layer followed by" + " further layers"); + } + sal_Int32 c = i; + for (;; ++c) { + if (c == conf.getLength() || conf[c] == ' ') { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: missing ':' in \"" + conf + "\""); + } + if (conf[c] == ':') { + break; + } + } + sal_Int32 n = conf.indexOf(' ', c + 1); + if (n == -1) { + n = conf.getLength(); + } + OUString type(conf.copy(i, c - i)); + OUString url(conf.copy(c + 1, n - c - 1)); + if (type == "xcsxcu") { + sal_uInt32 nStartTime = osl_getGlobalTimer(); + parseXcsXcuLayer(layer, url); + SAL_INFO("configmgr", "parseXcsXcuLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms"); + layer += 2; //TODO: overflow + } else if (type == "bundledext") { + parseXcsXcuIniLayer(layer, url, false); + layer += 2; //TODO: overflow + } else if (type == "sharedext") { + if (sharedExtensionLayer_ != -1) { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: multiple \"sharedext\" layers"); + } + sharedExtensionLayer_ = layer; + parseXcsXcuIniLayer(layer, url, true); + layer += 2; //TODO: overflow + } else if (type == "userext") { + if (userExtensionLayer_ != -1) { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: multiple \"userext\" layers"); + } + userExtensionLayer_ = layer; + parseXcsXcuIniLayer(layer, url, true); + layer += 2; //TODO: overflow + } else if (type == "res") { + sal_uInt32 nStartTime = osl_getGlobalTimer(); + parseResLayer(layer, url); + SAL_INFO("configmgr", "parseResLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms"); + ++layer; //TODO: overflow +#if ENABLE_DCONF + } else if (type == "dconf") { + if (url == "!") { + modificationTarget_ = ModificationTarget::Dconf; + dconf::readLayer(data_, Data::NO_LAYER); + } else if (url == "*") { + dconf::readLayer(data_, layer); + } else { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: unknown \"dconf\" kind \"" + url + + "\""); + } + ++layer; //TODO: overflow +#endif +#if defined(_WIN32) + } else if (type == "winreg") { + WinRegType eType; + if (url == "LOCAL_MACHINE" || url.isEmpty()/*backwards comp.*/) { + eType = WinRegType::LOCAL_MACHINE; + } else if (url == "CURRENT_USER") { + eType = WinRegType::CURRENT_USER; + } else { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: unknown \"winreg\" kind \"" + url + + "\""); + } + OUString aTempFileURL; + if (dumpWindowsRegistry(&aTempFileURL, eType)) { + parseFileLeniently(&parseXcuFile, aTempFileURL, layer, nullptr, nullptr, nullptr); + if (!getenv("SAL_CONFIG_WINREG_RETAIN_TMP")) + osl::File::remove(aTempFileURL); + } + ++layer; //TODO: overflow +#endif + } else if (type == "user") { + bool write; + if (url.startsWith("!", &url)) { + write = true; + } else if (url.startsWith("*", &url)) { + write = false; + } else { + write = true; // for backwards compatibility + } + if (url.isEmpty()) { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: empty \"user\" URL"); + } + bool ignore = false; +#if ENABLE_DCONF + if (write) { + OUString token( + expand("${SYSUSERCONFIG}/libreoffice/dconfwrite")); + osl::DirectoryItem it; + osl::FileBase::RC e = osl::DirectoryItem::get(token, it); + ignore = e == osl::FileBase::E_None; + SAL_INFO( + "configmgr", + "dconf write (<" << token << "> " << +e << "): " + << int(ignore)); + if (ignore) { + modificationTarget_ = ModificationTarget::Dconf; + } + } +#endif + if (!ignore) { + if (write) { + modificationTarget_ = ModificationTarget::File; + modificationFileUrl_ = url; + } + parseModificationLayer(write ? Data::NO_LAYER : layer, url); + } + ++layer; //TODO: overflow + } else { + throw css::uno::RuntimeException( + "CONFIGURATION_LAYERS: unknown layer type \"" + type + "\""); + } + i = n; + } +} + +Components::~Components() +{ + // get flag if _exit was already called which is a sign to not secure user config. + // this is used for win only currently where calling _exit() unfortunately still + // calls destructors (what is not wanted). May be needed for other systems, too + // (unknown yet) but can do no harm + const bool bExitWasCalled(comphelper::BackupFileHelper::getExitWasCalled()); + +#ifndef _WIN32 + // we can add a SAL_WARN here for other systems where the destructor gets called after + // an _exit() call. Still safe - the getExitWasCalled() is used, but a hint that _exit + // behaves different on a system + SAL_WARN_IF(bExitWasCalled, "configmgr", "Components::~Components() called after _exit() call"); +#endif + + if (bExitWasCalled) + { + // do not write, re-join threads + osl::MutexGuard g(*lock_); + + if (writeThread_.is()) + { + writeThread_->join(); + } + } + else + { + // write changes + flushModifications(); + } + + for (auto const& rootElem : roots_) + { + rootElem->setAlive(false); + } +} + +void Components::parseFileLeniently( + FileParser * parseFile, OUString const & url, int layer, + Partial const * partial, Modifications * modifications, + Additions * additions) +{ + assert(parseFile != nullptr); + try { + (*parseFile)(url, layer, data_, partial, modifications, additions); + } catch (const css::container::NoSuchElementException &) { + throw; + } catch (const css::uno::Exception &) { //TODO: more specific exception catching + // Ignore invalid XML files, instead of completely preventing OOo from + // starting: + TOOLS_WARN_EXCEPTION( + "configmgr", + "error reading \"" << url << "\""); + } +} + +void Components::parseFiles( + int layer, OUString const & extension, FileParser * parseFile, + OUString const & url, bool recursive) +{ + osl::Directory dir(url); + switch (dir.open()) { + case osl::FileBase::E_None: + break; + case osl::FileBase::E_NOENT: + if (!recursive) { + return; + } + [[fallthrough]]; + default: + throw css::uno::RuntimeException( + "cannot open directory " + url); + } + for (;;) { + osl::DirectoryItem i; + osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32); + if (rc == osl::FileBase::E_NOENT) { + break; + } + if (rc != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "cannot iterate directory " + url); + } + osl::FileStatus stat( + osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName | + osl_FileStatus_Mask_FileURL); + if (i.getFileStatus(stat) != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "cannot stat in directory " + url); + } + if (stat.getFileType() == osl::FileStatus::Directory) { //TODO: symlinks + parseFiles(layer, extension, parseFile, stat.getFileURL(), true); + } else { + OUString file(stat.getFileName()); + if (file.endsWith(extension)) { + try { + parseFileLeniently( + parseFile, stat.getFileURL(), layer, nullptr, nullptr, nullptr); + } catch (css::container::NoSuchElementException & e) { + if (stat.getFileType() == osl::FileStatus::Link) { + SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">"); + continue; + } + throw css::uno::RuntimeException( + "stat'ed file does not exist: " + e.Message); + } + } + } + } +} + +void Components::parseFileList( + int layer, FileParser * parseFile, std::u16string_view urls, + bool recordAdditions) +{ + for (sal_Int32 i = 0;;) { + OUString url(o3tl::getToken(urls, 0, ' ', i)); + if (!url.isEmpty()) { + Additions * adds = nullptr; + if (recordAdditions) { + adds = data_.addExtensionXcuAdditions(url, layer); + } + try { + parseFileLeniently(parseFile, url, layer, nullptr, nullptr, adds); + } catch (const css::container::NoSuchElementException &) { + TOOLS_WARN_EXCEPTION("configmgr", "file does not exist"); + if (adds != nullptr) { + data_.removeExtensionXcuAdditions(url); + } + } + } + if (i == -1) { + break; + } + } +} + +void Components::parseXcdFiles(int layer, OUString const & url) { + osl::Directory dir(url); + switch (dir.open()) { + case osl::FileBase::E_None: + break; + case osl::FileBase::E_NOENT: + return; + default: + throw css::uno::RuntimeException( + "cannot open directory " + url); + } + UnresolvedVector unres; + std::set< OUString > existingDeps; + std::set< OUString > processedDeps; + for (;;) { + osl::DirectoryItem i; + osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32); + if (rc == osl::FileBase::E_NOENT) { + break; + } + if (rc != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "cannot iterate directory " + url); + } + osl::FileStatus stat( + osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName | + osl_FileStatus_Mask_FileURL); + if (i.getFileStatus(stat) != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "cannot stat in directory " + url); + } + if (stat.getFileType() != osl::FileStatus::Directory) { //TODO: symlinks + OUString file(stat.getFileName()); + OUString name; + if (file.endsWith(".xcd", &name)) { + existingDeps.insert(name); + rtl::Reference< ParseManager > manager; + try { + manager = new ParseManager( + stat.getFileURL(), + new XcdParser(layer, processedDeps, data_)); + } catch (css::container::NoSuchElementException & e) { + if (stat.getFileType() == osl::FileStatus::Link) { + SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">"); + continue; + } + throw css::uno::RuntimeException( + "stat'ed file does not exist: " + e.Message); + } + if (manager->parse(nullptr)) { + processedDeps.insert(name); + } else { + unres.emplace_back(name, manager); + } + } + } + } + while (!unres.empty()) { + bool resolved = false; + for (UnresolvedVector::iterator i(unres.begin()); i != unres.end();) { + if (i->manager->parse(&existingDeps)) { + processedDeps.insert(i->name); + i = unres.erase(i); + resolved = true; + } else { + ++i; + } + } + if (!resolved) { + throw css::uno::RuntimeException( + "xcd: unresolved dependencies in " + url); + } + } +} + +void Components::parseXcsXcuLayer(int layer, OUString const & url) { + parseXcdFiles(layer, url); + parseFiles(layer, ".xcs", &parseXcsFile, url + "/schema", false); + parseFiles(layer + 1, ".xcu", &parseXcuFile, url + "/data", false); +} + +void Components::parseXcsXcuIniLayer( + int layer, OUString const & url, bool recordAdditions) +{ + // Check if ini file exists (otherwise .override would still read global + // SCHEMA/DATA variables, which could interfere with unrelated environment + // variables): + if (rtl::Bootstrap(url).getHandle() == nullptr) return; + + OUStringBuffer prefix("${.override:"); + for (sal_Int32 i = 0; i != url.getLength(); ++i) { + sal_Unicode c = url[i]; + switch (c) { + case '$': + case ':': + case '\\': + prefix.append('\\'); + [[fallthrough]]; + default: + prefix.append(c); + } + } + prefix.append(':'); + OUString urls(prefix + "SCHEMA}"); + rtl::Bootstrap::expandMacros(urls); + if (!urls.isEmpty()) { + parseFileList(layer, &parseXcsFile, urls, false); + } + urls = prefix.makeStringAndClear() + "DATA}"; + rtl::Bootstrap::expandMacros(urls); + if (!urls.isEmpty()) { + parseFileList(layer + 1, &parseXcuFile, urls, recordAdditions); + } +} + +void Components::parseResLayer(int layer, std::u16string_view url) { + OUString resUrl(OUString::Concat(url) + "/res"); + parseXcdFiles(layer, resUrl); + parseFiles(layer, ".xcu", &parseXcuFile, resUrl, false); +} + +void Components::parseModificationLayer(int layer, OUString const & url) { + try { + parseFileLeniently(&parseXcuFile, url, layer, nullptr, nullptr, nullptr); + } catch (css::container::NoSuchElementException &) { + SAL_INFO( + "configmgr", "user registrymodifications.xcu does not (yet) exist"); + // Migrate old user layer data (can be removed once migration is no + // longer relevant, probably OOo 4; also see hack for xsi namespace in + // xmlreader::XmlReader::registerNamespaceIri): + parseFiles( + layer, ".xcu", &parseXcuFile, + expand( + "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") + ":UserInstallation}/user/registry/data"), + false); + } +} + +int Components::getExtensionLayer(bool shared) const { + int layer = shared ? sharedExtensionLayer_ : userExtensionLayer_; + if (layer == -1) { + throw css::uno::RuntimeException( + "insert extension xcs/xcu file into undefined layer"); + } + return layer; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/components.hxx b/configmgr/source/components.hxx new file mode 100644 index 000000000..5d7b6b596 --- /dev/null +++ b/configmgr/source/components.hxx @@ -0,0 +1,166 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> +#include <string_view> + +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <rtl/ref.hxx> +#include <o3tl/sorted_vector.hxx> + +#include "additions.hxx" +#include "data.hxx" +#include "modifications.hxx" + +namespace com::sun::star { + namespace beans { class XPropertySet; } + namespace uno { + class Any; + class XComponentContext; + } +} + +namespace configmgr { + +class Broadcaster; +class Node; +class Partial; +class RootAccess; + +class Components { +public: + static Components & getSingleton( + css::uno::Reference< css::uno::XComponentContext > const & context); + + static bool allLocales(std::u16string_view locale); + + rtl::Reference< Node > resolvePathRepresentation( + OUString const & pathRepresentation, + OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer) + const; + + rtl::Reference< Node > getTemplate( OUString const & fullName) const; + + void addRootAccess(rtl::Reference< RootAccess > const & access); + + void removeRootAccess(RootAccess * access); + + void initGlobalBroadcaster( + Modifications const & modifications, + rtl::Reference< RootAccess > const & exclude, + Broadcaster * broadcaster); + + void addModification(std::vector<OUString> const & path); + + void writeModifications(); + + void flushModifications(); + // must be called with configmgr::lock unacquired; must be called before + // shutdown if writeModifications has ever been called (probably + // indirectly, via removeExtensionXcuFile) + + void insertExtensionXcsFile(bool shared, OUString const & fileUri); + + void insertExtensionXcuFile( + bool shared, OUString const & fileUri, + Modifications * modifications); + + void removeExtensionXcuFile( + OUString const & fileUri, Modifications * modifications); + + void insertModificationXcuFile( + OUString const & fileUri, + std::set< OUString > const & includedPaths, + std::set< OUString > const & excludedPaths, + Modifications * modifications); + + css::beans::Optional< css::uno::Any > + getExternalValue(std::u16string_view descriptor); + +private: + Components(const Components&) = delete; + Components& operator=(const Components&) = delete; + + typedef void FileParser( + OUString const &, int, Data &, Partial const *, Modifications *, + Additions *); +public: + explicit Components( + css::uno::Reference< css::uno::XComponentContext > const & context); + + ~Components(); +private: + + void parseFileLeniently( + FileParser * parseFile, OUString const & url, int layer, + Partial const * partial, Modifications * modifications, + Additions * additions); + + void parseFiles( + int layer, OUString const & extension, FileParser * parseFile, + OUString const & url, bool recursive); + + void parseFileList( + int layer, FileParser * parseFile, std::u16string_view urls, + bool recordAdditions); + + void parseXcdFiles(int layer, OUString const & url); + + void parseXcsXcuLayer(int layer, OUString const & url); + + void parseXcsXcuIniLayer( + int layer, OUString const & url, bool recordAdditions); + + void parseResLayer(int layer, std::u16string_view url); + + void parseModificationLayer(int layer, OUString const & url); + + int getExtensionLayer(bool shared) const; + + typedef + config_map< + css::uno::Reference< + css::beans::XPropertySet > > + ExternalServices; + + class WriteThread; + + enum class ModificationTarget { None, File, Dconf }; + + css::uno::Reference< css::uno::XComponentContext > + context_; + Data data_; + o3tl::sorted_vector< RootAccess * > roots_; + ExternalServices externalServices_; + rtl::Reference< WriteThread > writeThread_; + int sharedExtensionLayer_; + int userExtensionLayer_; + ModificationTarget modificationTarget_; + OUString modificationFileUrl_; + std::shared_ptr<osl::Mutex> lock_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/config_map.hxx b/configmgr/source/config_map.hxx new file mode 100644 index 000000000..5d2990d5a --- /dev/null +++ b/configmgr/source/config_map.hxx @@ -0,0 +1,37 @@ +/* -*- 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/. + */ +#ifndef CONFIG_MAP_HXX +#define CONFIG_MAP_HXX + +#include <map> +#include <rtl/ustring.hxx> + +// The realisation here is that while a map is a reasonably compact +// representation, there is often no need to have it completely +// sorted, so we can use a fast in-line length comparison as the +// initial compare, rather than sorting of sub string contents. + +struct LengthContentsCompare +{ + bool operator()(std::u16string_view a, std::u16string_view b) const + { + if (a.size() == b.size()) + return a < b; + else + return a.size() < b.size(); + } +}; + +template <class T> struct config_map : public std::map<OUString, T, LengthContentsCompare> +{ +}; + +#endif // CONFIG_MAP_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/configmgr.component b/configmgr/source/configmgr.component new file mode 100644 index 000000000..ff46b6070 --- /dev/null +++ b/configmgr/source/configmgr.component @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.configuration.ConfigurationProvider" + constructor="com_sun_star_comp_configuration_ConfigurationProvider_get_implementation"> + <service name="com.sun.star.configuration.ConfigurationProvider"/> + </implementation> + <implementation name="com.sun.star.comp.configuration.ConfigurationRegistry" + constructor="com_sun_star_comp_configuration_ConfigurationRegistry_get_implementation"> + <service name="com.sun.star.configuration.ConfigurationRegistry"/> + </implementation> + <implementation name="com.sun.star.comp.configuration.DefaultProvider" + constructor="com_sun_star_comp_configuration_DefaultProvider_get_implementation" + single-instance="true"> + <service name="com.sun.star.configuration.DefaultProvider"/> + <singleton name="com.sun.star.configuration.theDefaultProvider"/> + </implementation> + <implementation name="com.sun.star.comp.configuration.ReadOnlyAccess" + constructor="com_sun_star_comp_configuration_ReadOnlyAccess_get_implementation"> + <service name="com.sun.star.configuration.ReadOnlyAccess"/> + </implementation> + <implementation name="com.sun.star.comp.configuration.ReadWriteAccess" + constructor="com_sun_star_comp_configuration_ReadWriteAccess_get_implementation"> + <service name="com.sun.star.configuration.ReadWriteAccess"/> + </implementation> + <implementation name="com.sun.star.comp.configuration.Update" + constructor="com_sun_star_comp_configuration_Update_get_implementation"> + <service name="com.sun.star.configuration.Update_Service"/> + <singleton name="com.sun.star.configuration.Update"/> + </implementation> +</component> diff --git a/configmgr/source/configurationprovider.cxx b/configmgr/source/configurationprovider.cxx new file mode 100644 index 000000000..b8bdcb426 --- /dev/null +++ b/configmgr/source/configurationprovider.cxx @@ -0,0 +1,403 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XLocalizable.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/XFlushListener.hpp> +#include <com/sun/star/util/XFlushable.hpp> +#include <com/sun/star/util/XRefreshListener.hpp> +#include <com/sun/star/util/XRefreshable.hpp> +#include <cppu/unotype.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <sal/types.h> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> + +#include <i18nlangtag/languagetag.hxx> +#include <utility> + +#include "components.hxx" +#include "configurationprovider.hxx" +#include "lock.hxx" +#include "defaultprovider.hxx" +#include "rootaccess.hxx" + +namespace configmgr::configuration_provider { + +namespace { + +constexpr OUStringLiteral accessServiceName = + u"com.sun.star.configuration.ConfigurationAccess"; +constexpr OUStringLiteral updateAccessServiceName = + u"com.sun.star.configuration.ConfigurationUpdateAccess"; + +void badNodePath() { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider expects a single," + " non-empty, string nodepath argument"), + nullptr); +} + +typedef + cppu::WeakComponentImplHelper< + css::lang::XServiceInfo, css::lang::XMultiServiceFactory, + css::util::XRefreshable, css::util::XFlushable, + css::lang::XLocalizable > + ServiceBase; + +class Service: + private cppu::BaseMutex, public ServiceBase +{ +public: + explicit Service( + const css::uno::Reference< css::uno::XComponentContext >& context): + ServiceBase(m_aMutex), context_(context), default_(true), + lock_( lock() ) + { + assert(context.is()); + } + + Service( + const css::uno::Reference< css::uno::XComponentContext >& context, + OUString locale): + ServiceBase(m_aMutex), context_(context), locale_(std::move(locale)), + default_(false), + lock_( lock() ) + { + assert(context.is()); + } + +private: + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + virtual ~Service() override {} + + virtual void SAL_CALL disposing() override { flushModifications(); } + + virtual OUString SAL_CALL getImplementationName() override + { + return default_ + ? default_provider::getImplementationName() + : "com.sun.star.comp.configuration.ConfigurationProvider"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override + { + return default_ + ? default_provider::getSupportedServiceNames() + : css::uno::Sequence<OUString> { "com.sun.star.configuration.ConfigurationProvider" }; + } + + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance( + OUString const & aServiceSpecifier) override; + + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL + createInstanceWithArguments( + OUString const & ServiceSpecifier, + css::uno::Sequence< css::uno::Any > const & Arguments) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getAvailableServiceNames() override; + + virtual void SAL_CALL refresh() override; + + virtual void SAL_CALL addRefreshListener( + css::uno::Reference< css::util::XRefreshListener > const & l) override; + + virtual void SAL_CALL removeRefreshListener( + css::uno::Reference< css::util::XRefreshListener > const & l) override; + + virtual void SAL_CALL flush() override; + + virtual void SAL_CALL addFlushListener( + css::uno::Reference< css::util::XFlushListener > const & l) override; + + virtual void SAL_CALL removeFlushListener( + css::uno::Reference< css::util::XFlushListener > const & l) override; + + virtual void SAL_CALL setLocale(css::lang::Locale const & eLocale) override; + + virtual css::lang::Locale SAL_CALL getLocale() override; + + void flushModifications() const; + + css::uno::Reference< css::uno::XComponentContext > context_; + OUString locale_; + bool default_; + std::shared_ptr<osl::Mutex> lock_; +}; + +css::uno::Reference< css::uno::XInterface > Service::createInstance( + OUString const & aServiceSpecifier) +{ + return createInstanceWithArguments( + aServiceSpecifier, css::uno::Sequence< css::uno::Any >()); +} + +css::uno::Reference< css::uno::XInterface > +Service::createInstanceWithArguments( + OUString const & ServiceSpecifier, + css::uno::Sequence< css::uno::Any > const & Arguments) +{ + OUString nodepath; + OUString locale; + for (sal_Int32 i = 0; i < Arguments.getLength(); ++i) { + css::beans::NamedValue v1; + css::beans::PropertyValue v2; + OUString name; + css::uno::Any value; + if (Arguments[i] >>= v1) { + name = v1.Name; + value = v1.Value; + } else if (Arguments[i] >>= v2) { + name = v2.Name; + value = v2.Value; + } else if (Arguments.getLength() == 1 && (Arguments[i] >>= nodepath)) { + // For backwards compatibility, allow a single string argument that + // denotes nodepath. + if (nodepath.isEmpty()) { + badNodePath(); + } + break; + } else { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider expects" + " NamedValue or PropertyValue arguments"), + nullptr); + } + // For backwards compatibility, allow "nodepath" and "Locale" in any + // case: + if (name.equalsIgnoreAsciiCase("nodepath")) { + if (!nodepath.isEmpty() || !(value >>= nodepath) || + nodepath.isEmpty()) + { + badNodePath(); + } + } else if (name.equalsIgnoreAsciiCase("locale")) { + if (!locale.isEmpty() || !(value >>= locale) || + locale.isEmpty()) + { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider expects" + " at most one, non-empty, string Locale argument"), + nullptr); + } + } + } + if (nodepath.isEmpty()) { + badNodePath(); + } + // For backwards compatibility, allow a nodepath that misses the leading + // slash: + if (nodepath[0] != '/') { + nodepath = "/" + nodepath; + } + if (locale.isEmpty()) { + //TODO: should the Access use the dynamically changing locale_ instead? + locale = locale_; + if (locale.isEmpty()) { + locale = "en-US"; + } + } + bool update; + if (ServiceSpecifier == accessServiceName) { + update = false; + } else if (ServiceSpecifier == updateAccessServiceName) { + update = true; + } else { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider does not support" + " service " + ServiceSpecifier), + static_cast< cppu::OWeakObject * >(this)); + } + osl::MutexGuard guard(*lock_); + Components & components = Components::getSingleton(context_); + rtl::Reference root( + new RootAccess(components, nodepath, locale, update)); + if (root->isValue()) { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider: there is a leaf" + " value at nodepath " + nodepath), + static_cast< cppu::OWeakObject * >(this)); + } + components.addRootAccess(root); + return static_cast< cppu::OWeakObject * >(root.get()); +} + +css::uno::Sequence< OUString > Service::getAvailableServiceNames() +{ + return { accessServiceName, updateAccessServiceName }; +} + +void Service::refresh() { + //TODO + cppu::OInterfaceContainerHelper * cont = rBHelper.getContainer( + cppu::UnoType< css::util::XRefreshListener >::get()); + if (cont != nullptr) { + css::lang::EventObject ev(static_cast< cppu::OWeakObject * >(this)); + cont->notifyEach(&css::util::XRefreshListener::refreshed, ev); + } +} + +void Service::addRefreshListener( + css::uno::Reference< css::util::XRefreshListener > const & l) +{ + rBHelper.addListener( + cppu::UnoType< css::util::XRefreshListener >::get(), l); +} + +void Service::removeRefreshListener( + css::uno::Reference< css::util::XRefreshListener > const & l) +{ + rBHelper.removeListener( + cppu::UnoType< css::util::XRefreshListener >::get(), l); +} + +void Service::flush() { + flushModifications(); + cppu::OInterfaceContainerHelper * cont = rBHelper.getContainer( + cppu::UnoType< css::util::XFlushListener >::get()); + if (cont != nullptr) { + css::lang::EventObject ev(static_cast< cppu::OWeakObject * >(this)); + cont->notifyEach(&css::util::XFlushListener::flushed, ev); + } +} + +void Service::addFlushListener( + css::uno::Reference< css::util::XFlushListener > const & l) +{ + rBHelper.addListener(cppu::UnoType< css::util::XFlushListener >::get(), l); +} + +void Service::removeFlushListener( + css::uno::Reference< css::util::XFlushListener > const & l) +{ + rBHelper.removeListener( + cppu::UnoType< css::util::XFlushListener >::get(), l); +} + +void Service::setLocale(css::lang::Locale const & eLocale) +{ + osl::MutexGuard guard(*lock_); + locale_ = LanguageTag::convertToBcp47( eLocale, false); +} + +css::lang::Locale Service::getLocale() { + osl::MutexGuard guard(*lock_); + css::lang::Locale loc; + if (! locale_.isEmpty()) { + loc = LanguageTag::convertToLocale( locale_, false); + } + return loc; +} + +void Service::flushModifications() const { + Components * components; + { + osl::MutexGuard guard(*lock_); + components = &Components::getSingleton(context_); + } + components->flushModifications(); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_configuration_ConfigurationProvider_get_implementation( + css::uno::XComponentContext* Context, css::uno::Sequence<css::uno::Any> const& Arguments) +{ + if (!Arguments.hasElements()) { + auto p = css::configuration::theDefaultProvider::get(Context); + p->acquire(); + return p.get(); + } else { + OUString locale; + for (sal_Int32 i = 0; i < Arguments.getLength(); ++i) { + css::beans::NamedValue v1; + css::beans::PropertyValue v2; + OUString name; + css::uno::Any value; + if (Arguments[i] >>= v1) { + name = v1.Name; + value = v1.Value; + } else if (Arguments[i] >>= v2) { + name = v2.Name; + value = v2.Value; + } else { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider factory" + " expects NamedValue or PropertyValue arguments"), + nullptr); + } + // For backwards compatibility, allow "Locale" and (ignored) + // "EnableAsync" in any case: + if (name.equalsIgnoreAsciiCase("locale")) { + if (!locale.isEmpty() || !(value >>= locale) || + locale.isEmpty()) + { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider" + " factory expects at most one, non-empty, string" + " Locale argument"), + nullptr); + } + } else if (!name.equalsIgnoreAsciiCase("enableasync")) { + throw css::uno::Exception( + ("com.sun.star.configuration.ConfigurationProvider factory:" + " unknown argument " + name), + nullptr); + } + } + return cppu::acquire(new Service(Context, locale)); + } +} + +} + +css::uno::Reference< css::uno::XInterface > createDefault( + css::uno::Reference< css::uno::XComponentContext > const & context) +{ + return static_cast< cppu::OWeakObject * >(new Service(context)); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/configurationprovider.hxx b/configmgr/source/configurationprovider.hxx new file mode 100644 index 000000000..96d40b895 --- /dev/null +++ b/configmgr/source/configurationprovider.hxx @@ -0,0 +1,41 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> + +namespace com::sun::star { + namespace uno { + class XComponentContext; + class XInterface; + } +} + +namespace configmgr::configuration_provider { + +css::uno::Reference< css::uno::XInterface > createDefault( + css::uno::Reference< css::uno::XComponentContext > + const & context); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/configurationregistry.cxx b/configmgr/source/configurationregistry.cxx new file mode 100644 index 000000000..b2fd214f3 --- /dev/null +++ b/configmgr/source/configurationregistry.cxx @@ -0,0 +1,641 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/registry/InvalidRegistryException.hpp> +#include <com/sun/star/registry/InvalidValueException.hpp> +#include <com/sun/star/registry/RegistryKeyType.hpp> +#include <com/sun/star/registry/RegistryValueType.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/registry/XSimpleRegistry.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/TypeClass.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/XFlushable.hpp> +#include <cppu/unotype.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <mutex> +#include <rtl/ustring.hxx> +#include <utility> +#include <sal/types.h> + +namespace com::sun::star::util { + class XFlushListener; +} + +namespace configmgr::configuration_registry { + +namespace { + +class Service: + public cppu::WeakImplHelper< + css::lang::XServiceInfo, css::registry::XSimpleRegistry, + css::util::XFlushable > +{ +public: + explicit Service(css::uno::Reference< css::uno::XComponentContext > const & context); + +private: + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + virtual ~Service() override {} + + virtual OUString SAL_CALL getImplementationName() override + { return "com.sun.star.comp.configuration.ConfigurationRegistry"; } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override + { return { "com.sun.star.configuration.ConfigurationRegistry" }; } + + virtual OUString SAL_CALL getURL() override; + + virtual void SAL_CALL open( + OUString const & rURL, sal_Bool bReadOnly, sal_Bool) override; + + virtual sal_Bool SAL_CALL isValid() override; + + virtual void SAL_CALL close() override; + + virtual void SAL_CALL destroy() override; + + virtual css::uno::Reference< css::registry::XRegistryKey > SAL_CALL + getRootKey() override; + + virtual sal_Bool SAL_CALL isReadOnly() override; + + virtual void SAL_CALL mergeKey(OUString const &, OUString const &) override; + + virtual void SAL_CALL flush() override; + + virtual void SAL_CALL addFlushListener( + css::uno::Reference< css::util::XFlushListener > const &) override; + + virtual void SAL_CALL removeFlushListener( + css::uno::Reference< css::util::XFlushListener > const &) override; + + void checkValid(); + + void checkValid_RuntimeException(); + + void doClose(); + + css::uno::Reference< css::lang::XMultiServiceFactory > provider_; + std::mutex mutex_; + css::uno::Reference< css::uno::XInterface > access_; + OUString url_; + bool readOnly_; + + friend class RegistryKey; +}; + +class RegistryKey: + public cppu::WeakImplHelper< css::registry::XRegistryKey > +{ +public: + RegistryKey(Service & service, css::uno::Any value): + service_(service), value_(std::move(value)) {} + +private: + RegistryKey(const RegistryKey&) = delete; + RegistryKey& operator=(const RegistryKey&) = delete; + + virtual ~RegistryKey() override {} + + virtual OUString SAL_CALL getKeyName() override; + + virtual sal_Bool SAL_CALL isReadOnly() override; + + virtual sal_Bool SAL_CALL isValid() override; + + virtual css::registry::RegistryKeyType SAL_CALL getKeyType( + OUString const &) override; + + virtual css::registry::RegistryValueType SAL_CALL getValueType() override; + + virtual sal_Int32 SAL_CALL getLongValue() override; + + virtual void SAL_CALL setLongValue(sal_Int32) override; + + virtual css::uno::Sequence< sal_Int32 > SAL_CALL getLongListValue() override; + + virtual void SAL_CALL setLongListValue( + css::uno::Sequence< sal_Int32 > const &) override; + + virtual OUString SAL_CALL getAsciiValue() override; + + virtual void SAL_CALL setAsciiValue(OUString const &) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getAsciiListValue() override; + + virtual void SAL_CALL setAsciiListValue( + css::uno::Sequence< OUString > const &) override; + + virtual OUString SAL_CALL getStringValue() override; + + virtual void SAL_CALL setStringValue(OUString const &) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getStringListValue() override; + + virtual void SAL_CALL setStringListValue( + css::uno::Sequence< OUString > const &) override; + + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getBinaryValue() override; + + virtual void SAL_CALL setBinaryValue(css::uno::Sequence< sal_Int8 > const &) override; + + virtual css::uno::Reference< css::registry::XRegistryKey > SAL_CALL openKey( + OUString const & aKeyName) override; + + virtual css::uno::Reference< css::registry::XRegistryKey > SAL_CALL + createKey(OUString const &) override; + + virtual void SAL_CALL closeKey() override; + + virtual void SAL_CALL deleteKey(OUString const &) override; + + virtual + css::uno::Sequence< css::uno::Reference< css::registry::XRegistryKey > > + SAL_CALL openKeys() override; + + virtual css::uno::Sequence< OUString > SAL_CALL getKeyNames() override; + + virtual sal_Bool SAL_CALL createLink( + OUString const &, OUString const &) override; + + virtual void SAL_CALL deleteLink(OUString const &) override; + + virtual OUString SAL_CALL getLinkTarget(OUString const &) override; + + virtual OUString SAL_CALL getResolvedName( + OUString const & aKeyName) override; + + Service & service_; + css::uno::Any value_; +}; + +Service::Service( + css::uno::Reference< css::uno::XComponentContext > const & context) + : readOnly_(false) +{ + assert(context.is()); + try { + provider_.set( + context->getServiceManager()->createInstanceWithContext( + "com.sun.star.configuration.DefaultProvider", context), + css::uno::UNO_QUERY_THROW); + } catch (css::uno::RuntimeException &) { + throw; + } catch (css::uno::Exception & e) { + throw css::uno::DeploymentException( + ("component context fails to supply service" + " com.sun.star.configuration.DefaultProvider of type" + " com.sun.star.lang.XMultiServiceFactory: " + e.Message), + context); + } +} + +OUString Service::getURL() { + std::unique_lock g(mutex_); + checkValid_RuntimeException(); + return url_; +} + +void Service::open(OUString const & rURL, sal_Bool bReadOnly, sal_Bool) +{ + //TODO: bCreate + std::unique_lock g(mutex_); + if (access_.is()) { + doClose(); + } + css::uno::Sequence< css::uno::Any > args{ css::uno::Any( + css::beans::NamedValue("nodepath", css::uno::Any(rURL))) }; + try { + access_ = provider_->createInstanceWithArguments( + (bReadOnly + ? OUString("com.sun.star.configuration.ConfigurationAccess") + : OUString( + "com.sun.star.configuration.ConfigurationUpdateAccess")), + args); + } catch (css::uno::RuntimeException &) { + throw; + } catch (css::uno::Exception & e) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: open failed: " + + e.Message, + static_cast< cppu::OWeakObject * >(this), anyEx ); + } + url_ = rURL; + readOnly_ = bReadOnly; +} + +sal_Bool Service::isValid() { + std::unique_lock g(mutex_); + return access_.is(); +} + +void Service::close() +{ + std::unique_lock g(mutex_); + checkValid(); + doClose(); +} + +void Service::destroy() +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Reference< css::registry::XRegistryKey > Service::getRootKey() +{ + std::unique_lock g(mutex_); + checkValid(); + return new RegistryKey(*this, css::uno::Any(access_)); +} + +sal_Bool Service::isReadOnly() { + std::unique_lock g(mutex_); + checkValid_RuntimeException(); + return readOnly_; +} + +void Service::mergeKey(OUString const &, OUString const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +void Service::flush() +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +void Service::addFlushListener( + css::uno::Reference< css::util::XFlushListener > const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +void Service::removeFlushListener( + css::uno::Reference< css::util::XFlushListener > const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +void Service::checkValid() { + if (!access_.is()) { + throw css::registry::InvalidRegistryException( + "com.sun.star.configuration.ConfigurationRegistry: not valid", + static_cast< cppu::OWeakObject * >(this)); + } +} + +void Service::checkValid_RuntimeException() { + if (!access_.is()) { + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not valid", + static_cast< cppu::OWeakObject * >(this)); + } +} + +void Service::doClose() { + access_.clear(); +} + +OUString RegistryKey::getKeyName() { + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); + css::uno::Reference< css::container::XNamed > named; + if (value_ >>= named) { + return named->getName(); + } + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +sal_Bool RegistryKey::isReadOnly() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); + return service_.readOnly_; //TODO: read-only sub-nodes in update access? +} + +sal_Bool RegistryKey::isValid() { + return service_.isValid(); +} + +css::registry::RegistryKeyType RegistryKey::getKeyType(OUString const &) +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + return css::registry::RegistryKeyType_KEY; +} + +css::registry::RegistryValueType RegistryKey::getValueType() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + css::uno::Type t(value_.getValueType()); + switch (t.getTypeClass()) { + case css::uno::TypeClass_LONG: + return css::registry::RegistryValueType_LONG; + case css::uno::TypeClass_STRING: + return css::registry::RegistryValueType_STRING; + case css::uno::TypeClass_SEQUENCE: + if (t == cppu::UnoType< css::uno::Sequence< sal_Int8 > >::get()) { + return css::registry::RegistryValueType_BINARY; + } else if (t == cppu::UnoType< css::uno::Sequence< sal_Int32 > >::get()) + { + return css::registry::RegistryValueType_LONGLIST; + } else if (t == + cppu::UnoType< css::uno::Sequence< OUString > >::get()) + { + return css::registry::RegistryValueType_STRINGLIST; + } + [[fallthrough]]; + default: + return css::registry::RegistryValueType_NOT_DEFINED; + } +} + +sal_Int32 RegistryKey::getLongValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + sal_Int32 v = 0; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setLongValue(sal_Int32) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Sequence< sal_Int32 > RegistryKey::getLongListValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + css::uno::Sequence< sal_Int32 > v; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setLongListValue(css::uno::Sequence< sal_Int32 > const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +OUString RegistryKey::getAsciiValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + OUString v; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setAsciiValue(OUString const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Sequence< OUString > RegistryKey::getAsciiListValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + css::uno::Sequence< OUString > v; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setAsciiListValue(css::uno::Sequence< OUString > const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +OUString RegistryKey::getStringValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + OUString v; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setStringValue(OUString const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Sequence< OUString > RegistryKey::getStringListValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + css::uno::Sequence< OUString > v; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setStringListValue( + css::uno::Sequence< OUString > const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Sequence< sal_Int8 > RegistryKey::getBinaryValue() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid(); + css::uno::Sequence< sal_Int8 > v; + if (value_ >>= v) { + return v; + } + throw css::registry::InvalidValueException( + "com.sun.star.configuration.ConfigurationRegistry", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::setBinaryValue(css::uno::Sequence< sal_Int8 > const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Reference< css::registry::XRegistryKey > RegistryKey::openKey( + OUString const & aKeyName) +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); + css::uno::Reference< css::container::XHierarchicalNameAccess > access; + if (value_ >>= access) { + try { + return new RegistryKey( + service_, access->getByHierarchicalName(aKeyName)); + } catch (css::container::NoSuchElementException &) {} + } + return css::uno::Reference< css::registry::XRegistryKey >(); +} + +css::uno::Reference< css::registry::XRegistryKey > RegistryKey::createKey( + OUString const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +void RegistryKey::closeKey() +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); +} + +void RegistryKey::deleteKey(OUString const &) +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Sequence< css::uno::Reference< css::registry::XRegistryKey > > +RegistryKey::openKeys() +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +css::uno::Sequence< OUString > RegistryKey::getKeyNames() +{ + throw css::uno::RuntimeException( + "com.sun.star.configuration.ConfigurationRegistry: not implemented", + static_cast< cppu::OWeakObject * >(this)); +} + +sal_Bool RegistryKey::createLink(OUString const &, OUString const &) +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); + return false; +} + +void RegistryKey::deleteLink(OUString const &) +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); +} + +OUString RegistryKey::getLinkTarget(OUString const &) +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); + return OUString(); +} + +OUString RegistryKey::getResolvedName(OUString const & aKeyName) +{ + std::unique_lock g(service_.mutex_); + service_.checkValid_RuntimeException(); + return aKeyName; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_configuration_ConfigurationRegistry_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new Service(context)); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/data.cxx b/configmgr/source/data.cxx new file mode 100644 index 000000000..f173ee155 --- /dev/null +++ b/configmgr/source/data.cxx @@ -0,0 +1,336 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <cstddef> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ref.hxx> +#include <rtl/string.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <o3tl/string_view.hxx> + +#include "additions.hxx" +#include "data.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "rootnode.hxx" +#include "setnode.hxx" + +namespace configmgr { + +namespace { + +bool decode( + std::u16string_view encoded, std::size_t begin, std::size_t end, + OUString * decoded) +{ + assert( + begin <= end && end <= encoded.size() && + decoded != nullptr); + OUStringBuffer buf(end - begin); + while (begin != end) { + sal_Unicode c = encoded[begin++]; + if (c == '&') { + if (o3tl::starts_with(encoded.substr(begin), u"amp;")) { + buf.append('&'); + begin += RTL_CONSTASCII_LENGTH("amp;"); + } else if (o3tl::starts_with(encoded.substr(begin), u"quot;")) { + buf.append('"'); + begin += RTL_CONSTASCII_LENGTH("quot;"); + } else if (o3tl::starts_with(encoded.substr(begin), u"apos;")) { + buf.append('\''); + begin += RTL_CONSTASCII_LENGTH("apos;"); + } else { + return false; + } + assert(begin <= end); + } else { + buf.append(c); + } + } + *decoded = buf.makeStringAndClear(); + return true; +} + +} + +OUString Data::createSegment( + std::u16string_view templateName, OUString const & name) +{ + if (templateName.empty()) { + return name; + } + OUStringBuffer buf(128); + buf.append(templateName); + //TODO: verify template name contains no bad chars? + buf.append("['"); + for (sal_Int32 i = 0; i < name.getLength(); ++i) { + sal_Unicode c = name[i]; + switch (c) { + case '&': + buf.append("&"); + break; + case '"': + buf.append("""); + break; + case '\'': + buf.append("'"); + break; + default: + buf.append(c); + break; + } + } + buf.append("']"); + return buf.makeStringAndClear(); +} + +sal_Int32 Data::parseSegment( + OUString const & path, sal_Int32 index, OUString * name, + bool * setElement, OUString * templateName) +{ + assert( + index >= 0 && index <= path.getLength() && name != nullptr && + setElement != nullptr); + sal_Int32 i = index; + while (i < path.getLength() && path[i] != '/' && path[i] != '[') { + ++i; + } + if (i == path.getLength() || path[i] == '/') { + *name = path.copy(index, i - index); + *setElement = false; + return i; + } + if (templateName != nullptr) { + if (i - index == 1 && path[index] == '*') { + templateName->clear(); + } else { + *templateName = path.copy(index, i - index); + } + } + if (++i == path.getLength()) { + return -1; + } + sal_Unicode del = path[i++]; + if (del != '\'' && del != '"') { + return -1; + } + sal_Int32 j = path.indexOf(del, i); + if (j == -1 || j + 1 == path.getLength() || path[j + 1] != ']' || + !decode(path, i, j, name)) + { + return -1; + } + *setElement = true; + return j + 2; +} + +OUString Data::fullTemplateName( + std::u16string_view component, std::u16string_view name) +{ + if (component.find(':') != std::u16string_view::npos || name.find(':') != std::u16string_view::npos) { + throw css::uno::RuntimeException( + OUString::Concat("bad component/name pair containing colon ") + component + "/" + + name); + } + return OUString::Concat(component) + ":" + name; +} + +bool Data::equalTemplateNames( + OUString const & shortName, OUString const & longName) +{ + if (shortName.indexOf(':') == -1) { + sal_Int32 i = longName.indexOf(':') + 1; + assert(i > 0); + return + rtl_ustr_compare_WithLength( + shortName.getStr(), shortName.getLength(), + longName.getStr() + i, longName.getLength() - i) == + 0; + } else { + return shortName == longName; + } +} + +Data::Data(): root_(new RootNode) {} + +rtl::Reference< Node > Data::resolvePathRepresentation( + OUString const & pathRepresentation, + OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer) + const +{ + if (pathRepresentation.isEmpty() || pathRepresentation[0] != '/') { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + if (path != nullptr) { + path->clear(); + } + if (pathRepresentation == "/") { + if (canonicRepresentation != nullptr) { + *canonicRepresentation = pathRepresentation; + } + if (finalizedLayer != nullptr) { + *finalizedLayer = NO_LAYER; + } + return root_; + } + OUString seg; + bool setElement; + OUString templateName; + sal_Int32 n = parseSegment(pathRepresentation, 1, &seg, &setElement, nullptr); + if (n == -1 || setElement) + { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + NodeMap const & components = getComponents(); + NodeMap::const_iterator i(components.find(seg)); + OUStringBuffer canonic(128); + rtl::Reference< Node > parent; + int finalized = NO_LAYER; + for (rtl::Reference< Node > p(i == components.end() ? nullptr : i->second);;) { + if (!p.is()) { + return p; + } + if (canonicRepresentation != nullptr) { + canonic.append('/'); + canonic.append(createSegment(templateName, seg)); + } + if (path != nullptr) { + path->push_back(seg); + } + finalized = std::min(finalized, p->getFinalized()); + if (n != pathRepresentation.getLength() && + pathRepresentation[n++] != '/') + { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + // for backwards compatibility, ignore a final slash + if (n == pathRepresentation.getLength()) { + if (canonicRepresentation != nullptr) { + *canonicRepresentation = canonic.makeStringAndClear(); + } + if (finalizedLayer != nullptr) { + *finalizedLayer = finalized; + } + return p; + } + parent = p; + templateName.clear(); + n = parseSegment( + pathRepresentation, n, &seg, &setElement, &templateName); + if (n == -1) { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + // For backwards compatibility, allow set members to be accessed with + // simple path segments, like group members: + p = p->getMember(seg); + if (setElement) { + switch (parent->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + if (!templateName.isEmpty()) { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + break; + case Node::KIND_SET: + if (!templateName.isEmpty() && + !static_cast< SetNode * >(parent.get())->isValidTemplate( + templateName)) + { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + break; + default: + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + if (!templateName.isEmpty() && p != nullptr) { + assert(!p->getTemplateName().isEmpty()); + if (!equalTemplateNames(templateName, p->getTemplateName())) { + throw css::uno::RuntimeException( + "bad path " + pathRepresentation); + } + } + } + } +} + +rtl::Reference< Node > Data::getTemplate( + int layer, OUString const & fullName) const +{ + return templates.findNode(layer, fullName); +} + +NodeMap & Data::getComponents() const { + return root_->getMembers(); +} + +Additions * Data::addExtensionXcuAdditions( + OUString const & url, int layer) +{ + rtl::Reference item(new ExtensionXcu); + ExtensionXcuAdditions::iterator i( + extensionXcuAdditions_.emplace( + url, rtl::Reference< ExtensionXcu >()).first); + if (i->second.is()) { + throw css::uno::RuntimeException( + "already added extension xcu " + url); + } + i->second = item; + item->layer = layer; + return &item->additions; +} + +rtl::Reference< Data::ExtensionXcu > Data::removeExtensionXcuAdditions( + OUString const & url) +{ + ExtensionXcuAdditions::iterator i(extensionXcuAdditions_.find(url)); + if (i == extensionXcuAdditions_.end()) { + // This can happen, as migration of pre OOo 3.3 UserInstallation + // extensions in dp_registry::backend::configuration::BackendImpl:: + // PackageImpl::processPackage_ can cause just-in-time creation of + // extension xcu files that are never added via addExtensionXcuAdditions + // (also, there might be url spelling differences between calls to + // addExtensionXcuAdditions and removeExtensionXcuAdditions?): + SAL_INFO( + "configmgr", + "unknown Data::removeExtensionXcuAdditions(" << url << ")"); + return rtl::Reference< ExtensionXcu >(); + } + rtl::Reference< ExtensionXcu > item(i->second); + extensionXcuAdditions_.erase(i); + return item; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/data.hxx b/configmgr/source/data.hxx new file mode 100644 index 000000000..c3614e643 --- /dev/null +++ b/configmgr/source/data.hxx @@ -0,0 +1,99 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <climits> +#include "config_map.hxx" +#include <vector> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <salhelper/simplereferenceobject.hxx> + +#include "additions.hxx" +#include "modifications.hxx" +#include "nodemap.hxx" + +namespace configmgr { + +class Node; + +struct Data { + enum { NO_LAYER = INT_MAX }; + + struct ExtensionXcu: public salhelper::SimpleReferenceObject { + int layer; + Additions additions; + }; + + NodeMap templates; + + Modifications modifications; + + static OUString createSegment( + std::u16string_view templateName, OUString const & name); + + static sal_Int32 parseSegment( + OUString const & path, sal_Int32 index, OUString * name, + bool * setElement, OUString * templateName); + + static OUString fullTemplateName( + std::u16string_view component, std::u16string_view name); + + //TODO: better rules under which circumstances a short template name matches + static bool equalTemplateNames( + OUString const & shortName, OUString const & longName); + + Data(); + + rtl::Reference< Node > resolvePathRepresentation( + OUString const & pathRepresentation, + OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer) + const; + + rtl::Reference< Node > getTemplate( + int layer, OUString const & fullName) const; + + NodeMap & getComponents() const; + + Additions * addExtensionXcuAdditions( + OUString const & url, int layer); + + rtl::Reference< ExtensionXcu > removeExtensionXcuAdditions( + OUString const & url); + +private: + Data(const Data&) = delete; + Data& operator=(const Data&) = delete; + + typedef config_map< rtl::Reference< ExtensionXcu > > + ExtensionXcuAdditions; + + rtl::Reference< Node > root_; + + ExtensionXcuAdditions extensionXcuAdditions_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/dconf.cxx b/configmgr/source/dconf.cxx new file mode 100644 index 000000000..9db51fa17 --- /dev/null +++ b/configmgr/source/dconf.cxx @@ -0,0 +1,1598 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> +#include <cstring> +#include <forward_list> +#include <limits> +#include <vector> + +extern "C" { + //TODO: <https://bugzilla.gnome.org/show_bug.cgi?id=754245> + // "common/dconf-changeset.h etc. lack extern "C" wrapper for C++", fixed on current dconf + // master (towards 0.40?) now with + // <https://gitlab.gnome.org/GNOME/dconf/-/commit/db3d4df6d1a763698f27b013dc42da8d4ae02639> + // "Merge branch 'wip/issue-23' into 'master'" +#include <dconf/dconf.h> +} + +#include <com/sun/star/uno/Sequence.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include "data.hxx" +#include "dconf.hxx" +#include "groupnode.hxx" +#include "localizedpropertynode.hxx" +#include "localizedvaluenode.hxx" +#include "nodemap.hxx" +#include "propertynode.hxx" +#include "setnode.hxx" + +// component-data is encoded in dconf as follows: +// +// * The node hierarchy (starting at component nodes with names like +// "org.openoffice.Setup") maps to dconf paths underneath +// "/org/libreoffice/registry/". +// +// * Component, group, set, and localized property nodes map to dconf dirs, +// while property and localized value nodes map to dconf keys. +// +// * The names of nodes that are not set elements are used directly as dconf +// path segments. (The syntax for node names is any non-empty sequences of +// any Unicode scalar values except U+0000--0008, U+000B--000C, U+000E--001F, +// U+002F SOLIDUS, and U+FFFE--FFFF. TODO: "<aruiz> sberg, in general I think +// it'd be nice if you used path separators instead of dots though, they have +// meaning in dconf/gvdb world :-)"?) +// +// * The names of set element nodes are encoded as dconf path segments as +// follows: each occurrence of U+0000 NULL is replace by the three characters +// "\00", each occurrence of U+002F SOLIDUS is replaced by the three +// characters "\2F", and each occurrence of U+005C REVERSE SOLIDUS is replaced +// by the three characters "\5C". +// +// * Set elements (which must themselves be either sets or groups) map to +// "indirection" dconf dirs as follows: +// +// ** The dir must contain a key named "op" of string type, with a value of +// "fuse", "replace", or "remove". +// +// ** If "op" is "fuse" or "replace", the dir must contain exactly the following +// further keys and dirs: +// +// *** The dir must contain a key named "template" of string type, containing +// the full template name, encoded as follows: each occurrence of U+0000 +// NULL is replace by the three characters "\00" and each occurrence of +// U+005C REVERSE SOLIDUS is replaced by the three characters "\5C". +// +// *** The dir must contain a dir named "content" that contains the set +// element's (i.e., set or group node's) real content. +// +// ** If "op" is "remove", the dir must contain no further keys or dirs. +// +// * Property and localized property value "fuse" operations map to GVariant +// instances as follows: +// +// ** Non-nillable boolean values map to GVariant boolean instances. +// +// ** Non-nillable short values map to GVariant int16 instances. +// +// ** Non-nillable int values map to GVariant int32 instances. +// +// ** Non-nillable long values map to GVariant int64 instances. +// +// ** Non-nillable double values map to GVariant double instances. +// +// ** Non-nillable string values map to GVariant string instances, with the +// following encoding: each occurrence of U+0000 NULL is replace by the three +// characters "\00" and each occurrence of U+005C REVERSE SOLIDUS is replaced +// by the three characters "\5C". +// +// ** Non-nillable hexbinary values map to GVariant byte array instances. +// +// ** Non-nillable list values recursively map to GVariant array instances. +// +// ** Nillable values recursively map to GVariant maybe instances. +// +// * Property "remove" operations map to GVariant instances of empty tuple type. +// +// Finalization: The component-update.dtd allows for finalization of +// oor:component-data, node, and prop elements, while dconf allows for locking +// of individual keys. That does not match, but just mark the individual Node +// instances that correspond to individual dconf keys as finalized for +// non-writable dconf keys. +// +// TODO: support "mandatory" and "external"? + +namespace configmgr::dconf { + +namespace { + +template<typename T> class GObjectHolder { +public: + explicit GObjectHolder(T * object): object_(object) {} + + ~GObjectHolder() { + if (object_ != nullptr) { + g_object_unref(object_); + } + } + + T * get() const { return object_; } + +private: + GObjectHolder(GObjectHolder const &) = delete; + GObjectHolder& operator =(GObjectHolder const &) = delete; + + T * object_; +}; + +class GVariantHolder { +public: + explicit GVariantHolder(GVariant * variant = nullptr): variant_(variant) {} + + ~GVariantHolder() { unref(); } + + void reset(GVariant * variant) { + unref(); + variant_ = variant; + } + + void release() { variant_ = nullptr; } + + GVariant * get() const { return variant_; } + +private: + GVariantHolder(GVariantHolder const &) = delete; + GVariantHolder& operator =(GVariantHolder const &) = delete; + + void unref() { + if (variant_ != nullptr) { + g_variant_unref(variant_); + } + } + + GVariant * variant_; +}; + +class GVariantTypeHolder { +public: + explicit GVariantTypeHolder(GVariantType * type): type_(type) {} + + ~GVariantTypeHolder() { + if (type_ != nullptr) { + g_variant_type_free(type_); + } + } + + GVariantType * get() const { return type_; } + +private: + GVariantTypeHolder(GVariantTypeHolder const &) = delete; + GVariantTypeHolder& operator =(GVariantTypeHolder const &) = delete; + + GVariantType * type_; +}; + +class StringArrayHolder { +public: + explicit StringArrayHolder(gchar ** array): array_(array) {} + + ~StringArrayHolder() { g_strfreev(array_); } + + gchar ** get() const { return array_; } + +private: + StringArrayHolder(StringArrayHolder const &) = delete; + StringArrayHolder& operator =(StringArrayHolder const &) = delete; + + gchar ** array_; +}; + +class ChangesetHolder { +public: + explicit ChangesetHolder(DConfChangeset * changeset): + changeset_(changeset) + {} + + ~ChangesetHolder() { + if (changeset_ != nullptr) { + dconf_changeset_unref(changeset_); + } + } + + DConfChangeset * get() const { return changeset_; } + +private: + ChangesetHolder(ChangesetHolder const &) = delete; + ChangesetHolder& operator =(ChangesetHolder const &) = delete; + + DConfChangeset * changeset_; +}; + +OString getRoot() { + return "/org/libreoffice/registry"; +} + +bool decode(OUString * string, bool slash) { + for (sal_Int32 i = 0;; ++i) { + i = string->indexOf('\\', i); + if (i == -1) { + return true; + } + if (string->match("00", i + 1)) { + *string = string->replaceAt(i, 3, OUStringChar(u'\0')); + } else if (slash && string->match("2F", i + 1)) { + *string = string->replaceAt(i, 3, u"/"); + } else if (string->match("5C", i + 1)) { + *string = string->replaceAt(i + 1, 2, u""); + } else { + SAL_WARN("configmgr.dconf", "bad escape in " << *string); + return false; + } + } +} + +bool getBoolean( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_BOOLEAN)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match boolean property"); + return false; + } + *value <<= bool(g_variant_get_boolean(variant.get())); + return true; +} + +bool getShort( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT16)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match short property"); + return false; + } + *value <<= sal_Int16(g_variant_get_int16(variant.get())); + return true; +} + +bool getInt( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT32)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match int property"); + return false; + } + *value <<= sal_Int32(g_variant_get_int32(variant.get())); + return true; +} + +bool getLong( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT64)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match long property"); + return false; + } + *value <<= sal_Int64(g_variant_get_int64(variant.get())); + return true; +} + +bool getDouble( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_DOUBLE)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match double property"); + return false; + } + *value <<= double(g_variant_get_double(variant.get())); + return true; +} + +bool getStringValue( + OString const & key, GVariantHolder const & variant, OUString * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_STRING)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match string property"); + return false; + } + gsize n; + char const * p = g_variant_get_string(variant.get(), &n); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long string value for key " << key); + return false; + } + if (!rtl_convertStringToUString( + &value->pData, p, static_cast<sal_Int32>(n), RTL_TEXTENCODING_UTF8, + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) + { + SAL_WARN("configmgr.dconf", "non--UTF-8 string value for key " << key); + return false; + } + return decode(value, false); +} + +bool getString( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + OUString v; + if (!getStringValue(key, variant, &v)) { + return false; + } + *value <<= v; + return true; +} + +bool getHexbinaryValue( + OString const & key, GVariantHolder const & variant, + css::uno::Sequence<sal_Int8> * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ay") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match hexbinary property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (guchar)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long hexbinary value for key " << key); + return false; + } + value->realloc(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int8) == sizeof (guchar), "size mismatch"); + std::memcpy(value->getArray(), p, n * sizeof (guchar)); + // assuming that n * sizeof (guchar) is small enough for std::size_t + return true; +} + +bool getHexbinary( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + css::uno::Sequence<sal_Int8> v; + if (!getHexbinaryValue(key, variant, &v)) { + return false; + } + *value <<= v; + return true; +} + +bool getBooleanList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ab") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match boolean list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (guchar)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long boolean list for key " << key); + return false; + } + css::uno::Sequence<sal_Bool> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Bool) == sizeof (guchar), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (guchar)); + // assuming that n * sizeof (guchar) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getShortList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "an") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match short list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gint16)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long short list for key " << key); + return false; + } + css::uno::Sequence<sal_Int16> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int16) == sizeof (gint16), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gint16)); + // assuming that n * sizeof (gint16) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getIntList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ai") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match int list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gint32)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long int list for key " << key); + return false; + } + css::uno::Sequence<sal_Int32> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int32) == sizeof (gint32), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gint32)); + // assuming that n * sizeof (gint32) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getLongList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ax") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match long list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gint64)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long long list for key " << key); + return false; + } + css::uno::Sequence<sal_Int64> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int64) == sizeof (gint64), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gint64)); + // assuming that n * sizeof (gint64) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getDoubleList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ad") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match double list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gdouble)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long double list for key " << key); + return false; + } + css::uno::Sequence<double> v(static_cast<sal_Int32>(n)); + static_assert(std::is_same<double, gdouble>::value, "type mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gdouble)); + // assuming that n * sizeof (gdouble) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getStringList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "as") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match string list property"); + return false; + } + gsize n = g_variant_n_children(variant.get()); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long string list for key " << key); + return false; + } + css::uno::Sequence<OUString> v(static_cast<sal_Int32>(n)); + for (gsize i = 0; i != n; ++i) { + GVariantHolder c(g_variant_get_child_value(variant.get(), i)); + if (!getStringValue(key, c, v.getArray() + i)) { + return false; + } + } + *value <<= v; + return true; +} + +bool getHexbinaryList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "aay") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match hexbinary list property"); + return false; + } + gsize n = g_variant_n_children(variant.get()); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long hexbinary list for key " << key); + return false; + } + css::uno::Sequence<css::uno::Sequence<sal_Int8>> v( + static_cast<sal_Int32>(n)); + for (gsize i = 0; i != n; ++i) { + GVariantHolder c(g_variant_get_child_value(variant.get(), i)); + if (!getHexbinaryValue(key, c, v.getArray() + i)) { + return false; + } + } + *value <<= v; + return true; +} + +bool getAny( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + char const * t = g_variant_get_type_string(variant.get()); + if (std::strcmp(t, "b") == 0) { + return getBoolean(key, variant, value); + } + if (std::strcmp(t, "n") == 0) { + return getShort(key, variant, value); + } + if (std::strcmp(t, "i") == 0) { + return getInt(key, variant, value); + } + if (std::strcmp(t, "x") == 0) { + return getLong(key, variant, value); + } + if (std::strcmp(t, "d") == 0) { + return getDouble(key, variant, value); + } + if (std::strcmp(t, "s") == 0) { + return getString(key, variant, value); + } + if (std::strcmp(t, "ay") == 0) { + return getHexbinary(key, variant, value); + } + if (std::strcmp(t, "ab") == 0) { + return getBooleanList(key, variant, value); + } + if (std::strcmp(t, "an") == 0) { + return getShortList(key, variant, value); + } + if (std::strcmp(t, "ai") == 0) { + return getIntList(key, variant, value); + } + if (std::strcmp(t, "ax") == 0) { + return getLongList(key, variant, value); + } + if (std::strcmp(t, "ad") == 0) { + return getDoubleList(key, variant, value); + } + if (std::strcmp(t, "as") == 0) { + return getStringList(key, variant, value); + } + if (std::strcmp(t, "aay") == 0) { + return getHexbinaryList(key, variant, value); + } + SAL_WARN( + "configmgr.dconf", "bad key " << key << " does not match any property"); + return false; +} + +enum class ReadValue { Error, Value, Remove }; + +ReadValue readValue( + GObjectHolder<DConfClient> const & client, OString const & path, Type type, + bool nillable, bool removable, css::uno::Any * value) +{ + assert(value != nullptr); + assert(!value->hasValue()); + assert(!path.endsWith("/")); + GVariantHolder v(dconf_client_read(client.get(), path.getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "cannot read key " << path); + return ReadValue::Error; + } + if (removable && std::strcmp(g_variant_get_type_string(v.get()), "()") == 0) + { + return ReadValue::Remove; + } + bool nil; + if (nillable) { + if (g_variant_classify(v.get()) != G_VARIANT_CLASS_MAYBE) { + SAL_WARN( + "configmgr.dconf", + "bad key " << path << " does not match nillable property"); + } + v.reset(g_variant_get_maybe(v.get())); + nil = v.get() == nullptr; + } else { + nil = false; + } + if (!nil) { + switch (type) { + case TYPE_ANY: + if (!getAny(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_BOOLEAN: + if (!getBoolean(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_SHORT: + if (!getShort(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_INT: + if (!getInt(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_LONG: + if (!getLong(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_DOUBLE: + if (!getDouble(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_STRING: + if (!getString(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_HEXBINARY: + if (!getHexbinary(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_BOOLEAN_LIST: + if (!getBooleanList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_SHORT_LIST: + if (!getShortList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_INT_LIST: + if (!getIntList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_LONG_LIST: + if (!getLongList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_DOUBLE_LIST: + if (!getDoubleList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_STRING_LIST: + if (!getStringList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_HEXBINARY_LIST: + if (!getHexbinaryList(path, v, value)) { + return ReadValue::Error; + } + break; + default: + assert(false); // cannot happen + } + } + return ReadValue::Value; +} + +void finalize( + GObjectHolder<DConfClient> const & client, OString const & path, + rtl::Reference<Node> const & node, int layer) +{ + if (!dconf_client_is_writable(client.get(), path.getStr())) { + node->setFinalized(layer); + } +} + +void readDir( + Data & data, int layer, rtl::Reference<Node> const & node, + NodeMap & members, GObjectHolder<DConfClient> const & client, + OString const & dir) +{ + StringArrayHolder a(dconf_client_list(client.get(), dir.getStr(), nullptr)); + for (char const * const * p = a.get(); *p != nullptr; ++p) { + std::size_t n = std::strlen(*p); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long dir/key in dir " << dir); + continue; + } + OString s(*p, static_cast<sal_Int32>(n)); + OString path(dir + s); + OUString name; + if (!rtl_convertStringToUString( + &name.pData, s.getStr(), s.getLength(), RTL_TEXTENCODING_UTF8, + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) + { + SAL_WARN("configmgr.dconf", "non--UTF-8 dir/key in dir " << dir); + continue; + } + bool isDir = name.endsWith("/", &name); + OUString templ; + bool remove; + bool replace; + if (node.is() && node->kind() == Node::KIND_SET) { + if (!isDir) { + SAL_WARN( + "configmgr.dconf", + "bad key " << path << " does not match set element"); + continue; + } + if (!decode(&name, true)) { + continue; + } + enum class Op { None, Fuse, Replace, Remove }; + Op op = Op::None; + bool content = false; + bool bad = false; + StringArrayHolder a2( + dconf_client_list(client.get(), path.getStr(), nullptr)); + for (char const * const * p2 = a2.get(); *p2 != nullptr; ++p2) { + if (std::strcmp(*p2, "op") == 0) { + OString path2(path + "op"); + GVariantHolder v( + dconf_client_read(client.get(), path2.getStr())); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "cannot read key " << path2); + bad = true; + break; + } + OUString ops; + if (!getStringValue(path2, v, &ops)) { + bad = true; + break; + } + if (ops == "fuse") { + op = Op::Fuse; + } else if (ops == "replace") { + op = Op::Replace; + } else if (ops == "remove") { + op = Op::Remove; + } else { + SAL_WARN( + "configmgr.dconf", + "bad key " << path2 << " value " << ops); + bad = true; + break; + } + } else if (std::strcmp(*p2, "template") == 0) { + OString path2(path + "template"); + GVariantHolder v( + dconf_client_read(client.get(), path2.getStr())); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "cannot read key " << path2); + bad = true; + break; + } + if (!getStringValue(path2, v, &templ)) { + bad = true; + break; + } + if (!static_cast<SetNode *>(node.get())-> + isValidTemplate(templ)) + { + SAL_WARN( + "configmgr.dconf", + "bad key " << path2 << " value " << templ + << " denotes unsupported set element template"); + bad = true; + break; + } + } else if (std::strcmp(*p2, "content/") == 0) { + content = true; + } else { + SAL_WARN( + "configmgr.dconf", + "bad dir/key " << p2 + << " in set element indirection dir " << path); + bad = true; + break; + } + } + if (bad) { + continue; + } + switch (op) { + default: // case Op::None: + SAL_WARN( + "configmgr.dconf", + "bad set element indirection dir " << path + << " missing \"op\" key"); + continue; + case Op::Fuse: + case Op::Replace: + if (templ.isEmpty() || !content) { + SAL_WARN( + "configmgr.dconf", + "missing \"content\" and/or \"template\" dir/key in " + "\"op\" = \"fuse\"/\"remove\" set element" + " indirection dir " << path); + continue; + } + path += "content/"; + remove = false; + replace = op == Op::Replace; + break; + case Op::Remove: + if (!templ.isEmpty() || content) { + SAL_WARN( + "configmgr.dconf", + "bad \"content\" and/or \"template\" dir/key in \"op\" " + "= \"remove\" set element indirection dir " + << path); + continue; + } + remove = true; + replace = false; + break; + } + } else { + remove = false; + replace = false; + } + rtl::Reference<Node> member(members.findNode(layer, name)); + bool insert = !member.is(); + if (!remove) { + if (replace || insert) { + if (!node.is()) { + SAL_WARN("configmgr.dconf", "bad unmatched " << path); + continue; + } + switch (node->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + member.set(new LocalizedValueNode(layer)); + break; + case Node::KIND_GROUP: + if (!static_cast<GroupNode *>(node.get())->isExtensible()) { + SAL_WARN("configmgr.dconf", "bad unmatched " << path); + continue; + } + member.set( + new PropertyNode( + layer, TYPE_ANY, true, css::uno::Any(), true)); + break; + case Node::KIND_SET: + assert(!templ.isEmpty()); + member = data.getTemplate(layer, templ); + if (!member.is()) { + SAL_WARN( + "configmgr.dconf", + "bad " << path << " denoting undefined template " + << templ); + continue; + } + member = member->clone(true); + break; + default: + assert(false); // cannot happen + } + } else if (!templ.isEmpty() && templ != member->getTemplateName()) { + SAL_WARN( + "configmgr.dconf", + "bad " << path + << " denoting set element of non-matching template " + << member->getTemplateName()); + continue; + } + } + if (member.is()) { + if (member->getFinalized() < layer) { + continue; + } + switch (member->kind()) { + case Node::KIND_PROPERTY: + { + if (isDir) { + SAL_WARN( + "configmgr.dconf", + "bad dir " << path << " does not match property"); + continue; + } + rtl::Reference<PropertyNode> prop( + static_cast<PropertyNode *>(member.get())); + css::uno::Any value; + switch (readValue( + client, path, prop->getStaticType(), + prop->isNillable(), prop->isExtension(), + &value)) + { + case ReadValue::Error: + continue; + case ReadValue::Value: + prop->setValue(layer, value); + finalize(client, path, member, layer); + break; + case ReadValue::Remove: + remove = true; + break; + } + break; + } + case Node::KIND_LOCALIZED_VALUE: + { + if (isDir) { + SAL_WARN( + "configmgr.dconf", + "bad dir " << path + << " does not match localized value"); + continue; + } + assert( + node.is() + && node->kind() == Node::KIND_LOCALIZED_PROPERTY); + rtl::Reference<LocalizedPropertyNode> locProp( + static_cast<LocalizedPropertyNode *>(node.get())); + css::uno::Any value; + if (readValue( + client, path, locProp->getStaticType(), + locProp->isNillable(), false, &value) + == ReadValue::Error) + { + continue; + } + static_cast<LocalizedValueNode *>(member.get())->setValue( + layer, value); + finalize(client, path, member, layer); + break; + } + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + case Node::KIND_SET: + if (!isDir) { + SAL_WARN( + "configmgr.dconf", + "bad key " << path + << " does not match localized property, group, or" + " set, respectively"); + continue; + } + assert(path.endsWith("/")); + readDir( + data, layer, member, member->getMembers(), client, path); + break; + default: + assert(false); // cannot happen + } + } + if (remove) { + if (!(member.is() && member->getMandatory())) { + members.erase(name); + } + } else if (replace) { + members.erase(name); + members.insert(NodeMap::value_type(name, member)); + } else if (insert) { + members.insert(NodeMap::value_type(name, member)); + } + } +} + +OString encodeSegment(OUString const & name, bool setElement) { + if (!setElement) { + return name.toUtf8(); + } + OUStringBuffer buf; + for (sal_Int32 i = 0; i != name.getLength(); ++i) { + sal_Unicode c = name[i]; + switch (c) { + case '\0': + buf.append("\\00"); + break; + case '/': + buf.append("\\2F"); + break; + case '\\': + buf.append("\\5C"); + break; + default: + buf.append(c); + } + } + return buf.makeStringAndClear().toUtf8(); +} + +OString encodeString(OUString const & value) { + OUStringBuffer buf; + for (sal_Int32 i = 0; i != value.getLength(); ++i) { + sal_Unicode c = value[i]; + switch (c) { + case '\0': + buf.append("\\00"); + break; + case '\\': + buf.append("\\5C"); + break; + default: + buf.append(c); + } + } + return buf.makeStringAndClear().toUtf8(); +} + +bool addProperty( + ChangesetHolder const & changeset, OString const & pathRepresentation, + Type type, bool nillable, css::uno::Any const & value) +{ + Type dynType = getDynamicType(value); + assert(dynType != TYPE_ERROR); + if (type == TYPE_ANY) { + type = dynType; + } + GVariantHolder v; + std::forward_list<GVariantHolder> children; + if (dynType == TYPE_NIL) { + switch (type) { + case TYPE_BOOLEAN: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_BOOLEAN, nullptr)); + break; + case TYPE_SHORT: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT16, nullptr)); + break; + case TYPE_INT: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT32, nullptr)); + break; + case TYPE_LONG: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT64, nullptr)); + break; + case TYPE_DOUBLE: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_DOUBLE, nullptr)); + break; + case TYPE_STRING: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_STRING, nullptr)); + break; + case TYPE_HEXBINARY: + case TYPE_BOOLEAN_LIST: + case TYPE_SHORT_LIST: + case TYPE_INT_LIST: + case TYPE_LONG_LIST: + case TYPE_DOUBLE_LIST: + case TYPE_STRING_LIST: + case TYPE_HEXBINARY_LIST: + { + static char const * const typeString[ + TYPE_HEXBINARY_LIST - TYPE_HEXBINARY + 1] + = { "ay", "ab", "an", "ai", "ax", "ad", "as", "aay" }; + GVariantTypeHolder ty( + g_variant_type_new(typeString[type - TYPE_HEXBINARY])); + if (ty.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_type_new failed"); + return false; + } + v.reset(g_variant_new_maybe(ty.get(), nullptr)); + break; + } + default: + assert(false); // this cannot happen + break; + } + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_maybe failed"); + return false; + } + } else { + switch (type) { + case TYPE_BOOLEAN: + v.reset(g_variant_new_boolean(value.get<bool>())); + break; + case TYPE_SHORT: + v.reset(g_variant_new_int16(value.get<sal_Int16>())); + break; + case TYPE_INT: + v.reset(g_variant_new_int32(value.get<sal_Int32>())); + break; + case TYPE_LONG: + v.reset(g_variant_new_int64(value.get<sal_Int64>())); + break; + case TYPE_DOUBLE: + v.reset(g_variant_new_double(value.get<double>())); + break; + case TYPE_STRING: + v.reset( + g_variant_new_string( + encodeString(value.get<OUString>()).getStr())); + break; + case TYPE_HEXBINARY: + { + css::uno::Sequence<sal_Int8> seq( + value.get<css::uno::Sequence<sal_Int8>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int8) == sizeof (guchar), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int8))); + break; + } + case TYPE_BOOLEAN_LIST: + { + css::uno::Sequence<sal_Bool> seq( + value.get<css::uno::Sequence<sal_Bool>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert(sizeof (sal_Bool) == 1, "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_BOOLEAN, seq.getConstArray(), + seq.getLength(), sizeof (sal_Bool))); + break; + } + case TYPE_SHORT_LIST: + { + css::uno::Sequence<sal_Int16> seq( + value.get<css::uno::Sequence<sal_Int16>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int16) == sizeof (gint16), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_INT16, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int16))); + //TODO: endian-ness? + break; + } + case TYPE_INT_LIST: + { + css::uno::Sequence<sal_Int32> seq( + value.get<css::uno::Sequence<sal_Int32>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int32) == sizeof (gint32), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_INT32, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int32))); + //TODO: endian-ness? + break; + } + case TYPE_LONG_LIST: + { + css::uno::Sequence<sal_Int64> seq( + value.get<css::uno::Sequence<sal_Int64>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int64) == sizeof (gint64), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_INT64, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int64))); + //TODO: endian-ness? + break; + } + case TYPE_DOUBLE_LIST: + { + css::uno::Sequence<double> seq( + value.get<css::uno::Sequence<double>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (double) == sizeof (gdouble), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_DOUBLE, seq.getConstArray(), + seq.getLength(), sizeof (double))); + //TODO: endian-ness? + break; + } + case TYPE_STRING_LIST: + { + const css::uno::Sequence<OUString> seq( + value.get<css::uno::Sequence<OUString>>()); + std::vector<GVariant *> vs; + for (OUString const & s : seq) { + children.emplace_front( + g_variant_new_string(encodeString(s).getStr())); + if (children.front().get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "g_variant_new_string failed"); + return false; + } + vs.push_back(children.front().get()); + } + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + v.reset( + g_variant_new_array( + G_VARIANT_TYPE_STRING, vs.data(), seq.getLength())); + break; + } + case TYPE_HEXBINARY_LIST: + { + const css::uno::Sequence<css::uno::Sequence<sal_Int8>> seqSeq( + value.get< + css::uno::Sequence<css::uno::Sequence<sal_Int8>>>()); + std::vector<GVariant *> vs; + for (css::uno::Sequence<sal_Int8> const & seq : seqSeq) { + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int8) == sizeof (guchar), "size mismatch"); + children.emplace_front( + g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int8))); + if (children.front().get() == nullptr) { + SAL_WARN( + "configmgr.dconf", + "g_variant_new_fixed_array failed"); + return false; + } + vs.push_back(children.front().get()); + } + GVariantTypeHolder ty(g_variant_type_new("aay")); + if (ty.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_type_new failed"); + return false; + } + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + v.reset( + g_variant_new_array(ty.get(), vs.data(), seqSeq.getLength())); + break; + } + default: + assert(false); // this cannot happen + break; + } + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "GVariant creation failed"); + return false; + } + if (nillable) { + GVariantHolder v1(g_variant_new_maybe(nullptr, v.get())); + if (v1.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_maybe failed"); + return false; + } + v.release(); + v.reset(v1.get()); + v1.release(); + } + } + dconf_changeset_set( + changeset.get(), pathRepresentation.getStr(), v.get()); + for (auto & i: children) { + i.release(); + } + v.release(); + return true; +} + +bool addNode( + Components & components, ChangesetHolder const & changeset, + rtl::Reference<Node> const & parent, OString const & pathRepresentation, + rtl::Reference<Node> const & node) +{ + switch (node->kind()) { + case Node::KIND_PROPERTY: + { + PropertyNode * prop = static_cast<PropertyNode *>(node.get()); + if (!addProperty( + changeset, pathRepresentation, prop->getStaticType(), + prop->isNillable(), prop->getValue(components))) + { + return false; + } + break; + } + case Node::KIND_LOCALIZED_VALUE: + { + //TODO: name.isEmpty()? + LocalizedPropertyNode * locprop + = static_cast<LocalizedPropertyNode *>(parent.get()); + if (!addProperty( + changeset, pathRepresentation, + locprop->getStaticType(), locprop->isNillable(), + static_cast<LocalizedValueNode *>(node.get())->getValue())) + { + return false; + } + break; + } + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + case Node::KIND_SET: + for (auto const & i: node->getMembers()) { + OUString templ(i.second->getTemplateName()); + OString path( + pathRepresentation + "/" + + encodeSegment(i.first, !templ.isEmpty())); + if (!templ.isEmpty()) { + path += "/"; + GVariantHolder v(g_variant_new_string("replace")); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), v.get()); + v.release(); + v.reset(g_variant_new_string(encodeString(templ).getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), + v.get()); + v.release(); + path += "content"; + } + if (!addNode(components, changeset, parent, path, i.second)) { + return false; + } + } + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } + return true; +} + +bool addModifications( + Components & components, ChangesetHolder const & changeset, + OString const & parentPathRepresentation, + rtl::Reference<Node> const & parent, OUString const & nodeName, + rtl::Reference<Node> const & node, + Modifications::Node const & modifications) +{ + // It is never necessary to write oor:finalized or oor:mandatory attributes, + // as they cannot be set via the UNO API. + if (modifications.children.empty()) { + assert(parent.is()); + // components themselves have no parent but must have children + if (node.is()) { + OUString templ(node->getTemplateName()); + OString path( + parentPathRepresentation + "/" + + encodeSegment(nodeName, !templ.isEmpty())); + if (!templ.isEmpty()) { + path += "/"; + GVariantHolder v(g_variant_new_string("replace")); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), v.get()); + v.release(); + v.reset(g_variant_new_string(encodeString(templ).getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), + v.get()); + v.release(); + path += "content"; + } + if (!addNode(components, changeset, parent, path, node)) { + return false; + } + } else { + switch (parent->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + { + GVariantHolder v(g_variant_new_tuple(nullptr, 0)); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "g_variant_new_tuple failed"); + return false; + } + OString path(parentPathRepresentation); + if (!nodeName.isEmpty()) { // KIND_LOCALIZED_PROPERTY + path += "/" + encodeSegment(nodeName, false); + } + dconf_changeset_set( + changeset.get(), path.getStr(), v.get()); + v.release(); + break; + } + case Node::KIND_SET: + { + OString path( + parentPathRepresentation + "/" + + encodeSegment(nodeName, true) + "/"); + GVariantHolder v(g_variant_new_string("remove")); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), + v.get()); + v.release(); + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), + nullptr); + dconf_changeset_set( + changeset.get(), OString(path + "content/").getStr(), + nullptr); + break; + } + default: + assert(false); // this cannot happen + break; + } + } + } else { + assert(node.is()); + OUString templ(node->getTemplateName()); + OString path( + parentPathRepresentation + "/" + + encodeSegment(nodeName, !templ.isEmpty())); + if (!templ.isEmpty()) { + path += "/"; + GVariantHolder v(g_variant_new_string("fuse")); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), v.get()); + v.release(); + v.reset(g_variant_new_string(encodeString(templ).getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), v.get()); + v.release(); + path += "content"; + } + for (auto const & i: modifications.children) { + if (!addModifications( + components, changeset, path, node, i.first, + node->getMember(i.first), i.second)) + { + return false; + } + } + } + return true; +} + +} + +void readLayer(Data & data, int layer) { + GObjectHolder<DConfClient> client(dconf_client_new()); + if (client.get() == nullptr) { + SAL_WARN("configmgr.dconf", "dconf_client_new failed"); + return; + } + readDir( + data, layer, rtl::Reference<Node>(), data.getComponents(), client, + getRoot() + "/"); +} + +void writeModifications(Components & components, Data & data) { + GObjectHolder<DConfClient> client(dconf_client_new()); + if (client.get() == nullptr) { + SAL_WARN("configmgr.dconf", "dconf_client_new failed"); + } + ChangesetHolder cs(dconf_changeset_new()); + if (cs.get() == nullptr) { + SAL_WARN("configmgr.dconf", "dconf_changeset_new failed"); + return; + } + for (auto const & i: data.modifications.getRoot().children) { + if (!addModifications( + components, cs, getRoot(), rtl::Reference<Node>(), i.first, + data.getComponents().findNode(Data::NO_LAYER, i.first), + i.second)) + { + return; + } + } + if (!dconf_client_change_sync( + client.get(), cs.get(), nullptr, nullptr, nullptr)) + { + //TODO: GError + SAL_WARN("configmgr.dconf", "dconf_client_change_sync failed"); + return; + } + data.modifications.clear(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/dconf.hxx b/configmgr/source/dconf.hxx new file mode 100644 index 000000000..9fa51247c --- /dev/null +++ b/configmgr/source/dconf.hxx @@ -0,0 +1,27 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +namespace configmgr { + class Components; + struct Data; +} + +namespace configmgr::dconf { + +void readLayer(Data & data, int layer); + +void writeModifications(Components & components, Data & data); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/defaultprovider.cxx b/configmgr/source/defaultprovider.cxx new file mode 100644 index 000000000..7161659e0 --- /dev/null +++ b/configmgr/source/defaultprovider.cxx @@ -0,0 +1,53 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> + +#include "configurationprovider.hxx" +#include "defaultprovider.hxx" +#include "lock.hxx" + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_configuration_DefaultProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + osl::MutexGuard guard(*configmgr::lock()); + css::uno::Reference<css::uno::XInterface> singleton( + configmgr::configuration_provider::createDefault(context)); + singleton->acquire(); + return singleton.get(); +} + +namespace configmgr::default_provider +{ +OUString getImplementationName() { return "com.sun.star.comp.configuration.DefaultProvider"; } + +css::uno::Sequence<OUString> getSupportedServiceNames() +{ + return { "com.sun.star.configuration.DefaultProvider" }; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/defaultprovider.hxx b/configmgr/source/defaultprovider.hxx new file mode 100644 index 000000000..d20ba200b --- /dev/null +++ b/configmgr/source/defaultprovider.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <com/sun/star/uno/Sequence.hxx> + +namespace configmgr::default_provider +{ +OUString getImplementationName(); + +css::uno::Sequence<OUString> getSupportedServiceNames(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/groupnode.cxx b/configmgr/source/groupnode.cxx new file mode 100644 index 000000000..00934e917 --- /dev/null +++ b/configmgr/source/groupnode.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <sal/config.h> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> + +#include "data.hxx" +#include "groupnode.hxx" +#include "node.hxx" +#include "nodemap.hxx" + +namespace configmgr { + +GroupNode::GroupNode( + int layer, bool extensible, OUString templateName): + Node(layer), extensible_(extensible), templateName_(std::move(templateName)), + mandatory_(Data::NO_LAYER) +{} + +rtl::Reference< Node > GroupNode::clone(bool keepTemplateName) const { + return new GroupNode(*this, keepTemplateName); +} + +NodeMap & GroupNode::getMembers() { + return members_; +} + +OUString GroupNode::getTemplateName() const { + return templateName_; +} + +void GroupNode::setMandatory(int layer) { + mandatory_ = layer; +} + +int GroupNode::getMandatory() const { + return mandatory_; +} + + +GroupNode::GroupNode(GroupNode const & other, bool keepTemplateName): + Node(other), extensible_(other.extensible_), mandatory_(other.mandatory_) +{ + other.members_.cloneInto(&members_); + if (keepTemplateName) { + templateName_ = other.templateName_; + } +} + +GroupNode::~GroupNode() {} + +Node::Kind GroupNode::kind() const { + return KIND_GROUP; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/groupnode.hxx b/configmgr/source/groupnode.hxx new file mode 100644 index 000000000..961ecc090 --- /dev/null +++ b/configmgr/source/groupnode.hxx @@ -0,0 +1,63 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> + +#include "node.hxx" +#include "nodemap.hxx" + +namespace configmgr +{ +class GroupNode : public Node +{ +public: + GroupNode(int layer, bool extensible, OUString templateName); + + virtual rtl::Reference<Node> clone(bool keepTemplateName) const override; + + virtual NodeMap& getMembers() override; + + virtual OUString getTemplateName() const override; + + virtual void setMandatory(int layer) override; + + virtual int getMandatory() const override; + + bool isExtensible() const { return extensible_; } + +private: + GroupNode(GroupNode const& other, bool keepTemplateName); + + virtual ~GroupNode() override; + + virtual Kind kind() const override; + + bool extensible_; + NodeMap members_; + OUString templateName_; // non-empty if this node is a template, free node, or set member + int mandatory_; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/localizedpropertynode.cxx b/configmgr/source/localizedpropertynode.cxx new file mode 100644 index 000000000..6e47264f2 --- /dev/null +++ b/configmgr/source/localizedpropertynode.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <sal/config.h> + +#include <rtl/ref.hxx> + +#include "localizedpropertynode.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "type.hxx" + +namespace configmgr { + +LocalizedPropertyNode::LocalizedPropertyNode( + int layer, Type staticType, bool nillable): + Node(layer), staticType_(staticType), nillable_(nillable) +{} + +rtl::Reference< Node > LocalizedPropertyNode::clone(bool) const { + return new LocalizedPropertyNode(*this); +} + +NodeMap & LocalizedPropertyNode::getMembers() { + return members_; +} + + +LocalizedPropertyNode::LocalizedPropertyNode( + LocalizedPropertyNode const & other): + Node(other), staticType_(other.staticType_), nillable_(other.nillable_) +{ + other.members_.cloneInto(&members_); +} + +LocalizedPropertyNode::~LocalizedPropertyNode() {} + +Node::Kind LocalizedPropertyNode::kind() const { + return KIND_LOCALIZED_PROPERTY; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/localizedpropertynode.hxx b/configmgr/source/localizedpropertynode.hxx new file mode 100644 index 000000000..d4831a89f --- /dev/null +++ b/configmgr/source/localizedpropertynode.hxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ref.hxx> + +#include "node.hxx" +#include "nodemap.hxx" +#include "type.hxx" + +namespace configmgr +{ +class LocalizedPropertyNode : public Node +{ +public: + LocalizedPropertyNode(int layer, Type staticType, bool nillable); + + virtual rtl::Reference<Node> clone(bool keepTemplateName) const override; + + virtual NodeMap& getMembers() override; + + Type getStaticType() const { return staticType_; } + + bool isNillable() const { return nillable_; } + +private: + LocalizedPropertyNode(LocalizedPropertyNode const& other); + + virtual ~LocalizedPropertyNode() override; + + virtual Kind kind() const override; + + Type staticType_; // as specified in the component-schema (TYPE_ANY, ..., TYPE_HEXBINARY_LIST; + // not TYPE_ERROR or TYPE_NIL) + bool nillable_; + NodeMap members_; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/localizedvaluenode.cxx b/configmgr/source/localizedvaluenode.cxx new file mode 100644 index 000000000..816975063 --- /dev/null +++ b/configmgr/source/localizedvaluenode.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/uno/Any.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> + +#include "localizedvaluenode.hxx" +#include "node.hxx" + +namespace configmgr { + +LocalizedValueNode::LocalizedValueNode(int layer, css::uno::Any value): + Node(layer), value_(std::move(value)) +{} + +LocalizedValueNode::LocalizedValueNode(int layer): + Node(layer) +{} + +rtl::Reference< Node > LocalizedValueNode::clone(bool) const { + return new LocalizedValueNode(*this); +} + +OUString LocalizedValueNode::getTemplateName() const { + return "*"; +} + + +void LocalizedValueNode::setValue(int layer, css::uno::Any const & value) +{ + setLayer(layer); + if (&value != &value_) + value_ = value; +} + +LocalizedValueNode::~LocalizedValueNode() {} + +Node::Kind LocalizedValueNode::kind() const { + return KIND_LOCALIZED_VALUE; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/localizedvaluenode.hxx b/configmgr/source/localizedvaluenode.hxx new file mode 100644 index 000000000..07949ac5a --- /dev/null +++ b/configmgr/source/localizedvaluenode.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <com/sun/star/uno/Any.hxx> +#include <rtl/ref.hxx> + +#include "node.hxx" + +namespace configmgr +{ +class LocalizedValueNode : public Node +{ +public: + explicit LocalizedValueNode(int layer); + LocalizedValueNode(int layer, css::uno::Any value); + + virtual rtl::Reference<Node> clone(bool keepTemplateName) const override; + + virtual OUString getTemplateName() const override; + + const css::uno::Any& getValue() const { return value_; } + css::uno::Any* getValuePtr(int layer) + { + setLayer(layer); + return &value_; + } + + void setValue(int layer, css::uno::Any const& value); + +private: + LocalizedValueNode(LocalizedValueNode const&) = default; + + virtual ~LocalizedValueNode() override; + + virtual Kind kind() const override; + + css::uno::Any value_; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/lock.cxx b/configmgr/source/lock.cxx new file mode 100644 index 000000000..65b490b78 --- /dev/null +++ b/configmgr/source/lock.cxx @@ -0,0 +1,38 @@ +/* -*- 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 <sal/config.h> + +#include <osl/mutex.hxx> + +#include "lock.hxx" + +namespace configmgr +{ +std::shared_ptr<osl::Mutex> const& lock() +{ + // fdo#31494# get ownership right + // Ensure that the mutex lives as long as all its consumers, otherwise + // the configmgr DLL exit delete this before unotools releases some configmgr derived reference. + static std::shared_ptr<osl::Mutex> theLock = std::make_shared<osl::Mutex>(); + return theLock; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/lock.hxx b/configmgr/source/lock.hxx new file mode 100644 index 000000000..195dbf5e7 --- /dev/null +++ b/configmgr/source/lock.hxx @@ -0,0 +1,31 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include <osl/mutex.hxx> +#include <memory> + +namespace configmgr +{ +std::shared_ptr<osl::Mutex> const& lock(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/modifications.cxx b/configmgr/source/modifications.cxx new file mode 100644 index 000000000..d84904c3f --- /dev/null +++ b/configmgr/source/modifications.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <rtl/ustring.hxx> + +#include "modifications.hxx" + +namespace configmgr { + +Modifications::Modifications() {} + +Modifications::~Modifications() {} + +void Modifications::add(std::vector<OUString> const & path) { + Node * p = &root_; + bool wasPresent = false; + for (auto const& pathItem : path) { + Node::Children::iterator j(p->children.find(pathItem)); + if (j == p->children.end()) { + if (wasPresent && p->children.empty()) { + return; + } + j = p->children.emplace(pathItem, Node()).first; + wasPresent = false; + } else { + wasPresent = true; + } + p = &j->second; + } + p->children.clear(); +} + +void Modifications::remove(std::vector<OUString> const & path) { + assert(!path.empty()); + Node * p = &root_; + for (auto i(path.begin());;) { + Node::Children::iterator j(p->children.find(*i)); + if (j == p->children.end()) { + break; + } + if (++i == path.end()) { + p->children.erase(j); + if (p->children.empty()) { + std::vector<OUString> parent(path); + parent.pop_back(); + remove(parent); + } + break; + } + p = &j->second; + } +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/modifications.hxx b/configmgr/source/modifications.hxx new file mode 100644 index 000000000..22e608ac7 --- /dev/null +++ b/configmgr/source/modifications.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vector> + +#include <rtl/ustring.hxx> + +#include <boost/unordered_map.hpp> +#include <config_dconf.h> + +namespace configmgr +{ +class Modifications +{ +public: + struct Node + { + typedef boost::unordered_map<OUString, Node, OUStringHash> Children; + + Children children; + }; + + Modifications(); + + ~Modifications(); + + void add(std::vector<OUString> const& path); + + void remove(std::vector<OUString> const& path); + +#if ENABLE_DCONF + void clear() { root_.children.clear(); } +#endif + + bool empty() const { return root_.children.empty(); } + + Node const& getRoot() const { return root_; } + +private: + Modifications(const Modifications&) = delete; + Modifications& operator=(const Modifications&) = delete; + + Node root_; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/node.cxx b/configmgr/source/node.cxx new file mode 100644 index 000000000..8f00d3887 --- /dev/null +++ b/configmgr/source/node.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> + +#include "data.hxx" +#include "node.hxx" +#include "nodemap.hxx" + +namespace configmgr { + +NodeMap & Node::getMembers() { + assert(false); + throw css::uno::RuntimeException("this cannot happen"); +} + +OUString Node::getTemplateName() const { + return OUString(); +} + +void Node::setMandatory(int layer) { + (void) layer; // avoid warnings + assert(layer == Data::NO_LAYER); +} + +int Node::getMandatory() const { + return Data::NO_LAYER; +} + +void Node::setLayer(int layer) { + assert(layer >= layer_); + layer_ = layer; +} + + +void Node::setFinalized(int layer) { + finalized_ = layer; +} + + +rtl::Reference< Node > Node::getMember(OUString const & name) { + NodeMap const & members = getMembers(); + NodeMap::const_iterator i(members.find(name)); + return i == members.end() ? rtl::Reference< Node >() : i->second; +} + +Node::Node(int layer): layer_(layer), finalized_(Data::NO_LAYER) {} + +Node::Node(const Node & other): + SimpleReferenceObject(), layer_(other.layer_), finalized_(other.finalized_) +{} + +Node::~Node() {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/node.hxx b/configmgr/source/node.hxx new file mode 100644 index 000000000..b858c9e42 --- /dev/null +++ b/configmgr/source/node.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <salhelper/simplereferenceobject.hxx> + +namespace configmgr { + +class NodeMap; + +class Node: public salhelper::SimpleReferenceObject { +public: + enum Kind { + KIND_PROPERTY, KIND_LOCALIZED_PROPERTY, KIND_LOCALIZED_VALUE, + KIND_GROUP, KIND_SET, KIND_ROOT }; + + virtual Kind kind() const = 0; + + virtual rtl::Reference< Node > clone(bool keepTemplateName) const = 0; + + virtual NodeMap & getMembers(); + virtual OUString getTemplateName() const; + + virtual void setMandatory(int layer); + virtual int getMandatory() const; + + void setLayer(int layer); + int getLayer() const { return layer_;} + + void setFinalized(int layer); + int getFinalized() const { return finalized_;} + + rtl::Reference< Node > getMember(OUString const & name); + +protected: + explicit Node(int layer); + explicit Node(const Node & other); + + virtual ~Node() override; +private: + int layer_; + int finalized_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/nodemap.cxx b/configmgr/source/nodemap.cxx new file mode 100644 index 000000000..e21578b28 --- /dev/null +++ b/configmgr/source/nodemap.cxx @@ -0,0 +1,53 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <rtl/ustring.hxx> + +#include "node.hxx" +#include "nodemap.hxx" + +namespace configmgr +{ +void NodeMap::cloneInto(NodeMap* target) const +{ + assert(target != nullptr && target->empty()); + NodeMapImpl clone(maImpl); + for (auto& elem : clone) + { + elem.second = elem.second->clone(true); + } + std::swap(clone, target->maImpl); + target->clearCache(); +} + +rtl::Reference<Node> NodeMap::findNode(int layer, OUString const& name) const +{ + const_iterator i; + if (maCache == end() || maCache->first != name) + maCache = const_cast<NodeMap*>(this)->maImpl.find(name); + i = maCache; + return i == end() || i->second->getLayer() > layer ? rtl::Reference<Node>() : i->second; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/nodemap.hxx b/configmgr/source/nodemap.hxx new file mode 100644 index 000000000..19447c7f7 --- /dev/null +++ b/configmgr/source/nodemap.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include "config_map.hxx" +#include <rtl/ref.hxx> +#include "node.hxx" + +namespace configmgr { + +typedef config_map< rtl::Reference< Node > > NodeMapImpl; +class NodeMap +{ + NodeMapImpl maImpl; + + NodeMap(const NodeMap &rMap) = delete; + + public: + typedef NodeMapImpl::iterator iterator; + typedef NodeMapImpl::const_iterator const_iterator; + typedef NodeMapImpl::value_type value_type; + + NodeMap() { clearCache(); } + bool empty() const { return maImpl.empty(); } + iterator find(const OUString &aStr) { return maImpl.find( aStr ); } + + const_iterator find(const OUString &aStr) const { return maImpl.find( aStr ); } + iterator begin() { return maImpl.begin(); } + const_iterator begin() const { return maImpl.begin(); } + + iterator end() { return maImpl.end(); } + const_iterator end() const { return maImpl.end(); } + + rtl::Reference<Node> &operator[](const OUString &aStr) { clearCache(); return maImpl[aStr]; } + std::pair<iterator,bool> insert(const value_type &vt) { clearCache(); return maImpl.insert(vt); } + void erase(const iterator &it) { maImpl.erase(it); clearCache(); } + void erase(const OUString &aStr) { maImpl.erase(aStr); clearCache(); } + + rtl::Reference< Node > findNode(int layer, OUString const & name) const; + void cloneInto(NodeMap * target) const; + +private: + // We get a large number of repeated identical lookups. + mutable const_iterator maCache; + void clearCache() { maCache = maImpl.end(); } +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/parsemanager.cxx b/configmgr/source/parsemanager.cxx new file mode 100644 index 000000000..36dea373d --- /dev/null +++ b/configmgr/source/parsemanager.cxx @@ -0,0 +1,91 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <set> + +#include <sal/log.hxx> +#include <sal/types.h> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "parsemanager.hxx" +#include "parser.hxx" + +namespace configmgr { + +ParseManager::ParseManager( + OUString const & url, rtl::Reference< Parser > const & parser) + : reader_(url), parser_(parser), itemNamespaceId_(-1) +{ + assert(parser.is()); + int id; + id = reader_.registerNamespaceIri( + xmlreader::Span( + RTL_CONSTASCII_STRINGPARAM("http://openoffice.org/2001/registry"))); + assert(id == NAMESPACE_OOR); + id = reader_.registerNamespaceIri( + xmlreader::Span( + RTL_CONSTASCII_STRINGPARAM("http://www.w3.org/2001/XMLSchema"))); + assert(id == NAMESPACE_XS); + id = reader_.registerNamespaceIri( + xmlreader::Span( + RTL_CONSTASCII_STRINGPARAM( + "http://www.w3.org/2001/XMLSchema-instance"))); + assert(id == NAMESPACE_XSI); + (void)id; +} + +bool ParseManager::parse(std::set< OUString > const * existingDependencies) { + sal_uInt32 startTime( osl_getGlobalTimer() ); + for (;;) { + switch (itemData_.is() + ? xmlreader::XmlReader::Result::Begin + : reader_.nextItem( + parser_->getTextMode(), &itemData_, &itemNamespaceId_)) + { + case xmlreader::XmlReader::Result::Begin: + if (!parser_->startElement( + reader_, itemNamespaceId_, itemData_, existingDependencies)) + { + SAL_INFO("configmgr", "parsing " << reader_.getUrl() << " took " << (osl_getGlobalTimer() - startTime) << " ms, fail"); + return false; + } + break; + case xmlreader::XmlReader::Result::End: + parser_->endElement(reader_); + break; + case xmlreader::XmlReader::Result::Text: + parser_->characters(itemData_); + break; + case xmlreader::XmlReader::Result::Done: + SAL_INFO("configmgr", "parsing " << reader_.getUrl() << " took " << (osl_getGlobalTimer() - startTime) << " ms, success"); + return true; + } + itemData_.clear(); + } +} + +ParseManager::~ParseManager() {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/parsemanager.hxx b/configmgr/source/parsemanager.hxx new file mode 100644 index 000000000..fba61ec07 --- /dev/null +++ b/configmgr/source/parsemanager.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> + +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + + +namespace configmgr { + +class Parser; + +class ParseManager: public salhelper::SimpleReferenceObject { +public: + ParseManager( + OUString const & url, rtl::Reference< Parser > const & parser); + + bool parse(std::set< OUString > const * existingDependencies); + + enum { NAMESPACE_OOR = 1, NAMESPACE_XS = 2, NAMESPACE_XSI = 3 }; + +private: + virtual ~ParseManager() override; + + xmlreader::XmlReader reader_; + rtl::Reference< Parser > parser_; + xmlreader::Span itemData_; + int itemNamespaceId_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/parser.hxx b/configmgr/source/parser.hxx new file mode 100644 index 000000000..a3984203c --- /dev/null +++ b/configmgr/source/parser.hxx @@ -0,0 +1,53 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> + +#include <salhelper/simplereferenceobject.hxx> +#include <xmlreader/xmlreader.hxx> + +namespace xmlreader { struct Span; } + +namespace configmgr { + +class Parser: public salhelper::SimpleReferenceObject { +public: + virtual xmlreader::XmlReader::Text getTextMode() = 0; + + virtual bool startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * existingDependencies) = 0; + + virtual void endElement(xmlreader::XmlReader const & reader) = 0; + + virtual void characters(xmlreader::Span const & text) = 0; + +protected: + Parser() {} + + virtual ~Parser() override {} +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/partial.cxx b/configmgr/source/partial.cxx new file mode 100644 index 000000000..f31e98549 --- /dev/null +++ b/configmgr/source/partial.cxx @@ -0,0 +1,134 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <set> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include "data.hxx" +#include "partial.hxx" + +namespace configmgr { + +namespace { + +bool parseSegment( + OUString const & path, sal_Int32 * index, OUString * segment) +{ + assert( + index != nullptr && *index >= 0 && *index <= path.getLength() && + segment != nullptr); + if (path[(*index)++] == '/') { + OUString name; + bool setElement; + OUString templateName; + *index = Data::parseSegment( + path, *index, &name, &setElement, &templateName); + if (*index != -1) { + *segment = Data::createSegment(templateName, name); + return *index == path.getLength(); + } + } + throw css::uno::RuntimeException("bad path " + path); +} + +} + +Partial::Partial( + std::set< OUString > const & includedPaths, + std::set< OUString > const & excludedPaths) +{ + // The Partial::Node tree built up here encodes the following information: + // * Inner node, startInclude: an include starts here that contains excluded + // sub-trees + // * Inner node, !startInclude: contains in-/excluded sub-trees + // * Leaf node, startInclude: an include starts here + // * Leaf node, !startInclude: an exclude starts here + for (auto const& includedPath : includedPaths) + { + sal_Int32 n = 0; + for (Node * p = &root_;;) { + OUString seg; + bool end = parseSegment(includedPath, &n, &seg); + p = &p->children[seg]; + if (p->startInclude) { + break; + } + if (end) { + p->children.clear(); + p->startInclude = true; + break; + } + } + } + for (auto const& excludedPath : excludedPaths) + { + sal_Int32 n = 0; + for (Node * p = &root_;;) { + OUString seg; + bool end = parseSegment(excludedPath, &n, &seg); + if (end) { + p->children[seg].clear(); + break; + } + Node::Children::iterator j(p->children.find(seg)); + if (j == p->children.end()) { + break; + } + p = &j->second; + } + } +} + +Partial::~Partial() {} + +Partial::Containment Partial::contains(std::vector<OUString> const & path) const { + //TODO: For set elements, the segment names recorded in the node tree need + // not match the corresponding path segments, so this function can fail. + + // * If path ends at a leaf node or goes past a leaf node: + // ** If that leaf node is startInclude: => CONTAINS_NODE + // ** If that leaf node is !startInclude: => CONTAINS_NOT + // * If path ends at inner node: + // ** If there is some startInclude along its trace: => CONTAINS_NODE + // ** If there is no startInclude along its trace: => CONTAINS_SUBNODES + Node const * p = &root_; + bool bIncludes = false; + for (auto const& elemPath : path) + { + Node::Children::const_iterator j(p->children.find(elemPath)); + if (j == p->children.end()) { + return p->startInclude ? CONTAINS_NODE : CONTAINS_NOT; + } + p = &j->second; + bIncludes |= p->startInclude; + } + return p->children.empty() && !p->startInclude + ? CONTAINS_NOT + : bIncludes ? CONTAINS_NODE : CONTAINS_SUBNODES; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/partial.hxx b/configmgr/source/partial.hxx new file mode 100644 index 000000000..265c70124 --- /dev/null +++ b/configmgr/source/partial.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> +#include <boost/unordered_map.hpp> + +#include <rtl/ustring.hxx> + +namespace configmgr { + +class Partial { +public: + enum Containment { CONTAINS_NOT, CONTAINS_SUBNODES, CONTAINS_NODE }; + + Partial( + std::set< OUString > const & includedPaths, + std::set< OUString > const & excludedPaths); + + ~Partial(); + + Containment contains(std::vector<OUString> const & path) const; + +private: + Partial(const Partial&) = delete; + Partial& operator=(const Partial&) = delete; + + struct Node { + typedef boost::unordered_map< OUString, Node, OUStringHash > Children; + + Node(): startInclude(false) {} + void clear() { startInclude=false; children.clear(); } + + Children children; + bool startInclude; + }; + + Node root_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/propertynode.cxx b/configmgr/source/propertynode.cxx new file mode 100644 index 000000000..351025a2f --- /dev/null +++ b/configmgr/source/propertynode.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <utility> + +#include "components.hxx" +#include "node.hxx" +#include "propertynode.hxx" +#include "type.hxx" + +namespace configmgr { + +PropertyNode::PropertyNode( + int layer, Type staticType, bool nillable, css::uno::Any value, + bool extension): + Node(layer), staticType_(staticType), nillable_(nillable), + extension_(extension), value_(std::move(value)) +{} + +rtl::Reference< Node > PropertyNode::clone(bool) const { + return new PropertyNode(*this); +} + + +css::uno::Any const & PropertyNode::getValue(Components & components) { + if (!externalDescriptor_.isEmpty()) { + css::beans::Optional< css::uno::Any > val( + components.getExternalValue(externalDescriptor_)); + if (val.IsPresent) { + value_ = val.Value; //TODO: check value type + } + externalDescriptor_.clear(); // must not throw + } + SAL_WARN_IF( + !(value_.hasValue() || nillable_), "configmgr", + "non-nillable property without value"); + return value_; +} + +void PropertyNode::setValue(int layer, css::uno::Any const & value) { + setLayer(layer); + value_ = value; + externalDescriptor_.clear(); +} + +css::uno::Any *PropertyNode::getValuePtr(int layer) +{ + setLayer(layer); + externalDescriptor_.clear(); + return &value_; +} + +void PropertyNode::setExternal(int layer, OUString const & descriptor) { + assert(!descriptor.isEmpty()); + setLayer(layer); + externalDescriptor_ = descriptor; +} + +PropertyNode::~PropertyNode() {} + +Node::Kind PropertyNode::kind() const { + return KIND_PROPERTY; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/propertynode.hxx b/configmgr/source/propertynode.hxx new file mode 100644 index 000000000..108b8e944 --- /dev/null +++ b/configmgr/source/propertynode.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <com/sun/star/uno/Any.hxx> +#include <rtl/ref.hxx> + +#include "node.hxx" +#include "type.hxx" + + +namespace configmgr { + +class Components; + +class PropertyNode: public Node { +public: + PropertyNode( + int layer, Type staticType, bool nillable, + css::uno::Any value, bool extension); + + virtual rtl::Reference< Node > clone(bool keepTemplateName) const override; + + Type getStaticType() const { return staticType_;} + + bool isNillable() const { return nillable_;} + + css::uno::Any const & getValue(Components & components); + + void setValue(int layer, css::uno::Any const & value); + css::uno::Any *getValuePtr(int layer); + + void setExternal(int layer, OUString const & descriptor); + + bool isExtension() const { return extension_;} + +private: + PropertyNode(PropertyNode const&) = default; + + virtual ~PropertyNode() override; + + virtual Kind kind() const override; + + Type staticType_; + // as specified in the component-schema (TYPE_ANY, ..., + // TYPE_HEXBINARY_LIST; not TYPE_ERROR or TYPE_NIL) + bool nillable_; + bool extension_; + OUString externalDescriptor_; + css::uno::Any value_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/readonlyaccess.cxx b/configmgr/source/readonlyaccess.cxx new file mode 100644 index 000000000..76ec606d5 --- /dev/null +++ b/configmgr/source/readonlyaccess.cxx @@ -0,0 +1,121 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/NotInitializedException.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> +#include <sal/types.h> + +#include "components.hxx" +#include "lock.hxx" +#include "rootaccess.hxx" + +namespace configmgr::read_only_access { + +namespace { + +class Service: + public cppu::WeakImplHelper< + css::lang::XServiceInfo, css::lang::XInitialization, + css::container::XHierarchicalNameAccess > +{ +public: + explicit Service( + css::uno::Reference< css::uno::XComponentContext > context): + context_(std::move(context)) {} + +private: + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + virtual ~Service() override {} + + virtual OUString SAL_CALL getImplementationName() override + { return "com.sun.star.comp.configuration.ReadOnlyAccess"; } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override + { return { "com.sun.star.configuration.ReadOnlyAccess" }; } + + virtual void SAL_CALL initialize( + css::uno::Sequence< css::uno::Any > const & aArguments) override; + + virtual css::uno::Any SAL_CALL getByHierarchicalName( + OUString const & aName) override + { return getRoot()->getByHierarchicalName(aName); } + + virtual sal_Bool SAL_CALL hasByHierarchicalName(OUString const & aName) override + { return getRoot()->hasByHierarchicalName(aName); } + + rtl::Reference< RootAccess > getRoot(); + + css::uno::Reference< css::uno::XComponentContext > context_; + + osl::Mutex mutex_; + rtl::Reference< RootAccess > root_; +}; + +void Service::initialize(css::uno::Sequence< css::uno::Any > const & aArguments) +{ + OUString locale; + if (aArguments.getLength() != 1 || !(aArguments[0] >>= locale)) { + throw css::lang::IllegalArgumentException( + "not exactly one string argument", + static_cast< cppu::OWeakObject * >(this), -1); + } + osl::MutexGuard g1(mutex_); + if (root_.is()) { + throw css::uno::RuntimeException( + "already initialized", static_cast< cppu::OWeakObject * >(this)); + } + osl::MutexGuard g2(*lock()); + Components & components = Components::getSingleton(context_); + root_ = new RootAccess(components, "/", locale, false); + components.addRootAccess(root_); +} + +rtl::Reference< RootAccess > Service::getRoot() { + osl::MutexGuard g(mutex_); + if (!root_.is()) { + throw css::lang::NotInitializedException( + "not initialized", static_cast< cppu::OWeakObject * >(this)); + } + return root_; +} + +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_configuration_ReadOnlyAccess_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new configmgr::read_only_access::Service(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/readwriteaccess.cxx b/configmgr/source/readwriteaccess.cxx new file mode 100644 index 000000000..57e8b298a --- /dev/null +++ b/configmgr/source/readwriteaccess.cxx @@ -0,0 +1,144 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <com/sun/star/configuration/XReadWriteAccess.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/NotInitializedException.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> +#include <sal/types.h> + +#include "components.hxx" +#include "lock.hxx" +#include "rootaccess.hxx" + +namespace configmgr::read_write_access { + +namespace { + +class Service: + public cppu::WeakImplHelper< + css::lang::XServiceInfo, css::lang::XInitialization, + css::configuration::XReadWriteAccess > +{ +public: + explicit Service( + css::uno::Reference< css::uno::XComponentContext > context): + context_(std::move(context)) {} + +private: + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + virtual ~Service() override {} + + virtual OUString SAL_CALL getImplementationName() override + { return "com.sun.star.comp.configuration.ReadWriteAccess"; } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override + { return { "com.sun.star.configuration.ReadWriteAccess" }; } + + virtual void SAL_CALL initialize( + css::uno::Sequence< css::uno::Any > const & aArguments) override; + + virtual css::uno::Any SAL_CALL getByHierarchicalName( + OUString const & aName) override + { return getRoot()->getByHierarchicalName(aName); } + + virtual sal_Bool SAL_CALL hasByHierarchicalName(OUString const & aName) override + { return getRoot()->hasByHierarchicalName(aName); } + + virtual void SAL_CALL replaceByHierarchicalName( + OUString const & aName, css::uno::Any const & aElement) override + { getRoot()->replaceByHierarchicalName(aName, aElement); } + + virtual void SAL_CALL commitChanges() override + { getRoot()->commitChanges(); } + + virtual sal_Bool SAL_CALL hasPendingChanges() override + { return getRoot()->hasPendingChanges(); } + + virtual css::uno::Sequence< ::css::util::ElementChange > SAL_CALL getPendingChanges() override + { return getRoot()->getPendingChanges(); } + + css::beans::Property SAL_CALL getPropertyByHierarchicalName( + OUString const & aHierarchicalName) + override + { return getRoot()->getPropertyByHierarchicalName(aHierarchicalName); } + + sal_Bool SAL_CALL hasPropertyByHierarchicalName( + OUString const & aHierarchicalName) override + { return getRoot()->hasPropertyByHierarchicalName(aHierarchicalName); } + + rtl::Reference< RootAccess > getRoot(); + + css::uno::Reference< css::uno::XComponentContext > context_; + + osl::Mutex mutex_; + rtl::Reference< RootAccess > root_; +}; + +void Service::initialize(css::uno::Sequence< css::uno::Any > const & aArguments) +{ + OUString locale; + if (aArguments.getLength() != 1 || !(aArguments[0] >>= locale)) { + throw css::lang::IllegalArgumentException( + "not exactly one string argument", + static_cast< cppu::OWeakObject * >(this), -1); + } + osl::MutexGuard g1(mutex_); + if (root_.is()) { + throw css::uno::RuntimeException( + "already initialized", static_cast< cppu::OWeakObject * >(this)); + } + osl::MutexGuard g2(*lock()); + Components & components = Components::getSingleton(context_); + root_ = new RootAccess(components, "/", locale, true); + components.addRootAccess(root_); +} + +rtl::Reference< RootAccess > Service::getRoot() { + osl::MutexGuard g(mutex_); + if (!root_.is()) { + throw css::lang::NotInitializedException( + "not initialized", static_cast< cppu::OWeakObject * >(this)); + } + return root_; +} + +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_configuration_ReadWriteAccess_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new configmgr::read_write_access::Service(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/rootaccess.cxx b/configmgr/source/rootaccess.cxx new file mode 100644 index 000000000..ff7adddef --- /dev/null +++ b/configmgr/source/rootaccess.cxx @@ -0,0 +1,313 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <utility> +#include <vector> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/ChangesEvent.hpp> +#include <com/sun/star/util/ChangesSet.hpp> +#include <com/sun/star/util/ElementChange.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/util/XChangesListener.hpp> +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <comphelper/sequence.hxx> +#include <cppu/unotype.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> + +#include "broadcaster.hxx" +#include "components.hxx" +#include "data.hxx" +#include "lock.hxx" +#include "modifications.hxx" +#include "node.hxx" +#include "rootaccess.hxx" + +namespace configmgr { + +RootAccess::RootAccess( + Components & components, OUString pathRepresentation, + OUString locale, bool update): + Access(components), pathRepresentation_(std::move(pathRepresentation)), + locale_(std::move(locale)), + lock_( lock() ), + update_(update), finalized_(false), alive_(true) +{ +} + +std::vector<OUString> RootAccess::getAbsolutePath() { + getNode(); + return path_; +} + +void RootAccess::initBroadcaster( + Modifications::Node const & modifications, Broadcaster * broadcaster) +{ + assert(broadcaster != nullptr); + std::vector< css::util::ElementChange > changes; + initBroadcasterAndChanges( + modifications, broadcaster, changesListeners_.empty() ? nullptr : &changes); + if (changes.empty()) + return; + + css::util::ChangesSet set(comphelper::containerToSequence(changes)); + for (auto const& changesListener : changesListeners_) + { + cppu::OWeakObject* pSource = this; + css::uno::Reference< css::uno::XInterface > xBase( pSource, css::uno::UNO_QUERY ); + broadcaster->addChangesNotification( + changesListener, + css::util::ChangesEvent( + pSource, css::uno::Any( xBase ), set)); + } +} + +void RootAccess::acquire() noexcept { + Access::acquire(); +} + +void RootAccess::release() noexcept { + Access::release(); +} + +OUString const & RootAccess::getAbsolutePathRepresentation() { + getNode(); // turn pathRepresentation_ into canonic form + return pathRepresentation_; +} + + +void RootAccess::setAlive(bool b) { + alive_ = b; +} + +void RootAccess::addChangesListener( + css::uno::Reference< css::util::XChangesListener > const & aListener) +{ + assert(thisIs(IS_ANY)); + { + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + if (!aListener.is()) { + throw css::uno::RuntimeException( + "null listener", static_cast< cppu::OWeakObject * >(this)); + } + if (!isDisposed()) { + changesListeners_.insert(aListener); + return; + } + } + try { + aListener->disposing( + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } catch (css::lang::DisposedException &) {} +} + +void RootAccess::removeChangesListener( + css::uno::Reference< css::util::XChangesListener > const & aListener) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + ChangesListeners::iterator i(changesListeners_.find(aListener)); + if (i != changesListeners_.end()) { + changesListeners_.erase(i); + } +} + +void RootAccess::commitChanges() +{ + assert(thisIs(IS_UPDATE)); + if (!alive_) + { + return; + } + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + + checkLocalizedPropertyAccess(); + int finalizedLayer; + Modifications globalMods; + commitChildChanges( + ((getComponents().resolvePathRepresentation( + pathRepresentation_, nullptr, nullptr, &finalizedLayer) + == node_) && + finalizedLayer == Data::NO_LAYER), + &globalMods); + getComponents().writeModifications(); + getComponents().initGlobalBroadcaster(globalMods, this, &bc); + } + bc.send(); +} + +sal_Bool RootAccess::hasPendingChanges() { + assert(thisIs(IS_UPDATE)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + //TODO: Optimize: + std::vector< css::util::ElementChange > changes; + reportChildChanges(&changes); + return !changes.empty(); +} + +css::uno::Sequence< ::css::util::ElementChange > RootAccess::getPendingChanges() +{ + assert(thisIs(IS_UPDATE)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + std::vector< css::util::ElementChange > changes; + reportChildChanges(&changes); + return comphelper::containerToSequence(changes); +} + +RootAccess::~RootAccess() +{ + osl::MutexGuard g(*lock_); + if (alive_) + getComponents().removeRootAccess(this); +} + +std::vector<OUString> RootAccess::getRelativePath() { + return std::vector<OUString>(); +} + +OUString RootAccess::getRelativePathRepresentation() { + return OUString(); +} + +rtl::Reference< Node > RootAccess::getNode() { + if (!node_.is()) { + OUString canonic; + int finalizedLayer; + node_ = getComponents().resolvePathRepresentation( + pathRepresentation_, &canonic, &path_, &finalizedLayer); + if (!node_.is()) { + throw css::uno::RuntimeException( + "cannot find " + pathRepresentation_, nullptr); + // RootAccess::queryInterface indirectly calls + // RootAccess::getNode, so if this RootAccess were passed out in + // RuntimeException.Context, client code that called + // queryInterface on it would cause trouble; therefore, + // RuntimeException.Context is left null here + } + pathRepresentation_ = canonic; + assert(!path_.empty() || node_->kind() == Node::KIND_ROOT); + if (!path_.empty()) { + name_ = path_.back(); + } + finalized_ = finalizedLayer != Data::NO_LAYER; + } + return node_; +} + +bool RootAccess::isFinalized() { + getNode(); + return finalized_; +} + +const OUString & RootAccess::getNameInternal() { + getNode(); + return name_; +} + +rtl::Reference< RootAccess > RootAccess::getRootAccess() { + return this; +} + +rtl::Reference< Access > RootAccess::getParentAccess() { + return rtl::Reference< Access >(); +} + +void RootAccess::addTypes(std::vector< css::uno::Type > * types) const { + assert(types != nullptr); + types->push_back(cppu::UnoType< css::util::XChangesNotifier >::get()); + types->push_back(cppu::UnoType< css::util::XChangesBatch >::get()); +} + +void RootAccess::addSupportedServiceNames( + std::vector<OUString> * services) +{ + assert(services != nullptr); + services->push_back("com.sun.star.configuration.AccessRootElement"); + if (update_) { + services->push_back("com.sun.star.configuration.UpdateRootElement"); + } +} + +void RootAccess::initDisposeBroadcaster(Broadcaster * broadcaster) { + assert(broadcaster != nullptr); + for (auto const& changesListener : changesListeners_) + { + broadcaster->addDisposeNotification( + changesListener, + css::lang::EventObject(static_cast< cppu::OWeakObject * >(this))); + } + Access::initDisposeBroadcaster(broadcaster); +} + +void RootAccess::clearListeners() noexcept { + changesListeners_.clear(); + Access::clearListeners(); +} + +css::uno::Any RootAccess::queryInterface(css::uno::Type const & aType) +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + css::uno::Any res(Access::queryInterface(aType)); + if (res.hasValue()) { + return res; + } + res = cppu::queryInterface( + aType, static_cast< css::util::XChangesNotifier * >(this)); + if (res.hasValue()) { + return res; + } + if (!res.hasValue() && update_) { + res = cppu::queryInterface( + aType, static_cast< css::util::XChangesBatch * >(this)); + } + return res; +} + +OUString RootAccess::getImplementationName() +{ + assert(thisIs(IS_ANY)); + osl::MutexGuard g(*lock_); + checkLocalizedPropertyAccess(); + return "configmgr.RootAccess"; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/rootaccess.hxx b/configmgr/source/rootaccess.hxx new file mode 100644 index 000000000..4eb90d36c --- /dev/null +++ b/configmgr/source/rootaccess.hxx @@ -0,0 +1,144 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> +#include <vector> + +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include "access.hxx" +#include "modifications.hxx" + +namespace com::sun::star { + namespace uno { + class Any; + class Type; + } + namespace util { class XChangesListener; } +} + +namespace configmgr { + +class Broadcaster; +class Components; +class Node; + +class RootAccess: + public Access, public css::util::XChangesNotifier, + public css::util::XChangesBatch +{ +public: + RootAccess( + Components & components, OUString pathRepresentation, + OUString locale, bool update); + + virtual std::vector<OUString> getAbsolutePath() override; + + virtual void initBroadcaster( + Modifications::Node const & modifications, Broadcaster * broadcaster) override; + + virtual void SAL_CALL acquire() noexcept override; + + virtual void SAL_CALL release() noexcept override; + + OUString const & getAbsolutePathRepresentation(); + + const OUString& getLocale() const { return locale_;} + + bool isUpdate() const { return update_;} + + void setAlive(bool b); + + virtual void SAL_CALL addChangesListener( + css::uno::Reference< css::util::XChangesListener > + const & aListener) override; + + virtual void SAL_CALL removeChangesListener( + css::uno::Reference< css::util::XChangesListener > + const & aListener) override; + + virtual void SAL_CALL commitChanges() override; + + virtual sal_Bool SAL_CALL hasPendingChanges() override; + + virtual css::uno::Sequence< ::css::util::ElementChange > SAL_CALL getPendingChanges() override; + +private: + virtual ~RootAccess() override; + + virtual std::vector<OUString> getRelativePath() override; + + virtual OUString getRelativePathRepresentation() override; + + virtual rtl::Reference< Node > getNode() override; + + virtual bool isFinalized() override; + + virtual const OUString & getNameInternal() override; + + virtual rtl::Reference< RootAccess > getRootAccess() override; + + virtual rtl::Reference< Access > getParentAccess() override; + + virtual void addTypes(std::vector< css::uno::Type > * types) + const override; + + virtual void addSupportedServiceNames( + std::vector<OUString> * services) override; + + virtual void initDisposeBroadcaster(Broadcaster * broadcaster) override; + + virtual void clearListeners() noexcept override; + + virtual css::uno::Any SAL_CALL queryInterface( + css::uno::Type const & aType) override; + + virtual OUString SAL_CALL getImplementationName() override; + + typedef + std::multiset< + css::uno::Reference< + css::util::XChangesListener > > + ChangesListeners; + + OUString pathRepresentation_; + OUString locale_; + std::vector<OUString> path_; + rtl::Reference< Node > node_; + OUString name_; + ChangesListeners changesListeners_; + + std::shared_ptr<osl::Mutex> lock_; + + bool update_:1; + bool finalized_:1; + bool alive_:1; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/rootnode.cxx b/configmgr/source/rootnode.cxx new file mode 100644 index 000000000..ce70d688c --- /dev/null +++ b/configmgr/source/rootnode.cxx @@ -0,0 +1,39 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <rtl/ref.hxx> + +#include "data.hxx" +#include "node.hxx" +#include "rootnode.hxx" + +namespace configmgr { + +RootNode::RootNode(): Node(Data::NO_LAYER) {} + +RootNode::~RootNode() {} + +Node::Kind RootNode::kind() const { + return KIND_ROOT; +} + +rtl::Reference< Node > RootNode::clone(bool) const { + assert(false); // this cannot happen + return rtl::Reference< Node >(); +} + +NodeMap & RootNode::getMembers() { + return members_; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/rootnode.hxx b/configmgr/source/rootnode.hxx new file mode 100644 index 000000000..84e10ef26 --- /dev/null +++ b/configmgr/source/rootnode.hxx @@ -0,0 +1,39 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ref.hxx> + +#include "node.hxx" +#include "nodemap.hxx" + +namespace configmgr +{ +class RootNode : public Node +{ +public: + RootNode(); + +private: + virtual ~RootNode() override; + + virtual Kind kind() const override; + + virtual rtl::Reference<Node> clone(bool keepTemplateName) const override; + + virtual NodeMap& getMembers() override; + + NodeMap members_; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/setnode.cxx b/configmgr/source/setnode.cxx new file mode 100644 index 000000000..5e15ac31d --- /dev/null +++ b/configmgr/source/setnode.cxx @@ -0,0 +1,90 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> + +#include "data.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "setnode.hxx" + +namespace configmgr { + +SetNode::SetNode( + int layer, OUString defaultTemplateName, + OUString templateName): + Node(layer), defaultTemplateName_(std::move(defaultTemplateName)), + templateName_(std::move(templateName)), mandatory_(Data::NO_LAYER) +{} + +rtl::Reference< Node > SetNode::clone(bool keepTemplateName) const { + return new SetNode(*this, keepTemplateName); +} + +NodeMap & SetNode::getMembers() { + return members_; +} + +OUString SetNode::getTemplateName() const { + return templateName_; +} + +void SetNode::setMandatory(int layer) { + mandatory_ = layer; +} + +int SetNode::getMandatory() const { + return mandatory_; +} + + +bool SetNode::isValidTemplate(OUString const & templateName) const { + return Data::equalTemplateNames(templateName, defaultTemplateName_) || + std::any_of( + additionalTemplateNames_.begin(), + additionalTemplateNames_.end(), + [&templateName](OUString const & longName) { return Data::equalTemplateNames(templateName, longName); } ); +} + +SetNode::SetNode(SetNode const & other, bool keepTemplateName): + Node(other), defaultTemplateName_(other.defaultTemplateName_), + additionalTemplateNames_(other.additionalTemplateNames_), + mandatory_(other.mandatory_) +{ + other.members_.cloneInto(&members_); + if (keepTemplateName) { + templateName_ = other.templateName_; + } +} + +SetNode::~SetNode() {} + +Node::Kind SetNode::kind() const { + return KIND_SET; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/setnode.hxx b/configmgr/source/setnode.hxx new file mode 100644 index 000000000..706d153da --- /dev/null +++ b/configmgr/source/setnode.hxx @@ -0,0 +1,73 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vector> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> + +#include "node.hxx" +#include "nodemap.hxx" + +namespace configmgr { + +class SetNode: public Node { +public: + SetNode( + int layer, OUString defaultTemplateName, + OUString templateName); + + virtual rtl::Reference< Node > clone(bool keepTemplateName) const override; + + virtual NodeMap & getMembers() override; + + virtual OUString getTemplateName() const override; + + virtual void setMandatory(int layer) override; + + virtual int getMandatory() const override; + + OUString const & getDefaultTemplateName() const { return defaultTemplateName_;} + + std::vector<OUString> & getAdditionalTemplateNames() { return additionalTemplateNames_;} + + bool isValidTemplate(OUString const & templateName) const; + +private: + SetNode(SetNode const & other, bool keepTemplateName); + + virtual ~SetNode() override; + + virtual Kind kind() const override; + + OUString defaultTemplateName_; + std::vector<OUString> additionalTemplateNames_; + NodeMap members_; + OUString templateName_; + // non-empty if this node is a template, free node, or set member + int mandatory_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/type.cxx b/configmgr/source/type.cxx new file mode 100644 index 000000000..b8fab28ac --- /dev/null +++ b/configmgr/source/type.cxx @@ -0,0 +1,162 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/TypeClass.hpp> +#include <cppu/unotype.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include "type.hxx" + +namespace configmgr { + +bool isListType(Type type) { + return type >= TYPE_BOOLEAN_LIST; +} + +Type elementType(Type type) { + switch (type) { + case TYPE_BOOLEAN_LIST: + return TYPE_BOOLEAN; + case TYPE_SHORT_LIST: + return TYPE_SHORT; + case TYPE_INT_LIST: + return TYPE_INT; + case TYPE_LONG_LIST: + return TYPE_LONG; + case TYPE_DOUBLE_LIST: + return TYPE_DOUBLE; + case TYPE_STRING_LIST: + return TYPE_STRING; + case TYPE_HEXBINARY_LIST: + return TYPE_HEXBINARY; + default: + assert(false); + throw css::uno::RuntimeException("this cannot happen"); + } +} + +css::uno::Type const & mapType(Type type) { + switch (type) { + case TYPE_ANY: + return cppu::UnoType< css::uno::Any >::get(); + case TYPE_BOOLEAN: + return cppu::UnoType< sal_Bool >::get(); + case TYPE_SHORT: + return cppu::UnoType< sal_Int16 >::get(); + case TYPE_INT: + return cppu::UnoType< sal_Int32 >::get(); + case TYPE_LONG: + return cppu::UnoType< sal_Int64 >::get(); + case TYPE_DOUBLE: + return cppu::UnoType< double >::get(); + case TYPE_STRING: + return cppu::UnoType< OUString >::get(); + case TYPE_HEXBINARY: + return cppu::UnoType< css::uno::Sequence< sal_Int8 > >::get(); + case TYPE_BOOLEAN_LIST: + return cppu::UnoType< css::uno::Sequence< sal_Bool > >::get(); + case TYPE_SHORT_LIST: + return cppu::UnoType< css::uno::Sequence< sal_Int16 > >::get(); + case TYPE_INT_LIST: + return cppu::UnoType< css::uno::Sequence< sal_Int32 > >::get(); + case TYPE_LONG_LIST: + return cppu::UnoType< css::uno::Sequence< sal_Int64 > >::get(); + case TYPE_DOUBLE_LIST: + return cppu::UnoType< css::uno::Sequence< double > >::get(); + case TYPE_STRING_LIST: + return cppu::UnoType< css::uno::Sequence< OUString > >::get(); + case TYPE_HEXBINARY_LIST: + return cppu::UnoType< + css::uno::Sequence< css::uno::Sequence< sal_Int8 > > >::get(); + default: + assert(false); + throw css::uno::RuntimeException("this cannot happen"); + } +} + +Type getDynamicType(css::uno::Any const & value) { + switch (value.getValueType().getTypeClass()) { + case css::uno::TypeClass_VOID: + return TYPE_NIL; + case css::uno::TypeClass_BOOLEAN: + return TYPE_BOOLEAN; + case css::uno::TypeClass_BYTE: + return TYPE_SHORT; + case css::uno::TypeClass_SHORT: + return TYPE_SHORT; + case css::uno::TypeClass_UNSIGNED_SHORT: + return value.has< sal_Int16 >() ? TYPE_SHORT : TYPE_INT; + case css::uno::TypeClass_LONG: + return TYPE_INT; + case css::uno::TypeClass_UNSIGNED_LONG: + return value.has< sal_Int32 >() ? TYPE_INT : TYPE_LONG; + case css::uno::TypeClass_HYPER: + return TYPE_LONG; + case css::uno::TypeClass_UNSIGNED_HYPER: + return value.has< sal_Int64 >() ? TYPE_LONG : TYPE_ERROR; + case css::uno::TypeClass_FLOAT: + case css::uno::TypeClass_DOUBLE: + return TYPE_DOUBLE; + case css::uno::TypeClass_STRING: + return TYPE_STRING; + case css::uno::TypeClass_SEQUENCE: //TODO + { + OUString name(value.getValueType().getTypeName()); + if ( name == "[]byte" ) { + return TYPE_HEXBINARY; + } else if (name == "[]boolean") + { + return TYPE_BOOLEAN_LIST; + } else if ( name == "[]short" ) + { + return TYPE_SHORT_LIST; + } else if ( name == "[]long" ) + { + return TYPE_INT_LIST; + } else if ( name == "[]hyper" ) + { + return TYPE_LONG_LIST; + } else if (name == "[]double") + { + return TYPE_DOUBLE_LIST; + } else if (name == "[]string") + { + return TYPE_STRING_LIST; + } else if (name == "[][]byte") + { + return TYPE_HEXBINARY_LIST; + } + } + [[fallthrough]]; + default: + return TYPE_ERROR; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/type.hxx b/configmgr/source/type.hxx new file mode 100644 index 000000000..7fd096ac1 --- /dev/null +++ b/configmgr/source/type.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include <sal/types.h> + +namespace com::sun::star::uno { + class Any; + class Type; +} + +namespace configmgr { + +enum Type { + TYPE_ERROR, TYPE_NIL, TYPE_ANY, TYPE_BOOLEAN, TYPE_SHORT, TYPE_INT, + TYPE_LONG, TYPE_DOUBLE, TYPE_STRING, TYPE_HEXBINARY, TYPE_BOOLEAN_LIST, + TYPE_SHORT_LIST, TYPE_INT_LIST, TYPE_LONG_LIST, TYPE_DOUBLE_LIST, + TYPE_STRING_LIST, TYPE_HEXBINARY_LIST }; + +bool isListType(Type type); + +Type elementType(Type type); + +css::uno::Type const & mapType(Type type); + +Type getDynamicType(css::uno::Any const & value); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/update.cxx b/configmgr/source/update.cxx new file mode 100644 index 000000000..1cc2a06fe --- /dev/null +++ b/configmgr/source/update.cxx @@ -0,0 +1,150 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <set> + +#include <com/sun/star/configuration/XUpdate.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include "broadcaster.hxx" +#include "components.hxx" +#include "lock.hxx" +#include "modifications.hxx" +#include "rootaccess.hxx" + +namespace configmgr::update { + +namespace { + +std::set< OUString > seqToSet( + css::uno::Sequence< OUString > const & sequence) +{ + return std::set< OUString >( sequence.begin(), sequence.end() ); +} + +class Service: + public cppu::WeakImplHelper< css::configuration::XUpdate > +{ +public: + explicit Service(const css::uno::Reference< css::uno::XComponentContext >& context): + context_(context) + { + assert(context.is()); + lock_ = lock(); + } + +private: + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + virtual ~Service() override {} + + virtual void SAL_CALL insertExtensionXcsFile( + sal_Bool shared, OUString const & fileUri) override; + + virtual void SAL_CALL insertExtensionXcuFile( + sal_Bool shared, OUString const & fileUri) override; + + virtual void SAL_CALL removeExtensionXcuFile(OUString const & fileUri) override; + + virtual void SAL_CALL insertModificationXcuFile( + OUString const & fileUri, + css::uno::Sequence< OUString > const & includedPaths, + css::uno::Sequence< OUString > const & excludedPaths) override; + + std::shared_ptr<osl::Mutex> lock_; + css::uno::Reference< css::uno::XComponentContext > context_; +}; + +void Service::insertExtensionXcsFile( + sal_Bool shared, OUString const & fileUri) +{ + osl::MutexGuard g(*lock_); + Components::getSingleton(context_).insertExtensionXcsFile(shared, fileUri); +} + +void Service::insertExtensionXcuFile( + sal_Bool shared, OUString const & fileUri) +{ + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + Components & components = Components::getSingleton(context_); + Modifications mods; + components.insertExtensionXcuFile(shared, fileUri, &mods); + components.initGlobalBroadcaster( + mods, rtl::Reference< RootAccess >(), &bc); + } + bc.send(); +} + +void Service::removeExtensionXcuFile(OUString const & fileUri) +{ + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + Components & components = Components::getSingleton(context_); + Modifications mods; + components.removeExtensionXcuFile(fileUri, &mods); + components.initGlobalBroadcaster( + mods, rtl::Reference< RootAccess >(), &bc); + } + bc.send(); +} + +void Service::insertModificationXcuFile( + OUString const & fileUri, + css::uno::Sequence< OUString > const & includedPaths, + css::uno::Sequence< OUString > const & excludedPaths) +{ + Broadcaster bc; + { + osl::MutexGuard g(*lock_); + Components & components = Components::getSingleton(context_); + Modifications mods; + components.insertModificationXcuFile( + fileUri, seqToSet(includedPaths), seqToSet(excludedPaths), &mods); + components.initGlobalBroadcaster( + mods, rtl::Reference< RootAccess >(), &bc); + } + bc.send(); +} + +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_configuration_Update_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new configmgr::update::Service(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/valueparser.cxx b/configmgr/source/valueparser.cxx new file mode 100644 index 000000000..82778e6c8 --- /dev/null +++ b/configmgr/source/valueparser.cxx @@ -0,0 +1,454 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/sequence.hxx> +#include <rtl/math.h> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "localizedvaluenode.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "parsemanager.hxx" +#include "propertynode.hxx" +#include "type.hxx" +#include "valueparser.hxx" + +namespace configmgr { + +namespace { + +bool parseHexDigit(char c, int * value) { + assert(value != nullptr); + if (c >= '0' && c <= '9') { + *value = c - '0'; + return true; + } + if (c >= 'A' && c <= 'F') { + *value = c - 'A' + 10; + return true; + } + if (c >= 'a' && c <= 'f') { + *value = c - 'a' + 10; + return true; + } + return false; +} + +bool parseValue(xmlreader::Span const & text, sal_Bool * value) { + assert(text.is() && value != nullptr); + if (text == "true" || text == "1") { + *value = true; + return true; + } + if (text == "false" || text == "0") { + *value = false; + return true; + } + return false; +} + +bool parseValue(xmlreader::Span const & text, sal_Int16 * value) { + assert(text.is() && value != nullptr); + // For backwards compatibility, support hexadecimal values: + sal_Int32 n = + rtl_str_shortenedCompareIgnoreAsciiCase_WithLength( + text.begin, text.length, RTL_CONSTASCII_STRINGPARAM("0X"), + RTL_CONSTASCII_LENGTH("0X")) == 0 ? + static_cast< sal_Int32 >( + OString( + text.begin + RTL_CONSTASCII_LENGTH("0X"), + text.length - RTL_CONSTASCII_LENGTH("0X")).toUInt32(16)) : + OString(text.begin, text.length).toInt32(); + //TODO: check valid lexical representation + if (n >= SAL_MIN_INT16 && n <= SAL_MAX_INT16) { + *value = static_cast< sal_Int16 >(n); + return true; + } + return false; +} + +bool parseValue(xmlreader::Span const & text, sal_Int32 * value) { + assert(text.is() && value != nullptr); + // For backwards compatibility, support hexadecimal values: + *value = + rtl_str_shortenedCompareIgnoreAsciiCase_WithLength( + text.begin, text.length, RTL_CONSTASCII_STRINGPARAM("0X"), + RTL_CONSTASCII_LENGTH("0X")) == 0 ? + static_cast< sal_Int32 >( + OString( + text.begin + RTL_CONSTASCII_LENGTH("0X"), + text.length - RTL_CONSTASCII_LENGTH("0X")).toUInt32(16)) : + OString(text.begin, text.length).toInt32(); + //TODO: check valid lexical representation + return true; +} + +bool parseValue(xmlreader::Span const & text, sal_Int64 * value) { + assert(text.is() && value != nullptr); + // For backwards compatibility, support hexadecimal values: + *value = + rtl_str_shortenedCompareIgnoreAsciiCase_WithLength( + text.begin, text.length, RTL_CONSTASCII_STRINGPARAM("0X"), + RTL_CONSTASCII_LENGTH("0X")) == 0 ? + static_cast< sal_Int64 >( + OString( + text.begin + RTL_CONSTASCII_LENGTH("0X"), + text.length - RTL_CONSTASCII_LENGTH("0X")).toUInt64(16)) : + OString(text.begin, text.length).toInt64(); + //TODO: check valid lexical representation + return true; +} + +bool parseValue(xmlreader::Span const & text, double * value) { + assert(text.is() && value != nullptr); + *value = rtl_math_stringToDouble( + text.begin, text.begin + text.length, '.', 0, nullptr, nullptr); + //TODO: check valid lexical representation + return true; +} + +bool parseValue(xmlreader::Span const & text, OUString * value) { + assert(text.is() && value != nullptr); + *value = text.convertFromUtf8(); + return true; +} + +bool parseValue( + xmlreader::Span const & text, css::uno::Sequence< sal_Int8 > * value) +{ + assert(text.is() && value != nullptr); + if ((text.length & 1) != 0) { + return false; + } + std::vector< sal_Int8 > seq; + for (sal_Int32 i = 0; i != text.length;) { + int n1; + int n2; + if (!parseHexDigit(text.begin[i++], &n1) || + !parseHexDigit(text.begin[i++], &n2)) + { + return false; + } + seq.push_back(static_cast< sal_Int8 >((n1 << 4) | n2)); + } + *value = comphelper::containerToSequence(seq); + return true; +} + +template< typename T > css::uno::Any parseSingleValue( + xmlreader::Span const & text) +{ + T val; + if (!parseValue(text, &val)) { + throw css::uno::RuntimeException("invalid value"); + } + return css::uno::Any(val); +} + +template< typename T > css::uno::Any parseListValue( + OString const & separator, xmlreader::Span const & text) +{ + std::vector< T > seq; + xmlreader::Span sep; + if (separator.isEmpty()) { + sep = xmlreader::Span(RTL_CONSTASCII_STRINGPARAM(" ")); + } else { + sep = xmlreader::Span(separator.getStr(), separator.getLength()); + } + if (text.length != 0) { + for (xmlreader::Span t(text);;) { + sal_Int32 i = rtl_str_indexOfStr_WithLength( + t.begin, t.length, sep.begin, sep.length); + T val; + if (!parseValue( + xmlreader::Span(t.begin, i == -1 ? t.length : i), &val)) + { + throw css::uno::RuntimeException("invalid value"); + } + seq.push_back(val); + if (i < 0) { + break; + } + t.begin += i + sep.length; + t.length -= i + sep.length; + } + } + return css::uno::Any(comphelper::containerToSequence(seq)); +} + +css::uno::Any parseValue( + OString const & separator, xmlreader::Span const & text, Type type) +{ + switch (type) { + case TYPE_ANY: + throw css::uno::RuntimeException("invalid value of type any"); + case TYPE_BOOLEAN: + return parseSingleValue< sal_Bool >(text); + case TYPE_SHORT: + return parseSingleValue< sal_Int16 >(text); + case TYPE_INT: + return parseSingleValue< sal_Int32 >(text); + case TYPE_LONG: + return parseSingleValue< sal_Int64 >(text); + case TYPE_DOUBLE: + return parseSingleValue< double >(text); + case TYPE_STRING: + return parseSingleValue< OUString >(text); + case TYPE_HEXBINARY: + return parseSingleValue< css::uno::Sequence< sal_Int8 > >(text); + case TYPE_BOOLEAN_LIST: + return parseListValue< sal_Bool >(separator, text); + case TYPE_SHORT_LIST: + return parseListValue< sal_Int16 >(separator, text); + case TYPE_INT_LIST: + return parseListValue< sal_Int32 >(separator, text); + case TYPE_LONG_LIST: + return parseListValue< sal_Int64 >(separator, text); + case TYPE_DOUBLE_LIST: + return parseListValue< double >(separator, text); + case TYPE_STRING_LIST: + return parseListValue< OUString >(separator, text); + case TYPE_HEXBINARY_LIST: + return parseListValue< css::uno::Sequence< sal_Int8 > >( + separator, text); + default: + assert(false); + throw css::uno::RuntimeException("this cannot happen"); + } +} + +} + +ValueParser::ValueParser(int layer): type_(TYPE_ERROR), layer_(layer), state_() {} + +ValueParser::~ValueParser() {} + +xmlreader::XmlReader::Text ValueParser::getTextMode() const { + if (!node_) + return xmlreader::XmlReader::Text::NONE; + + switch (state_) { + case State::Text: + if (!items_.empty()) { + break; + } + [[fallthrough]]; + case State::IT: + return + (type_ == TYPE_STRING || type_ == TYPE_STRING_LIST || + !separator_.isEmpty()) + ? xmlreader::XmlReader::Text::Raw + : xmlreader::XmlReader::Text::Normalized; + default: + break; + } + return xmlreader::XmlReader::Text::NONE; +} + +bool ValueParser::startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name) +{ + if (!node_.is()) { + return false; + } + switch (state_) { + case State::Text: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && name == "it" && + isListType(type_) && separator_.isEmpty()) + { + pad_.clear(); + // before first <it>, characters are not ignored; assume they + // are only whitespace + state_ = State::IT; + return true; + } + [[fallthrough]]; + case State::IT: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "unicode" && + (type_ == TYPE_STRING || type_ == TYPE_STRING_LIST)) + { + sal_Int32 scalar = -1; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "scalar") + { + if (!parseValue(reader.getAttributeValue(true), &scalar)) { + scalar = -1; + } + break; + } + } + if (scalar >= 0 && scalar < 0x20 && scalar != 0x09 && + scalar != 0x0A && scalar != 0x0D) + { + char c = static_cast< char >(scalar); + pad_.add(&c, 1); + } else if (scalar == 0xFFFE) { + pad_.add(RTL_CONSTASCII_STRINGPARAM("\xEF\xBF\xBE")); + } else if (scalar == 0xFFFF) { + pad_.add(RTL_CONSTASCII_STRINGPARAM("\xEF\xBF\xBF")); + } else { + throw css::uno::RuntimeException( + "bad unicode scalar attribute in " + reader.getUrl()); + } + state_ = state_ == State::Text ? State::TextUnicode : State::ITUnicode; + return true; + } + break; + default: + break; + } + throw css::uno::RuntimeException( + "bad member <" + name.convertFromUtf8() + "> in " + reader.getUrl()); +} + +bool ValueParser::endElement() { + if (!node_.is()) { + return false; + } + switch (state_) { + case State::Text: + { + css::uno::Any *pValue = nullptr; + + switch (node_->kind()) { + case Node::KIND_PROPERTY: + pValue = static_cast< PropertyNode * >(node_.get())->getValuePtr(layer_); + break; + case Node::KIND_LOCALIZED_PROPERTY: + { + NodeMap & members = node_->getMembers(); + NodeMap::iterator i(members.find(localizedName_)); + LocalizedValueNode *pLVNode; + if (i == members.end()) { + pLVNode = new LocalizedValueNode(layer_); + members.insert( + NodeMap::value_type(localizedName_, pLVNode )); + } else { + pLVNode = static_cast< LocalizedValueNode * >(i->second.get()); + } + pValue = pLVNode->getValuePtr(layer_); + } + break; + default: + assert(false); // this cannot happen + return false; + } + + if (items_.empty()) { + *pValue = parseValue(separator_, pad_.get(), type_); + pad_.clear(); + } else { + switch (type_) { + case TYPE_BOOLEAN_LIST: + *pValue = convertItems< sal_Bool >(); + break; + case TYPE_SHORT_LIST: + *pValue = convertItems< sal_Int16 >(); + break; + case TYPE_INT_LIST: + *pValue = convertItems< sal_Int32 >(); + break; + case TYPE_LONG_LIST: + *pValue = convertItems< sal_Int64 >(); + break; + case TYPE_DOUBLE_LIST: + *pValue = convertItems< double >(); + break; + case TYPE_STRING_LIST: + *pValue = convertItems< OUString >(); + break; + case TYPE_HEXBINARY_LIST: + *pValue = convertItems< css::uno::Sequence< sal_Int8 > >(); + break; + default: + assert(false); // this cannot happen + break; + } + items_.clear(); + } + separator_.clear(); + node_.clear(); + } + break; + case State::TextUnicode: + state_ = State::Text; + break; + case State::ITUnicode: + state_ = State::IT; + break; + case State::IT: + items_.push_back( + parseValue(OString(), pad_.get(), elementType(type_))); + pad_.clear(); + state_ = State::Text; + break; + } + return true; +} + +void ValueParser::characters(xmlreader::Span const & text) { + if (node_.is()) { + assert(state_ == State::Text || state_ == State::IT); + pad_.add(text.begin, text.length); + } +} + +void ValueParser::start( + rtl::Reference< Node > const & node, OUString const & localizedName) +{ + assert(node.is() && !node_.is()); + node_ = node; + localizedName_ = localizedName; + state_ = State::Text; +} + + +template< typename T > css::uno::Any ValueParser::convertItems() { + css::uno::Sequence< T > seq(items_.size()); + auto seqRange = asNonConstRange(seq); + for (sal_Int32 i = 0; i < seq.getLength(); ++i) { + bool ok = (items_[i] >>= seqRange[i]); + assert(ok); + (void) ok; // avoid warnings + } + return css::uno::Any(seq); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/valueparser.hxx b/configmgr/source/valueparser.hxx new file mode 100644 index 000000000..33404b838 --- /dev/null +++ b/configmgr/source/valueparser.hxx @@ -0,0 +1,85 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vector> + +#include <rtl/ref.hxx> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <xmlreader/pad.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "type.hxx" + +namespace com::sun::star::uno { + class Any; +} +namespace xmlreader { struct Span; } + +namespace configmgr { + +class Node; + +class ValueParser { +public: + explicit ValueParser(int layer); + + ~ValueParser(); + + xmlreader::XmlReader::Text getTextMode() const; + + bool startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name); + + bool endElement(); + + void characters(xmlreader::Span const & text); + + void start( + rtl::Reference< Node > const & property, + OUString const & localizedName = OUString()); + + int getLayer() const { return layer_;} + + Type type_; + OString separator_; + +private: + ValueParser(const ValueParser&) = delete; + ValueParser& operator=(const ValueParser&) = delete; + + template< typename T > css::uno::Any convertItems(); + + enum class State { Text, TextUnicode, IT, ITUnicode }; + + int layer_; + rtl::Reference< Node > node_; + OUString localizedName_; + State state_; + xmlreader::Pad pad_; + std::vector< css::uno::Any > items_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/winreg.cxx b/configmgr/source/winreg.cxx new file mode 100644 index 000000000..381150fc2 --- /dev/null +++ b/configmgr/source/winreg.cxx @@ -0,0 +1,331 @@ +/* -*- 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/. + * + */ + +#include <cwchar> +#include <memory> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <msiquery.h> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/file.h> +#include <osl/file.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include "winreg.hxx" +#include "writemodfile.hxx" + +#define MAX_KEY_LENGTH 255 + +namespace configmgr { + +namespace { +// This is not a generic registry reader. We assume the following structure: +// Last element of Key becomes prop, first part is the path and optionally nodes, +// when the node has oor:op attribute. +// Values can be the following: Value (string), Type (string, optional), +// Final (dword, optional), External (dword, optional), ExternalBackend (string, optional), +// Nil (dword, optional) +// +// For example the following registry setting: +// [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\LibreOffice\org.openoffice.UserProfile\Data\o] +// "Value"="Example Corp." +// "Final"=dword:00000001 +// becomes the following in configuration: +// <!-- set the Company name --> +// <item oor:path="/org.openoffice.UserProfile/Data"> +// <prop oor:name="o" oor:finalized="true"> +// <value>Example Corp.</value> +// </prop> +// </item> +// +// Another example: +// [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\LibreOffice\org.openoffice.Office.OptionsDialog\OptionsDialogGroups\ProductName/#fuse\Pages\Java/#fuse\Hide] +// "Value"="true" +// becomes the following in configuration: +// <!-- Hide Tools - Options - LibreOffice - Advanced panel --> +// <item oor:path="/org.openoffice.Office.OptionsDialog/OptionsDialogGroups"> +// <node oor:name="ProductName" oor:op="fuse"> +// <node oor:name="Pages"> +// <node oor:name="Java" oor:op="fuse"> +// <prop oor:name="Hide"> +// <value>true</value> +// </prop> +// </node> +// </node> +// </node> +// </item> +// +// Third example (property of an extensible group -> needs type): +// [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\LibreOffice\org.openoffice.Office.Jobs\Jobs\org.openoffice.Office.Jobs:Job['UpdateCheck']\Arguments\AutoCheckEnabled] +// "Value"="false" +// "Final"=dword:00000001 +// "Type"="xs:boolean" +// becomes the following in configuration: +// <item oor:path="/org.openoffice.Office.Jobs/Jobs/org.openoffice.Office.Jobs:Job['UpdateCheck']/Arguments"> +// <prop oor:name="AutoCheckEnabled" oor:type="xs:boolean" oor:finalized="true"> +// <value>false</value> +// </prop> +// </item> +// +// External (component data) example: +// [HKEY_CURRENT_USER\Software\Policies\LibreOffice\org.openoffice.UserProfile\Data\o] +// "Value"="company" +// "Final"=dword:00000001 +// "External"=dword:00000001 +// "ExternalBackend"="com.sun.star.configuration.backend.LdapUserProfileBe" +// becomes the following in configuration: +// <item oor:path="/org.openoffice.UserProfile/Data"> +// <prop oor:name="o" oor:finalized="true"> +// <value oor:external="com.sun.star.configuration.backend.LdapUserProfileBe company"/> +// </prop> +// </item> +// +// Nil example: +// Empty value (<value></value>) and nil value (<value xsi:nil="true"/>) are different. +// In case of some path settings, the base path setting has to be cleared. +// [HKEY_CURRENT_USER\Software\Policies\LibreOffice\org.openoffice.Office.Common\Path\Current\Work] +// "Value"="" +// "Final"=dword:00000001 +// "Nil"=dword:00000001 +// [HKEY_CURRENT_USER\Software\Policies\LibreOffice\org.openoffice.Office.Paths\Paths\org.openoffice.Office.Paths:NamedPath['Work']\WritePath] +// "Value"="file:///H:/" +// "Final"=dword:00000001 +// becomes the following in configuration: +// <item oor:path="/org.openoffice.Office.Common/Path/Current"> +// <prop oor:name="Work" oor:finalized="true"> +// <value xsi:nil="true"/> +// </prop> +// </item> +// <item oor:path="/org.openoffice.Office.Paths/Paths/org.openoffice.Office.Paths:NamedPath['Work']"> +// <prop oor:name="WritePath" oor:finalized="true"> +// <value>file:///H:/</value> +// </prop> +// </item> + +void dumpWindowsRegistryKey(HKEY hKey, OUString const & aKeyName, TempFile &aFileHandle) +{ + HKEY hCurKey; + + if(RegOpenKeyExW( + hKey, o3tl::toW(aKeyName.getStr()), 0, + KEY_READ, &hCurKey) + == ERROR_SUCCESS) + { + DWORD nSubKeys = 0; + DWORD nValues = 0; + DWORD nLongestValueNameLen, nLongestValueLen; + // Query the number of subkeys + RegQueryInfoKeyW(hCurKey, nullptr, nullptr, nullptr, &nSubKeys, nullptr, nullptr, &nValues, &nLongestValueNameLen, &nLongestValueLen, nullptr, nullptr); + if(nSubKeys) + { + //Look for subkeys in this key + for(DWORD i = 0; i < nSubKeys; i++) + { + wchar_t buffKeyName[MAX_KEY_LENGTH]; + buffKeyName[0] = '\0'; + DWORD buffSize=MAX_KEY_LENGTH; + OUString aSubkeyName; + //Get subkey name + RegEnumKeyExW(hCurKey, i, buffKeyName, &buffSize, nullptr, nullptr, nullptr, nullptr); + + //Make up full key name + if(aKeyName.isEmpty()) + aSubkeyName = aKeyName + o3tl::toU(buffKeyName); + else + aSubkeyName = aKeyName + "\\" + o3tl::toU(buffKeyName); + + //Recursion, until no more subkeys are found + dumpWindowsRegistryKey(hKey, aSubkeyName, aFileHandle); + } + } + else if(nValues) + { + // No more subkeys, we are at a leaf + auto pValueName = std::unique_ptr<wchar_t[]>( + new wchar_t[nLongestValueNameLen + 1]); + auto pValue = std::unique_ptr<wchar_t[]>( + new wchar_t[nLongestValueLen/sizeof(wchar_t) + 1]); + + bool bFinal = false; + bool bExternal = false; + bool bNil = false; + OUString aValue; + OUString aType; + OUString aExternalBackend; + + for(DWORD i = 0; i < nValues; ++i) + { + DWORD nValueNameLen = nLongestValueNameLen + 1; + DWORD nValueLen = nLongestValueLen + 1; + + RegEnumValueW(hCurKey, i, pValueName.get(), &nValueNameLen, nullptr, nullptr, reinterpret_cast<LPBYTE>(pValue.get()), &nValueLen); + + if (!wcscmp(pValueName.get(), L"Value")) + aValue = o3tl::toU(pValue.get()); + else if (!wcscmp(pValueName.get(), L"Type")) + aType = o3tl::toU(pValue.get()); + else if (!wcscmp(pValueName.get(), L"Final")) + { + if (*reinterpret_cast<DWORD*>(pValue.get()) == 1) + bFinal = true; + } + else if (!wcscmp(pValueName.get(), L"Nil")) + { + if (*reinterpret_cast<DWORD*>(pValue.get()) == 1) + bNil = true; + } + else if (!wcscmp(pValueName.get(), L"External")) + { + if (*reinterpret_cast<DWORD*>(pValue.get()) == 1) + bExternal = true; + } + else if (!wcscmp(pValueName.get(), L"ExternalBackend")) + aExternalBackend = o3tl::toU(pValue.get()); + } + if (bExternal) + { + // type and external are mutually exclusive + aType.clear(); + + // Prepend backend, like in + // "com.sun.star.configuration.backend.LdapUserProfileBe company" + if (!aExternalBackend.isEmpty()) + aValue = aExternalBackend + " " + aValue; + } + + sal_Int32 aLastSeparator = aKeyName.lastIndexOf('\\'); + OUString aPathAndNodes = aKeyName.copy(0, aLastSeparator); + OUString aProp = aKeyName.copy(aLastSeparator + 1); + bool bHasNode = false; + sal_Int32 nCloseNode = 0; + + aFileHandle.writeString("<item oor:path=\""); + for(sal_Int32 nIndex = 0;;) + { + OUString aNextPathPart = aPathAndNodes.getToken(0, '\\', nIndex); + + if(!aNextPathPart.isEmpty()) + { + if((aNextPathPart.lastIndexOf("/#") != -1) || bHasNode) + { + bHasNode = true; + nCloseNode++; + aFileHandle.writeString("\"><node oor:name=\""); + sal_Int32 nCommandSeparator = aNextPathPart.lastIndexOf('#'); + if(nCommandSeparator != -1) + { + OUString aNodeOp = aNextPathPart.copy(nCommandSeparator + 1); + writeAttributeValue(aFileHandle, aNextPathPart.subView(0, nCommandSeparator - 1)); + aFileHandle.writeString("\" oor:op=\""); + writeAttributeValue(aFileHandle, aNodeOp); + } + else + { + writeAttributeValue(aFileHandle, aNextPathPart); + } + } + else + { + writeAttributeValue( + aFileHandle, OUStringConcatenation("/" + aNextPathPart)); + } + } + else + { + aFileHandle.writeString("\">"); + break; + } + } + + aFileHandle.writeString("<prop oor:name=\""); + writeAttributeValue(aFileHandle, aProp); + aFileHandle.writeString("\""); + if(!aType.isEmpty()) + { + aFileHandle.writeString(" oor:type=\""); + writeAttributeValue(aFileHandle, aType); + aFileHandle.writeString("\""); + } + if(bFinal) + aFileHandle.writeString(" oor:finalized=\"true\""); + aFileHandle.writeString("><value"); + if (aValue.isEmpty() && bNil) + { + aFileHandle.writeString(" xsi:nil=\"true\"/"); + } + else if (bExternal) + { + aFileHandle.writeString(" oor:external=\""); + writeAttributeValue(aFileHandle, aValue); + aFileHandle.writeString("\"/"); + } + else + { + aFileHandle.writeString(">"); + writeValueContent(aFileHandle, aValue); + aFileHandle.writeString("</value"); + } + aFileHandle.writeString("></prop>"); + for(; nCloseNode > 0; nCloseNode--) + aFileHandle.writeString("</node>"); + aFileHandle.writeString("</item>\n"); + } + RegCloseKey(hCurKey); + } +} +} + +bool dumpWindowsRegistry(OUString* pFileURL, WinRegType eType) +{ + HKEY hKey; + HKEY hDomain = eType == LOCAL_MACHINE ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + if(RegOpenKeyExW(hDomain, L"SOFTWARE\\Policies\\LibreOffice", 0, KEY_READ, &hKey) != ERROR_SUCCESS) + { + SAL_INFO( + "configmgr", + ("Windows registry settings do not exist in HKLM\\SOFTWARE\\Policies\\LibreOffice")); + return false; + } + + TempFile aFileHandle; + switch (osl::FileBase::createTempFile(nullptr, &aFileHandle.handle, pFileURL)) { + case osl::FileBase::E_None: + break; + case osl::FileBase::E_ACCES: + SAL_INFO( + "configmgr", + ("cannot create temp Windows registry dump (E_ACCES)")); + return false; + default: + throw css::uno::RuntimeException( + "cannot create temporary file"); + } + aFileHandle.url = *pFileURL; + aFileHandle.writeString( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items" + " xmlns:oor=\"http://openoffice.org/2001/registry\"" + " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"); + dumpWindowsRegistryKey(hKey, "", aFileHandle); + aFileHandle.writeString("</oor:items>"); + oslFileError e = aFileHandle.closeWithoutUnlink(); + if (e != osl_File_E_None) + SAL_WARN("configmgr", "osl_closeFile failed with " << +e); + RegCloseKey(hKey); + return true; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/winreg.hxx b/configmgr/source/winreg.hxx new file mode 100644 index 000000000..132a1f6d4 --- /dev/null +++ b/configmgr/source/winreg.hxx @@ -0,0 +1,21 @@ +/* -*- 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/. + * + */ + +#pragma once + +namespace configmgr { + +enum WinRegType { LOCAL_MACHINE, CURRENT_USER }; + +bool dumpWindowsRegistry(OUString* pFileURL, WinRegType eType); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/writemodfile.cxx b/configmgr/source/writemodfile.cxx new file mode 100644 index 000000000..09fe0949b --- /dev/null +++ b/configmgr/source/writemodfile.cxx @@ -0,0 +1,640 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <cstddef> +#include <limits> +#include <string_view> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <o3tl/safeint.hxx> +#include <osl/file.h> +#include <osl/file.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/textcvt.h> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <xmlreader/span.hxx> + +#include "data.hxx" +#include "groupnode.hxx" +#include "localizedpropertynode.hxx" +#include "localizedvaluenode.hxx" +#include "modifications.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "propertynode.hxx" +#include "type.hxx" +#include "writemodfile.hxx" + +namespace configmgr { + +class Components; + +namespace { + +OString convertToUtf8(std::u16string_view text) { + OString s; + assert(text.size() <= o3tl::make_unsigned(std::numeric_limits<sal_Int32>::max())); + if (!rtl_convertUStringToString( + &s.pData, text.data(), text.size(), + RTL_TEXTENCODING_UTF8, + (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))) + { + throw css::uno::RuntimeException( + "cannot convert to UTF-8"); + } + return s; +} + +} // anonymous namespace + +TempFile::~TempFile() { + if (handle == nullptr) + return; + + if (!closed) { + oslFileError e = osl_closeFile(handle); + if (e != osl_File_E_None) { + SAL_WARN("configmgr", "osl_closeFile failed with " << +e); + } + } + osl::FileBase::RC e = osl::File::remove(url); + if (e != osl::FileBase::E_None) { + SAL_WARN( + "configmgr", + "osl::File::remove(" << url << ") failed with " << +e); + } +} + +#ifdef _WIN32 +oslFileError TempFile::closeWithoutUnlink() { + flush(); + oslFileError e = osl_closeFile(handle); + handle = nullptr; + closed = true; + return e; +} +#endif + +void TempFile::closeAndRename(const OUString &_url) { + oslFileError e = flush(); + if (e != osl_File_E_None) { + throw css::uno::RuntimeException( + "cannot write to " + url); + } + e = osl_closeFile(handle); + closed = true; + if (e != osl_File_E_None) { + throw css::uno::RuntimeException( + "cannot close " + url); + } + if (osl::File::replace(url, _url) != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "cannot move " + url); + } + handle = nullptr; +} + +oslFileError TempFile::flush() { + oslFileError e = osl_File_E_None; + if (!buffer.isEmpty()) { + sal_uInt64 nBytesWritten = 0; + e = osl_writeFile(handle, buffer.getStr(), + static_cast< sal_uInt32 >(buffer.getLength()), + &nBytesWritten); + if (nBytesWritten != static_cast< sal_uInt32 >(buffer.getLength())) { + // queue up any error / exception until close. + buffer.remove(0, static_cast< sal_Int32 >( nBytesWritten ) ); + } else { + buffer.setLength(0); + } + } + return e; +} + +void TempFile::writeString(std::string_view text) { + buffer.append(text.data(), text.size()); + if (buffer.getLength() > 0x10000) + flush(); +} + +namespace { + +void writeValueContent_(TempFile &, bool) = delete; + // silence loplugin:salbool +void writeValueContent_(TempFile &handle, sal_Bool value) { + if (value) { + handle.writeString("true"); + } else { + handle.writeString("false"); + } +} + +void writeValueContent_(TempFile &handle, sal_Int16 value) { + handle.writeString(OString::number(value)); +} + +void writeValueContent_(TempFile &handle, sal_Int32 value) { + handle.writeString(OString::number(value)); +} + +void writeValueContent_(TempFile &handle, sal_Int64 value) { + handle.writeString(OString::number(value)); +} + +void writeValueContent_(TempFile &handle, double value) { + handle.writeString(OString::number(value)); +} + +void writeValueContent_(TempFile &handle, std::u16string_view value) { + writeValueContent(handle, value); +} + +void writeValueContent_( + TempFile &handle, css::uno::Sequence< sal_Int8 > const & value) +{ + for (const auto & v : value) { + static char const hexDigit[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', + 'D', 'E', 'F' }; + handle.writeString( + std::string_view(hexDigit + ((v >> 4) & 0xF), 1)); + handle.writeString(std::string_view(hexDigit + (v & 0xF), 1)); + } +} + +template< typename T > void writeSingleValue( + TempFile &handle, css::uno::Any const & value) +{ + handle.writeString(">"); + T val = T(); + value >>= val; + writeValueContent_(handle, val); + handle.writeString("</value>"); +} + +template< typename T > void writeListValue( + TempFile &handle, css::uno::Any const & value) +{ + handle.writeString(">"); + css::uno::Sequence< T > val; + value >>= val; + for (sal_Int32 i = 0; i < val.getLength(); ++i) { + if (i != 0) { + handle.writeString(" "); + } + writeValueContent_(handle, std::as_const(val)[i]); + } + handle.writeString("</value>"); +} + +template< typename T > void writeItemListValue( + TempFile &handle, css::uno::Any const & value) +{ + handle.writeString(">"); + css::uno::Sequence< T > val; + value >>= val; + for (const auto & i : std::as_const(val)) { + handle.writeString("<it>"); + writeValueContent_(handle, i); + handle.writeString("</it>"); + } + handle.writeString("</value>"); +} + +void writeValue(TempFile &handle, Type type, css::uno::Any const & value) { + switch (type) { + case TYPE_BOOLEAN: + writeSingleValue< sal_Bool >(handle, value); + break; + case TYPE_SHORT: + writeSingleValue< sal_Int16 >(handle, value); + break; + case TYPE_INT: + writeSingleValue< sal_Int32 >(handle, value); + break; + case TYPE_LONG: + writeSingleValue< sal_Int64 >(handle, value); + break; + case TYPE_DOUBLE: + writeSingleValue< double >(handle, value); + break; + case TYPE_STRING: + writeSingleValue< OUString >(handle, value); + break; + case TYPE_HEXBINARY: + writeSingleValue< css::uno::Sequence< sal_Int8 > >(handle, value); + break; + case TYPE_BOOLEAN_LIST: + writeListValue< sal_Bool >(handle, value); + break; + case TYPE_SHORT_LIST: + writeListValue< sal_Int16 >(handle, value); + break; + case TYPE_INT_LIST: + writeListValue< sal_Int32 >(handle, value); + break; + case TYPE_LONG_LIST: + writeListValue< sal_Int64 >(handle, value); + break; + case TYPE_DOUBLE_LIST: + writeListValue< double >(handle, value); + break; + case TYPE_STRING_LIST: + writeItemListValue< OUString >(handle, value); + break; + case TYPE_HEXBINARY_LIST: + writeItemListValue< css::uno::Sequence< sal_Int8 > >(handle, value); + break; + default: // TYPE_ERROR, TYPE_NIL, TYPE_ANY + assert(false); // this cannot happen + } +} + +void writeNode( + Components & components, TempFile &handle, + rtl::Reference< Node > const & parent, std::u16string_view name, + rtl::Reference< Node > const & node) +{ + static xmlreader::Span const typeNames[] = { + xmlreader::Span(), xmlreader::Span(), xmlreader::Span(), + // TYPE_ERROR, TYPE_NIL, TYPE_ANY + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:boolean")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:short")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:int")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:long")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:double")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:string")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:hexBinary")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:boolean-list")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:short-list")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:int-list")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:long-list")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:double-list")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:string-list")), + xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:hexBinary-list")) }; + switch (node->kind()) { + case Node::KIND_PROPERTY: + { + PropertyNode * prop = static_cast< PropertyNode * >(node.get()); + handle.writeString("<prop oor:name=\""); + writeAttributeValue(handle, name); + handle.writeString("\" oor:op=\"fuse\""); + Type type = prop->getStaticType(); + Type dynType = getDynamicType(prop->getValue(components)); + assert(dynType != TYPE_ERROR); + if (type == TYPE_ANY) { + type = dynType; + if (type != TYPE_NIL) { + handle.writeString(" oor:type=\""); + handle.writeString( + std::string_view( + typeNames[type].begin, typeNames[type].length)); + handle.writeString("\""); + } + } + handle.writeString("><value"); + if (dynType == TYPE_NIL) { + handle.writeString(" xsi:nil=\"true\"/>"); + } else { + writeValue(handle, type, prop->getValue(components)); + } + handle.writeString("</prop>"); + } + break; + case Node::KIND_LOCALIZED_PROPERTY: + handle.writeString("<prop oor:name=\""); + writeAttributeValue(handle, name); + handle.writeString("\" oor:op=\"fuse\">"); + for (auto const& member : node->getMembers()) + { + writeNode(components, handle, node, member.first, member.second); + } + handle.writeString("</prop>"); + break; + case Node::KIND_LOCALIZED_VALUE: + { + handle.writeString("<value"); + if (!name.empty()) { + handle.writeString(" xml:lang=\""); + writeAttributeValue(handle, name); + handle.writeString("\""); + } + Type type = static_cast< LocalizedPropertyNode * >(parent.get())-> + getStaticType(); + css::uno::Any value( + static_cast< LocalizedValueNode * >(node.get())->getValue()); + Type dynType = getDynamicType(value); + assert(dynType != TYPE_ERROR); + if (type == TYPE_ANY) { + type = dynType; + if (type != TYPE_NIL) { + handle.writeString(" oor:type=\""); + handle.writeString( + std::string_view( + typeNames[type].begin, typeNames[type].length)); + handle.writeString("\""); + } + } + if (dynType == TYPE_NIL) { + handle.writeString(" xsi:nil=\"true\"/>"); + } else { + writeValue(handle, type, value); + } + } + break; + case Node::KIND_GROUP: + case Node::KIND_SET: + handle.writeString("<node oor:name=\""); + writeAttributeValue(handle, name); + if (!node->getTemplateName().isEmpty()) { // set member + handle.writeString("\" oor:op=\"replace"); + } + handle.writeString("\">"); + for (auto const& member : node->getMembers()) + { + writeNode(components, handle, node, member.first, member.second); + } + handle.writeString("</node>"); + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } +} + +// helpers to allow sorting of configmgr::Modifications::Node +typedef std::pair< const OUString, configmgr::Modifications::Node > ModNodePairEntry; +struct PairEntrySorter +{ + bool operator() (const ModNodePairEntry* pValue1, const ModNodePairEntry* pValue2) const + { + return pValue1->first.compareTo(pValue2->first) < 0; + } +}; + +void writeModifications( + Components & components, TempFile &handle, + std::u16string_view parentPathRepresentation, + rtl::Reference< Node > const & parent, OUString const & nodeName, + rtl::Reference< Node > const & node, + Modifications::Node const & modifications) +{ + // It is never necessary to write oor:finalized or oor:mandatory attributes, + // as they cannot be set via the UNO API. + if (modifications.children.empty()) { + assert(parent.is()); + // components themselves have no parent but must have children + handle.writeString("<item oor:path=\""); + writeAttributeValue(handle, parentPathRepresentation); + handle.writeString("\">"); + if (node.is()) { + writeNode(components, handle, parent, nodeName, node); + } else { + switch (parent->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + handle.writeString("<value"); + if (!nodeName.isEmpty()) { + handle.writeString(" xml:lang=\""); + writeAttributeValue(handle, nodeName); + handle.writeString("\""); + } + handle.writeString(" oor:op=\"remove\"/>"); + break; + case Node::KIND_GROUP: + assert( + static_cast< GroupNode * >(parent.get())->isExtensible()); + handle.writeString("<prop oor:name=\""); + writeAttributeValue(handle, nodeName); + handle.writeString("\" oor:op=\"remove\"/>"); + break; + case Node::KIND_SET: + handle.writeString("<node oor:name=\""); + writeAttributeValue(handle, nodeName); + handle.writeString("\" oor:op=\"remove\"/>"); + break; + default: + assert(false); // this cannot happen + break; + } + } + handle.writeString("</item>\n"); + } else { + assert(node.is()); + OUString pathRep( + OUString::Concat(parentPathRepresentation) + "/" + + Data::createSegment(node->getTemplateName(), nodeName)); + + // copy configmgr::Modifications::Node's to a sortable list. Use pointers + // to just reference the data instead of copying it + std::vector< const ModNodePairEntry* > ModNodePairEntryVector; + ModNodePairEntryVector.reserve(modifications.children.size()); + + for (const auto& rCand : modifications.children) + { + ModNodePairEntryVector.push_back(&rCand); + } + + // sort the list + std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter()); + + // now use the list to write entries in sorted order + // instead of random as from the unordered map + for (const auto & i : ModNodePairEntryVector) + { + writeModifications( + components, handle, pathRep, node, i->first, + node->getMember(i->first), i->second); + } + } +} + +} + +void writeAttributeValue(TempFile &handle, std::u16string_view value) { + std::size_t i = 0; + std::size_t j = i; + for (; j != value.size(); ++j) { + assert( + value[j] == 0x0009 || value[j] == 0x000A || value[j] == 0x000D || + (value[j] >= 0x0020 && value[j] != 0xFFFE && value[j] != 0xFFFF)); + switch(value[j]) { + case '\x09': + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("	"); + i = j + 1; + break; + case '\x0A': + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("
"); + i = j + 1; + break; + case '\x0D': + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("
"); + i = j + 1; + break; + case '"': + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("""); + i = j + 1; + break; + case '&': + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("&"); + i = j + 1; + break; + case '<': + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("<"); + i = j + 1; + break; + default: + break; + } + } + handle.writeString(convertToUtf8(value.substr(i, j - i))); +} + +void writeValueContent(TempFile &handle, std::u16string_view value) { + std::size_t i = 0; + std::size_t j = i; + for (; j != value.size(); ++j) { + char16_t c = value[j]; + if ((c < 0x0020 && c != 0x0009 && c != 0x000A && c != 0x000D) || + c == 0xFFFE || c == 0xFFFF) + { + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("<unicode oor:scalar=\""); + handle.writeString(OString::number(c)); + handle.writeString("\"/>"); + i = j + 1; + } else if (c == '\x0D') { + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("
"); + i = j + 1; + } else if (c == '&') { + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("&"); + i = j + 1; + } else if (c == '<') { + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString("<"); + i = j + 1; + } else if (c == '>') { + // "MUST, for compatibility, be escaped [...] when it appears in the + // string ']]>'": + handle.writeString(convertToUtf8(value.substr(i, j - i))); + handle.writeString(">"); + i = j + 1; + } + } + handle.writeString(convertToUtf8(value.substr(i, j - i))); +} + +void writeModFile( + Components & components, OUString const & url, Data const & data) +{ + sal_Int32 i = url.lastIndexOf('/'); + assert(i != -1); + OUString dir(url.copy(0, i)); + switch (osl::Directory::createPath(dir)) { + case osl::FileBase::E_None: + case osl::FileBase::E_EXIST: + break; + case osl::FileBase::E_ACCES: + SAL_INFO( + "configmgr", + ("cannot create registrymodifications.xcu path (E_ACCES); changes" + " will be lost")); + return; + default: + throw css::uno::RuntimeException( + "cannot create directory " + dir); + } + TempFile tmp; + switch (osl::FileBase::createTempFile(&dir, &tmp.handle, &tmp.url)) { + case osl::FileBase::E_None: + break; + case osl::FileBase::E_ACCES: + SAL_INFO( + "configmgr", + ("cannot create temp registrymodifications.xcu (E_ACCES); changes" + " will be lost")); + return; + default: + throw css::uno::RuntimeException( + "cannot create temporary file in " + dir); + } + tmp.writeString( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items" + " xmlns:oor=\"http://openoffice.org/2001/registry\"" + " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"); + //TODO: Do not write back information about those removed items that did not + // come from the .xcs/.xcu files, anyway (but had been added dynamically + // instead): + + // For profilesafemode it is necessary to detect changes in the + // registrymodifications file, this is done based on file size in bytes and crc32. + // Unfortunately this write is based on writing unordered map entries, which creates + // valid and semantically equal XML-Files, bubt with different crc32 checksums. For + // the future usage it will be preferable to have easily comparable config files + // which is guaranteed by writing the entries in sorted order. Indeed with this change + // (and in the recursive writeModifications call) the same config files get written + + // copy configmgr::Modifications::Node's to a sortable list. Use pointers + // to just reference the data instead of copying it + std::vector< const ModNodePairEntry* > ModNodePairEntryVector; + ModNodePairEntryVector.reserve(data.modifications.getRoot().children.size()); + + for (const auto& rCand : data.modifications.getRoot().children) + { + ModNodePairEntryVector.push_back(&rCand); + } + + // sort the list + std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter()); + + // now use the list to write entries in sorted order + // instead of random as from the unordered map + for (const auto& j : ModNodePairEntryVector) + { + writeModifications( + components, tmp, u"", rtl::Reference< Node >(), j->first, + data.getComponents().findNode(Data::NO_LAYER, j->first), + j->second); + } + tmp.writeString("</oor:items>\n"); + tmp.closeAndRename(url); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/writemodfile.hxx b/configmgr/source/writemodfile.hxx new file mode 100644 index 000000000..d663321d4 --- /dev/null +++ b/configmgr/source/writemodfile.hxx @@ -0,0 +1,63 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <rtl/strbuf.hxx> +#include <rtl/ustring.hxx> +#include <osl/file.h> + +namespace configmgr { + +class Components; +struct Data; + +struct TempFile { + OUString url; + oslFileHandle handle; + bool closed; + OStringBuffer buffer; + + TempFile(): handle(nullptr), closed(false) {} + ~TempFile(); + void closeAndRename(const OUString &url); + oslFileError flush(); +#ifdef _WIN32 + oslFileError closeWithoutUnlink(); +#endif + void writeString(std::string_view text); + +private: + TempFile(const TempFile&) = delete; + TempFile& operator=(const TempFile&) = delete; +}; + +void writeAttributeValue(TempFile &handle, std::u16string_view value); +void writeValueContent(TempFile &handle, std::u16string_view value); + +void writeModFile( + Components & components, OUString const & url, Data const & data); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xcdparser.cxx b/configmgr/source/xcdparser.cxx new file mode 100644 index 000000000..a069c6b99 --- /dev/null +++ b/configmgr/source/xcdparser.cxx @@ -0,0 +1,174 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <climits> +#include <set> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ustring.hxx> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "parsemanager.hxx" +#include "xcdparser.hxx" +#include "xcsparser.hxx" +#include "xcuparser.hxx" +#include "xmldata.hxx" + +namespace configmgr { + +XcdParser::XcdParser( + int layer, std::set< OUString > const & processedDependencies, Data & data): + layer_(layer), processedDependencies_(processedDependencies), data_(data), + state_(STATE_START), dependencyOptional_(), nesting_() +{} + +XcdParser::~XcdParser() {} + +xmlreader::XmlReader::Text XcdParser::getTextMode() { + return nestedParser_.is() + ? nestedParser_->getTextMode() : xmlreader::XmlReader::Text::NONE; +} + +bool XcdParser::startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * existingDependencies) +{ + if (nestedParser_.is()) { + assert(nesting_ != LONG_MAX); + ++nesting_; + return nestedParser_->startElement( + reader, nsId, name, existingDependencies); + } + switch (state_) { + case STATE_START: + if (nsId == ParseManager::NAMESPACE_OOR && name == "data") { + state_ = STATE_DEPENDENCIES; + return true; + } + break; + case STATE_DEPENDENCIES: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "dependency") + { + if (dependencyFile_.isEmpty()) { + dependencyOptional_ = false; + xmlreader::Span attrFile; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == xmlreader::XmlReader::NAMESPACE_NONE && + //TODO: _OOR + attrLn == "file") + { + attrFile = reader.getAttributeValue(false); + } else if ((attrNsId == + xmlreader::XmlReader::NAMESPACE_NONE) && + attrLn == "optional") + { + dependencyOptional_ = xmldata::parseBoolean( + reader.getAttributeValue(true)); + } + } + if (!attrFile.is()) { + throw css::uno::RuntimeException( + "no dependency file attribute in " + reader.getUrl()); + } + dependencyFile_ = attrFile.convertFromUtf8(); + if (dependencyFile_.isEmpty()) { + throw css::uno::RuntimeException( + "bad dependency file attribute in " + reader.getUrl()); + } + } + if ((processedDependencies_.find(dependencyFile_) == + processedDependencies_.end()) && + (!dependencyOptional_ || existingDependencies == nullptr || + (existingDependencies->find(dependencyFile_) != + existingDependencies->end()))) + { + return false; + } + state_ = STATE_DEPENDENCY; + dependencyFile_.clear(); + return true; + } + state_ = STATE_COMPONENTS; + [[fallthrough]]; + case STATE_COMPONENTS: + if (nsId == ParseManager::NAMESPACE_OOR && + name == "component-schema") + { + nestedParser_ = new XcsParser(layer_, data_); + nesting_ = 1; + return nestedParser_->startElement( + reader, nsId, name, existingDependencies); + } + if (nsId == ParseManager::NAMESPACE_OOR && + (name == "component-data" || name == "items")) + { + nestedParser_ = new XcuParser(layer_ + 1, data_, nullptr, nullptr, nullptr); + nesting_ = 1; + return nestedParser_->startElement( + reader, nsId, name, existingDependencies); + } + break; + default: // STATE_DEPENDENCY + assert(false); // this cannot happen + break; + } + throw css::uno::RuntimeException( + "bad member <" + name.convertFromUtf8() + "> in " + reader.getUrl()); +} + +void XcdParser::endElement(xmlreader::XmlReader const & reader) { + if (nestedParser_.is()) { + nestedParser_->endElement(reader); + if (--nesting_ == 0) { + nestedParser_.clear(); + } + } else { + switch (state_) { + case STATE_DEPENDENCY: + state_ = STATE_DEPENDENCIES; + break; + case STATE_DEPENDENCIES: + case STATE_COMPONENTS: + break; + default: + assert(false); // this cannot happen + break; + } + } +} + +void XcdParser::characters(xmlreader::Span const & text) { + if (nestedParser_.is()) { + nestedParser_->characters(text); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xcdparser.hxx b/configmgr/source/xcdparser.hxx new file mode 100644 index 000000000..1ca32931e --- /dev/null +++ b/configmgr/source/xcdparser.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "parser.hxx" + +namespace xmlreader { struct Span; } + +namespace configmgr { + +struct Data; + +class XcdParser: public Parser { +public: + XcdParser( + int layer, std::set< OUString > const & processedDependencies, + Data & data); + +private: + virtual ~XcdParser() override; + + virtual xmlreader::XmlReader::Text getTextMode() override; + + virtual bool startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * existingDependencies) override; + + virtual void endElement(xmlreader::XmlReader const & reader) override; + + virtual void characters(xmlreader::Span const & text) override; + + enum State { + STATE_START, STATE_DEPENDENCIES, STATE_DEPENDENCY, STATE_COMPONENTS }; + + int layer_; + std::set< OUString > const & processedDependencies_; + Data & data_; + State state_; + OUString dependencyFile_; + bool dependencyOptional_; + rtl::Reference< Parser > nestedParser_; + long nesting_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xcsparser.cxx b/configmgr/source/xcsparser.cxx new file mode 100644 index 000000000..947792c0a --- /dev/null +++ b/configmgr/source/xcsparser.cxx @@ -0,0 +1,596 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> +#include <set> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ref.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "data.hxx" +#include "localizedpropertynode.hxx" +#include "groupnode.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "parsemanager.hxx" +#include "propertynode.hxx" +#include "setnode.hxx" +#include "xcsparser.hxx" +#include "xmldata.hxx" + +namespace configmgr { + +namespace { + +// Conservatively merge a template or component (and its recursive parts) into +// an existing instance: +void merge( + rtl::Reference< Node > const & original, + rtl::Reference< Node > const & update) +{ + assert( + original.is() && update.is() && original->kind() == update->kind() && + update->getFinalized() == Data::NO_LAYER); + if (update->getLayer() < original->getLayer() || + update->getLayer() > original->getFinalized()) + return; + + switch (original->kind()) { + case Node::KIND_PROPERTY: + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_LOCALIZED_VALUE: + break; //TODO: merge certain parts? + case Node::KIND_GROUP: + for (auto const& updateMember : update->getMembers()) + { + NodeMap & members = original->getMembers(); + NodeMap::iterator i1(members.find(updateMember.first)); + if (i1 == members.end()) { + if (updateMember.second->kind() == Node::KIND_PROPERTY && + static_cast< GroupNode * >( + original.get())->isExtensible()) + { + members.insert(updateMember); + } + } else if (updateMember.second->kind() == i1->second->kind()) { + merge(i1->second, updateMember.second); + } + } + break; + case Node::KIND_SET: + for (auto const& updateMember : update->getMembers()) + { + NodeMap & members = original->getMembers(); + NodeMap::iterator i1(members.find(updateMember.first)); + if (i1 == members.end()) { + if (static_cast< SetNode * >(original.get())-> + isValidTemplate(updateMember.second->getTemplateName())) + { + members.insert(updateMember); + } + } else if (updateMember.second->kind() == i1->second->kind() && + (updateMember.second->getTemplateName() == + i1->second->getTemplateName())) + { + merge(i1->second, updateMember.second); + } + } + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } +} + +} + +XcsParser::XcsParser(int layer, Data & data): + valueParser_(layer), data_(data), state_(STATE_START), ignoring_() +{} + +XcsParser::~XcsParser() {} + +xmlreader::XmlReader::Text XcsParser::getTextMode() { + return valueParser_.getTextMode(); +} + +bool XcsParser::startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * /*existingDependencies*/) +{ + if (valueParser_.startElement(reader, nsId, name)) { + return true; + } + if (state_ == STATE_START) { + if (nsId == ParseManager::NAMESPACE_OOR && + name == "component-schema") + { + handleComponentSchema(reader); + state_ = STATE_COMPONENT_SCHEMA; + ignoring_ = 0; + return true; + } + } else { + //TODO: ignoring component-schema import, component-schema uses, and + // prop constraints; accepting all four at illegal places (and with + // illegal content): + if (ignoring_ > 0 || + (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + (name == "info" || name == "import" || + name == "uses" || name == "constraints"))) + { + assert(ignoring_ < LONG_MAX); + ++ignoring_; + return true; + } + switch (state_) { + case STATE_COMPONENT_SCHEMA: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "templates") + { + state_ = STATE_TEMPLATES; + return true; + } + [[fallthrough]]; + case STATE_TEMPLATES_DONE: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "component") + { + state_ = STATE_COMPONENT; + assert(elements_.empty()); + elements_.push( + Element( + new GroupNode(valueParser_.getLayer(), false, ""), + componentName_)); + return true; + } + break; + case STATE_TEMPLATES: + if (elements_.empty()) { + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "group") + { + handleGroup(reader, true); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "set") + { + handleSet(reader, true); + return true; + } + break; + } + [[fallthrough]]; + case STATE_COMPONENT: + assert(!elements_.empty()); + switch (elements_.top().node->kind()) { + case Node::KIND_PROPERTY: + case Node::KIND_LOCALIZED_PROPERTY: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "value") + { + handlePropValue(reader, elements_.top().node); + return true; + } + break; + case Node::KIND_GROUP: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "prop") + { + handleProp(reader); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "node-ref") + { + handleNodeRef(reader); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "group") + { + handleGroup(reader, false); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "set") + { + handleSet(reader, false); + return true; + } + break; + case Node::KIND_SET: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "item") + { + handleSetItem( + reader, + static_cast< SetNode * >(elements_.top().node.get())); + return true; + } + break; + default: // Node::KIND_LOCALIZED_VALUE + assert(false); // this cannot happen + break; + } + break; + case STATE_COMPONENT_DONE: + break; + default: // STATE_START + assert(false); // this cannot happen + break; + } + } + throw css::uno::RuntimeException( + "bad member <" + name.convertFromUtf8() + "> in " + reader.getUrl()); +} + +void XcsParser::endElement(xmlreader::XmlReader const & reader) { + if (valueParser_.endElement()) { + return; + } + if (ignoring_ > 0) { + --ignoring_; + } else if (!elements_.empty()) { + Element top(std::move(elements_.top())); + elements_.pop(); + if (top.node.is()) { + if (elements_.empty()) { + switch (state_) { + case STATE_TEMPLATES: + { + auto itPair = data_.templates.insert({top.name, top.node}); + if (!itPair.second) { + merge(itPair.first->second, top.node); + } + } + break; + case STATE_COMPONENT: + { + NodeMap & components = data_.getComponents(); + auto itPair = components.insert({top.name, top.node}); + if (!itPair.second) { + merge(itPair.first->second, top.node); + } + state_ = STATE_COMPONENT_DONE; + } + break; + default: + assert(false); + throw css::uno::RuntimeException( + "this cannot happen"); + } + } else { + if (!elements_.top().node->getMembers().insert( + NodeMap::value_type(top.name, top.node)).second) + { + throw css::uno::RuntimeException( + "duplicate " + top.name + " in " + reader.getUrl()); + } + } + } + } else { + switch (state_) { + case STATE_COMPONENT_SCHEMA: + // To support old, broken extensions with .xcs files that contain + // empty <component-schema> elements: + state_ = STATE_COMPONENT_DONE; + break; + case STATE_TEMPLATES: + state_ = STATE_TEMPLATES_DONE; + break; + case STATE_TEMPLATES_DONE: + throw css::uno::RuntimeException( + "no component element in " + reader.getUrl()); + case STATE_COMPONENT_DONE: + break; + default: + assert(false); // this cannot happen + } + } +} + +void XcsParser::characters(xmlreader::Span const & text) { + valueParser_.characters(text); +} + +void XcsParser::handleComponentSchema(xmlreader::XmlReader & reader) { + //TODO: oor:version, xml:lang attributes + OStringBuffer buf(256); + buf.append('.'); + bool hasPackage = false; + bool hasName = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "package") + { + if (hasPackage) { + throw css::uno::RuntimeException( + "multiple component-schema package attributes in " + + reader.getUrl()); + } + hasPackage = true; + xmlreader::Span s(reader.getAttributeValue(false)); + buf.insert(0, s.begin, s.length); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "name") + { + if (hasName) { + throw css::uno::RuntimeException( + "multiple component-schema name attributes in " + + reader.getUrl()); + } + hasName = true; + xmlreader::Span s(reader.getAttributeValue(false)); + buf.append(s.begin, s.length); + } + } + if (!hasPackage) { + throw css::uno::RuntimeException( + "no component-schema package attribute in " + reader.getUrl()); + } + if (!hasName) { + throw css::uno::RuntimeException( + "no component-schema name attribute in " + reader.getUrl()); + } + componentName_ = xmlreader::Span(buf.getStr(), buf.getLength()). + convertFromUtf8(); +} + +void XcsParser::handleNodeRef(xmlreader::XmlReader & reader) { + bool hasName = false; + OUString name; + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no node-ref name attribute in " + reader.getUrl()); + } + rtl::Reference< Node > tmpl( + data_.getTemplate( + valueParser_.getLayer(), + xmldata::parseTemplateReference( + component, hasNodeType, nodeType, nullptr))); + if (!tmpl.is()) { + //TODO: this can erroneously happen as long as import/uses attributes + // are not correctly processed + throw css::uno::RuntimeException( + "unknown node-ref " + name + " in " + reader.getUrl()); + } + rtl::Reference< Node > node(tmpl->clone(false)); + node->setLayer(valueParser_.getLayer()); + elements_.push(Element(node, name)); +} + +void XcsParser::handleProp(xmlreader::XmlReader & reader) { + bool hasName = false; + OUString name; + valueParser_.type_ = TYPE_ERROR; + bool localized = false; + bool nillable = true; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "type") + { + valueParser_.type_ = xmldata::parseType( + reader, reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "localized") + { + localized = xmldata::parseBoolean(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "nillable") + { + nillable = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no prop name attribute in " + reader.getUrl()); + } + if (valueParser_.type_ == TYPE_ERROR) { + throw css::uno::RuntimeException( + "no prop type attribute in " + reader.getUrl()); + } + elements_.push( + Element( + (localized + ? rtl::Reference< Node >( + new LocalizedPropertyNode( + valueParser_.getLayer(), valueParser_.type_, nillable)) + : rtl::Reference< Node >( + new PropertyNode( + valueParser_.getLayer(), valueParser_.type_, nillable, + css::uno::Any(), false))), + name)); +} + +void XcsParser::handlePropValue( + xmlreader::XmlReader & reader, rtl::Reference< Node > const & property) +{ + xmlreader::Span attrSeparator; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "separator") + { + attrSeparator = reader.getAttributeValue(false); + if (attrSeparator.length == 0) { + throw css::uno::RuntimeException( + "bad oor:separator attribute in " + reader.getUrl()); + } + } + } + valueParser_.separator_ = OString( + attrSeparator.begin, attrSeparator.length); + valueParser_.start(property); +} + +void XcsParser::handleGroup(xmlreader::XmlReader & reader, bool isTemplate) { + bool hasName = false; + OUString name; + bool extensible = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "extensible") + { + extensible = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no group name attribute in " + reader.getUrl()); + } + if (isTemplate) { + name = Data::fullTemplateName(componentName_, name); + } + elements_.push( + Element( + new GroupNode( + valueParser_.getLayer(), extensible, + isTemplate ? name : OUString()), + name)); +} + +void XcsParser::handleSet(xmlreader::XmlReader & reader, bool isTemplate) { + bool hasName = false; + OUString name; + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no set name attribute in " + reader.getUrl()); + } + if (isTemplate) { + name = Data::fullTemplateName(componentName_, name); + } + elements_.push( + Element( + new SetNode( + valueParser_.getLayer(), + xmldata::parseTemplateReference( + component, hasNodeType, nodeType, nullptr), + isTemplate ? name : OUString()), + name)); +} + +void XcsParser::handleSetItem(xmlreader::XmlReader & reader, SetNode * set) { + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } + } + set->getAdditionalTemplateNames().push_back( + xmldata::parseTemplateReference(component, hasNodeType, nodeType, nullptr)); + elements_.push(Element(rtl::Reference< Node >(), "")); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xcsparser.hxx b/configmgr/source/xcsparser.hxx new file mode 100644 index 000000000..f2c5c7742 --- /dev/null +++ b/configmgr/source/xcsparser.hxx @@ -0,0 +1,101 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> +#include <stack> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> +#include <xmlreader/xmlreader.hxx> + +#include "node.hxx" +#include "parser.hxx" +#include "valueparser.hxx" + +namespace xmlreader { struct Span; } + +namespace configmgr { + +class SetNode; +struct Data; + +class XcsParser: public Parser { +public: + XcsParser(int layer, Data & data); + +private: + virtual ~XcsParser() override; + + virtual xmlreader::XmlReader::Text getTextMode() override; + + virtual bool startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * existingDependencies) override; + + virtual void endElement(xmlreader::XmlReader const & reader) override; + + virtual void characters(xmlreader::Span const & text) override; + + void handleComponentSchema(xmlreader::XmlReader & reader); + + void handleNodeRef(xmlreader::XmlReader & reader); + + void handleProp(xmlreader::XmlReader & reader); + + void handlePropValue( + xmlreader::XmlReader & reader, rtl::Reference< Node > const & property); + + void handleGroup(xmlreader::XmlReader & reader, bool isTemplate); + + void handleSet(xmlreader::XmlReader & reader, bool isTemplate); + + void handleSetItem(xmlreader::XmlReader & reader, SetNode * set); + + enum State { + STATE_START, STATE_COMPONENT_SCHEMA, STATE_TEMPLATES, + STATE_TEMPLATES_DONE, STATE_COMPONENT, STATE_COMPONENT_DONE }; + + struct Element { + rtl::Reference< Node > node; + OUString name; + + Element( + rtl::Reference< Node > theNode, + OUString theName): + node(std::move(theNode)), name(std::move(theName)) {} + }; + + typedef std::stack< Element > ElementStack; + + ValueParser valueParser_; + Data & data_; + OUString componentName_; + State state_; + long ignoring_; + ElementStack elements_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xcuparser.cxx b/configmgr/source/xcuparser.cxx new file mode 100644 index 000000000..af21518ab --- /dev/null +++ b/configmgr/source/xcuparser.cxx @@ -0,0 +1,968 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <set> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ref.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "data.hxx" +#include "localizedpropertynode.hxx" +#include "localizedvaluenode.hxx" +#include "groupnode.hxx" +#include "modifications.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "parsemanager.hxx" +#include "partial.hxx" +#include "propertynode.hxx" +#include "setnode.hxx" +#include "xcuparser.hxx" +#include "xmldata.hxx" + +namespace configmgr { + +XcuParser::XcuParser( + int layer, Data & data, Partial const * partial, + Modifications * broadcastModifications, Additions * additions): + valueParser_(layer), data_(data), + partial_(partial), broadcastModifications_(broadcastModifications), + additions_(additions), recordModifications_(layer == Data::NO_LAYER), + trackPath_( + partial_ != nullptr || broadcastModifications_ != nullptr || additions_ != nullptr || + recordModifications_) +{} + +XcuParser::~XcuParser() {} + +xmlreader::XmlReader::Text XcuParser::getTextMode() { + return valueParser_.getTextMode(); +} + +bool XcuParser::startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * /*existingDependencies*/) +{ + if (valueParser_.startElement(reader, nsId, name)) { + return true; + } + if (state_.empty()) { + if (nsId == ParseManager::NAMESPACE_OOR && + name == "component-data") + { + handleComponentData(reader); + } else if (nsId == ParseManager::NAMESPACE_OOR && name == "items") + { + state_.push(State::Modify(rtl::Reference< Node >())); + } else { + throw css::uno::RuntimeException( + "bad root element <" + name.convertFromUtf8() + "> in " + + reader.getUrl()); + } + } else if (state_.top().ignore) { + state_.push(State::Ignore(false)); + } else if (!state_.top().node.is()) { + if (nsId != xmlreader::XmlReader::NAMESPACE_NONE || name != "item") + { + throw css::uno::RuntimeException( + "bad items node member <" + name.convertFromUtf8() + "> in " + + reader.getUrl()); + } + handleItem(reader); + } else { + switch (state_.top().node->kind()) { + case Node::KIND_PROPERTY: + if (nsId != xmlreader::XmlReader::NAMESPACE_NONE || + name != "value") + { + throw css::uno::RuntimeException( + "bad property node member <" + name.convertFromUtf8() + + "> in " + reader.getUrl()); + } + handlePropValue( + reader, + static_cast< PropertyNode * >(state_.top().node.get())); + break; + case Node::KIND_LOCALIZED_PROPERTY: + if (nsId != xmlreader::XmlReader::NAMESPACE_NONE || + name != "value") + { + throw css::uno::RuntimeException( + "bad localized property node member <" + + name.convertFromUtf8() + "> in " + reader.getUrl()); + } + handleLocpropValue( + reader, + static_cast< LocalizedPropertyNode * >( + state_.top().node.get())); + break; + case Node::KIND_LOCALIZED_VALUE: + throw css::uno::RuntimeException( + "bad member <" + name.convertFromUtf8() + "> in " + + reader.getUrl()); + case Node::KIND_GROUP: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "prop") + { + handleGroupProp( + reader, + static_cast< GroupNode * >(state_.top().node.get())); + } else if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "node") + { + handleGroupNode(reader, state_.top().node); + } else { + throw css::uno::RuntimeException( + "bad group node member <" + name.convertFromUtf8() + + "> in " + reader.getUrl()); + } + break; + case Node::KIND_SET: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "node") + { + handleSetNode( + reader, static_cast< SetNode * >(state_.top().node.get())); + } else if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "prop") + { + SAL_WARN( + "configmgr", + "bad set node <prop> member in \"" << reader.getUrl() + << '"'); + state_.push(State::Ignore(false)); + } else { + throw css::uno::RuntimeException( + "bad set node member <" + name.convertFromUtf8() + + "> in " + reader.getUrl()); + } + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } + } + return true; +} + +void XcuParser::endElement(xmlreader::XmlReader const &) { + if (valueParser_.endElement()) { + return; + } + assert(!state_.empty()); + bool pop = state_.top().pop; + rtl::Reference< Node > insert; + OUString name; + if (state_.top().insert) { + insert = state_.top().node; + assert(insert.is()); + name = state_.top().name; + } + state_.pop(); + if (insert.is()) { + assert(!state_.empty() && state_.top().node.is()); + state_.top().node->getMembers()[name] = insert; + } + if (pop && !path_.empty()) { + path_.pop_back(); + // </item> will pop less than <item> pushed, but that is harmless, + // as the next <item> will reset path_ + } +} + +void XcuParser::characters(xmlreader::Span const & text) { + valueParser_.characters(text); +} + +XcuParser::Operation XcuParser::parseOperation(xmlreader::Span const & text) { + assert(text.is()); + if (text == "modify") { + return OPERATION_MODIFY; + } + if (text == "replace") { + return OPERATION_REPLACE; + } + if (text == "fuse") { + return OPERATION_FUSE; + } + if (text == "remove") { + return OPERATION_REMOVE; + } + throw css::uno::RuntimeException( + "invalid op " + text.convertFromUtf8()); +} + +void XcuParser::handleComponentData(xmlreader::XmlReader & reader) { + OStringBuffer buf(256); + buf.append('.'); + bool hasPackage = false; + bool hasName = false; + Operation op = OPERATION_MODIFY; + bool finalized = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "package") + { + if (hasPackage) { + throw css::uno::RuntimeException( + "multiple component-update package attributes in " + + reader.getUrl()); + } + hasPackage = true; + xmlreader::Span s(reader.getAttributeValue(false)); + buf.insert(0, s.begin, s.length); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "name") + { + if (hasName) { + throw css::uno::RuntimeException( + "multiple component-update name attributes in " + + reader.getUrl()); + } + hasName = true; + xmlreader::Span s(reader.getAttributeValue(false)); + buf.append(s.begin, s.length); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "op") + { + op = parseOperation(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "finalized") + { + finalized = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasPackage) { + throw css::uno::RuntimeException( + "no component-data package attribute in " + reader.getUrl()); + } + if (!hasName) { + throw css::uno::RuntimeException( + "no component-data name attribute in " + reader.getUrl()); + } + componentName_ = xmlreader::Span(buf.getStr(), buf.getLength()). + convertFromUtf8(); + if (trackPath_) { + assert(path_.empty()); + path_.push_back(componentName_); + if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT) + { + state_.push(State::Ignore(true)); + return; + } + } + rtl::Reference< Node > node( + data_.getComponents().findNode(valueParser_.getLayer(), + componentName_)); + if (!node.is()) { + SAL_WARN( + "configmgr", + "unknown component \"" << componentName_ << "\" in \"" + << reader.getUrl() << '"'); + state_.push(State::Ignore(true)); + return; + } + switch (op) { + case OPERATION_MODIFY: + case OPERATION_FUSE: + break; + default: + throw css::uno::RuntimeException( + "invalid operation on root node in " + reader.getUrl()); + } + int finalizedLayer = std::min( + finalized ? valueParser_.getLayer() : Data::NO_LAYER, + node->getFinalized()); + node->setFinalized(finalizedLayer); + if (finalizedLayer < valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + state_.push(State::Modify(node)); +} + +void XcuParser::handleItem(xmlreader::XmlReader & reader) { + xmlreader::Span attrPath; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "path") { + attrPath = reader.getAttributeValue(false); + } + } + if (!attrPath.is()) { + throw css::uno::RuntimeException( + "missing path attribute in " + reader.getUrl()); + } + OUString path(attrPath.convertFromUtf8()); + int finalizedLayer; + rtl::Reference< Node > node( + data_.resolvePathRepresentation( + path, nullptr, &path_, &finalizedLayer)); + if (!node.is()) { + SAL_WARN( + "configmgr", + "unknown item \"" << path << "\" in \"" << reader.getUrl() << '"'); + state_.push(State::Ignore(true)); + return; + } + assert(!path_.empty()); + componentName_ = path_.front(); + if (trackPath_) { + if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT) + { + state_.push(State::Ignore(true)); + return; + } + } else { + path_.clear(); + } + switch (node->kind()) { + case Node::KIND_PROPERTY: + case Node::KIND_LOCALIZED_VALUE: + SAL_WARN( + "configmgr", + "item of bad type \"" << path << "\" in \"" << reader.getUrl() + << '"'); + state_.push(State::Ignore(true)); + return; + case Node::KIND_LOCALIZED_PROPERTY: + valueParser_.type_ = static_cast< LocalizedPropertyNode * >( + node.get())->getStaticType(); + break; + default: + break; + } + if (finalizedLayer < valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + state_.push(State::Modify(node)); +} + +void XcuParser::handlePropValue( + xmlreader::XmlReader & reader, PropertyNode * prop) + { + bool nil = false; + OString separator; + OUString external; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_XSI && attrLn == "nil") { + nil = xmldata::parseBoolean(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "type") + { + Type type = xmldata::parseType( + reader, reader.getAttributeValue(true)); + if (valueParser_.type_ != TYPE_ANY && type != valueParser_.type_) { + throw css::uno::RuntimeException( + "invalid value type in " + reader.getUrl()); + } + valueParser_.type_ = type; + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "separator") + { + xmlreader::Span s(reader.getAttributeValue(false)); + if (s.length == 0) { + throw css::uno::RuntimeException( + "bad oor:separator attribute in " + reader.getUrl()); + } + separator = OString(s.begin, s.length); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "external") + { + external = reader.getAttributeValue(true).convertFromUtf8(); + if (external.isEmpty()) { + throw css::uno::RuntimeException( + "bad oor:external attribute value in " + reader.getUrl()); + } + } + } + if (nil) { + if (!prop->isNillable()) { + throw css::uno::RuntimeException( + "xsi:nil attribute for non-nillable prop in " + reader.getUrl()); + } + if (!external.isEmpty()) { + throw css::uno::RuntimeException( + "xsi:nil and oor:external attributes for prop in " + + reader.getUrl()); + } + prop->setValue(valueParser_.getLayer(), css::uno::Any()); + state_.push(State::Ignore(false)); + } else if (external.isEmpty()) { + valueParser_.separator_ = separator; + valueParser_.start(prop); + } else { + prop->setExternal(valueParser_.getLayer(), external); + state_.push(State::Ignore(false)); + } +} + +void XcuParser::handleLocpropValue( + xmlreader::XmlReader & reader, LocalizedPropertyNode * locprop) +{ + OUString name; + bool nil = false; + OString separator; + Operation op = OPERATION_FUSE; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == xmlreader::XmlReader::NAMESPACE_XML && + attrLn == "lang") + { + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_XSI && + attrLn == "nil") + { + nil = xmldata::parseBoolean(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "type") + { + Type type = xmldata::parseType( + reader, reader.getAttributeValue(true)); + if (valueParser_.type_ != TYPE_ANY && type != valueParser_.type_) { + throw css::uno::RuntimeException( + "invalid value type in " + reader.getUrl()); + } + valueParser_.type_ = type; + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "separator") + { + xmlreader::Span s(reader.getAttributeValue(false)); + if (s.length == 0) { + throw css::uno::RuntimeException( + "bad oor:separator attribute in " + reader.getUrl()); + } + separator = OString(s.begin, s.length); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "op") + { + op = parseOperation(reader.getAttributeValue(true)); + } + } + if (trackPath_) { + path_.push_back(name); + if (partial_ != nullptr && + partial_->contains(path_) != Partial::CONTAINS_NODE) + { + state_.push(State::Ignore(true)); + return; + } + } + NodeMap & members = locprop->getMembers(); + NodeMap::iterator i(members.find(name)); + if (i != members.end() && i->second->getLayer() > valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + if (nil && !locprop->isNillable()) { + throw css::uno::RuntimeException( + "xsi:nil attribute for non-nillable prop in " + reader.getUrl()); + } + switch (op) { + case OPERATION_FUSE: + { + bool pop = false; + if (nil) { + if (i == members.end()) { + members[name] = new LocalizedValueNode( + valueParser_.getLayer(), css::uno::Any()); + } else { + static_cast< LocalizedValueNode * >( + i->second.get())->setValue( + valueParser_.getLayer(), css::uno::Any()); + } + state_.push(State::Ignore(true)); + } else { + valueParser_.separator_ = separator; + valueParser_.start(locprop, name); + pop = true; + } + if (trackPath_) { + recordModification(false); + if (pop) { + path_.pop_back(); + } + } + } + break; + case OPERATION_REMOVE: + //TODO: only allow if parent.op == OPERATION_FUSE + //TODO: disallow removing when e.g. lang=""? + if (i != members.end()) { + members.erase(i); + } + state_.push(State::Ignore(true)); + recordModification(false); + break; + default: + throw css::uno::RuntimeException( + "bad op attribute for value element in " + reader.getUrl()); + } +} + +void XcuParser::handleGroupProp( + xmlreader::XmlReader & reader, GroupNode * group) +{ + bool hasName = false; + OUString name; + Type type = TYPE_ERROR; + Operation op = OPERATION_MODIFY; + bool finalized = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "type") + { + type = xmldata::parseType(reader, reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "op") + { + op = parseOperation(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "finalized") + { + finalized = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no prop name attribute in " + reader.getUrl()); + } + if (trackPath_) { + path_.push_back(name); + //TODO: This ignores locprop values for which specific include paths + // exist (i.e., for which contains(locprop path) = CONTAINS_SUBNODES): + if (partial_ != nullptr && + partial_->contains(path_) != Partial::CONTAINS_NODE) + { + state_.push(State::Ignore(true)); + return; + } + } + NodeMap & members = group->getMembers(); + NodeMap::iterator i(members.find(name)); + if (i == members.end()) { + handleUnknownGroupProp(reader, group, name, type, op, finalized); + } else { + switch (i->second->kind()) { + case Node::KIND_PROPERTY: + handlePlainGroupProp(reader, group, i, name, type, op, finalized); + break; + case Node::KIND_LOCALIZED_PROPERTY: + handleLocalizedGroupProp( + reader, + static_cast< LocalizedPropertyNode * >(i->second.get()), name, + type, op, finalized); + break; + default: + throw css::uno::RuntimeException( + "inappropriate prop " + name + " in " + reader.getUrl()); + } + } +} + +void XcuParser::handleUnknownGroupProp( + xmlreader::XmlReader const & reader, GroupNode const * group, + OUString const & name, Type type, Operation operation, bool finalized) +{ + switch (operation) { + case OPERATION_REPLACE: + case OPERATION_FUSE: + if (group->isExtensible()) { + if (type == TYPE_ERROR) { + throw css::uno::RuntimeException( + "missing type attribute for prop " + name + " in " + + reader.getUrl()); + } + valueParser_.type_ = type; + rtl::Reference< Node > prop( + new PropertyNode( + valueParser_.getLayer(), TYPE_ANY, true, css::uno::Any(), + true)); + if (finalized) { + prop->setFinalized(valueParser_.getLayer()); + } + state_.push(State::Insert(prop, name)); + recordModification(false); + break; + } + [[fallthrough]]; + default: + SAL_WARN( + "configmgr", + "unknown property \"" << name << "\" in \"" << reader.getUrl() + << '"'); + state_.push(State::Ignore(true)); + break; + } +} + +void XcuParser::handlePlainGroupProp( + xmlreader::XmlReader const & reader, GroupNode * group, + NodeMap::iterator const & propertyIndex, std::u16string_view name, + Type type, Operation operation, bool finalized) +{ + PropertyNode * property = static_cast< PropertyNode * >( + propertyIndex->second.get()); + if (property->getLayer() > valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + int finalizedLayer = std::min( + finalized ? valueParser_.getLayer() : Data::NO_LAYER, + property->getFinalized()); + property->setFinalized(finalizedLayer); + if (finalizedLayer < valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + if (type != TYPE_ERROR && property->getStaticType() != TYPE_ANY && + type != property->getStaticType()) + { + throw css::uno::RuntimeException( + OUString::Concat("invalid type for prop ") + name + " in " + reader.getUrl()); + } + valueParser_.type_ = type == TYPE_ERROR ? property->getStaticType() : type; + switch (operation) { + case OPERATION_MODIFY: + case OPERATION_REPLACE: + case OPERATION_FUSE: + state_.push(State::Modify(property)); + recordModification(false); + break; + case OPERATION_REMOVE: + if (!property->isExtension()) { + throw css::uno::RuntimeException( + OUString::Concat("invalid remove of non-extension prop ") + name + " in " + + reader.getUrl()); + } + group->getMembers().erase(propertyIndex); + state_.push(State::Ignore(true)); + recordModification(false); + break; + } +} + +void XcuParser::handleLocalizedGroupProp( + xmlreader::XmlReader const & reader, LocalizedPropertyNode * property, + OUString const & name, Type type, Operation operation, bool finalized) +{ + if (property->getLayer() > valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + int finalizedLayer = std::min( + finalized ? valueParser_.getLayer() : Data::NO_LAYER, + property->getFinalized()); + property->setFinalized(finalizedLayer); + if (finalizedLayer < valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + if (type != TYPE_ERROR && property->getStaticType() != TYPE_ANY && + type != property->getStaticType()) + { + throw css::uno::RuntimeException( + "invalid type for prop " + name + " in " + reader.getUrl()); + } + valueParser_.type_ = type == TYPE_ERROR ? property->getStaticType() : type; + switch (operation) { + case OPERATION_MODIFY: + case OPERATION_FUSE: + state_.push(State::Modify(property)); + break; + case OPERATION_REPLACE: + { + rtl::Reference< Node > replacement( + new LocalizedPropertyNode( + valueParser_.getLayer(), property->getStaticType(), + property->isNillable())); + replacement->setFinalized(property->getFinalized()); + state_.push(State::Insert(replacement, name)); + recordModification(false); + } + break; + case OPERATION_REMOVE: + throw css::uno::RuntimeException( + "invalid remove of non-extension prop " + name + " in " + + reader.getUrl()); + } +} + +void XcuParser::handleGroupNode( + xmlreader::XmlReader & reader, rtl::Reference< Node > const & group) +{ + bool hasName = false; + OUString name; + Operation op = OPERATION_MODIFY; + bool finalized = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "op") + { + op = parseOperation(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "finalized") + { + finalized = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no node name attribute in " + reader.getUrl()); + } + if (trackPath_) { + path_.push_back(name); + if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT) + { + state_.push(State::Ignore(true)); + return; + } + } + rtl::Reference< Node > child( + group->getMembers().findNode(valueParser_.getLayer(), name)); + if (!child.is()) { + SAL_WARN( + "configmgr", + "unknown node \"" << name << "\" in \"" << reader.getUrl() << '"'); + state_.push(State::Ignore(true)); + return; + } + Node::Kind kind = child->kind(); + if (kind != Node::KIND_GROUP && kind != Node::KIND_SET) { + throw css::uno::RuntimeException( + "bad <node> \"" + name + "\" of non group/set kind in " + + reader.getUrl()); + } + if (op != OPERATION_MODIFY && op != OPERATION_FUSE) { + throw css::uno::RuntimeException( + "invalid operation on group node in " + reader.getUrl()); + } + int finalizedLayer = std::min( + finalized ? valueParser_.getLayer() : Data::NO_LAYER, + child->getFinalized()); + child->setFinalized(finalizedLayer); + if (finalizedLayer < valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + state_.push(State::Modify(child)); +} + +void XcuParser::handleSetNode(xmlreader::XmlReader & reader, SetNode * set) { + bool hasName = false; + OUString name; + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + Operation op = OPERATION_MODIFY; + bool finalized = false; + bool mandatory = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "op") + { + op = parseOperation(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "finalized") + { + finalized = xmldata::parseBoolean(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "mandatory") + { + mandatory = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no node name attribute in " + reader.getUrl()); + } + if (trackPath_) { + path_.push_back(name); + if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT) + { + state_.push(State::Ignore(true)); + return; + } + } + OUString templateName( + xmldata::parseTemplateReference( + component, hasNodeType, nodeType, &set->getDefaultTemplateName())); + if (!set->isValidTemplate(templateName)) { + throw css::uno::RuntimeException( + "set member node " + name + " references invalid template " + + templateName + " in " + reader.getUrl()); + } + rtl::Reference< Node > tmpl( + data_.getTemplate(valueParser_.getLayer(), templateName)); + if (!tmpl.is()) { + throw css::uno::RuntimeException( + "set member node " + name + " references undefined template " + + templateName + " in " + reader.getUrl()); + } + int finalizedLayer = finalized ? valueParser_.getLayer() : Data::NO_LAYER; + int mandatoryLayer = mandatory ? valueParser_.getLayer() : Data::NO_LAYER; + NodeMap & members = set->getMembers(); + NodeMap::iterator i(members.find(name)); + if (i != members.end()) { + finalizedLayer = std::min(finalizedLayer, i->second->getFinalized()); + i->second->setFinalized(finalizedLayer); + mandatoryLayer = std::min(mandatoryLayer, i->second->getMandatory()); + i->second->setMandatory(mandatoryLayer); + if (i->second->getLayer() > valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + } + if (finalizedLayer < valueParser_.getLayer()) { + state_.push(State::Ignore(true)); + return; + } + switch (op) { + case OPERATION_MODIFY: + if (i == members.end()) { + SAL_WARN( + "configmgr", + "ignoring modify of unknown set member node \"" << name + << "\" in \"" << reader.getUrl() << '"'); + state_.push(State::Ignore(true)); + } else { + state_.push(State::Modify(i->second)); + } + break; + case OPERATION_REPLACE: + { + rtl::Reference< Node > member(tmpl->clone(true)); + member->setLayer(valueParser_.getLayer()); + member->setFinalized(finalizedLayer); + member->setMandatory(mandatoryLayer); + state_.push(State::Insert(member, name)); + recordModification(i == members.end()); + } + break; + case OPERATION_FUSE: + if (i == members.end()) { + rtl::Reference< Node > member(tmpl->clone(true)); + member->setLayer(valueParser_.getLayer()); + member->setFinalized(finalizedLayer); + member->setMandatory(mandatoryLayer); + state_.push(State::Insert(member, name)); + recordModification(true); + } else { + state_.push(State::Modify(i->second)); + } + break; + case OPERATION_REMOVE: + { + // Ignore removal of unknown members and members made mandatory in + // this or a lower layer; forget about user-layer removals that no + // longer remove anything (so that paired additions/removals in the + // user layer do not grow registrymodifications.xcu unbounded): + bool known = i != members.end(); + if (known && + (mandatoryLayer == Data::NO_LAYER || + mandatoryLayer > valueParser_.getLayer())) + { + members.erase(i); + } + state_.push(State::Ignore(true)); + if (known) { + recordModification(false); + } + break; + } + } +} + +void XcuParser::recordModification(bool addition) { + if (broadcastModifications_ != nullptr) { + broadcastModifications_->add(path_); + } + if (addition && additions_ != nullptr) { + additions_->push_back(path_); + } + if (recordModifications_) { + data_.modifications.add(path_); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xcuparser.hxx b/configmgr/source/xcuparser.hxx new file mode 100644 index 000000000..e50b7b5a0 --- /dev/null +++ b/configmgr/source/xcuparser.hxx @@ -0,0 +1,152 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <set> +#include <stack> +#include <string_view> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <utility> +#include <xmlreader/xmlreader.hxx> + +#include "additions.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "parser.hxx" +#include "type.hxx" +#include "valueparser.hxx" + +namespace xmlreader { struct Span; } + +namespace configmgr { + +class GroupNode; +class LocalizedPropertyNode; +class Modifications; +class Partial; +class PropertyNode; +class SetNode; +struct Data; + +class XcuParser: public Parser { +public: + XcuParser( + int layer, Data & data, Partial const * partial, + Modifications * broadcastModifications, Additions * additions); + +private: + virtual ~XcuParser() override; + + virtual xmlreader::XmlReader::Text getTextMode() override; + + virtual bool startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * existingDependencies) override; + + virtual void endElement(xmlreader::XmlReader const & reader) override; + + virtual void characters(xmlreader::Span const & span) override; + + enum Operation { + OPERATION_MODIFY, OPERATION_REPLACE, OPERATION_FUSE, OPERATION_REMOVE }; + + static Operation parseOperation(xmlreader::Span const & text); + + void handleComponentData(xmlreader::XmlReader & reader); + + void handleItem(xmlreader::XmlReader & reader); + + void handlePropValue(xmlreader::XmlReader & reader, PropertyNode * prop); + + void handleLocpropValue( + xmlreader::XmlReader & reader, LocalizedPropertyNode * locprop); + + void handleGroupProp(xmlreader::XmlReader & reader, GroupNode * group); + + void handleUnknownGroupProp( + xmlreader::XmlReader const & reader, GroupNode const * group, + OUString const & name, Type type, Operation operation, + bool finalized); + + void handlePlainGroupProp( + xmlreader::XmlReader const & reader, GroupNode * group, + NodeMap::iterator const & propertyIndex, std::u16string_view name, + Type type, Operation operation, bool finalized); + + void handleLocalizedGroupProp( + xmlreader::XmlReader const & reader, LocalizedPropertyNode * property, + OUString const & name, Type type, Operation operation, + bool finalized); + + void handleGroupNode( + xmlreader::XmlReader & reader, rtl::Reference< Node > const & group); + + void handleSetNode(xmlreader::XmlReader & reader, SetNode * set); + + void recordModification(bool addition); + + struct State { + rtl::Reference< Node > node; // empty if ignore or <items> + OUString name; // empty and ignored if !insert + bool ignore; + bool insert; + bool pop; + + static State Ignore(bool thePop) { return State(thePop); } + + static State Modify(rtl::Reference< Node > const & theNode) + { return State(theNode); } + + static State Insert( + rtl::Reference< Node > const & theNode, OUString const & theName) + { return State(theNode, theName); } + + private: + explicit State(bool thePop): ignore(true), insert(false), pop(thePop) {} + + explicit State(rtl::Reference< Node > theNode): + node(std::move(theNode)), ignore(false), insert(false), pop(true) + {} + + State( + rtl::Reference< Node > theNode, OUString theName): + node(std::move(theNode)), name(std::move(theName)), ignore(false), insert(true), pop(true) + {} + }; + + ValueParser valueParser_; + Data & data_; + Partial const * partial_; + Modifications * broadcastModifications_; + Additions * additions_; + bool recordModifications_; + bool trackPath_; + OUString componentName_; + std::stack< State > state_; + std::vector<OUString> path_; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xmldata.cxx b/configmgr/source/xmldata.cxx new file mode 100644 index 000000000..ecb4dacab --- /dev/null +++ b/configmgr/source/xmldata.cxx @@ -0,0 +1,133 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/string.h> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "data.hxx" +#include "parsemanager.hxx" +#include "type.hxx" +#include "xmldata.hxx" + +namespace configmgr::xmldata { + +Type parseType( + xmlreader::XmlReader const & reader, xmlreader::Span const & text) +{ + assert(text.is()); + sal_Int32 i = rtl_str_indexOfChar_WithLength(text.begin, text.length, ':'); + if (i >= 0) { + xmlreader::Span token(text.begin + i + 1, text.length - (i + 1)); + switch (reader.getNamespaceId(xmlreader::Span(text.begin, i))) { + case ParseManager::NAMESPACE_OOR: + if (token == "any") + { + return TYPE_ANY; + } else if (token == "boolean-list") + { + return TYPE_BOOLEAN_LIST; + } else if (token == "short-list") + { + return TYPE_SHORT_LIST; + } else if (token == "int-list") + { + return TYPE_INT_LIST; + } else if (token == "long-list") + { + return TYPE_LONG_LIST; + } else if (token == "double-list") + { + return TYPE_DOUBLE_LIST; + } else if (token == "string-list") + { + return TYPE_STRING_LIST; + } else if (token == "hexBinary-list") + { + return TYPE_HEXBINARY_LIST; + } + break; + case ParseManager::NAMESPACE_XS: + if (token == "boolean") + { + return TYPE_BOOLEAN; + } else if (token =="short") + { + return TYPE_SHORT; + } else if (token =="int") + { + return TYPE_INT; + } else if (token =="long") + { + return TYPE_LONG; + } else if (token =="double") + { + return TYPE_DOUBLE; + } else if (token =="string") + { + return TYPE_STRING; + } else if (token =="hexBinary") + { + return TYPE_HEXBINARY; + } + break; + default: + break; + } + } + throw css::uno::RuntimeException( + "invalid type " + text.convertFromUtf8()); +} + +bool parseBoolean(xmlreader::Span const & text) { + assert(text.is()); + if (text == "true") { + return true; + } + if (text == "false") { + return false; + } + throw css::uno::RuntimeException( + "invalid boolean " + text.convertFromUtf8()); +} + +OUString parseTemplateReference( + std::u16string_view component, bool hasNodeType, + std::u16string_view nodeType, OUString const * defaultTemplateName) +{ + if (!hasNodeType) { + if (defaultTemplateName != nullptr) { + return *defaultTemplateName; + } + throw css::uno::RuntimeException( + "missing node-type attribute"); + } + return Data::fullTemplateName(component, nodeType); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/configmgr/source/xmldata.hxx b/configmgr/source/xmldata.hxx new file mode 100644 index 000000000..44296b38d --- /dev/null +++ b/configmgr/source/xmldata.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ustring.hxx> + +#include "type.hxx" + +namespace xmlreader { + class XmlReader; + struct Span; +} + +namespace configmgr::xmldata { + +Type parseType( + xmlreader::XmlReader const & reader, xmlreader::Span const & text); + +bool parseBoolean(xmlreader::Span const & text); + +OUString parseTemplateReference( + std::u16string_view component, bool hasNodeType, + std::u16string_view nodeType, OUString const * defaultTemplateName); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |