2017-05-07

20: To Develop the Second Sample UNO Extension (LibreOffice Extension or Apache OpenOffice Extension) in a Different Structure, Part Two

<The previous article in this series | The table of contents of this series | The next article in this series>

Main body START

To Know How to Handle (Read or Write) LibreOffice or Apache OpenOffice Writer or Calc Documents via Extensions from Java or Macro Programs

We Create the Second Sample UNO Extension Project

-Hypothesizer

We create the second sample UNO extension project, 'heyUnoExtensionsUnoExtension'. We create the project directory directly under the development directory.

Then, we create the project-specific Gradle build script or Ant build file directly under the project directory. The contents of the Gradle build script is this.

// # Change the target name
ext.TARGET_NAME = "thebiasplanet.heyunoextensionsunoextension.uno"

apply ("from": "../commonBuild01.gradle")

// # Change the default task
defaultTasks ("registerUnoExtension")
ext ({
 // Add this if necessary
 //CHECKSTYLE = "ON"
 // Add this if necessary
 //JAR_DEPLOY_DIRECTORY_NAME = "?"
 // Add this if necessary
 //WAR_DEPLOY_DIRECTORY_NAME = "?"
 // # Change this if necessary
 INCLUDED_JAR_FILE_NAMES = []
 // # Change this if necessary
 OTHER_CLASSES_PATHS = [JAVA_FILES_BASE_DIRECTORY_NAME, CLASSES_BASE_DIRECTORY_NAME, "../unoAdditionalDataTypesToDisclose/target/thebiasplanet.unoadditionaldatatypes.uno.jar", "../unoUtilitiesToDisclose/target/thebiasplanet.unoutilities.jar", "../coreUtilitiesToDisclose/target/thebiasplanet.coreutilities.jar", LIBREOFFICE_CLASSES_BASE_DIRECTORY_NAME + "/unoil.jar", LIBREOFFICE_CLASSES_BASE_DIRECTORY_NAME + "/jurt.jar", LIBREOFFICE_CLASSES_BASE_DIRECTORY_NAME + "/ridl.jar", LIBREOFFICE_CLASSES_BASE_DIRECTORY_NAME + "/juh.jar"]
 REFERENCED_PROJECT_DIRECTORY_NAMES = ["../coreUtilitiesToDisclose", "../unoUtilitiesToDisclose", "../unoAdditionalDataTypesToDisclose"]
})

apply ("from": "../commonBuild02.gradle")

Note that the UNO data types project Jar file, the UNO utilities project Jar file, and the core utilities project Jar file are referenced, but they won't be included in the Jar file of this project.

-Rebutter

Ah, in the first sample UNO extension, the UNO utilities project Jar file is included in the Jar file of the sample UNO extension. The difference comes from the difference of the places where the UNO utilities project Jar file is set in the Gradle build scripts.

-Hypothesizer

Yes. As we have registered those three Jar files directly into LibreOffice, we don't need to include those Jar files in UNO extensions any more. However, of course, we need to reference those Jar file at build times. Thus, those Jar files are set at that place in the Gradle build script.

-Rebutter

All right.

We Experiment to Create and Use a Global UNO Service Without any 'Global UNO Service' Instances Factory

-Hypothesizer

According to the item, No. 4, We create a UNO component, 'thebiasplanet.uno.heyunoextensionsunoextension.HeyUnoExtensionsUnoComponent', that implements the UNO interface, 'thebiasplanet.uno.heyunoextensionsunoextension.XHeyUnoExtensions'.

-Rebutter

The UNO interface has been already created in the UNO data types project.

-Hypothesizer

Yes. We create the 'source' directory directly under the sample UNO extension project directory, and create a Java class source file, 'java/thebiasplanet/uno/heyunoextensionsunoextension/HeyUnoExtensionsUnoComponent.java'. The contents of the source file is this.

// # Change the package name
package thebiasplanet.uno.heyunoextensionsunoextension;

import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.XInitialization;
import com.sun.star.uno.XComponentContext;
// # Add necessary classes and interfaces START
import com.sun.star.lang.IllegalArgumentException;
// # Add necessary classes and interfaces END

