UNO API error reporting improvement – EasyHack
In this blog post, I discuss the EayHack for improving UNO API error reporting. EasyHacks are good if you want to become familiar with LibreOffice programming, and this specific task is a good choice for beginners as it is a difficultyBeginner task.
What is UNO API?
UNO API is the programming interface that you can use to access LibreOffice capabilities programmatically. This API is usable across different languages, from LibreOffice BASIC macro programming to Python, Java, C++ and many more.
UNO API provides many services and functions, and using them should be according to the LibreOffice API documentation. The API is stable over different LibreOffice versions, and most of the API is even compatible with its older predecessor, OpenOffice!
Improving UNO API Error Reporting
Here I discuss improvement to error reporting in UNO API. While using the API, there can be situations with the incorrect use of API, or for any reason, some errors. Many of the functions already provide good error reports, but there are still places with primitive error reporting. In this cases, it is possible to provide improvements.
This suggested improvement is defined as an EasyHack:
Although it may look very easy at first, you have to be patient, and read more about it to make sure that your change is good and meaningful.
Finding Instances
First, you have to pick a C++ file to improve the UNO API error reporting inside it. You can use ‘grep’ tool to find instances. To do this, run this command in terminal:
$ git grep "throw .*RuntimeException *( *)" *.cxx
Then, pick one of the files, and work on it. You may have to change more than one instance of error reporting in a single C++ file. Using counter program ‘wc’, you can see that are are still more than 1400 instances of this change are remaining across more than 300 files.
Required Steps
It is important to do these steps to create a good patch to improve the UNO API reporting:
- Read and grasp the idea of the change from the similar commits from other people in the same EasyHack
- Read the code to understand the case where error occurs
- Choose appropriate Exception type
- Choose suitable constructor
- Understand and differentiate between the functions that are exported as API functions, and local functions.
- Provide good error messages that describe the situation.
- Reproduce and test the error message (if possible)
Similar Commits
There are similar commits, that are listed in the Bugzilla page of the issue tdf#42982. You can learn from them what Exception type to choose, how to write the error message, and many more things.
Please take a look at the this related commit, which improves error reporting for the UNO XPath API:
For example, consider the function registerExtensionInstance()
. The API documentation is here:
It is defined as:
void registerExtensionInstance ([in] com::sun::star::xml::xpath::XXPathExtension aExtension)
So, we know that it should take one string parameter. If not, we need to explicitly say that this parameter is lacking.
Choosing Exception Type
RuntimeException is usually the best choice, but for example, in commit 7e8806cd728bf906e1a8f1d649bef7337f297b1c you see that in case a parameter is not initialized, NotInitializedException is used. If the argument is empty or Null, IllegalArgumentException is a good choice, and if there is are no elements as expected, you can choose NoSuchElementException. But, remember that you can only replace the RuntimeException with the Exception types that are derivatives of it, to give a more specific Exception. That rule prevents you from replacing RuntimeException for example with NoSuchElementException, which is not derived from RuntimeException.
For a complete list of exception types inherited from RuntimeException, refer to the UNO IDL API reference:
There are multiple constructors for the Exceptions, so you should make sure that you are using the right one. This is the comment from Stephan, experienced LO developer:
RuntimeException
constructors either take no arguments or two arguments (Message, a string; and Context, acom::sun::star::uno::XInterface
reference to the relevant UNO object or a null reference).So, in basic/source/uno/namecont.cxx you would need a second argument
static_cast< cppu::OWeakObject * >(this)(where the cast is necessary as this derives from
XInterface
multiple times), and in the later files you would need to move your new, third argument to be the first one instead, replacing the emptyrtl::OUString()
.
As an example, you can see that RuntimeException
is sometimes called with only a message, and better, with the context. Also, in namecont.cxx
you can see this:
void NameContainer::replaceByName( const OUString& aName, const Any& aElement ) { const Type& aAnyType = aElement.getValueType(); if( mType != aAnyType ) { throw IllegalArgumentException("types do not match", getXWeak(), 2); } ... }
The getXWeak()
method provides the context, and 2 means that the 2nd parameter is problematic.
By looking into similar commits, you can also learn how to write error messages. For example, if a parameter is null, you can say that it does not exist, or is null.
Reading the Code
The code itself can show you the good choice for exception, and the error message. In the above patch related to XPathAPI, you have to understand what are the goal of the functions, and the meaning of the error.
For example, the first change is:
- if (!pCNode) { throw RuntimeException(); } + if (!pCNode) { throw RuntimeException("Could not use the namespace node in order to collect namespace declarations."); }
The error message is: “Could not use the namespace node in order to collect namespace declarations”. That is because the namespace node is used to collect the namespace. The function name is lcl_collectNamespaces, which means a local function to collect namespace, and it also this comment is informative:
// get all ns decls on a node (and parent nodes, if any)
As this is only a local function, and is not exported using SAL_CALL, parameter names may not be understandable outside the code itself. But if you see SAL_CALL, you can use the parameter name in the error message.
In some cases, you have to read more to understand what is the parameter. For example, consider this code snippet:
// get the node and document ::rtl::Reference<DOM::CDocument> const pCDoc( dynamic_cast<DOM::CDocument*>(xContextNode->getOwnerDocument().get())); - if (!pCDoc.is()) { throw RuntimeException(); } + if (!pCDoc.is()) { throw RuntimeException("Interface pointer for the owner document of the xContextNode does not exist."); }
In this case, get() function is used to get the interface pointer of the xContextNode->getOwnerDocument(), which can be described as the “owner document of the xContextNode”, and because pCDoc.is() is false, it means that it does not exist.
Testing UNO API Error Reporting
Using this BASIC code, you can see the error message in action:
Sub Main oXPath = createUnoService("com.sun.star.xml.xpath.XPathAPI") oXPath.registerExtensionInstance(Null) End Sub
This is the error message, before the change:
After the change, it becomes this:
The new error message is more understandable and meaningful. Please note that it is not always easy to generate such an error, because the exception may occur in specific situation that may not be easy to reproduce. But, when it is about lack of a parameter or similar situations, it is good to check the error message similar to the above BASIC code.
Final Words
Having good error messages in LibreOffice API helps macro programmers and developers who use LibreOffice programmatically. If you are interested in doing this EasyHack, make sure that you go through the above mentioned steps to improve the error reporting.