diff --git a/docs/io.sarl.docs.markdown/src/main/documentation/api/Naming.md b/docs/io.sarl.docs.markdown/src/main/documentation/api/Naming.md index d00e0ba284..495280f255 100644 --- a/docs/io.sarl.docs.markdown/src/main/documentation/api/Naming.md +++ b/docs/io.sarl.docs.markdown/src/main/documentation/api/Naming.md @@ -167,7 +167,8 @@ The Namespace service is defined as: [:ShowType:](io.sarl.api.naming.namespace.NamespaceService) -The functions `findObject` search for an object based on the given name (whatever it is an object of type `SarlName` representing the super-type of all the names, or a string representation of the URI). +The functions `findObject` search for an object based on the given name (whatever it is an object of type +`SarlName` representing the super-type of all the names, an URI, or a string representation of an URI). To use this service, you have to get it from the SRE, as illustrated below: @@ -179,7 +180,7 @@ To use this service, you have to get it from the SRE, as illustrated below: static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - var namingService = boostrap.getService(typeof(NamespaceService)) + var namingService = bootstrap.getService(typeof(NamespaceService)) var theAgent = namingService.findObject("agent:a7fbd4cc-9e1a-48c3-8ee8-3a7974ccb05c") [:Off] } diff --git a/docs/io.sarl.docs.markdown/src/main/documentation/api/Probing.md b/docs/io.sarl.docs.markdown/src/main/documentation/api/Probing.md new file mode 100644 index 0000000000..afee04b66d --- /dev/null +++ b/docs/io.sarl.docs.markdown/src/main/documentation/api/Probing.md @@ -0,0 +1,137 @@ +# SRE Observing and Probes + +[:Outline:] + +The multi-agent system is composed of multiple elements, including agents, behaviors, contexts and spaces, but not limited to. +Organizational models in multi-agent systems usually position agents as actors-observers within environments shared +by multiple agents and organizational structures at different levels of granularity. +Then, the agents may have the capacity to observe the environment and the other agents, without directly interacting +with them. The observing agent is able to extract information from another agent or from a society of agents, that could be opaque +according to a possible holonic architecture of the system. + +Observation means that data or information could be extracted from the observed object without filtering from this latter. +The observer is not intrusive from the agent point of view. In other words, the observed agent has no knowledge about the fact it +is observed or not. In this case, it cannot adapt its behavior on its observation status. + +Because an agent is defined as an autonomous entity, the agent is supposed to be enabled to specify if a part of itself +is observable or not, i.e. to specify the access rights. + +The right access management is not yet supported by the SARL API + +## What is Observable? + +The first question arising when considering the concept of observable multi-agent system is: what is observable? + +Any object within the multi-agent system that could be referred with a name is possibly subject of an observation. +The objects are referred according to a [specific naming convention](./Naming.md). + +Because observation is related to data extraction, only fields could be observed at the concrete level. +Consequently, the observable objects a the fields declared within an: +* agent +* behavior +* skill +* agent context +* space +* service + +## Probe: generic observer implementation + +### General Principles + +A probe is an implementation of the proxy and observer software design patterns for observable multi-agent system. +It is a software tool that extracts the data from the observed object and provide the data to the observer. +Since a probe is a proxy, it filters the interaction from the observer to the observed object by enabling the first +only to get the data (no other function declared into the observed object is accessible). +The implementation of a probe ensures that it is not intrusive to the observed object. + +The observer has to create a probe on a field of the observable object (agent, behavior, etc.). +Then, the probe could be read to obtain the data, or the observer could be notified each time the data has changed +(according to the observer software design pattern). + +### Concrete Definition + +A probe is defined as: + + [:ShowType:](io.sarl.api.probing.Probe) + +The functions are: +* `getName`: Reply the name of the probe, that is constant. +* `getUri`: Reply the URI of the observed object. +* `getValue` : Reply the observed value. +* `getType` : Reply the type of the observed value. +* `sync`: Force the synchronization of the observed value. +* `release`: Release any resource associated to the probe. +* `isActive`: Reply if this probe is active. When a probe is active, it could be synchronized to the observed object. +* `isInvalid`: Reply if this probe is invalid. When a probe is invalid, the value replied by `getValue` may not corresponds to the observed element's value. + +The following functions are provided for convenience, but they should be used with caution: +* `setValue`: Force the observed field to have a specific value (brokes the agent's autonomy) + +### Observe the Probe + +The observer software design pattern that enables to be notified when the observed value changed in implemented into the probe. +According to the standard implementation best practices of the Java programming language, an observer (in this design pattern) +is named a listener, and it defined by an interface. Two types of observers are defined on probes: [:probelistener:] +and [:probereleaselistener:]. + +[:probelistener:] is defined as: + + [:ShowType:](io.sarl.api.probing.[:probelistener]$IProbeListener$) + +It corresponds to the observer on value changes. + + +[:probereleaselistener:] is defined as: + + [:ShowType:](io.sarl.api.probing.[:probereleaselistener]$IProbeReleaseListener$) + +It corresponds to the observer on the release of a probe. + + +The functions `addProbeListener`, `addProbeReleaseListener`, `removeProbeListener` and `removeProbeReleaseListener` enable to (un)register an event listener on the probe. + + +## Probing Service + +In order to manage and run the different probes, the SRE must implement a dedicated service: the [:probeservicename:]. +It is defined as: + + [:ShowType:](io.sarl.api.probing.[:probeservicename]$ProbeService$) + + +Creating a probe is done by calling the `probe` function. Basically, you need to specify the [name](./Naming.md) of the +observed field, the expected type of the value, and optionally the name of the probe. + +From the probe service, you could force the synchronization of all the managed probes by calling the `sync` function. + +Finally, two functions are provided from retrieving the list of the managed probes `getProbes` and releasing all +the probes `releaseAllProbes`. + +To use this service, you have to get it from the SRE, as illustrated below: + + [:Success:] + package io.sarl.docs.namespace + import io.sarl.bootstrap.SRE + import io.sarl.api.probing.ProbeService + class MyProgram { + static def main(arguments : String*) { + [:On] + var bootstrap = SRE::getBootstrap + var probeService = bootstrap.getService(typeof(ProbeService)) + var probe = probeService.probe("agent:[:agentid]$a7fbd4cc-9e1a-48c3-8ee8-3a7974ccb05c$#[:emergencyfield]$emergency$", typeof([:fieldtype]$Integer$), "[:probename]$My Probe$") + while (true) { + System.out.println("Probe: " + probe.value) + } + [:Off] + } + } + [:End:] + +In this example, the probe service is obtained from the SRE. +An probe is attached to the field named [:emergencyfield:] of type [:fieldtype:] that is defined within the agent [:agentid:]. +The name [:probename:] is given to the probe. +The example loops for displaying the observed value (of course it is not the most efficient usage of a probe). + + +[:Include:](../legal.inc) + diff --git a/docs/io.sarl.docs.markdown/src/main/documentation/api/SRE.md b/docs/io.sarl.docs.markdown/src/main/documentation/api/SRE.md index c997c32714..89634fa40b 100644 --- a/docs/io.sarl.docs.markdown/src/main/documentation/api/SRE.md +++ b/docs/io.sarl.docs.markdown/src/main/documentation/api/SRE.md @@ -97,7 +97,7 @@ For starting the SRE without agent, you have to invoke [:startWithoutAgentFct:]: static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - var context = boostrap.[:startWithoutAgentFct](startWithoutAgent) + var context = bootstrap.[:startWithoutAgentFct](startWithoutAgent) [:Off] } } @@ -119,7 +119,7 @@ Both of them are launching an agent of a given type. For example, the following static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - var context = boostrap.[:startAgentFct](startAgent)(typeof([:myagent!])) + bootstrap.[:startAgentFct](startAgent)(typeof([:myagent!])) [:Off] } } @@ -136,7 +136,7 @@ In the following example, [:numberofagents!] agents are launched into the SRE. static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - var context = boostrap.startAgent([:numberofagents](5), typeof([:myagent!])) + bootstrap.startAgent([:numberofagents](5), typeof([:myagent!])) [:Off] } } @@ -155,8 +155,8 @@ precision floating point number [:param2:]. static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - var context = boostrap.startAgent([:numberofagents](5), typeof([:myagent!]), [:param1]("arg1"), [:param2](4.5)) - [:Off] + bootstrap.startAgent(typeof([:myagent!]), [:param1]$"arg1"$, [:param2]$4.5$) + [:Off] } } [:End:] @@ -195,7 +195,7 @@ This function enables to pass initialization arguments to the launched agent. [:On] var theIdentifier : UUID = computeOrGetTheAgentIdentifier() var bootstrap = SRE::getBootstrap - boostrap.[:startAgentWithIDFct](startAgentWithID)(typeof([:myagent!]), theIdentifier) + bootstrap.[:startAgentWithIDFct](startAgentWithID)(typeof([:myagent!]), theIdentifier) [:Off] } } @@ -234,12 +234,12 @@ the SRE is stopping. The following code shows you the start and stop of the SRE. [:Success:] package io.sarl.docs.bootstrap import io.sarl.bootstrap.SRE - agent [:MyAgent!] {} + agent [:myagent!] {} class MyProgram { static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - boostrap.startAgent([:numberofagents!], typeof([:myagent!])) + bootstrap.startAgent([:numberofagents!], typeof([:myagent!])) bootstrap.[:shutdownFct](shutdown) [:Off] } @@ -253,12 +253,12 @@ In the following example, we are waiting 15 seconds for stopping the SRE. [:Success:] package io.sarl.docs.bootstrap import io.sarl.bootstrap.SRE - agent [:MyAgent!] {} + agent [:myagent!] {} class MyProgram { static def main(arguments : String*) { [:On] var bootstrap = SRE::getBootstrap - boostrap.startAgent([:numberofagents!], typeof([:myagent!])) + bootstrap.startAgent([:numberofagents!], typeof([:myagent!])) bootstrap.[:shutdownFct](shutdown)(1500) [:Off] } @@ -290,13 +290,13 @@ The following functions are provided on the SRE bootstrap to change the SRE conf class MyProgram { static def main(arguments : String*) { var bootstrap = SRE::getBootstrap - bootstrap.[:setRandomContextUUID](setRandomContextUUID) - bootstrap.[:setBootAgentTypeContextUUID](setBootAgentTypeContextUUID) - bootstrap.[:setSpecificContextUUID](setSpecificContextUUID) - bootstrap.[:setUniverseContextUUID](setUniverseContextUUID)(UUID:randomUUID) - var id0 : UUID = bootstrap.[:getUniverseContextUUID](getUniverseContextUUID) - bootstrap.[:setUniverseSpaceUUID](setUniverseSpaceUUID)(UUID:randomUUID) - var id1 : UUID = bootstrap.[:getUniverseSpaceUUID](getUniverseSpaceUUID) + bootstrap.[:setRandomContextUUID]$setRandomContextUUID$ + bootstrap.[:setBootAgentTypeContextUUID]$setBootAgentTypeContextUUID$ + bootstrap.[:setSpecificContextUUID]$setSpecificContextUUID$ + bootstrap.[:setUniverseContextUUID]$setUniverseContextUUID$(UUID::randomUUID) + var id0 : UUID = bootstrap.[:getUniverseContextUUID]$getUniverseContextUUID$ + bootstrap.[:setUniverseSpaceUUID]$setUniverseSpaceUUID$(UUID::randomUUID) + var id1 : UUID = bootstrap.[:getUniverseSpaceUUID]$getUniverseSpaceUUID$ } } [:End:] @@ -333,7 +333,7 @@ You could control in a generic way the verbose level of the kernel logger by cal class MyProgram { static def main(arguments : String*) { [:On] - var [bootstrap = [:sre](SRE)::getBootstrap + var bootstrap = SRE::getBootstrap bootstrap.[:setVerboseLevelFct](setVerboseLevel)(2) [:Off] } diff --git a/docs/io.sarl.docs.markdown/src/main/documentation/index.md b/docs/io.sarl.docs.markdown/src/main/documentation/index.md index 4b9e254cfd..56b6c12d22 100644 --- a/docs/io.sarl.docs.markdown/src/main/documentation/index.md +++ b/docs/io.sarl.docs.markdown/src/main/documentation/index.md @@ -94,6 +94,7 @@ * [Programmatic Access to the SARL Run-time Environment](./api/SRE.md) * [Naming and Namespaces](./api/Naming.md) +* [SRe Observation and Probes](./api/Probing.md) ## Compilation and Generation Infrastructure diff --git a/main/apiplugins/io.sarl.api.naming/src/main/sarl/io/sarl/api/naming/namespace/NamespaceService.sarl b/main/apiplugins/io.sarl.api.naming/src/main/sarl/io/sarl/api/naming/namespace/NamespaceService.sarl index 06966ca968..1c48ed7b3d 100644 --- a/main/apiplugins/io.sarl.api.naming/src/main/sarl/io/sarl/api/naming/namespace/NamespaceService.sarl +++ b/main/apiplugins/io.sarl.api.naming/src/main/sarl/io/sarl/api/naming/namespace/NamespaceService.sarl @@ -23,6 +23,7 @@ package io.sarl.api.naming.namespace import com.google.common.util.concurrent.Service import io.sarl.api.naming.name.SarlName import io.sarl.api.naming.parser.INameParser +import java.net.URI /** * This service enables to manage the name spaces into the SRE. @@ -110,4 +111,40 @@ interface NamespaceService extends Service { return findObject(sarlName) } + /** + * Finds and replies the object with the given name and of the given type. + * + * @param name the name of the object. See the documentation of {@link NamespaceService} + * for details. + * @return the root context. A {@code null} value is replied if the object is not found. + * @since 0.12 + */ + @Pure + def findObject(name : URI, type : Class) : T with T { + var parser = this.nameParser + var sarlName = parser.decode(name) + if (sarlName === null) { + return null + } + return findObject(sarlName, type) + } + + /** + * Finds and replies the object with the given name and of the given type. + * + * @param name the name of the object. See the documentation of {@link NamespaceService} + * for details. + * @return the object with the given name. A {@code null} value is replied if the object is not found. + * @since 0.12 + */ + @Pure + def findObject(name : URI) : Object { + var parser = this.nameParser + var sarlName = parser.decode(name) + if (sarlName === null) { + return null + } + return findObject(sarlName) + } + } diff --git a/main/externalmaven/io.sarl.maven.docs.generator/src/main/java/io/sarl/maven/docs/parser/SarlDocumentationParser.java b/main/externalmaven/io.sarl.maven.docs.generator/src/main/java/io/sarl/maven/docs/parser/SarlDocumentationParser.java index c93e6db929..d5f115993a 100644 --- a/main/externalmaven/io.sarl.maven.docs.generator/src/main/java/io/sarl/maven/docs/parser/SarlDocumentationParser.java +++ b/main/externalmaven/io.sarl.maven.docs.generator/src/main/java/io/sarl/maven/docs/parser/SarlDocumentationParser.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.Reader; import java.lang.ref.WeakReference; +import java.lang.reflect.TypeVariable; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; @@ -2652,9 +2653,26 @@ public String passThrough(ParsingContext context, String dynamicTag, String para return ""; //$NON-NLS-1$ } + private void appendGenericTypes(StringBuilder it, Class type) { + if (type.getTypeParameters() != null && type.getTypeParameters().length > 0) { + it.append("<"); //$NON-NLS-1$ + boolean first = true; + for (final TypeVariable genType : type.getTypeParameters()) { + if (first) { + first = false; + } else { + it.append(", "); + } + it.append(genType.getName()); + } + it.append(">"); //$NON-NLS-1$ + } + } + private String extractInterface(Class type) { final StringBuilder it = new StringBuilder(); it.append("interface ").append(type.getSimpleName()); //$NON-NLS-1$ + appendGenericTypes(it, type); if (type.getSuperclass() != null && !Object.class.equals(type.getSuperclass())) { it.append(" extends ").append(type.getSuperclass().getSimpleName()); //$NON-NLS-1$ } @@ -2677,7 +2695,7 @@ private String extractEnumeration(Class type) { private String extractAnnotation(Class type) { final StringBuilder it = new StringBuilder(); - it.append("interface ").append(type.getSimpleName()); //$NON-NLS-1$ + it.append("annotation ").append(type.getSimpleName()); //$NON-NLS-1$ it.append(" {\n"); //$NON-NLS-1$ ReflectExtensions.appendPublicMethods(it, true, type); it.append("}"); //$NON-NLS-1$ @@ -2686,7 +2704,8 @@ private String extractAnnotation(Class type) { private String extractClass(Class type) { final StringBuilder it = new StringBuilder(); - it.append("interface ").append(type.getSimpleName()); //$NON-NLS-1$ + it.append("class ").append(type.getSimpleName()); //$NON-NLS-1$ + appendGenericTypes(it, type); if (type.getSuperclass() != null && !Object.class.equals(type.getSuperclass())) { it.append(" extends ").append(type.getSuperclass().getSimpleName()); //$NON-NLS-1$ } diff --git a/main/externalmaven/io.sarl.maven.docs.testing/src/main/java/io/sarl/maven/docs/testing/ReflectExtensions.java b/main/externalmaven/io.sarl.maven.docs.testing/src/main/java/io/sarl/maven/docs/testing/ReflectExtensions.java index 2d9700ceae..048b618b06 100644 --- a/main/externalmaven/io.sarl.maven.docs.testing/src/main/java/io/sarl/maven/docs/testing/ReflectExtensions.java +++ b/main/externalmaven/io.sarl.maven.docs.testing/src/main/java/io/sarl/maven/docs/testing/ReflectExtensions.java @@ -29,6 +29,7 @@ import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.net.URL; import java.util.ArrayList; @@ -40,6 +41,7 @@ import java.util.Objects; import java.util.TreeMap; import java.util.function.Function; +import java.util.function.Predicate; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -151,12 +153,27 @@ public static void appendPublicMethods(StringBuilder it, boolean indent, Class nameFormatter, Iterable> types) { + appendMethods(it, indent, nameFormatter, types, (it0) -> { + return Flags.isPublic(it0.getModifiers()) && !Utils.isHiddenMember(it0.getName()) + && !isDeprecated(it0) && !it0.isSynthetic() + && it0.getAnnotation(SyntheticMember.class) == null; + }); + } + + /** Extract the methods from the given types. + * + * @param it the output. + * @param indent indicates if the code should be indented. + * @param nameFormatter the formatter for the function's names. If {@code null}, no formatting is applied. + * @param types the types to parse. + * @param selector the selector of method. + */ + private static void appendMethods(StringBuilder it, boolean indent, Function nameFormatter, + Iterable> types, Predicate selector) { final List lines = new LinkedList<>(); for (final Class type : types) { for (final Method method : type.getDeclaredMethods()) { - if (Flags.isPublic(method.getModifiers()) && !Utils.isHiddenMember(method.getName()) - && !isDeprecated(method) && !method.isSynthetic() - && method.getAnnotation(SyntheticMember.class) == null) { + if (selector.test(method)) { final StringBuilder line = new StringBuilder(); if (indent) { line.append("\t"); //$NON-NLS-1$ @@ -199,6 +216,16 @@ public static void appendPublicMethods(StringBuilder it, boolean indent, Functio line.append(" : "); //$NON-NLS-1$ toType(line, method.getGenericReturnType(), false); } + boolean first = true; + for (final TypeVariable typeVariable : method.getTypeParameters()) { + if (first) { + line.append(" with "); + first = false; + } else { + line.append(", "); + } + line.append(typeVariable.getTypeName()); + } line.append("\n"); //$NON-NLS-1$ lines.add(line.toString()); } @@ -316,6 +343,8 @@ public static void toType(StringBuilder it, Type otype, boolean isVarArg) { } else if (type instanceof GenericArrayType) { toType(it, ((GenericArrayType) type).getGenericComponentType(), false); it.append("[]"); //$NON-NLS-1$ + } else if (type instanceof TypeVariable) { + it.append(((TypeVariable) type).getName()); } else { it.append(Object.class.getSimpleName()); } diff --git a/tests/io.sarl.api.naming.tests/src/test/sarl/io/sarl/api/naming/tests/namespace/FinderBasedNamespaceServiceTest.sarl b/tests/io.sarl.api.naming.tests/src/test/sarl/io/sarl/api/naming/tests/namespace/FinderBasedNamespaceServiceTest.sarl index a40f2ec26d..ab4dc449b8 100644 --- a/tests/io.sarl.api.naming.tests/src/test/sarl/io/sarl/api/naming/tests/namespace/FinderBasedNamespaceServiceTest.sarl +++ b/tests/io.sarl.api.naming.tests/src/test/sarl/io/sarl/api/naming/tests/namespace/FinderBasedNamespaceServiceTest.sarl @@ -32,11 +32,13 @@ import io.sarl.api.naming.parser.INameParser import io.sarl.api.naming.parser.ISchemeNameParser import io.sarl.api.naming.parser.UriBasedNameParser import io.sarl.api.naming.scheme.NameScheme +import io.sarl.api.naming.tests.namespace.mocks.MyContext import io.sarl.lang.annotation.PrivateAPI import io.sarl.lang.core.AgentContext import io.sarl.tests.api.Nullable import io.sarl.tests.api.extensions.ContextInitExtension import io.sarl.tests.api.extensions.JavaVersionCheckExtension +import java.net.URI import java.util.Collections import java.util.UUID import org.junit.jupiter.api.BeforeEach @@ -46,15 +48,15 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.^extension.ExtendWith import static io.sarl.tests.api.tools.TestAssertions.* +import static org.mockito.ArgumentMatchers.* import static org.mockito.Mockito.* import static extension io.sarl.tests.api.tools.TestMockito.mock -import static extension org.mockito.Mockito.spy import static extension org.junit.jupiter.api.Assertions.assertEquals import static extension org.junit.jupiter.api.Assertions.assertNotNull import static extension org.junit.jupiter.api.Assertions.assertNull import static extension org.junit.jupiter.api.Assertions.assertSame -import io.sarl.api.naming.tests.namespace.mocks.MyContext +import static extension org.mockito.Mockito.spy /** * @author $Author: sgalland$ @@ -388,4 +390,44 @@ class FinderBasedNamespaceServiceTest { this.service.findObject(name, typeof(Integer)).assertNull } + private def createURI(arg : String) : URI { + this.service.nameParser.normalize(URI::create(arg)) + } + + @Test + def findObject_URI_00 : void { + this.ctx0.assertSame(this.service.findObject(("context:" + this.cid0.toString).createURI)) + } + + @Test + def findObject_URI_01 : void { + var fld = this.service.findObject(("context:" + this.cid0.toString + "#myfield").createURI) + assertInstanceOf(typeof(FieldAccess), fld) + var fa = fld as FieldAccess + this.ctx0.assertSame(fa.instance) + fa.field.assertNotNull + "myfield".assertEquals(fa.field.name) + 34.assertEquals(fa.get) + } + + @Test + def findObject_URI_02 : void { + this.service.findObject(("context:" + this.cid0.toString + "#xyz").createURI).assertNull + } + + @Test + def findObject_URI_Class_00_Context : void { + this.ctx0.assertSame(this.service.findObject(("context:" + this.cid0.toString).createURI, typeof(AgentContext))) + } + + @Test + def findObject_URI_Class_00_Object : void { + this.ctx0.assertSame(this.service.findObject(("context:" + this.cid0.toString).createURI, typeof(Object))) + } + + @Test + def findObject_URI_Class_00_FieldAccess : void { + this.service.findObject(("context:" + this.cid0.toString).createURI, typeof(FieldAccess)).assertNull + } + }