// # Change the class name
public class HeyUnoExtensionsUnoComponent
  extends WeakBase
  implements XServiceInfo, XInitialization,
  // # Specify the UNO interface to implement
  XHeyUnoExtensions {
 private static final Set <String> SERVICE_NAMES_SET = new HashSet <String> ();
 private static final Class thisClass = new Object () { }.getClass ().getEnclosingClass ();
 static {
  // # Add service names START
  SERVICE_NAMES_SET.add ("thebiasplanet.uno.heyunoextensionsunoextension.HeyUnoExtensionsService");
  // # Add service names END
 }
 static final String [] SERVICE_NAMES_ARRAY = SERVICE_NAMES_SET.toArray (new String [SERVICE_NAMES_SET.size ()]);
 private XComponentContext componentContext = null;
 // # Add member variables START
 private String message = null;
 private Map <String, Object> componentContextExtraNameValueMap = null;
 // # Add member variables END
 
 static void setThisClassToServicesProvider (Map <String, Object []> p_unoComponentClassNameToUnoComponentClassAndServiceNamesArrayMap) {
  p_unoComponentClassNameToUnoComponentClassAndServiceNamesArrayMap.put (thisClass.getName (), new Object [] {thisClass, SERVICE_NAMES_ARRAY});
 }
 
 // # Change the class name
 public HeyUnoExtensionsUnoComponent (XComponentContext p_componentContext)
   throws IllegalArgumentException {
   componentContext = p_ componentContext;
 }
 
 public final void initialize (java.lang.Object [] p_arguments)
   throws com.sun.star.uno.Exception {
  // # Write the initialization START
  if (p_arguments != null && p_arguments.length == 1) {
   if (p_arguments[0] instanceof String) {
    message = (String) p_arguments[0];
    if (message == null) {
     throw new IllegalArgumentException ("The first argument can't be null.");
    }
   }
   else {
    throw new IllegalArgumentException("The first argument must be a String instance.");
   }
  }
  else {
   throw new IllegalArgumentException("The number of arguments must be 1.");
  }
  // # Write the initialization END
 }
 
 // # Add methods of the implemented UNO interface START
 public String sayHey (String p_name)
   throws IllegalArgumentException {
  if (p_name == null) {
   throw new IllegalArgumentException ("The first argument can't be null.");
  }
  return String.format ("%s, %s!", message, p_name);
 }
 // # Add methods of the implemented UNO interface END
 
 // # Add other member methods START
 // # Add other member methods END
 
 public String getImplementationName () {
  return thisClass.getName ();
 }
 
 public final boolean supportsService (String p_serviceName) {
  return SERVICE_NAMES_SET.contains (p_serviceName);
 }
 
 public final String [] getSupportedServiceNames () {
  return SERVICE_NAMES_ARRAY;
 }
}
-Rebutter

Basically, the contents are the same with the first sample UNO extension's.

-Hypothesizer

At this point, yes. We will add codes into this file later.

Then, we create the 'global UNO services' provider. The Java source file is 'java/thebiasplanet/uno/heyunoextensionsunoextension/HeyUnoExtensionsUnoExtensionGlobalServicesProvider.java'. The contents of the file is this.

// # Change the package name
package thebiasplanet.uno.heyunoextensionsunoextension;

import java.util.Map;
import java.util.HashMap;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.registry.XRegistryKey;
import thebiasplanet.unoutilities.serviceshandling.GlobalUnoServicesProviderUtility;

// # Change the class name
public class HeyUnoExtensionsUnoExtensionGlobalServicesProvider {
 private static final Map <String, Object []> UNO_COMPONENT_CLASS_NAME_TO_UNO_COMPONENT_CLASS_AND_SERVICE_NAMES_ARRAY_MAP = new HashMap <String, Object []> ();
 static {
  // # Add UNO component classes START
  HeyUnoExtensionsUnoComponent.setThisClassToServicesProvider (UNO_COMPONENT_CLASS_NAME_TO_UNO_COMPONENT_CLASS_AND_SERVICE_NAMES_ARRAY_MAP);
  // # Add UNO component classes END
 }
 
 public static XSingleComponentFactory __getComponentFactory (String p_unoComponentClassName) {
  return GlobalUnoServicesProviderUtility.getSingleComponentFactory (UNO_COMPONENT_CLASS_NAME_TO_UNO_COMPONENT_CLASS_AND_SERVICE_NAMES_ARRAY_MAP, p_unoComponentClassName);
 }
 
 public static boolean __writeRegistryServiceInfo (XRegistryKey p_registryKey) {
  return GlobalUnoServicesProviderUtility.writeServicesInformationToRegistry (UNO_COMPONENT_CLASS_NAME_TO_UNO_COMPONENT_CLASS_AND_SERVICE_NAMES_ARRAY_MAP, p_registryKey);
 }
}

