/************************************************************************* * * $RCSfile: JavaLoader.java,v $ * * $Revision: 1.4 $ * * last change: $Author: dbo $ $Date: 2002-06-14 13:09:52 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses * * - GNU Lesser General Public License Version 2.1 * - Sun Industry Standards Source License Version 1.1 * * Sun Microsystems Inc., October, 2000 * * GNU Lesser General Public License Version 2.1 * ============================================= * Copyright 2000 by Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, CA 94303, USA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * * Sun Industry Standards Source License Version 1.1 * ================================================= * The contents of this file are subject to the Sun Industry Standards * Source License Version 1.1 (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.openoffice.org/license.html. * * Software provided under this License is provided on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, * WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS, * MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING. * See the License for the specific provisions governing your rights and * obligations concerning the Software. * * The Initial Developer of the Original Code is: Sun Microsystems, Inc. * * Copyright: 2000 by Sun Microsystems, Inc. * * All Rights Reserved. * * Contributor(s): _______________________________________ * * ************************************************************************/ package com.sun.star.comp.loader; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.URLDecoder; import com.sun.star.loader.CannotActivateFactoryException; import com.sun.star.loader.XImplementationLoader; import com.sun.star.registry.CannotRegisterImplementationException; import com.sun.star.registry.RegistryKeyType; import com.sun.star.registry.RegistryValueType; import com.sun.star.registry.InvalidRegistryException; import com.sun.star.registry.InvalidValueException; import com.sun.star.registry.XRegistryKey; import com.sun.star.registry.XSimpleRegistry; import com.sun.star.lang.XSingleServiceFactory; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.lang.XServiceInfo; import com.sun.star.lang.ServiceNotRegisteredException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.lang.XInitialization; import com.sun.star.uno.XComponentContext; import com.sun.star.beans.XPropertySet; import com.sun.star.util.XMacroExpander; import com.sun.star.uno.XInterface; import com.sun.star.uno.Type; import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.AnyConverter; import java.io.IOException; import java.net.MalformedURLException; /** * The JavaLoader class provides the functionality of the com.sun.star.loader.Java * service. Therefor the JavaLoader activates external UNO components which are implemented in Java. * The loader is used by the ServiceManger. *

* @version $Revision: 1.4 $ $ $Date: 2002-06-14 13:09:52 $ * @author Markus Herzog * @see com.sun.star.loader.XImplementationLoader * @see com.sun.star.loader.Java * @see com.sun.star.comp.servicemanager.ServiceManager * @see com.sun.star.lang.ServiceManager * @since UDK1.0 */ public class JavaLoader implements XImplementationLoader, XServiceInfo, XInitialization { private static final boolean DEBUG = true; private static final void DEBUG(String dbg) { if (DEBUG) System.err.println( dbg ); } private static String[] supportedServices = { "com.sun.star.loader.Java" }; protected XMultiServiceFactory multiServiceFactory = null; private XMacroExpander m_xMacroExpander = null; private static final String EXPAND_PROTOCOL_PREFIX = "vnd.sun.star.expand:"; /** Expands macrofied url using the macro expander singleton. */ private String expand_url( String url ) throws RuntimeException { if (url != null && url.startsWith( EXPAND_PROTOCOL_PREFIX )) { try { if (m_xMacroExpander == null) { XPropertySet xProps = (XPropertySet)UnoRuntime.queryInterface( XPropertySet.class, multiServiceFactory ); if (xProps == null) { throw new com.sun.star.uno.RuntimeException( "service manager does not support XPropertySet!", this ); } XComponentContext xContext = (XComponentContext)AnyConverter.toObject( new Type( XComponentContext.class ), xProps.getPropertyValue( "DefaultContext" ) ); m_xMacroExpander = (XMacroExpander)AnyConverter.toObject( new Type( XMacroExpander.class ), xContext.getValueByName( "/singletons/com.sun.star.util.theMacroExpander" ) ); // decode uric class chars String macro = URLDecoder.decode( url.substring( EXPAND_PROTOCOL_PREFIX.length() ) /* cut protocol */ ); // expand macro string String ret = m_xMacroExpander.expandMacros( macro ); if (DEBUG) { System.err.println( "JavaLoader.expand_url(): " + url + " => " + macro + " => " + ret ); } return ret; } } catch (com.sun.star.uno.Exception exc) { throw new com.sun.star.uno.RuntimeException( exc.getMessage(), this ); } } return url; } /** default constructor */ /** * Creates a new instance of the JavaLoader class. *

* @return new instance */ public JavaLoader() {} /** * Creates a new JavaLoader object. The specified com.sun.star.lang.XMultiServiceFactory * is the ServiceManager service which can be deliviert to all components the JavaLoader is * loading. * To set the MultiServiceFactory you can use the com.sun.star.lang.XInitialization interface, either. *

* @return new instance * @param factory the ServiceManager * @see com.sun.star.lang.ServiceManager * @see com.sun.star.lang.ServiceManager * @see com.sun.star.lang.XInitialization */ public JavaLoader(XMultiServiceFactory factory) { multiServiceFactory = factory; } /** * Unlike the original intention, the method could be called every time a new * com.sun.star.lang.XMultiServiceFactory should be set at the loader. *

* @param args - the first parameter (args[0]) specifices the ServiceManager * @see com.sun.star.lang.XInitialization * @see com.sun.star.lang.ServiceManager */ public void initialize( java.lang.Object[] args ) throws com.sun.star.uno.Exception, com.sun.star.uno.RuntimeException { if (args.length == 0) throw new com.sun.star.lang.IllegalArgumentException("No arguments specified"); try { multiServiceFactory = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, args[0]); } catch (ClassCastException castEx) { throw new com.sun.star.lang.IllegalArgumentException( "The argument must be an instance of XMultiServiceFactory"); } } /** * Supplies the implementation name of the component. *

* @return the implementation name - here the class name * @see com.sun.star.lang.XServiceInfo */ public String getImplementationName() throws com.sun.star.uno.RuntimeException { return getClass().getName(); } /** * Verifies if a given service is supported by the component. *

* @return true,if service is suported - otherwise false * @param serviceName the name of the service that should be checked * @see com.sun.star.lang.XServiceInfo */ public boolean supportsService(String serviceName) throws com.sun.star.uno.RuntimeException { for ( int i = 0; i < supportedServices.length; i++ ) { if ( supportedServices[i].equals(serviceName) ) return true; } return false; } /** * Supplies a list of all service names supported by the component *

* @return a String array with all supported services * @see com.sun.star.lang.XServiceInfo */ public String[] getSupportedServiceNames() throws com.sun.star.uno.RuntimeException { return supportedServices; } /** * Provides a components factory. * The JavaLoader tries to load the class first. If a loacation URL is given the * RegistrationClassFinder is used to load the class. Otherwise the class is loaded thru the Class.forName * method. * To get the factory the inspects the class for the optional static member functions __getServiceFactory resp. * getServiceFactory (DEPRECATED). * If the function can not be found a default factory @see ComponentFactoryWrapper will be created. *

* @return the factory for the component (@see com.sun.star.lang.XSingleServiceFactory) * @param implementationName the implementation (class) name of the component * @param implementationLoaderUrl the URL of the implementation loader. Not used. * @param locationUrl points to an archive (JAR file) which contains a component * @param xKey * @see com.sun.star.lang.XImplementationLoader * @see com.sun.star.com.loader.RegistrationClassFinder */ public java.lang.Object activate( String implementationName, String implementationLoaderUrl, String locationUrl, XRegistryKey xKey ) throws CannotActivateFactoryException, com.sun.star.uno.RuntimeException { locationUrl = expand_url( locationUrl ); boolean needFactoryWrapper = false; Object returnObject = null; Class clazz = null; DEBUG("try to get factory for " + implementationName); // first we must get the class of the implementation // 1. If a location URL is given it is assumed that this points to a JAR file. // The components class name is stored in the manifest file. // 2. If only the implementation name is given, the class is loaded with the Class.forName() method try { if ( locationUrl != null ) { RegistrationClassFinder classFinder = new RegistrationClassFinder( locationUrl ); // 1. clazz = classFinder.getRegistrationClass(); } else { // 2. clazz = Class.forName( implementationName ); } } catch (java.net.MalformedURLException e) { CannotActivateFactoryException cae = new CannotActivateFactoryException( "Can not activate factory because " + e.toString() ); cae.fillInStackTrace(); throw cae; } catch (java.io.IOException e) { CannotActivateFactoryException cae = new CannotActivateFactoryException( "Can not activate factory because " + e.toString() ); cae.fillInStackTrace(); throw cae; } catch (java.lang.ClassNotFoundException e) { CannotActivateFactoryException cae = new CannotActivateFactoryException( "Can not activate factory because " + e.toString() ); cae.fillInStackTrace(); throw cae; } Class[] paramTypes = {String.class, XMultiServiceFactory.class, XRegistryKey.class}; Object[] params = { implementationName, multiServiceFactory, xKey }; // try to get factory from implemetation class // - new style: use the public static method __getServiceFactory // - old style: use the public static method getServiceFactory ( DEPRECATED ) Method method = null; try { method = clazz.getMethod("__getServiceFactory", paramTypes); } catch ( NoSuchMethodException noSuchMethodEx) { method = null; } catch ( SecurityException secEx) { method = null; } try { if ( method == null ) { method = clazz.getMethod("getServiceFactory", paramTypes); } Object oRet = method.invoke(clazz, params); if ( (oRet != null) && (oRet instanceof XSingleServiceFactory) ) { returnObject = (XSingleServiceFactory) oRet; } } catch ( NoSuchMethodException noSuchMethodEx) { needFactoryWrapper = true; } catch ( SecurityException secEx) { needFactoryWrapper = true; } catch ( IllegalAccessException e ) { throw new CannotActivateFactoryException("Can not activate the factory for " + implementationName + " because " + e.toString() ); } catch ( IllegalArgumentException e ) { throw new CannotActivateFactoryException("Can not activate the factory for " + implementationName + " because " + e.toString() ); } catch ( InvocationTargetException e ) { throw new CannotActivateFactoryException("Can not activate the factory for " + implementationName + " because " + e.getTargetException().toString() ); } // if no method is found make a factory wrapper for the implementation and return it if ( needFactoryWrapper ) { DEBUG ("create factory wrapper for " + implementationName); ComponentFactoryWrapper wrapp = new ComponentFactoryWrapper( implementationName, locationUrl ); returnObject = wrapp.getServiceFactory(implementationName, multiServiceFactory, xKey); } return returnObject; } /** * Registers the component in a registry under a given root key. If the component supports the optional * methods __writeRegistryServiceInfo, writeRegistryServiceInfo (DEPRECATED), the call is delegated to that * method. Otherwise a default registration will be accomplished. *

* @return true if registration is successfully - otherwise false * @param regKey the root key under that the component should be registred. * @param implementationLoaderUrl specifies the loader, the component is loaded by. * @param locationUrl points to an archive (JAR file) which contains a component * @see ComponentFactoryWrapper */ public boolean writeRegistryInfo( XRegistryKey regKey, String implementationLoaderUrl, String locationUrl ) throws CannotRegisterImplementationException, com.sun.star.uno.RuntimeException { locationUrl = expand_url( locationUrl ); boolean success = false; try { RegistrationClassFinder classFinder = new RegistrationClassFinder(locationUrl); Class clazz = classFinder.getRegistrationClass(); Class[] paramTypes = { XRegistryKey.class }; Object[] params = { regKey }; Method method = clazz.getMethod("__writeRegistryServiceInfo", paramTypes); Object oRet = method.invoke(clazz, params); if ( (oRet != null) && (oRet instanceof Boolean) ) success = ((Boolean) oRet).booleanValue(); } catch (Exception e) { // default registration ComponentFactoryWrapper wrapp = new ComponentFactoryWrapper(null, locationUrl); success = wrapp.writeRegistryServiceInfo(regKey); } return success; } /** * Supplies the factory for the JavaLoader *

* @return the factory for the JavaLoader * @param implName the name of the desired component * @param multiFactory the ServiceManager is delivered to the factory * @param regKey not used - can be null */ public static XSingleServiceFactory getServiceFactory( String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey) { if ( implName.equals(JavaLoader.class.getName()) ) return new JavaLoaderFactory( multiFactory ); return null; } /** * Registers the JavaLoader at the registry. *

* @return true if registration succseeded - otherwise false * @param regKey root key under which the JavaLoader should be regidstered */ public static boolean writeRegistryServiceInfo(XRegistryKey regKey) { boolean result = false; try { XRegistryKey newKey = regKey.createKey("/" + JavaLoader.class.getName() + "/UNO/SERVICE"); for (int i=0; i>>JavaLoader.writeRegistryServiceInfo " + ex); } return result; } } //********************************************************************************************************** //********************************************************************************************************** /** * The ComponentFactoryWrapper class provides methods to create a factory for a component and * the registration at a registry in a default manner. The class is used by the JavaLoader if the * a component does not comes with its own methods for creating a factory or for the registration. *

* @version $Revision: 1.4 $ $ $Date: 2002-06-14 13:09:52 $ * @author Markus Herzog * @since UDK1.0 */ class ComponentFactoryWrapper implements XServiceInfo, XSingleServiceFactory { private static final boolean DEBUG = false; private String serviceName = null; private String implName = null; private String locationUrl = null; private Class serviceClass = null; private XMultiServiceFactory aServiceManager = null; private boolean bServiceManagerAlreadySet = false; /** * Constructor for a ComponentFactoryWrapper object. *

* @param name specifies the name of the implementation. If no loaction URL is given * the implementation name must be the class name of the component. * @param lUrl points to an archive (JAR file) which contains a component. */ public ComponentFactoryWrapper( String name, String lUrl ) { implName = name; locationUrl = lUrl; } private static final void DEBUG( String dbg ) { if (DEBUG) System.err.println(">>>ComponentFactoryWrapper - " + dbg); } /** * Registers the component at the registry. First it is verified if component includes the optional * method __writeRegistryServiceInfo ( writeRegistryServiceInfo - DEPRECATED ). If so the call is delegated * to this method. Otherwise the component will be registered under its implementation name. *

* @return true if registration succseeded - otherwise false * @param regKey root key under which the JavaLoader should be regidstered */ public boolean writeRegistryServiceInfo( XRegistryKey regKey ) throws CannotRegisterImplementationException, com.sun.star.uno.RuntimeException { boolean success = false; boolean defaultRegistration = false; Class clazz = getServiceClass(); if ( clazz != null ) { try { Class[] paramTypes = { XRegistryKey.class }; Object[] params = { regKey }; Method method = null; try { method = clazz.getMethod("__writeRegistryServiceInfo", paramTypes); } catch (NoSuchMethodException noSuchMethodEx) { method = null; } catch (SecurityException securityEx) { method = null; } if (method == null) { method = clazz.getMethod("writeRegistryServiceInfo", paramTypes); } Object oRet = method.invoke(clazz, params); if ( (oRet != null) && (oRet instanceof Boolean) ) success = ((Boolean) oRet).booleanValue(); } catch (NoSuchMethodException noSuchMethodEx) { defaultRegistration = true; } catch (SecurityException securityEx) { defaultRegistration = true; } catch (IllegalAccessException e) { throw new CannotRegisterImplementationException("Can not register " + implName + " because " + e.toString() ); } catch (IllegalArgumentException e) { throw new CannotRegisterImplementationException("Can not register " + implName + " because " + e.toString() ); } catch (InvocationTargetException e) { throw new CannotRegisterImplementationException("Can not register " + implName + " because " + e.getTargetException() ); } if (defaultRegistration) { try { XRegistryKey newKey = regKey.createKey("/" + implName + "/UNO/SERVICES"); String names[] = getServiceNames(); for (int i=0; i * @return the factory for the component * @param impName the components implementation name * @param multiFac specifices the ServiceManager * @param regKey only used if the call is delegated */ public Object getServiceFactory( String impName, XMultiServiceFactory multiFac, XRegistryKey regKey) { aServiceManager = multiFac; try { Class clazz = getServiceClass(); Class[] paramTypes = { String.class, XMultiServiceFactory.class, XRegistryKey.class}; Object[] params = { impName, multiFac, regKey }; // try to get factory form implemetation class Method method = null; try { method = clazz.getMethod("__getServiceFactory", paramTypes); } catch (NoSuchMethodException noSuchMethodEx) { method = null; } catch (SecurityException securityEx) { method = null; } if (method == null) { method = clazz.getMethod("getServiceFactory", paramTypes); } return UnoRuntime.queryInterface( XSingleServiceFactory.class,method.invoke(clazz, params) ); } catch (NoSuchMethodException noSuchMethodEx) { } catch (SecurityException securityEx) { } catch (IllegalAccessException e) { } catch (IllegalArgumentException e) { } catch (InvocationTargetException e) { } // if any execption occurred this will be returned return this; } /** * set the ServiceManager at the component, * if the method __setSericeFactory( XMultiServiceFactor ) is found */ /** * Set the ServiceManager at the component. For that the component must support * the __setServiceManager methode. The method is called after a new instance of the * component is created. *

* @param obj the newly created component * @see createInstanceWithArguments * @see createInstance */ private void setServiceManager( Object obj ) { Class clazz = getServiceClass(); Class paramTypes[] = { XMultiServiceFactory.class }; Object[] args = { aServiceManager }; try { clazz.getDeclaredMethod( "__setServiceManager", paramTypes).invoke(obj, args); } catch (NoSuchMethodException e) {} catch (SecurityException e) {} catch (IllegalAccessException e) {} catch (IllegalArgumentException e) {} catch (InvocationTargetException e) {} } /** * Tries to instantiate a component by its implementation name. For it the Beans.instantiate * method is called. If any exception occured a com.sun.star.uno.Exception will be thrown. *

* @return * @see java.beans.Beans */ private Object createInstanceWithDefaultConstructor() throws com.sun.star.uno.Exception, com.sun.star.uno.RuntimeException { Object resObj = null; try { DEBUG ("try to instantiate " + implName ); resObj = java.beans.Beans.instantiate(getClass().getClassLoader(), implName); } catch (IOException e) { if (DEBUG) e.printStackTrace(); throw new com.sun.star.uno.Exception("Can not create an instance of " + implName + "\n" + e.toString()); } catch (ClassNotFoundException e) { if (DEBUG) e.printStackTrace(); throw new com.sun.star.uno.Exception("Can not create an instance of " + implName + "\n" + e.toString()); } catch (NoSuchMethodError e) { if (DEBUG) e.printStackTrace(); throw new com.sun.star.uno.Exception("Can not create an instance of " + implName + "\n" + e.toString()); } return resObj; } /** * Creates a new instance of the component. *

* @return newly instanciated component * @see com.sun.star.lang.XSimpleServiceFactory */ public Object createInstance() throws com.sun.star.uno.Exception, com.sun.star.uno.RuntimeException { Object resObj = createInstanceWithDefaultConstructor(); setServiceManager( resObj ); return resObj; } /** * Creates a new component with arguments. How the arguments are commited depends on the component. * For that the following order is used: * 1. The component supports the XInitialization interface: An object is created using Beans.instantiate. * The arguments are set with the initialize method. * 2. The component has a constructor with an array of objects as a argument. * 3. The component has any other constructor which can take the arguments. *

* @return newly created component * @param args the arguments which should be used */ public Object createInstanceWithArguments( Object[] args ) throws com.sun.star.uno.Exception, com.sun.star.uno.RuntimeException { java.lang.Object resObj = null; boolean useDefaultCtor = false; // first we try if the component supports the XInitialization interface Class clazz = getServiceClass(); if ( clazz.isAssignableFrom( XInitialization.class ) ) useDefaultCtor = true; else { try { Class parameterTypes[] = { Class.class, Object.class }; Method queryMeth = clazz.getMethod("queryInterface", parameterTypes ); useDefaultCtor = true; } catch (NoSuchMethodException e) {} catch (SecurityException e) {} } if (useDefaultCtor) { resObj = createInstanceWithDefaultConstructor(); XInitialization iniObj = (XInitialization) UnoRuntime.queryInterface( XInitialization.class, resObj ); iniObj.initialize( args ); } else { // try the constructor A(java.lang.Object[]) Class[] clazzParams = { Object[].class }; java.lang.reflect.Constructor ctor = null; try { ctor = clazz.getConstructor( clazzParams ); } catch (NoSuchMethodException noSuchMethodEx) { ctor = null; } catch (SecurityException securityEx) { ctor = null; } if (ctor == null) { // look for the first matching constructor java.lang.reflect.Constructor ctors[] = clazz.getDeclaredConstructors(); int i=0; while ( (i * @return the name of the implementation * @see com.sun.star.lang.XServiceInfo */ public String getImplementationName() throws com.sun.star.uno.RuntimeException { return implName; } /** * Verifies whether or not a service is supportet by the component. *

* @return true if the service is supported - otherwise false * @param requestedService name of the requested service */ public boolean supportsService(String requestedService) throws com.sun.star.uno.RuntimeException { boolean found = false; int i = 0; if (requestedService == null) throw new com.sun.star.uno.RuntimeException("no service requested"); String names[] = getServiceNames(); if (names.length == 0) throw new com.sun.star.uno.RuntimeException("no service name found"); while (i * @return list of the service names which are supported by the component * @see com.sun.star.lang.XServiceInfo */ public String[] getSupportedServiceNames() throws com.sun.star.uno.RuntimeException { return getServiceNames(); } /** * Supplies the class of the component. If the location URL is specified the * com.sun.star.comp.loader.RegistrationClassFinder is used to obtain the class. * Otherwise the java.lang.Class.forName is called with implementation name. *

* @return the class of the component * @see com.sun.star.comp.loader.RegistrationClassFinder */ Class getServiceClass() throws com.sun.star.uno.RuntimeException { if (serviceClass == null) { try { if ( locationUrl != null ) { RegistrationClassFinder classFinder = new RegistrationClassFinder( locationUrl ); serviceClass = classFinder.getRegistrationClass(); } else { serviceClass = Class.forName( implName ); } } catch ( java.net.MalformedURLException e ) { throw new com.sun.star.uno.RuntimeException( "can't get the class " + implName + ".\n" + e ); } catch ( java.io.IOException e ) { throw new com.sun.star.uno.RuntimeException( "can't get the class " + implName + ".\n" + e ); } catch ( java.lang.ClassNotFoundException e ) { throw new com.sun.star.uno.RuntimeException( "can't get the class " + implName + ".\n" + e ); } } return serviceClass; } /** * Extract the service names supported by the component. The optional static member * __serviceName ( serviceName DEPRECATED ) is used to specify the supported * service names. If no member can be found the implementation name is return. *

* @return a list with the supported service names */ public String[] getServiceNames() throws com.sun.star.uno.RuntimeException { String result[] = null; try { Class clazz = getServiceClass(); Object attrib = null; try { attrib = clazz.getDeclaredField("__serviceName").get(clazz); } catch (NoSuchFieldException e) { //********************* DEPRECATED ****************************************** attrib = clazz.getDeclaredField("serviceName").get(clazz); //*************************************************************************** } if ( attrib instanceof String ) { String str = (String) attrib; result = new String[1]; result[0] = str; } else result = (String[]) attrib; // expecting an array of Strings - otherwise an exception will be thrown } catch (NoSuchFieldException e) {} catch (SecurityException e) {} catch (IllegalAccessException e) {} if (result == null) { result = new String[1]; result[0] = implName; } return result; } public String toString() { String result = super.toString(); result += " implementation name: " +getImplementationName(); result += " supported services:"; String[] services = getSupportedServiceNames(); for (int i=0; i