/* -*- 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/.
*/
// Try to instantiate as many implementations as possible. Finds all
// implementations reachable via the service manager. If a given implementation
// is the only implementor of some service that has a zero-parameter
// constructor, instantiate the implementation through that service name. If a
// given implementation does not offer any such constructors (because it does not
// support any single-interface--based service, or because for each relevant
// service there are multiple implementations or it does not have an appropriate
// constructor) but does support at least one accumulation-based service, then
// instantiate it through its implementation name (a heuristic to identify
// instantiable implementations that appears to work well).
#include <sal/config.h>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <map>
#include <set>
#include <string_view>
#include <utility>
#include <vector>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/reflection/XServiceConstructorDescription.hpp>
#include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
#include <com/sun/star/frame/XDesktop.hpp>
#include <comphelper/sequence.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <rtl/strbuf.hxx>
#include <test/bootstrapfixture.hxx>
#include <vcl/svapp.hxx>
namespace {
OString msg(std::u16string_view string) {
return OUStringToOString(string, osl_getThreadTextEncoding());
}
OString msg(css::uno::Sequence<OUString> const & strings) {
OStringBuffer buf("{");
for (sal_Int32 i = 0; i != strings.getLength(); ++i) {
if (i != 0) {
buf.append(", ");
}
buf.append('"');
buf.append(msg(strings[i]));
buf.append('"');
}
buf.append('}');
return buf.makeStringAndClear();
}
bool unique(css::uno::Sequence<OUString> const & strings) {
// Assumes small sequences for which quadratic algorithm is acceptable:
for (sal_Int32 i = 0; i < strings.getLength() - 1; ++i) {
for (sal_Int32 j = i + 1; j != strings.getLength(); ++j) {
if (strings[j] == strings[i]) {
return false;
}
}
}
return true;
}
bool contains(
css::uno::Sequence<OUString> const & strings, OUString const & string)
{
return comphelper::findValue(strings, string) != -1;
}
bool contains(
css::uno::Sequence<OUString> const & strings1,
css::uno::Sequence<OUString> const & strings2)
{
// Assumes small sequences for which quadratic algorithm is acceptable:
return std::all_of(strings2.begin(), strings2.end(),
[&strings1](const OUString& rStr) { return contains(strings1, rStr); });
}
void addService(
css::uno::Reference<css::reflection::XServiceTypeDescription> const & service,
std::set<css::uno::Reference<css::reflection::XServiceTypeDescription>> * allServices)
{
assert(allServices != nullptr);
if (!allServices->insert(service).second) {
return;
}
const auto aMandatoryServices = service->getMandatoryServices();
for (auto const & serv : aMandatoryServices) {
addService(serv, allServices);
}
}
class Test: public test::BootstrapFixture {
public:
void test();
CPPUNIT_TEST_SUITE(Test);
CPPUNIT_TEST(test);
CPPUNIT_TEST_SUITE_END();
private:
void createInstance(
css::uno::Reference<css::container::XHierarchicalNameAccess> const & typeManager,
OUString const & name, bool withArguments,
OUString const & implementationName,
css::uno::Sequence<OUString> const & serviceNames,
std::vector<css::uno::Reference<css::lang::XComponent>> * components);
};
void Test::test() {
// On Windows, denylist the com.sun.star.comp.report.OReportDefinition
// implementation (reportdesign::OReportDefinition in
// reportdesign/source/core/api/ReportDefinition.cxx), as it spawns a thread
// that forever blocks in SendMessageW when no VCL event loop is running
// (reportdesign::<anon>::FactoryLoader::execute ->
// framework::Desktop::findFrame -> framework::TaskCreator::createTask ->
// <anon>::TaskCreatorService::createInstanceWithArguments ->
// <anon>::TaskCreatorService::impls_createContainerWindow ->
// <anon>::VCLXToolkit::createWindow ->
// <anon>::VCLXToolkit::ImplCreateWindow ->
// <anon>::VCLXToolkit::ImplCreateWindow -> WorkWindow::WorkWindow ->
// WorkWindow::ImplInit -> ImplBorderWindow::ImplBorderWindow ->
// ImplBorderWindow::ImplInit -> Window::ImplInit ->
// WinSalInstance::CreateFrame -> ImplSendMessage -> SendMessageW):
std::vector<OUString> denylist;
denylist.emplace_back("com.sun.star.comp.report.OReportDefinition");
// <https://bugs.documentfoundation.org/show_bug.cgi?id=89343>
// "~SwXMailMerge() goes into endless SwCache::Check()":
denylist.emplace_back("SwXMailMerge");
css::uno::Reference<css::container::XContentEnumerationAccess> enumAcc(
m_xContext->getServiceManager(), css::uno::UNO_QUERY_THROW);
css::uno::Reference<css::container::XHierarchicalNameAccess> typeMgr(
m_xContext->getValueByName(
u"/singletons/com.sun.star.reflection.theTypeDescriptionManager"_ustr),
css::uno::UNO_QUERY_THROW);
const css::uno::Sequence<OUString> serviceNames(
m_xContext->getServiceManager()->getAvailableServiceNames());
struct Constructor {
Constructor(
OUString const & theServiceName, bool theDefaultConstructor):
serviceName(theServiceName),
defaultConstructor(theDefaultConstructor)
{}
OUString serviceName;
bool defaultConstructor;
};
struct Implementation {
Implementation(
css::uno::Reference<css::lang::XServiceInfo> const & theFactory,
css::uno::Sequence<OUString> const & theServiceNames):
factory(theFactory), serviceNames(theServiceNames),
accumulationBased(false)
{}
css::uno::Reference<css::lang::XServiceInfo> const factory;
css::uno::Sequence<OUString> const serviceNames;
std::vector<Constructor> constructors;
bool accumulationBased;
};
std::map<OUString, Implementation> impls;
for (const auto& rServiceName : serviceNames) {
css::uno::Reference<css::container::XEnumeration> serviceImpls1(
enumAcc->createContentEnumeration(rServiceName),
css::uno::UNO_SET_THROW);
std::vector<css::uno::Reference<css::lang::XServiceInfo>> serviceImpls2;
while (serviceImpls1->hasMoreElements()) {
serviceImpls2.emplace_back(
serviceImpls1->nextElement(), css::uno::UNO_QUERY_THROW);
}
css::uno::Reference<css::reflection::XServiceTypeDescription2> desc;
if (typeMgr->hasByHierarchicalName(rServiceName)) {
desc.set(
typeMgr->getByHierarchicalName(rServiceName),
css::uno::UNO_QUERY_THROW);
}
if (serviceImpls2.empty()) {
if (desc.is()) {
CPPUNIT_ASSERT_MESSAGE(
(OString(
"no implementations of single-interface--based \""
+ msg(rServiceName) + "\"")
.getStr()),
!desc->isSingleInterfaceBased());
std::cout
<< "accumulation-based service \"" << rServiceName
<< "\" without implementations\n";
} else {
std::cout
<< "fantasy service name \"" << rServiceName
<< "\" without implementations\n";
}
} else {
for (auto const & j: serviceImpls2) {
OUString name(j->getImplementationName());
auto k = impls.find(name);
if (k == impls.end()) {
css::uno::Sequence<OUString> servs(
j->getSupportedServiceNames());
CPPUNIT_ASSERT_MESSAGE(
(OString(
"implementation \"" + msg(name)
+ "\" supports non-unique " + msg(servs))
.getStr()),
unique(servs));
k = impls.insert(
std::make_pair(name, Implementation(j, servs)))
.first;
} else {
CPPUNIT_ASSERT_MESSAGE(
(OString(
"multiple implementations named \"" + msg(name)
+ "\"")
.getStr()),
bool(j == k->second.factory));
}
CPPUNIT_ASSERT_MESSAGE(
(OString(
"implementation \"" + msg(name) + "\" supports "
+ msg(k->second.serviceNames) + " but not \""
+ msg(rServiceName) + "\"")
.getStr()),
contains(k->second.serviceNames, rServiceName));
if (desc.is()) {
if (desc->isSingleInterfaceBased()) {
if (serviceImpls2.size() == 1) {
const css::uno::Sequence<
css::uno::Reference<
css::reflection::XServiceConstructorDescription>>
ctors(desc->getConstructors());
auto pCtor = std::find_if(ctors.begin(), ctors.end(),
[](const auto& rCtor) { return !rCtor->getParameters().hasElements(); });
if (pCtor != ctors.end())
k->second.constructors.emplace_back(
rServiceName,
(*pCtor)->isDefaultConstructor());
}
} else {
k->second.accumulationBased = true;
}
} else {
std::cout
<< "implementation \"" << name
<< "\" supports fantasy service name \""
<< rServiceName << "\"\n";
}
}
}
}
std::vector<css::uno::Reference<css::lang::XComponent>> comps;
for (auto const & i: impls) {
if (std::find(denylist.begin(), denylist.end(), i.first)
== denylist.end())
{
if (i.second.constructors.empty()) {
if (i.second.accumulationBased) {
createInstance(
typeMgr, i.first, false, i.first, i.second.serviceNames, &comps);
} else {
std::cout
<< "no obvious way to instantiate implementation \""
<< i.first << "\"\n";
}
} else {
for (auto const & j: i.second.constructors) {
createInstance(
typeMgr, j.serviceName, !j.defaultConstructor, i.first,
i.second.serviceNames, &comps);
}
}
}
}
SolarMutexReleaser rel;
for (auto const & i: comps) {
// cannot call dispose() on XDesktop before calling terminate()
if (!css::uno::Reference<css::frame::XDesktop>(i, css::uno::UNO_QUERY))
i->dispose();
}
}
void Test::createInstance(
css::uno::Reference<css::container::XHierarchicalNameAccess> const & typeManager,
OUString const & name, bool withArguments,
OUString const & implementationName,
css::uno::Sequence<OUString> const & serviceNames,
std::vector<css::uno::Reference<css::lang::XComponent>> * components)
{
assert(components != nullptr);
css::uno::Reference<css::uno::XInterface> inst;
try {
if (withArguments) {
inst = m_xContext->getServiceManager()
->createInstanceWithArgumentsAndContext(
name, css::uno::Sequence<css::uno::Any>(), m_xContext);
} else {
inst = m_xContext->getServiceManager()->createInstanceWithContext(
name, m_xContext);
}
} catch (css::uno::Exception & e) {
css::uno::Any a(cppu::getCaughtException());
CPPUNIT_FAIL(
OString(
"instantiating \"" + msg(implementationName) + "\" via \""
+ msg(name) + "\" caused " + msg(a.getValueTypeName()) + " \""
+ msg(e.Message) + "\"")
.getStr());
}
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \""
+ msg(name) + "\" returned null reference")
.getStr()),
inst.is());
css::uno::Reference<css::lang::XComponent> comp(inst, css::uno::UNO_QUERY);
if (comp.is()) {
components->push_back(comp);
}
css::uno::Reference<css::lang::XServiceInfo> info(
inst, css::uno::UNO_QUERY);
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \""
+ msg(name) + "\" does not provide XServiceInfo")
.getStr()),
info.is());
OUString expImpl(implementationName);
css::uno::Sequence<OUString> expServs(serviceNames);
// Special cases:
if (name == "com.sun.star.comp.configuration.ConfigurationProvider") {
// Instantiating a ConfigurationProvider with no or empty args must
// return theDefaultProvider:
expImpl = "com.sun.star.comp.configuration.DefaultProvider";
expServs = {u"com.sun.star.configuration.DefaultProvider"_ustr};
} else if (name == "com.sun.star.datatransfer.clipboard.SystemClipboard") {
// SystemClipboard is a wrapper returning either a platform-specific or
// the generic VCLGenericClipboard:
expImpl = "com.sun.star.datatransfer.VCLGenericClipboard";
#if !defined(_WIN32)
} else if (name == "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1"
|| name == "com.sun.star.datatransfer.dnd.XdndSupport")
{
expImpl = "com.sun.star.datatransfer.dnd.VclGenericDragSource";
expServs = {u"com.sun.star.datatransfer.dnd.GenericDragSource"_ustr};
} else if (name == "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1"
|| name == "com.sun.star.datatransfer.dnd.XdndDropTarget")
{
expImpl = "com.sun.star.datatransfer.dnd.VclGenericDropTarget";
expServs = {u"com.sun.star.datatransfer.dnd.GenericDropTarget"_ustr};
#endif
} else if (name == "com.sun.star.ui.dialogs.FolderPicker") {
// FolderPicker is a wrapper returning either a platform-specific or the
// generic OfficeFolderPicker. In headless mode it is always the
// generic one.
expImpl = "com.sun.star.svtools.OfficeFolderPicker";
expServs = {u"com.sun.star.ui.dialogs.OfficeFolderPicker"_ustr};
} else if (expImpl == "com.sun.star.comp.Calc.SpreadsheetDocument") {
expImpl = "ScModelObj";
} else if (expImpl == "com.sun.star.comp.Draw.DrawingDocument"
|| expImpl == "com.sun.star.comp.Draw.PresentationDocument")
{
expImpl = "SdXImpressDocument";
} else if (expImpl == "com.sun.star.comp.Writer.GlobalDocument"
|| expImpl == "com.sun.star.comp.Writer.TextDocument"
|| expImpl == "com.sun.star.comp.Writer.WebDocument")
{
expImpl = "SwXTextDocument";
}
CPPUNIT_ASSERT_EQUAL_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \""
+ msg(name) + "\" reports wrong implementation name")
.getStr()),
expImpl, info->getImplementationName());
const css::uno::Sequence<OUString> servs(info->getSupportedServiceNames());
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \""
+ msg(name) + "\" reports non-unique " + msg(servs))
.getStr()),
unique(servs));
// Some implementations like "com.sun.star.comp.Calc.SpreadsheetDocument"
// report sub-services like
// "com.sun.star.sheet.SpreadsheetDocumentSettings", and
// "com.sun.star.document.OfficeDocument" that are not listed in the
// .component file, so check for containment instead of equality:
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \""
+ msg(name) + "\" reports " + msg(servs) + " different from "
+ msg(expServs))
.getStr()),
contains(servs, expServs));
std::set<css::uno::Reference<css::reflection::XServiceTypeDescription>> allservs;
for (auto const & serv: servs) {
if (!typeManager->hasByHierarchicalName(serv)) {
std::cout
<< "instantiating \"" << implementationName << "\" via \"" << name
<< "\" supports fantasy service name \"" << serv << "\"\n";
continue;
}
addService(
css::uno::Reference<css::reflection::XServiceTypeDescription>(
typeManager->getByHierarchicalName(serv), css::uno::UNO_QUERY_THROW),
&allservs);
}
css::uno::Reference<css::beans::XPropertySetInfo> propsinfo;
for (auto const & serv: allservs) {
auto const props = serv->getProperties();
for (auto const & prop: props) {
auto const optional
= (prop->getPropertyFlags() & css::beans::PropertyAttribute::OPTIONAL) != 0;
if (!propsinfo.is()) {
css::uno::Reference<css::beans::XPropertySet> propset(inst, css::uno::UNO_QUERY);
if (!propset.is()) {
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \"" + msg(name)
+ "\" reports service " + msg(serv->getName())
+ " with non-optional property \"" + msg(prop->getName())
+ "\" but does not implement css.uno.XPropertySet")
.getStr()),
optional);
continue;
}
propsinfo = propset->getPropertySetInfo();
if (!propsinfo.is()) {
//TODO: legal to return null in more cases? ("@returns NULL if the
// implementation cannot or will not provide information about the properties")
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \"" + msg(name)
+ "\" reports service " + msg(serv->getName())
+ " with non-optional property \"" + msg(prop->getName())
+ "\" but css.uno.XPropertySet::getPropertySetInfo returns null")
.getStr()),
optional);
continue;
}
}
if (!propsinfo->hasPropertyByName(prop->getName())) {
static std::set<std::pair<OUString, OUString>> const denylist{
{"com.sun.star.comp.chart.DataSeries", "BorderDash"},
{"com.sun.star.comp.chart2.ChartDocumentWrapper", "UserDefinedAttributes"},
{"com.sun.star.comp.dbu.OColumnControlModel", "Tabstop"},
{"com.sun.star.comp.report.OFormattedField", "Align"},
{"com.sun.star.comp.report.OFormattedField", "BackgroundColor"},
{"com.sun.star.comp.report.OFormattedField", "Border"},
{"com.sun.star.comp.report.OFormattedField", "DefaultControl"},
{"com.sun.star.comp.report.OFormattedField", "EffectiveDefault"},
{"com.sun.star.comp.report.OFormattedField", "EffectiveMax"},
{"com.sun.star.comp.report.OFormattedField", "EffectiveMin"},
{"com.sun.star.comp.report.OFormattedField", "EffectiveValue"},
{"com.sun.star.comp.report.OFormattedField", "Enabled"},
{"com.sun.star.comp.report.OFormattedField", "FontEmphasisMark"},
{"com.sun.star.comp.report.OFormattedField", "FontRelief"},
{"com.sun.star.comp.report.OFormattedField", "HelpText"},
{"com.sun.star.comp.report.OFormattedField", "HelpURL"},
{"com.sun.star.comp.report.OFormattedField", "MaxTextLen"},
{"com.sun.star.comp.report.OFormattedField", "Printable"},
{"com.sun.star.comp.report.OFormattedField", "ReadOnly"},
{"com.sun.star.comp.report.OFormattedField", "Spin"},
{"com.sun.star.comp.report.OFormattedField", "Tabstop"},
{"com.sun.star.comp.report.OFormattedField", "Text"},
{"com.sun.star.comp.report.OFormattedField", "TextColor"},
{"com.sun.star.comp.report.OFormattedField", "TextLineColor"},
{"com.sun.star.comp.report.OFormattedField", "TreatAsNumber"},
{"stardiv.Toolkit.UnoControlRoadmapModel", "Interactive"}};
if (denylist.find({implementationName, prop->getName()}) != denylist.end()) {
continue;
}
CPPUNIT_ASSERT_MESSAGE(
(OString(
"instantiating \"" + msg(implementationName) + "\" via \"" + msg(name)
+ "\" reports service " + msg(serv->getName())
+ " with non-optional property \"" + msg(prop->getName())
+ ("\" but css.uno.XPropertySet::getPropertySetInfo's hasPropertyByName"
" returns false"))
.getStr()),
optional);
}
}
}
}
CPPUNIT_TEST_SUITE_REGISTRATION(Test);
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.