From ee68162a4ad0298fa9794a96c8bea795e958ad11 Mon Sep 17 00:00:00 2001 From: gnosygnu Date: Fri, 8 May 2020 09:18:46 -0400 Subject: [PATCH] XOMW: Add more implementation for XomwServiceContainer [#632] --- .../src/gplx/xowa/mediawiki/XophpArray.java | 10 +- ...mwCannotReplaceActiveServiceException.java | 37 ++ .../XomwContainerDisabledException.java | 35 ++ .../XomwServiceAlreadyDefinedException.java | 38 ++ .../libs/services/XomwServiceContainer.java | 375 +++++++++--------- .../XomwServiceDisabledException.java | 37 ++ 6 files changed, 342 insertions(+), 190 deletions(-) create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwCannotReplaceActiveServiceException.java create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwContainerDisabledException.java create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceAlreadyDefinedException.java create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceDisabledException.java diff --git a/400_xowa/src/gplx/xowa/mediawiki/XophpArray.java b/400_xowa/src/gplx/xowa/mediawiki/XophpArray.java index db9a185da..154e076db 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/XophpArray.java +++ b/400_xowa/src/gplx/xowa/mediawiki/XophpArray.java @@ -71,7 +71,7 @@ public class XophpArray implements Bry_bfr_able, Iterable { } return rv; } - public XophpArray Add(Object val) { + public XophpArray Add(T val) { int key = newMemberIdx++; Set(XophpArrayItm.New_int(key, val)); return this; @@ -110,8 +110,8 @@ public class XophpArray implements Bry_bfr_able, Iterable { } return this; } - public XophpArray Add_many(Object... val) { - for (Object itm : val) { + public XophpArray Add_many(T... val) { + for (T itm : val) { Add(itm); } return this; @@ -165,9 +165,9 @@ public class XophpArray implements Bry_bfr_able, Iterable { public String Get_by_str(int key) {return (String)this.Get_by(Int_.To_str(key));} public String Get_by_str_or(String key, String or) {Object rv = this.Get_by(key); return rv == null ? or : (String)rv;} public String Get_by_str(String key) {return (String)this.Get_by(key);} - public Object Get_by(String key) { + public T Get_by(String key) { XophpArrayItm itm = (XophpArrayItm)hash.Get_by(key); - return itm == null ? null : itm.Val(); + return itm == null ? null : (T)itm.Val(); } public void Set(int key, Object val) { this.Set(XophpArrayItm.New_int(key, val)); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwCannotReplaceActiveServiceException.java b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwCannotReplaceActiveServiceException.java new file mode 100644 index 000000000..e72f84665 --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwCannotReplaceActiveServiceException.java @@ -0,0 +1,37 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.libs.services; + +import gplx.String_; +import gplx.xowa.mediawiki.XophpException; +import gplx.xowa.mediawiki.XophpRuntimeException; + +// MW.SRC:1.33.1 +/** + * Exception thrown when trying to replace an already active service. + */ +public class XomwCannotReplaceActiveServiceException extends XophpRuntimeException { + + /** + * @param string $serviceName + * @param Exception|null $previous + */ + public XomwCannotReplaceActiveServiceException(String serviceName) {this(serviceName, null);} + public XomwCannotReplaceActiveServiceException(String serviceName, XophpException previous) { + super(String_.Format("Cannot replace an active service: {0}", serviceName), 0, previous); + } + +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwContainerDisabledException.java b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwContainerDisabledException.java new file mode 100644 index 000000000..81782d5c3 --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwContainerDisabledException.java @@ -0,0 +1,35 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.libs.services; + +import gplx.xowa.mediawiki.XophpException; +import gplx.xowa.mediawiki.XophpRuntimeException; + +// MW.SRC:1.33.1 +/** + * Exception thrown when trying to access a service on a disabled container or factory. + */ +public class XomwContainerDisabledException extends XophpRuntimeException { + + /** + * @param Exception|null $previous + */ + public XomwContainerDisabledException(){this(null);} + public XomwContainerDisabledException(XophpException previous) { + super("Container disabled!", 0, previous); + } + +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceAlreadyDefinedException.java b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceAlreadyDefinedException.java new file mode 100644 index 000000000..6e60da2eb --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceAlreadyDefinedException.java @@ -0,0 +1,38 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.libs.services; + +import gplx.String_; +import gplx.xowa.mediawiki.XophpException; +import gplx.xowa.mediawiki.XophpRuntimeException; + +// MW.SRC:1.33.1 +/** + * Exception thrown when a service was already defined, but the + * caller expected it to not exist. + */ +public class XomwServiceAlreadyDefinedException extends XophpRuntimeException { + + /** + * @param string $serviceName + * @param Exception|null $previous + */ + public XomwServiceAlreadyDefinedException(String serviceName) {this(serviceName, null);} + public XomwServiceAlreadyDefinedException(String serviceName, XophpException previous) { + super(String_.Format("Service already defined: {0}", serviceName), 0, previous); + } + +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceContainer.java b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceContainer.java index 0137ca8b9..4a8215f97 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceContainer.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceContainer.java @@ -15,15 +15,18 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ package gplx.xowa.mediawiki.includes.libs.services; - -// MW.SRC:1.33.1 - import gplx.xowa.mediawiki.XophpArray; import gplx.xowa.mediawiki.XophpArray_; -import gplx.xowa.mediawiki.XophpCallbackOwner; +import gplx.xowa.mediawiki.XophpCallback; import gplx.xowa.mediawiki.XophpObject_; import gplx.xowa.mediawiki.XophpType_; - +/* +XOTODO: +* array_diff: https://www.php.net/manual/en/function.array-diff.php +* array_diff_key: https://www.php.net/manual/en/function.array-diff-key +* XomwAssert: /vendor/wikimedia/Assert/src +*/ +// MW.SRC:1.33.1 /** * ServiceContainer provides a generic service to manage named services using * lazy instantiation based on instantiator callback functions. @@ -49,12 +52,12 @@ public class XomwServiceContainer implements XomwDestructibleService { /** * @var callable[] */ - private XophpArray serviceInstantiators = new XophpArray(); + private XophpArray serviceInstantiators = new XophpArray(); /** * @var callable[][] */ - private XophpArray serviceManipulators = new XophpArray(); + private XophpArray> serviceManipulators = new XophpArray(); /** * @var bool[] disabled status, per service name @@ -106,76 +109,77 @@ public class XomwServiceContainer implements XomwDestructibleService { this.destroyed = true; } -// /** -// * @param array $wiringFiles A list of PHP files to load wiring information from. -// * Each file is loaded using PHP's include mechanism. Each file is expected to -// * return an associative array that maps service names to instantiator functions. -// */ -// public function loadWiringFiles(array $wiringFiles) { + /** + * @param array $wiringFiles A list of PHP files to load wiring information from. + * Each file is loaded using PHP's include mechanism. Each file is expected to + * return an associative array that maps service names to instantiator functions. + */ + public void loadWiringFiles(XophpArray wiringFiles) { // foreach ($wiringFiles as $file) { // // the wiring file is required to return an array of instantiators. // $wiring = require $file; // -// Assert::postcondition( -// is_array($wiring), -// "Wiring file $file is expected to return an array!" -// ); +// // Assert::postcondition( +// // is_array($wiring), +// // "Wiring file $file is expected to return an array!" +// // ); // // this.applyWiring($wiring); // } -// } + } + + /** + * Registers multiple services (aka a "wiring"). + * + * @param array $serviceInstantiators An associative array mapping service names to + * instantiator functions. + */ + public void applyWiring(XophpArray $serviceInstantiators) { + // Assert::parameterElementType('callable', $serviceInstantiators, '$serviceInstantiators'); -// /** -// * Registers multiple services (aka a "wiring"). -// * -// * @param array $serviceInstantiators An associative array mapping service names to -// * instantiator functions. -// */ -// public function applyWiring(array $serviceInstantiators) { -// Assert::parameterElementType('callable', $serviceInstantiators, '$serviceInstantiators'); -// // foreach ($serviceInstantiators as $name => $instantiator) { // this.defineService($name, $instantiator); // } -// } + } -// /** -// * Imports all wiring defined in $container. Wiring defined in $container -// * will override any wiring already defined locally. However, already -// * existing service instances will be preserved. -// * -// * @since 1.28 -// * -// * @param ServiceContainer $container -// * @param string[] $skip A list of service names to skip during import -// */ -// public function importWiring(ServiceContainer $container, $skip = []) { -// $newInstantiators = array_diff_key( -// $container.serviceInstantiators, -// array_flip($skip) -// ); + /** + * Imports all wiring defined in $container. Wiring defined in $container + * will override any wiring already defined locally. However, already + * existing service instances will be preserved. + * + * @since 1.28 + * + * @param ServiceContainer $container + * @param string[] $skip A list of service names to skip during import + */ + public void importWiring(XomwServiceContainer container) {this.importWiring(container, new XophpArray<>());} + public void importWiring(XomwServiceContainer container, XophpArray skip) { +// XophpArray newInstantiators = XophpArray_.array_diff_key( +// container.serviceInstantiators, +// XophpArray_.array_flip(skip) +// ); // -// this.serviceInstantiators = array_merge( +// this.serviceInstantiators = XophpArray_.array_merge( // this.serviceInstantiators, -// $newInstantiators -// ); +// newInstantiators +// ); // -// $newManipulators = array_diff( -// array_keys($container.serviceManipulators), -// $skip -// ); +// XophpArray newManipulators = XophpArray_.array_diff( +// XophpArray_.array_keys(container.serviceManipulators), +// skip +// ); // -// foreach ($newManipulators as $name) { -// if (isset(this.serviceManipulators[$name])) { -// this.serviceManipulators[$name] = array_merge( -// this.serviceManipulators[$name], -// $container.serviceManipulators[$name] -// ); +// for (String name : newManipulators) { +// if (XophpArray_.isset(this.serviceManipulators, name)) { +// this.serviceManipulators.Set(name, XophpArray_.array_merge( +// this.serviceManipulators.Get_by(name), +// container.serviceManipulators.Get_by(name) +// )); // } else { -// this.serviceManipulators[$name] = $container.serviceManipulators[$name]; +// this.serviceManipulators.Set(name, container.serviceManipulators.Get_by(name)); // } // } -// } + } /** * Returns true if a service is defined for $name, that is, if a call to getService($name) @@ -219,103 +223,103 @@ public class XomwServiceContainer implements XomwDestructibleService { return XophpArray_.array_keys(this.serviceInstantiators); } -// /** -// * Define a new service. The service must not be known already. -// * -// * @see getService(). -// * @see redefineService(). -// * -// * @param string $name The name of the service to register, for use with getService(). -// * @param callable $instantiator Callback that returns a service instance. -// * Will be called with this ServiceContainer instance as the only parameter. -// * Any extra instantiation parameters provided to the constructor will be -// * passed as subsequent parameters when invoking the instantiator. -// * -// * @throws RuntimeException if there is already a service registered as $name. -// */ -// public function defineService($name, callable $instantiator) { -// Assert::parameterType('string', $name, '$name'); -// -// if (this.hasService($name)) { -// throw new ServiceAlreadyDefinedException($name); -// } -// -// this.serviceInstantiators[$name] = $instantiator; -// } + /** + * Define a new service. The service must not be known already. + * + * @see getService(). + * @see redefineService(). + * + * @param string $name The name of the service to register, for use with getService(). + * @param callable $instantiator Callback that returns a service instance. + * Will be called with this ServiceContainer instance as the only parameter. + * Any extra instantiation parameters provided to the constructor will be + * passed as subsequent parameters when invoking the instantiator. + * + * @throws RuntimeException if there is already a service registered as $name. + */ + public void defineService(String name, XophpCallback instantiator) { + // Assert::parameterType('string', $name, '$name'); -// /** -// * Replace an already defined service. -// * -// * @see defineService(). -// * -// * @note This will fail if the service was already instantiated. If the service was previously -// * disabled, it will be re-enabled by this call. Any manipulators registered for the service -// * will remain in place. -// * -// * @param string $name The name of the service to register. -// * @param callable $instantiator Callback function that returns a service instance. -// * Will be called with this ServiceContainer instance as the only parameter. -// * The instantiator must return a service compatible with the originally defined service. -// * Any extra instantiation parameters provided to the constructor will be -// * passed as subsequent parameters when invoking the instantiator. -// * -// * @throws NoSuchServiceException if $name is not a known service. -// * @throws CannotReplaceActiveServiceException if the service was already instantiated. -// */ -// public function redefineService($name, callable $instantiator) { -// Assert::parameterType('string', $name, '$name'); -// -// if (!this.hasService($name)) { -// throw new NoSuchServiceException($name); -// } -// -// if (isset(this.services[$name])) { -// throw new CannotReplaceActiveServiceException($name); -// } -// -// this.serviceInstantiators[$name] = $instantiator; -// unset(this.disabled[$name]); -// } -// -// /** -// * Add a service manipulator callback for the given service. -// * This method may be used by extensions that need to wrap, replace, or re-configure a -// * service. It would typically be called from a MediaWikiServices hook handler. -// * -// * The manipulator callback is called just after the service is instantiated. -// * It can call methods on the service to change configuration, or wrap or otherwise -// * replace it. -// * -// * @see defineService(). -// * @see redefineService(). -// * -// * @note This will fail if the service was already instantiated. -// * -// * @since 1.32 -// * -// * @param string $name The name of the service to manipulate. -// * @param callable $manipulator Callback function that manipulates, wraps or replaces a -// * service instance. The callback receives the new service instance and this -// * ServiceContainer as parameters, as well as any extra instantiation parameters specified -// * when constructing this ServiceContainer. If the callback returns a value, that -// * value replaces the original service instance. -// * -// * @throws NoSuchServiceException if $name is not a known service. -// * @throws CannotReplaceActiveServiceException if the service was already instantiated. -// */ -// public function addServiceManipulator($name, callable $manipulator) { -// Assert::parameterType('string', $name, '$name'); -// -// if (!this.hasService($name)) { -// throw new NoSuchServiceException($name); -// } -// -// if (isset(this.services[$name])) { -// throw new CannotReplaceActiveServiceException($name); -// } -// -// this.serviceManipulators[$name][] = $manipulator; -// } + if (this.hasService(name)) { + throw new XomwServiceAlreadyDefinedException(name); + } + + this.serviceInstantiators.Set(name, instantiator); + } + + /** + * Replace an already defined service. + * + * @see defineService(). + * + * @note This will fail if the service was already instantiated. If the service was previously + * disabled, it will be re-enabled by this call. Any manipulators registered for the service + * will remain in place. + * + * @param string $name The name of the service to register. + * @param callable $instantiator Callback function that returns a service instance. + * Will be called with this ServiceContainer instance as the only parameter. + * The instantiator must return a service compatible with the originally defined service. + * Any extra instantiation parameters provided to the constructor will be + * passed as subsequent parameters when invoking the instantiator. + * + * @throws NoSuchServiceException if $name is not a known service. + * @throws CannotReplaceActiveServiceException if the service was already instantiated. + */ + public void redefineService(String name, XophpCallback instantiator) { + // Assert::parameterType('string', $name, '$name'); + + if (!this.hasService(name)) { + throw new XomwNoSuchServiceException(name); + } + + if (XophpArray_.isset(this.services, name)) { + throw new XomwCannotReplaceActiveServiceException(name); + } + + this.serviceInstantiators.Set(name, instantiator); + XophpArray_.unset(this.disabled, name); + } + + /** + * Add a service manipulator callback for the given service. + * This method may be used by extensions that need to wrap, replace, or re-configure a + * service. It would typically be called from a MediaWikiServices hook handler. + * + * The manipulator callback is called just after the service is instantiated. + * It can call methods on the service to change configuration, or wrap or otherwise + * replace it. + * + * @see defineService(). + * @see redefineService(). + * + * @note This will fail if the service was already instantiated. + * + * @since 1.32 + * + * @param string $name The name of the service to manipulate. + * @param callable $manipulator Callback function that manipulates, wraps or replaces a + * service instance. The callback receives the new service instance and this + * ServiceContainer as parameters, as well as any extra instantiation parameters specified + * when constructing this ServiceContainer. If the callback returns a value, that + * value replaces the original service instance. + * + * @throws NoSuchServiceException if $name is not a known service. + * @throws CannotReplaceActiveServiceException if the service was already instantiated. + */ + public void addServiceManipulator(String name, XophpCallback manipulator) { + // Assert::parameterType('string', $name, '$name'); + + if (!this.hasService(name)) { + throw new XomwNoSuchServiceException(name); + } + + if (XophpArray_.isset(this.services, name)) { + throw new XomwCannotReplaceActiveServiceException(name); + } + + this.serviceManipulators.Xet_by_ary(name).Add(manipulator); + } /** * Disables a service. @@ -401,11 +405,11 @@ public class XomwServiceContainer implements XomwDestructibleService { */ public Object getService(String name) { if (this.destroyed) { -// throw new XomwContainerDisabledException(); + throw new XomwContainerDisabledException(); } if (XophpArray_.isset(this.disabled, name)) { -// throw new XomwServiceDisabledException($name); + throw new XomwServiceDisabledException(name); } if (!XophpArray_.isset(this.services, name)) { @@ -422,35 +426,36 @@ public class XomwServiceContainer implements XomwDestructibleService { * @return object */ private Object createService(String name) { -// if (isset(this.serviceInstantiators[$name])) { -// $service = (this.serviceInstantiators[$name])( -// this, -// ...this.extraInstantiationParams -// ); -// -// if (isset(this.serviceManipulators[$name])) { -// foreach (this.serviceManipulators[$name] as $callback) { -// $ret = call_user_func_array( -// $callback, -// array_merge([ $service, this ], this.extraInstantiationParams) -// ); -// -// // If the manipulator callback returns an object, that object replaces -// // the original service instance. This allows the manipulator to wrap -// // or fully replace the service. -// if ($ret !== null) { -// $service = $ret; -// } -// } -// } -// -// // NOTE: when adding more wiring logic here, make sure importWiring() is kept in sync! -// } else { -// throw new NoSuchServiceException($name); -// } -// -// return $service; - return null; + Object service; + if (XophpArray_.isset(this.serviceInstantiators, name)) { + service = (this.serviceInstantiators.Get_by(name)).Call( + this, + this.extraInstantiationParams + ); + + if (XophpArray_.isset(this.serviceManipulators, name)) { + Object ret; + for (XophpCallback callback : this.serviceManipulators.Get_by(name)) { + ret = XophpCallback.call_user_func_array( + callback, + XophpArray_.array_merge(XophpArray.New(service, this), this.extraInstantiationParams) + ); + + // If the manipulator callback returns an object, that object replaces + // the original service instance. This allows the manipulator to wrap + // or fully replace the service. + if (ret != null) { + service = ret; + } + } + } + + // NOTE: when adding more wiring logic here, make sure importWiring() is kept in sync! + } else { + throw new XomwNoSuchServiceException(name); + } + + return service; } /** diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceDisabledException.java b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceDisabledException.java new file mode 100644 index 000000000..a609f5d9b --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/services/XomwServiceDisabledException.java @@ -0,0 +1,37 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.libs.services; + +import gplx.String_; +import gplx.xowa.mediawiki.XophpException; +import gplx.xowa.mediawiki.XophpRuntimeException; + +// MW.SRC:1.33.1 +/** + * Exception thrown when trying to access a disabled service. + */ +public class XomwServiceDisabledException extends XophpRuntimeException { + + /** + * @param string $serviceName + * @param Exception|null $previous + */ + public XomwServiceDisabledException(String serviceName) {this(serviceName, null);} + public XomwServiceDisabledException(String serviceName, XophpException previous) { + super(String_.Format("Service disabled: {0}", serviceName), 0, previous); + } + +}