That won't require any explanation if we remember what are described in the previous articles.

-Rebutter

OK.

-Hypothesizer

Then, we create resource files, 'MANIFEST.MF.addition', 'thebiasplanet.heyunoextensionsunoextension.uno.components', and 'manifest.xml'.

I won't show the contents of 'MANIFEST.MF.addition' here because they are obvious.

The contents of 'thebiasplanet.heyunoextensionsunoextension.uno.components' is this.

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://openoffice.org/2010/uno-components">
 <!-- # Change the jar file uri -->
 <component loader="com.sun.star.loader.Java2" uri="thebiasplanet.heyunoextensionsunoextension.uno.jar">
  <!-- # Add UNO component class names -->
  <implementation name="thebiasplanet.uno.heyunoextensionsunoextension.HeyUnoExtensionsUnoComponent">
   <!-- # Add service names -->
   <service name="thebiasplanet.uno.heyunoextensionsunoextension.HeyUnoExtensionsService"/>
  </implementation>
 </component>
</components>

The contents of 'manifest.xml' is this.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
 <!-- # Change the components file path -->
 <manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-components" manifest:full-path="thebiasplanet.heyunoextensionsunoextension.uno.components"/>
</manifest:manifest>

Note that it doesn't specify any UNO data types registry file because this project doesn't have any.

-Rebutter

I see.

-Hypothesizer

We won't create any 'global UNO service' instances factory this time to demonstrate that it isn't necessary if we instantiate the global UNO service through the global UNO service manager.

-Rebutter

OK.

-Hypothesizer

Now, we can build and register the sample UNO extension. As usual, let's do that by executing the 'gradle' command or the 'ant' command.

After that, we create a LibreOffice Basic macro Sub like this.

Sub testsUnoExtension2
 Dim l_args (0)
 l_args (0) = "Good morning"
 Dim l_heyUnoExtensionsService As Variant
 l_heyUnoExtensionsService = GetProcessServiceManager ().createInstanceWithArguments ("thebiasplanet.uno.heyunoextensionsunoextension.HeyUnoExtensionsService", l_args ())
 Msgbox (l_heyUnoExtensionsService.sayHey ("guys"))
End Sub
-Rebutter

Hmm, that's how we instantiate a global UNO service through the global UNO service manager.

-Hypothesizer

Yes. Let's execute the macro Sub.

-Rebutter

Well, the global UNO service was successfully instantiated.

-Hypothesizer

Thus, we demonstrated that what we call 'global UNO service' instances factories are just factories, not 'UNO new-style services' themselves as described in the reference documents.

We Experiment to Create and Use a UNO Component That Isn't Registered as a UNO Service

-Hypothesizer

According to the item, No. 5, we create another UNO component, but won't register it as a UNO service.

-Rebutter

Hmm.

-Hypothesizer

We will instantiate the UNO component not through the global UNO service manager, but through a method added to the first UNO component, 'thebiasplanet.uno.heyunoextensionsunoextension.HeyUnoExtensionsUnoComponent'.

-Rebutter

I see.

-Hypothesizer

First, we create the UNO interface, 'thebiasplanet.uno.heyunoextensionsunoextension.XRanter', for the second UNO component.

In the UNO data types project, we create a UNOIDL file, 'unoIdl/thebiasplanet/uno/heyunoextensionsunoextension/XRanter.idl' under the 'source' directory, and write this in it.

// # Change the defined variable name START
#ifndef __thebiasplanet_uno_hiunoextensionsunoextension_XRanter_idl__
#define __thebiasplanet_uno_hiunoextensionsunoextension_XRanter_idl__
// # Change the defined variable name END

#include <com/sun/star/uno/XInterface.idl>
// # Add necessary idls START
#include <com/sun/star/lang/IllegalArgumentException.idl>
// # Add necessary idls END

// # Change the module name
module thebiasplanet { module uno { module heyunoextensionsunoextension {
 // # Change the interface name and the parent interface
 interface XRanter: com::sun::star::uno::XInterface
 {
  // # Add methods START
  string rant ( [in] string p_name)
    raises (com::sun::star::lang::IllegalArgumentException);
  // # Add methods END
 };
}; }; };

#endif
-Rebutter

Well, there seems to be nothing that requires explanations.

