[Libreoffice] Simplified C++ configuration access
Stephan Bergmann
sbergman at redhat.com
Tue Jan 31 02:31:14 PST 2012
Hi all,
configmgr is a dreaded beast. Its UNO API is notorious for re-using
each and every generic UNO interface type that exists, in a way that
makes it hard to see the forest for the trees. To add insult to injury,
people always thought it a good idea to wrap the API in C++ "helper
classes" that are themselves baroque and obscure
(utl::ConfigItem::{EnableNotification,DisableNotification,IsInValueChange}
-- wtf?).
So I finally set out to simplify this (on the master branch towards LO 3.6):
At the lowest level, there are two new UNO entities, a singleton
css.configuration.ReadOnlyAccess (of type
css.conainer.XHierarchicalNameAccess) and a service
css.configuration.ReadWriteAccess (of type
css.configuration.XReadWriteAccess, subsuming
css.container.XHierarchicalNameReplace and css.util.XChangesBatch).
These simplify access to a given configuration item (node, set, or
property) by name. Modification still operates in batch mode. Each
instance of the ReadWriteAccess service creates its own batch context,
and modifications done through that instance's XHierarchicalNameReplace
need to be committed via its XChangesBatch.commitChanges method.
At the next level, the new unotools/configuration.hxx header provides
type-safe C++ wrappers for the various configuration entities on top of
the lower level UNO services. For example, the
utl::ConfigurationProperty class template can be used to wrap a given
configuration property of a specific type in a C++ class whose get and
set member functions internally handle the conversion between the UNO
API's generic Any and the concrete type of the given property. There is
also a utl::ConfigurationChanges class that wraps an instance of
ReadWriteAccess for (batch) modification.
At the top level, new headers automatically generated in module
officecfg provide such C++ wrappers for all of the static configuration
structure as described by the officecfg/registry/schema/**.xcs files.
For each configuration property (with path
/org.openoffice.Foo.Bar/Baz/Whatever) there is a C++ class
officecfg::Foo::Bar::Baz::Whatever (declared in header
officecfg/Foo/Bar.hxx) based on the utl::ConfigurationProperty template.
It is type safe (see above) and also prevents mistypings of
configuration paths (as they are represented by qualified C++ names
here, not string literals).
To get a property's value, all that is needed is to call the wrapper
class's static officecfg::Foo::Bar::Baz::Whatever::get member function,
which requires the XComponentContext as argument (use
comphelper::getProcessComponentContext in code that does not thread the
component context more locally).
Setting a property's value is three-step. First obtain a
ConfigurationChanges instance (via static member function
utl::ConfigurationChanges::create), then call
officecfg::Foo::Bar::Baz::Whatever::set (with the component context and
the given ConfigurationChanges instance in addition to the new value),
then call commit on the given ConfigurationChanges instance.
Localized configuration properties are currently treated the same as
non-localized ones, the wrappers only give access to the value for the
current locale.
Nillable configuration properties complicate things, in that the wrapper
needs to use boost::optional<T> instead of plain T (e.g.,
boost::optional<rtl::OUString> vs. rtl::OUString for a string-typed
property). This would have been quite a nuisance as most properties
were implicitly declared as nillable in the .xcs files (a missing
oor:nillable attribute means oor:nillable="true"), even though in
practice they never were used with nil values. I therefore changed all
those properties that already define a default <value> in
officecfg/registry/schema/**.xcs to an explicit oor:nillable="false".
Even though, strictly speaking, this change is incompatible, I think it
is warranted, as it more cleanly reflects the current use of those
properties, anyway, and should not make a difference in practice.
(One caveat though is the rare case of a non-nillable property that does
not have a default value defined in .xcs. While this is technically
legal, it will cause the C++ wrapper's get function to throw a
RuntimeException upon retrieving an empty (i.e., void) Any from
configmgr and trying to convert it to the given type. To avoid this,
either specify a default value in .xcs or make sure there's always a
value defined in .xcu.)
There are also C++ wrapper classes for all the configuration's groups
and sets, but they are probably needed less often (and only make
available the underlying UNO interfaces, at least for now). One case
where they are necessary is to add listeners to (part of) the
configuration. For example, to listen for changes to certain properties
within a given configuration group, one can obtain the group's
XMultiPropertySet via the corresponding C++ wrapper class's static get
member function and directly add an appropriate
XPropertiesChangeListener there.
I started to replace a number of old C++ wrappers
(utl::SourceViewConfig, utl::SvtInetOptions, utl::SvtCacheOptions,
util::SvtUndoOptions, SvxAsianConfig, ...), and intend to continue with
that -- until finally things like utl::ConfigItem can be removed. Let
me know if you would like to help with that.
Stephan
More information about the LibreOffice
mailing list