Home » Language IDEs » Objectteams » Some questions on design decisions
|
Re: Some questions on design decisions [message #515582 is a reply to message #515541] |
Fri, 19 February 2010 13:04 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Fri, 19 February 2010 05:31 |
1. Order of after callins
When callin methods from several roles are bound to the same based method, after callins are called in the same order as before callins. Would not it be better to have reverse order for after callins (e.g. LIFO)? The motivation behind this is the following the around callins may be used to allocate and free resources and the LIFO pattern is preferable for this case.
|
If you are talking about the semantics of "precedence" than this seem to be
trac ticket 328, right?
This issue was discovered by a student working on refactorings.
Eugene Hutorny wrote on Fri, 19 February 2010 05:31 |
2. Currently the team activation mode (local/global) is determined by the team user. In my opinion, it should be an explicit decision of the team vendor. And the language shall discourage the activation mode misusage.
|
My first answer would be a no and a yes.
No, I consider team activation to a large part as a matter of system composition,
not component building. Specifically the ordering of team activation can only
be determined by the system, not the individual team.
Yes, since team activation boils down to creating an instance and
calling activate() the team can indeed implement these things in a way as
to prevent "misusage", like: if no default constructor exists, activation via
configuration file (§5.5) is not possible, if a team overrides activate(Thread)
and checks whether or not the argument is the constant ALL_THREADS it
may simply ignore bogus attempts, throw an exception etc.
By local/global activation do you refer to per-thread vs. all threads or to
explicit using activate or within vs. §5.5? Do you have an example of what
misusage you'd like to prevent?
Eugene Hutorny wrote on Fri, 19 February 2010 05:31 |
3. Currently it is not possible for a team to play a role of its own (Illegal base class T, is an enclosing type of R (OTJLD 2.1.2(b))).
|
Not sure I understand you. Something like this?
public team class MyTeam {
protected class RoleForTeam playedBy MyTeam {
}
}
Any reasons why not use team fields and methods instead of
fields and methods of RoleForTeam? So, yes the team can certainly
be a participant in the interaction among roles, but it needs no role for
that. And if you really prefer a separate class (for better encapsulation e.g.)
why not use an unbound role, which still has access to all team methods
via MyTeam.this.m() rather than via callout.
A role is usually a view of something outside the team. Maybe I didn't
understand your intention.
Quote: | Not clear why this restriction is introduced since the team may play a role in a descendant team.
|
If I understand correctly the compiler allows a situation that it should no
by intention. Again, do you have an example?
Stephan
|
|
|
Re: Some questions on design decisions [message #515607 is a reply to message #515582] |
Fri, 19 February 2010 14:19 |
Eugene Hutorny Messages: 110 Registered: January 2010 |
Senior Member |
|
|
Stephan Herrmann wrote on Fri, 19 February 2010 08:04 |
If you are talking about the semantics of "precedence" than this seem to be
trac ticket 328, right?
This issue was discovered by a student working on refactorings.
|
Yes, that's what I meant.
Stephan Herrmann wrote on Fri, 19 February 2010 08:04 |
Eugene Hutorny wrote on Fri, 19 February 2010 05:31 |
2. Currently the team activation mode (local/global) is determined by the team user. In my opinion, it should be an explicit decision of the team vendor. And the language shall discourage the activation mode misusage.
|
My first answer would be a no and a yes.
No, I consider team activation to a large part as a matter of system composition,
not component building. Specifically the ordering of team activation can only
be determined by the system, not the individual team.
Yes, since team activation boils down to creating an instance and
calling activate() the team can indeed implement these things in a way as
to prevent "misusage", like: if no default constructor exists, activation via
configuration file (§5.5) is not possible, if a team overrides activate(Thread)
and checks whether or not the argument is the constant ALL_THREADS it
may simply ignore bogus attempts, throw an exception etc.
|
Yes, a team may block bogus attempts, but this will be runtime exception, not a compilation error.
Stephan Herrmann wrote on Fri, 19 February 2010 08:04 |
By local/global activation do you refer to per-thread vs. all threads or to
explicit using activate or within vs. §5.5? Do you have an example of what
misusage you'd like to prevent?
|
Yes, I meant per-thread vs. all threads activation.
For example there are two threads, first thread activates a team per thread, another - activates for all threads.
When the first thread deactivates the team (per thread) - would it be active for this thread?
// 1st thread 2nd thread
ateam.activate();
ateam.activate(ALL_THREADS)
ateam.deactivate();
// is ateam active for this thread?
Stephan Herrmann wrote on Fri, 19 February 2010 08:04 |
Eugene Hutorny wrote on Fri, 19 February 2010 05:31 |
3. Currently it is not possible for a team to play a role of its own (Illegal base class T, is an enclosing type of R (OTJLD 2.1.2(b))).
|
Not sure I understand you. Something like this?
public team class MyTeam {
protected class RoleForTeam playedBy MyTeam {
}
}
Any reasons why not use team fields and methods instead of
fields and methods of RoleForTeam? So, yes the team can certainly
be a participant in the interaction among roles, but it needs no role for
that. And if you really prefer a separate class (for better encapsulation e.g.)
why not use an unbound role, which still has access to all team methods
via MyTeam.this.m() rather than via callout.
A role is usually a view of something outside the team. Maybe I didn't
understand your intention.
Quote: | Not clear why this restriction is introduced since the team may play a role in a descendant team.
|
If I understand correctly the compiler allows a situation that it should no
by intention. Again, do you have an example?
Stephan
|
Please take a look at the example below:
// this class is needed just to overcome compiler error
public abstract team class Server {
public abstract String respond();
}
public team class WebServer extends Server {
public String respond() {
return "HTTP 1.1";
}
protected class Maintenance playedBy Server {
String respond() <- replace String respond();
@SuppressWarnings("basecall")
callin String respond() {
return "Server is on maintenace";
}
}
public void beginMaintenance() {
activate(ALL_THREADS);
}
public void endMaintenance() {
deactivate(ALL_THREADS);
}
server.respond(); // returns "HTTP 1.1"
server.beginMaintenance();
server.respond(); // returns "Server is on maintenace"
server.endMaintenance();
If such constructions are not allowed by language design, compiler should not allow a role played by a team superclass as well, e.g. issue error on Maintenance playedBy Server.
If such construction is allowed, compiler should not complain about Maintenance playedBy WebServer.
|
|
|
Re: Some questions on design decisions [message #515780 is a reply to message #515607] |
Sat, 20 February 2010 17:39 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Fri, 19 February 2010 09:19 |
Yes, a team may block bogus attempts, but this will be runtime exception, not a compilation error.
|
It certainly would be cool if the compiler could detect ill-behaving threads
in advance. Do you have any solution in mind?
Eugene Hutorny wrote on Fri, 19 February 2010 09:19 |
Stephan Herrmann wrote on Fri, 19 February 2010 08:04 |
By local/global activation do you refer to per-thread vs. all threads or to
explicit using activate or within vs. §5.5? Do you have an example of what
misusage you'd like to prevent?
|
Yes, I meant per-thread vs. all threads activation.
For example there are two threads, first thread activates a team per thread, another - activates for all threads.
When the first thread deactivates the team (per thread) - would it be active for this thread?
// 1st thread 2nd thread
ateam.activate();
ateam.activate(ALL_THREADS)
ateam.deactivate();
// is ateam active for this thread?
|
Well, we worked hard towards orthogonality of per-thread and global activation.
In your example each method call does as advertized, which means ateam is
not active for the 1st thread at the end of the scenario. The idea was to make
both kinds of activation harmonize rather than making one stronger than the other
(which one?).
I do believe we have every flexibility needed, and all combinations of different
activations will behave well. What you are seeking seems to be ways for
restricting the possible usages of team activation, which should be possible
to implement on top of the flexibility we have (the opposite would not work).
I still owe you an answer regarding the WebServer example (interesting!),
which I'll write up later.
Stephan
|
|
| |
Re: Some questions on design decisions [message #515807 is a reply to message #515780] |
Sun, 21 February 2010 08:27 |
Eugene Hutorny Messages: 110 Registered: January 2010 |
Senior Member |
|
|
Stephan Herrmann wrote on Sat, 20 February 2010 12:39 |
It certainly would be cool if the compiler could detect ill-behaving threads in advance. Do you have any solution in mind?
|
From the set of techniques you already used - declaring appropriate interface on the team class, as you did with ILowerable. It would feature the team class with an appropriate method
interface IActivatedLocally {
void activate();
}
interface IActivatedGlobally {
void activateForAllThreads();
}
public class team MyTeam implements IActivatedGlobally {
...
}
Although, it covers only two kinds of team activations out of four (may be five?) provided by OT/J, similar approach can be used for others.
By 4-5 provided kinds I mean: (1) within, (2) activate(), (3) activate(otherThread), (4) activate(ALL_THREADS), (5?) new Thread() { /* while ateam is activated for ALL_THREADS */ }
BTW, ILowerable creates a hole in the type - a typehole (according to my understanding of typehole model) which is then filled by the compiler. The typehole may be beneficial in many other cases, but this another topic and I'll start another thread.
Stephan Herrmann wrote on Sat, 20 February 2010 12:39 |
I do believe we have every flexibility needed, and all combinations of different
activations will behave well. What you are seeking seems to be ways for
restricting the possible usages of team activation, which should be possible
to implement on top of the flexibility we have (the opposite would not work).
|
I am not against the flexibility. I am for controlled flexibility - the team author should decide what kind of activation is allowed for this particular team.
Regards,
Eugene
|
|
| |
Re: Some questions on design decisions [message #515854 is a reply to message #515808] |
Sun, 21 February 2010 20:47 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Sun, 21 February 2010 03:33 |
I am not sure I understand what this means:
protected R() {
base();
}
Is it a call to MyTeam constructor? MyTeam is not a superclass for R, so what is the meaning of calling constructor of a class wich is not the superclass?
|
You guess right, in analogy to a super constructor call this is a base constructor call (§2.4.2).
It is what you need if you want to instantiate a bound role but have no
base instance. In that case it's the role's responsibility to create the base, too.
Quote: |
To answer the quiz - I would expect stack overflow.
|
Right again.
The program tries to recursively allocate an infinite number of objects.
Since you recognized the problem my point is not very strong, but the
example should demonstrate how the proposed design may create recursions
that are not perfectly obvious.
I've researched some evidence on why we added rule 2.1.2(b)
(back in March 2005).
It seems we didn't have a truly harmful example, but apparently we simply
wanted to rid ourselves of a complexity, we didn't consider worthwhile.
However, seeing your WebServer example I'm intrigued by this pattern,
so let's weigh costs vs. benefits:
Benefits
A role-playedBy-enclosing provides a very convenient way to implement
an exceptional state / mode of the enclosing object, while keeping this
solution perfectly local.
The exceptional mode does not clutter the regular behavior.
With a slight extension of the pattern one can create an arbitrary number
of modes, like:
public team class MyTeam {
enum Mode {NORMAL, MAINTENANCE, BOOT_IN_PROGRESS};
Mode mode;
protected class MaintenanceMode playedBy MyTeam
base when (MyTeam.this.mode == Mode.MAINTENANCE) { ... }
protected class BootingMode playedBy MyTeam
base when (MyTeam.this.mode == Mode.BOOT_IN_PROGRESS) { ... }
}
(caveat: declaring an enum inside a team currently breaks content
assist - this bug is fixed in the OTDT 1.4.0 final )
[Edited 2010-03-20]
I have never seen a cleaner implementation of objects with modes.
Costs
We don't want to trick developers into unexpected program errors:
- Recursions are more difficult to identify
- It's easy to forget that this kind of role can also adapt more
team instances in addition to the enclosing.
As for the recursions it appears that these arise (and are difficult to spot)
when and because the role accesses its enclosing instance in unusual
ways, either when creating a new outer using a base constructor call,
or similarly when doing a callout to the enclosing. Thus it should be
possible to effectively warn the developer by one or two new warnings:
- I could change 2.1.2(b) from an error into a warning, i.e., give a first
warning whenever a role is played by its enclosing
- Give a second - urgent - warning, if a role-playedby-enclosing issues
a base constructor call or defines a callout. Both can be done in more
explicit ways, so discouraging both constructs for this situation doesn't
seem to harm.
Regarding unexpected adaptation of a non-enclosing instance of the
enclosing team class (wow, that sounds complicated..), one might recommend
using the following guard in all of these roles:
public team class MyTeam {
protected class MaintenanceMode playedBy MyTeam
base when (MyTeam.this == base) { ... } // only adapt this team instance
}
If a team only contains mode roles of its own self, this can be further
generalized to
public team class MyTeam
base when (this == base) // only adapt this team instance
{ ...
}
I'd probably place this recommendation right into the OTJLD and phrase
the new warning such that it recommends to actually read this paragraph.
I'm seriously considering these changes to the OTJLD and the compiler,
because you convinced me that I was just overly anxious when defining
the rule 2.1.2(b).
Naturally, the indirection via a super team (WebServer extends Server)
should make no difference whatsoever.
Does anyone see a problem that I'm failing to see now?
Stephan
[Updated on: Sat, 20 March 2010 23:02] Report message to a moderator
|
|
|
Re: Some questions on design decisions [message #515930 is a reply to message #515854] |
Mon, 22 February 2010 09:35 |
Eugene Hutorny Messages: 110 Registered: January 2010 |
Senior Member |
|
|
Stephan Herrmann wrote on Sun, 21 February 2010 15:47 |
You guess right, in analogy to a super constructor call this is a base constructor call (§2.4.2).
It is what you need if you want to instantiate a bound role but have no
base instance. In that case it's the role's responsibility to create the base, too.
|
I have searched the documentation and have found no reference on ability to create a bound role without the base reference, except, of course, the §2.4.2 which does not elaborate why a call to the base constructor is needed.
Stephan Herrmann wrote on Sun, 21 February 2010 15:47 |
I have never seen a cleaner implementation of objects with modes.
|
Afterwards I have found a reference in the Kasper's work to the pattern:
Kasper Bilsted Graversen, p.96 |
In Graversen and Beyer (2002,p. 76-84) we proposed Internal roles as a strategy for using and implementing acquisition dispatching. A internal role model a specific state of an intrinsic
|
Regards,
Eugene
[Updated on: Tue, 23 February 2010 10:33] Report message to a moderator
|
|
|
Re: Some questions on design decisions [message #516001 is a reply to message #515930] |
Mon, 22 February 2010 12:52 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Mon, 22 February 2010 04:35 |
I have searched the documentation and have found no reference on ability to create a bound role without the base reference, except, of course, the §2.4.2 which does not elaborate why a call to the base constructor is needed.
|
You're right, §2.4.2(b) needs to be expanded by a short explanation.
Eugene Hutorny wrote on Mon, 22 February 2010 04:35 |
Afterwards I have found a reference in the Kasper's work to the pattern:
Kasper Bilsted Graversen, p.96 |
In Graversen and Beyer (2002,p. 7684) we proposed Internal roles as a strategy for using and implementing acquisition dispatching. A internal role model a specific state of an intrinsic
|
|
Thanks for the reference (oops, the 2002 paper doesn't have > 7000 pages!?!).
For some reason that slipped my attention, so thanks again for bringing it up.
Stephan
|
|
| |
Re: Some questions on design decisions [message #568678 is a reply to message #515607] |
Sat, 20 February 2010 17:39 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Fri, 19 February 2010 09:19
> Yes, a team may block bogus attempts, but this will be runtime exception, not a compilation error.
It certainly would be cool if the compiler could detect ill-behaving threads
in advance. Do you have any solution in mind?
Eugene Hutorny wrote on Fri, 19 February 2010 09:19
> Stephan Herrmann wrote on Fri, 19 February 2010 08:04
> > By local/global activation do you refer to per-thread vs. all threads or to
> > explicit using activate or within vs. §5.5? Do you have an example of what
> > misusage you'd like to prevent?
>
>
> Yes, I meant per-thread vs. all threads activation.
>
> For example there are two threads, first thread activates a team per thread, another - activates for all threads.
> When the first thread deactivates the team (per thread) - would it be active for this thread?
>
>
> // 1st thread 2nd thread
> ateam.activate();
> ateam.activate(ALL_THREADS)
> ateam.deactivate();
> // is ateam active for this thread?
Well, we worked hard towards orthogonality of per-thread and global activation.
In your example each method call does as advertized, which means ateam is
not active for the 1st thread at the end of the scenario. The idea was to make
both kinds of activation harmonize rather than making one stronger than the other
(which one?).
I do believe we have every flexibility needed, and all combinations of different
activations will behave well. What you are seeking seems to be ways for
restricting the possible usages of team activation, which should be possible
to implement on top of the flexibility we have (the opposite would not work).
I still owe you an answer regarding the WebServer example (interesting!),
which I'll write up later.
Stephan
|
|
| |
Re: Some questions on design decisions [message #568706 is a reply to message #568678] |
Sun, 21 February 2010 08:27 |
Eugene Hutorny Messages: 110 Registered: January 2010 |
Senior Member |
|
|
Stephan Herrmann wrote on Sat, 20 February 2010 12:39
> It certainly would be cool if the compiler could detect ill-behaving threads in advance. Do you have any solution in mind?
From the set of techniques you already used - declaring appropriate interface on the team class, as you did with ILowerable. It would feature the team class with an appropriate method
interface IActivatedLocally {
void activate();
}
interface IActivatedGlobally {
void activateForAllThreads();
}
public class team MyTeam implements IActivatedGlobally {
...
}
Although, it covers only two kinds of team activations out of four (may be five?) provided by OT/J, similar approach can be used for others.
By 4-5 provided kinds I mean: (1) within, (2) activate(), (3) activate(otherThread), (4) activate(ALL_THREADS), (5?) new Thread() { /* while ateam is activated for ALL_THREADS */ }
BTW, ILowerable creates a hole in the type - a typehole (according to my understanding of typehole model) which is then filled by the compiler. The typehole may be beneficial in many other cases, but this another topic and I'll start another thread.
Stephan Herrmann wrote on Sat, 20 February 2010 12:39
> I do believe we have every flexibility needed, and all combinations of different
> activations will behave well. What you are seeking seems to be ways for
> restricting the possible usages of team activation, which should be possible
> to implement on top of the flexibility we have (the opposite would not work).
I am not against the flexibility. I am for controlled flexibility - the team author should decide what kind of activation is allowed for this particular team.
Regards,
Eugene
|
|
| |
Re: Some questions on design decisions [message #568805 is a reply to message #568719] |
Sun, 21 February 2010 20:47 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Sun, 21 February 2010 03:33
> I am not sure I understand what this means:
>
>
> protected R() {
> base();
> }
>
>
> Is it a call to MyTeam constructor? MyTeam is not a superclass for R, so what is the meaning of calling constructor of a class wich is not the superclass?
You guess right, in analogy to a super constructor call this is a http://www.objectteams.org/def/1.3/s2.html#s2.4.2
It is what you need if you want to instantiate a bound role but have no
base instance. In that case it's the role's responsibility to create the base, too.
Quote:
> To answer the quiz - I would expect stack overflow.
Right again.
The program tries to recursively allocate an infinite number of objects.
Since you recognized the problem my point is not very strong, but the
example should demonstrate how the proposed design may create recursions
that are not perfectly obvious.
I've researched some evidence on why we added rule 2.1.2(b)
(back in March 2005).
It seems we didn't have a truly harmful example, but apparently we simply
wanted to rid ourselves of a complexity, we didn't consider worthwhile.
However, seeing your WebServer example I'm intrigued by this pattern,
so let's weigh costs vs. benefits:
Benefits
A role-playedBy-enclosing provides a very convenient way to implement
an exceptional state / mode of the enclosing object, while keeping this
solution perfectly local.
The exceptional mode does not clutter the regular behavior.
With a slight extension of the pattern one can create an arbitrary number
of modes, like:
public team class MyTeam {
enum Mode {NORMAL, MAINTENANCE, BOOT_IN_PROGRESS};
Mode mode;
protected class MaintenanceMode playedBy MyTeam
base when (MyTeam.this.mode == Mode.MAINTENANCE) { ... }
protected class BootingMode playedBy MyTeam
base when (MyTeam.this.mode == Mode.BOOT_IN_PROGRESS) { ... }
}
(caveat: declaring an enum inside a team currently breaks content
assist :( - fixed in SVN but current release has this bug)
I have never seen a cleaner implementation of objects with modes.
Costs
We don't want to trick developers into unexpected program errors:
Recursions are more difficult to identify
It's easy to forget that this kind of role can also adapt more
team instances in addition to the enclosing.
As for the recursions it appears that these arise (and are difficult to spot)
when and because the role accesses its enclosing instance in unusual
ways, either when creating a new outer using a base constructor call,
or similarly when doing a callout to the enclosing. Thus it should be
possible to effectively warn the developer by one or two new warnings:
I could change 2.1.2(b) from an error into a warning, i.e., give a first
warning whenever a role is played by its enclosing
Give a second - urgent - warning, if a role-playedby-enclosing issues
a base constructor call or defines a callout. Both can be done in more
explicit ways, so discouraging both constructs for this situation doesn't
seem to harm.
Regarding unexpected adaptation of a non-enclosing instance of the
enclosing team class (wow, that sounds complicated..), one might recommend
using the following guard in all of these roles:
public team class MyTeam {
protected class MaintenanceMode playedBy MyTeam
base when (MyTeam.this == base) { ... } // only adapt this team instance
}
If a team only contains mode roles of its own self, this can be further
generalized to
public team class MyTeam
base when (this == base) // only adapt this team instance
{ ...
}
I'd probably place this recommendation right into the OTJLD and phrase
the new warning such that it recommends to actually read this paragraph.
I'm seriously considering these changes to the OTJLD and the compiler,
because you convinced me that I was just overly anxious when defining
the rule 2.1.2(b).
Naturally, the indirection via a super team (WebServer extends Server)
should make no difference whatsoever.
Does anyone see a problem that I'm failing to see now?
Stephan
|
|
|
Re: Some questions on design decisions [message #568881 is a reply to message #568805] |
Mon, 22 February 2010 09:35 |
Eugene Hutorny Messages: 110 Registered: January 2010 |
Senior Member |
|
|
Stephan Herrmann wrote on Sun, 21 February 2010 15:47
> You guess right, in analogy to a super constructor call this is a base constructor call (§2.4.2).
> It is what you need if you want to instantiate a bound role but have no
> base instance. In that case it's the role's responsibility to create the base, too.
I have searched the documentation and have found no reference on ability to create a bound role without the base reference, except, of course, the http://www.objectteams.org/def/1.3/s2.html#s2.4.2 which does not elaborate why a call to the base constructor is needed.
Stephan Herrmann wrote on Sun, 21 February 2010 15:47
> I have never seen a cleaner implementation of objects with modes.
Afterwards I have found a reference in the Kasper's work to the pattern:
Kasper Bilsted Graversen, p.96
> In Graversen and Beyer (2002,p. 7684) we proposed Internal roles as a strategy for using and implementing acquisition dispatching. A internal role model a specific state of an intrinsic
Regards,
Eugene
|
|
|
Re: Some questions on design decisions [message #568992 is a reply to message #568881] |
Mon, 22 February 2010 12:52 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Eugene Hutorny wrote on Mon, 22 February 2010 04:35
> I have searched the documentation and have found no reference on ability to create a bound role without the base reference, except, of course, the §2.4.2 which does not elaborate why a call to the base constructor is needed.
You're right, §2.4.2(b) needs to be expanded by a short explanation.
Eugene Hutorny wrote on Mon, 22 February 2010 04:35
> Afterwards I have found a reference in the Kasper's work to the pattern:
>
> Kasper Bilsted Graversen, p.96
> > In Graversen and Beyer (2002,p. 7684) we proposed Internal roles as a strategy for using and implementing acquisition dispatching. A internal role model a specific state of an intrinsic
Thanks for the reference (oops, the 2002 paper doesn't have > 7000 pages!?!).
For some reason that slipped my attention, so thanks again for bringing it up.
Stephan
|
|
| |
Goto Forum:
Current Time: Sun Sep 22 21:04:36 GMT 2024
Powered by FUDForum. Page generated in 0.04870 seconds
|