-Hypothesizer

Second, we add a method, 'getInnerRanter', into the UNO interface, 'thebiasplanet.uno.heyunoextensionsunoextension.XHeyUnoExtensions', like this.

  XRanter getInnerRanter ( [in] string p_message)
    raises (com::sun::star::lang::IllegalArgumentException);
-Rebutter

The method returns an instance of the second UNO interface, 'thebiasplanet.uno.heyunoextensionsunoextension.XRanter'.

-Hypothesizer

Third, we build the UNO data types project.

-Rebutter

OK.

-Hypothesizer

Fourth, in the UNO extension project, we create the second UNO component Java source file, 'java/thebiasplanet/uno/heyunoextensionsunoextension/RanterUnoComponent.java', and write this in it.

// # Change the package name
package thebiasplanet.uno.heyunoextensionsunoextension;

import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.lang.XInitialization;
import com.sun.star.uno.XComponentContext;
// # Add necessary classes and interfaces START
import com.sun.star.lang.IllegalArgumentException;
// # Add necessary classes and interfaces END

// # Change the class name
public class RanterUnoComponent
  extends WeakBase
  implements XInitialization,
  // # Specify the UNO interface to implement
  XRanter {
 private static final Class thisClass = new Object () { }.getClass ().getEnclosingClass ();
 private XComponentContext componentContext = null;
 // # Add member variables START
 private String message = null;
 // # Add member variables END
 
 // # Change the class name
 public RanterUnoComponent (XComponentContext p_componentContext)
   throws IllegalArgumentException {
  componentContext = p_componentContext;
 }
 
 public final void initialize (java.lang.Object [] p_arguments)
   throws com.sun.star.uno.Exception {
  // # Write the initialization START
  if (p_arguments != null && p_arguments.length == 1) {
   if (p_arguments[0] instanceof String) {
    message = (String) p_arguments[0];
    if (message == null) {
     throw new IllegalArgumentException ("The first argument can't be null.");
    }
   }
   else {
    throw new IllegalArgumentException("The first argument must be a String instance.");
   }
  }
  else {
   throw new IllegalArgumentException("The number of arguments must be 1.");
  }
  // # Write the initialization END
 }
 
 // # Add methods of the implemented UNO interface START
 public String rant (String p_name)
   throws IllegalArgumentException {
  if (p_name == null) {
   throw new IllegalArgumentException ("The first argument can't be null.");
  }
  return String.format ("%s? No way! Go to hell, %s!", message, p_name);
 }
 // # Add methods of the implemented UNO interface END
 
 // # Add other member methods START
 // # Add other member methods END
}
-Rebutter

Well, as this UNO component isn't registered as a UNO service, it doesn't implement 'com.sun.star.lang.XServiceInfo', and some codes that exist in the first UNO component isn't required.

-Hypothesizer

Fifth, we add this code into the first UNO component Java source file, 'java/thebiasplanet/uno/heyunoextensionsunoextension/HeyUnoExtensionsUnoComponent.java', to add the 'getInnerRanter' method.

 public XRanter getInnerRanter (String p_message)
   throws IllegalArgumentException {
  RanterUnoComponent l_innerRanter = new RanterUnoComponent (componentContext);
  try {
   l_innerRanter.initialize (new String [] {p_message});
  }
  catch (IllegalArgumentException l_exception) {
   throw l_exception;
  }
  catch (com.sun.star.uno.Exception l_exception) {
  }
  return l_innerRanter;
  
 }
-Rebutter

Oh, the method instantiates the second UNO component by the new operator.

-Hypothesizer

Yes.

And sixth, we add this code into the UNO components setting file to set the second UNO component.

  <implementation name="thebiasplanet.uno.heyunoextensionsunoextension.RanterUnoComponent">
  </implementation>
-Rebutter

As it isn't registered as a global UNO service, there is no 'service' tag in it.

-Hypothesizer

Now, we can build and register the UNO extension.

To test the modification, we add this code at the last of the previous LibreOffice Basic Sub.

 Dim l_ranter As Variant
 l_ranter = l_heyUnoExtensionsService.getInnerRanter ("Good afternoon")
 Msgbox (l_ranter.rant ("bro"))
-Rebutter

Um. The Sub worked as expected.

-Hypothesizer

Thus, we have demonstrated how to create and use a UNO component without registering it as a UNO service.

Main body END

<The previous article in this series | The table of contents of this series | The next article in this series>