I've extended Java to make it more DCI compliant, bringing it in line
with languages such as Javascript and Ruby, when it comes to DCI
compliancy. In my opinion, it is now one of the leading languages
when it comes to nailing DCI certification. Comments are appreciated!
PS. I might have been a little generous in my definitions of
"extending the language" and "syntax" here...
Who likes and who doesn't?
> 2. I'm interested in seeing the implementation. One way you might have done this would be to use the reflection API to create a new class on-the-fly, composing the original class and the "role" class at the point of injection, and to re-wire the method tables to re-type the extant object. (Does the VM allow that — in particular, the latter re-wiring?) If you could do that then this looks a lot like the Scala implementation and one might even be able to talk about interoperability between Java and Scala in a DCI world.
>
Would be interesting if that was the case that's exactly what I did in
ClearMud a few month ago. Though I did cheat some what fooling the run
time
There's nothing wrong with unit testing. I believe without TDD dynamic languages would never have risen to where they are now. But a Java coder will expect that certain things just work, you just don't write unit tests to check on the compiler. Just as you don't write unit tests for pre/postconditions or invariants if you have language support for those. Any there are other advantages to static type systems, like more powerful refactorings, dependency analysis etc. What's the point of struggling with Java and its limited type system if you get none of those? Sounds like self-punishment, maybe DCI for Catholics?
My advice is that if you ask people whether they prefer your syntax or Qi4j, also ask them for their absolute preference. My guess is that those who favor your way over Qi4j will also prefer Ruby or something similar over both. So they won't use it, and what's the point then?
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Thursday, September 29, 2011 3:46 PM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>
> extant object. (Does the VM allow that - in particular, the latter re-
> Still, I can imagine being laughed at, if I tried to sell this to my
> Java SOA colleagues...
As Robbie Williams would say:"first they ignore you, then they laugh
at you, then they hate you, then you win"
That's actually an interesting question. It's typed but it differs
from the rest of the type system in C# (except for foreach). It's
pattern based.
from item in collection
where item.Id > 10
select item
In the above statement the comparison is typed as any other statement
but the where and select is pattern based. That is as long as the type
of the collection has a method that matches the sought signature or
that there's an extension method that matches everything is fine
> OK, that's quite cool that the compiler can check that for example
> "item" has an attribute "id" (or a getter for "id"?).
That part of the expression is typed as any other comparison in C#.
The type of item is known based on the type of the collection so
item.Id > 10 is just an ordinary expression. The select and where
however are not they are methods but not based in an interface or
type. There's several signatures possible for each and as long as one
with a matching signature is available for the source collection it
will compile
collection.Where<TSource> ((TSource item) => item.Id > 10).Select<TSource> ((TSource item) => item);
and that's what gets compiled. You get full compile-time type safety, code completion, analyzability, refactoring. Anything you'd want a static language for. And that's what makes LINQ so attractive to programmers. They'll hate to write code in a matter that constantly makes them wish their language would just support this better. Making them switch to a dynamic language would be much easier.
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of rune funch
> Sent: Thursday, September 29, 2011 10:51 PM
> To: dci-ev...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Thursday, September 29, 2011 10:50 PM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>
I think they all do, and much more elegantly so.
> Show me a few lines of code to inject methods, then I'll go take a
> look.
Pick your language and google. Ruby has method_missing, Python has __getattr__, and I assume so do JRuby and Jython. Groovy has the meta object protocol. You can use any of those to dynamically add methods and call them without any special syntax.
Have you solved the name scoping problems in your Java version? That might be a bit hard to pull off in dynamic languages, but like I said before, if you know your active context, your implementation of method_missing etc. can respect that. You can also "unload" methods, but I'm still waiting for someone to explain to me how this solves any problem.
> But I still want to leverage type safety in role implementations - can
> I do that with JRuby, Groovy or Jython? It is only the context
> implementation which suffers with my solution.
In DCI, isn't all your interesting code going to be in RoleMethods? It seems you're looking for some kind of compromise, but which parts would you keep static? Data classes only? Static typing is a PITL thing, you'd be using it everywhere expect in the code that matters "in the large".
I don't think it's a good idea to split dynamic and static code if the dynamic part predominates, it would feel more sensible otherwise (like 'dynamic' in C#). People either go all-in on dynamic or on static. But if you can draw a good line, maybe you'd want to create your data classes in Java and your contexts and roles in Groovy? I don't know how this works in any JVM-based environment, you might not be able to use POJOs as data objects. (A C# object would have to implement IDynamicObject to allow for custom dispatching.)
In this case I'd definitely choose Groovy if possible, because it should be much easier to switch between those than between, say, Java and Ruby.)
Stefan
There's actually a pretty straightforward answer: It is. Your sample gets translated to
collection.Where<TSource> ((TSource item) => item.Id > 10).Select<TSource> ((TSource item) => item);
How does this contribution further DCI evolution?
DCI works well where DCI works well. There are places where it stumbles. I'm happy to see work on this list to move DCI adaptation forward in broad programming areas.
While it is good to bring DCI even to the niche, minority programming areas such as you mention, I would rather that we help Ant advance our understanding of DCI in a particular context than to terminate the dialogue — especially just after what might be a significant advance in that area.
I want people on this list to think big. The goal isn't to just make this work at home, but to influence the industry. I have a reasonable shot at motivating the Java standards effort to make changes to Java so it supports DCI. This has been my vision from early on. It was the reason I travelled to meet Rickard Öberg and his CTO and offered all the support that Trygve and I could give them in doing Qi4j.
Now, I have been invited to give an inaugural Heart of Technology Lecture in Potsdam in March, 2012. It is likely that all of the relevant people will be there, and in particular, people from the AOP community. This is an opportunity to take this community's message forward. I'm checking with the organizer to see if we can make a small group presentation. But more than that, it will be an opportunity (I hope) to meet with the likes of Guy Steele and other key Java community members to present our case. It would be nice to have a concrete list of Java changes that would support our vision.
Anyone who wants to go off and fulfill their vision of transforming the JRuby community is of course welcome to do so. My goal is to honor Trygve's DCI vision by opening the door to broad applicability, and to gain insights into the real programming problems of DCI by banging it up against one of the most broadly used programming platforms on earth. I think Ant is doing a good job of that, and if he doesn't deserve our support, he at least deserves not to be told to stop doing what he's doing.
Start with what you think the syntax SHOULD be, and let's go from there.
LINQ is typesafe. I don't know where you're going with the "but..." part of your answer. Query expressions are transformed to regular C#, then translated like anything else. I know LINQ internals pretty well, but your answer confused even me. There is no separate type system for LINQ, so I wanted to clarify this.
> First of all selects are not translated
> if there's no projection so the last part goes
That was just for illustration. Yes, there's a special case for 'select's that do nothing.
> second of all the above
> is not using anything from the LINQ name space but an non generic
> instance method.
Sorry, but that's just wrong. The exact translation of Ant's where clause .Where (item => item.Id > 10). Whether this will link to a Enumerable or Queryable method, another extension method or an instance method cannot be told from this snippet. Whether the methods are generic or not is unclear too. At this point, C#'s normal method resolution mechanism kicks in, and it might choose a non-generic method, or use type inferencing to choose a generic one. The usual translation is Queryable or Enumerable.Where using the type parameters I spelled out for clarity. Anything else is very rare.
> And (not that that's part of your translation)
> collection is a really bad name for the object because in reality it's
> not a collection but a single object that has where implemented top
> play with monads and idioms;
Huh? It might be a collection, or something else. Don't see the problem. The relationship of LINQ to monads is overrated, it's an abuse of the system that creates unreadable code.
> The result of the where is also of a different type than the source.
Really? You can do this, but nobody does. Usually, a 'where' just filters, and this does not change the type.
Why are you even trying to fisk me here?
Better slicing is what both Qi4j and re-mix are trying to achieve, and that's also what makes Scala a good fit for DCI. It's what hyperslices and use case slices are all about, so I like your use of the word slice!
I don't tell anyone to give up type safety. Actually, you can take it from my cold dead hands, and that's not because I don't see the advantages of dynamic languages but because I've made a choice and I accept the consequences. My only point is that if you cannot have type safety in the heart of your system, you're in a strange place.
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of James O. Coplien
> Sent: Friday, September 30, 2011 12:58 PM
> To: dci-ev...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>
> Sorry, but that's just wrong. The exact translation of Ant's where clause .Where (item => item.Id > 10). Whether this will link to a Enumerable or Queryable method, another extension method or an instance method cannot be told from this snippet.
No but the code is mine I have the implementation. It's not wrong and
I don't know how this is even remotely related to DCI.
> Whether the methods are generic or not is unclear too. At this point, C#'s normal method resolution mechanism kicks in,
That's the point it's NOT the normal method overload resolution until
the the translation has happened. Which creates a lot more flexibility
and extensibility. The latter being the reason why the compiler team
chose to make it pattern-based rather than type based.
Ant asked if LINQ was typed the answer I gave was yes but query
comprehension uses different type rules than anything else in C#
except foreach which is also pattern-based. Since the question
originated from some one trying to map a concept to a different tool I
think those details are important not to mention that you could easily
use a pattern-based type system when it comes to RolePlayers so in
regards to DCI the concept of having selected parts of the language
being based on a pattern-based type system becomes even more
interesting
-Rune
That's not my goal. I think that if you want to get a Java programmer to use DCI, you should either
a) offer them a way to do it in Java that meets their expectations, including static type checking, or
b) advise them to move to a dynamic language altogether.
This is not about transforming the JRuby/Jython/Groovy community. And obviously, I prefer a).
> I would rather that we help Ant advance our
> understanding of DCI in a particular context than to terminate the
> dialogue - especially just after what might be a significant advance
> in that area.
Well, he was asking for opinions, and I offered one. I have no beef with Ant's code if it's supposed to be experimental and improve our understanding. But if he worries about Java seniors laughing at him, I believe he thinks beyond the boundaries of our little google group. That's what I was talking about.
> I want people on this list to think big. The goal isn't to just make
> this work at home, but to influence the industry. I have a reasonable
> shot at motivating the Java standards effort to make changes to Java
> so it supports DCI. This has been my vision from early on. It was the
> reason I travelled to meet Rickard Öberg and his CTO and offered all
> the support that Trygve and I could give them in doing Qi4j.
There's a chance that Java will include something like C#'s 'dynamic', which would mean Ant could get rid of the strange calling syntax.
But the really interesting part, from a Java POV, would be to extend static typing to DCI. If you look at Qi4j and re-mix, we use as much static typing as we can. But there are limits and opportunities, e.g.:
- in Qi4j, you need to define your composition in a central place, which can create hard dependency problems.
- in re-mix, you usually cast objects at runtime to access the methods of their mixins, which solves the dependency problem but limits the checking that can be done at compile-time. To do better, we'd have to use code generation (which is easy enough to code, but a PITA to use)
- a DCI-ready type system could go much further (I believe you'd have to give up on some of the dynamicism you often refer to, such as unmixin mixins at runtime, though)
- if a data object has a method foo (A a), and is used in a context where role R is bound to an object of type A, a good type system could map this method to foo (R r) in the role contract
- I'm sure there's more
I haven't had a real chance to look at ObjectTeams yet, I'd be excited to learn that it has some solutions to these problems. The bad news is that no single feature added to Java will solve these hard problems. I guess Stephan has a story or two about how hard it is to design such a language. Knowing the JCP, I don't know.
You mentioned you like Ant's syntax better than Qi4j's. What is it that you like less in Qi4j? Can't be the calling syntax. The annotations? The use of interfaces? The latter would lead to a separate debate (are explicit role interfaces or role contracts a good thing or a bad thing?)
> Now, I have been invited to give an inaugural Heart of Technology
> Lecture in Potsdam in March, 2012. It is likely that all of the
> relevant people will be there, and in particular, people from the AOP
> community. This is an opportunity to take this community's message
> forward. I'm checking with the organizer to see if we can make a small
> group presentation. But more than that, it will be an opportunity (I
> hope) to meet with the likes of Guy Steele and other key Java
> community members to present our case. It would be nice to have a
> concrete list of Java changes that would support our vision.
>
> Anyone who wants to go off and fulfill their vision of transforming
> the JRuby community is of course welcome to do so. My goal is to honor
> Trygve's DCI vision by opening the door to broad applicability, and to
> gain insights into the real programming problems of DCI by banging it
> up against one of the most broadly used programming platforms on earth.
I was under the impression that you'd rather look for the perfect DCI language than succumb to any existing platform, but I have no issue with designing a new Java. There should be a clear vision up-front though. Should it be something like C#'s dynamic, or do we try and go all the way to support DCI in the type system like a Java coder would expect it?
My starting point would be to code something in Java/Qi4j or C#/re-mix and then
- find an integrated syntax for all the annotation/attribute stuff
- one by one, find solutions for the leaks that these libraries leave open, like those I mentioned above
However, just look how much effort that was for Stephan: https://www.ohloh.net/p/objectteams
I can't think of a simple modification that makes Java a good fit for DCI. Even Scala does not solve the basic dependency problems.
> I think Ant is doing a good job of that, and if he doesn't deserve our
> support, he at least deserves not to be told to stop doing what he's
> doing.
I think if he asks for opinions, he deserves an honest answer. If he's looking for a research platform, fine with me. But knowing Ant I believe he has something immediately usable in mind, not?
But maybe I'm just missing the big breakthrough here. My reading was that Ant's version brings Java closer to dynamic languages (except for the strange syntax), but loses all advantages of static typing on the way. Is there anything else that I skipped over?
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of rune funch
> Sent: Friday, September 30, 2011 4:42 PM
> To: dci-ev...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>
> That's not my goal. I think that if you want to get a Java programmer to use DCI, you should either
> a) offer them a way to do it in Java that meets their expectations, including static type checking, or
> b) advise them to move to a dynamic language altogether.
It is not my goals to convert Java programmers. I actually think that is a lost cause, for many of the reasons that Ant has mentioned that people won't like his code.
I want to transform the Java leadership.
> This is not about transforming the JRuby/Jython/Groovy community. And obviously, I prefer a).
To each their own. My goal would be to get as much static type support in Java as I can for DCI. That will take some hard language design work. Scala, combined with Ant's work, and the Qi4j experience, combine to paint a hopeful picture. That's where I'm headed.
Love to hear that! Like I said, my interest is not in converting to dynamic, but in bringing enough compile-time type info for DCI to make the static typing struggle worthwhile.
Did you have a chance yet to check out OT/J? I would assume that it covers at least some of that ground?
I understand the difference in philosophy and in implementation, I have yet to see the impact this has on DCI code. With object injection, the methods are injected on-demand. With class injection, they are always there. One difference with class injection is that the compiler can verify that the class fulfills the role contract.
> > Have you solved the name scoping problems in your Java version?
>
> I think so, see my previous post. Or perhaps I don't know which
> scoping problem you are talking about. You mean if a role player is
> playing two roles which have identical methods, which one gets called?
> Yes, that is solved, see previous comments.
Found it, thanks. Problem is, you need to provide an optional parameter. If you omit this parameter, a conflict might arise at runtime, which is harder to find, plus you'll have to go back to every call and add the qualifier. At this point, the code might be compiled and out of your hands. (Assuming that composition occurs at deployment time or runtime.)
Might seem a bit reaching, but if you build a product or platform rather than a project, this is an issue.
> > You can also "unload" methods, but I'm still waiting for someone to
> explain to me how this solves any problem.
>
> If you mean remove the methods which were injected in a context, at
> the end of the context, then I am of the opinion that it is extremely
> important to do so.
>
> You add methods within a context, and that makes sense within that
> context. To leave those methods hanging on to the object after the
> context is complete is dangerous, because the objects from the
> context's interaction may go off in their directions and the context
> is missing in which they can interact. Why give the programmer the
> chance to do something stupid? I would rather remove the injected
> methods and let the object leave the context looking the way it came
> in, just with
> (potentially) different state. Much like an actor removes his makeup
> after the play has finished.
My take is that calls are only valid from inside the context, so if you can solve this problem (e.g. using visibility), you solved the other one too. OTOH, if your only requirement is that the context must be alive, you can still make calls from outside the context. Also, chances are that a solution to visibility will also solve name conflicts.
> A more concrete example: if you use my new Java, and you add a
> resource like a database to a role, and inject that role into an
> object, it could be fatal to then call a role-method on that role
> outside of the context, because the connection to the database might have been closed.
>
> > In DCI, isn't all your interesting code going to be in RoleMethods?
>
> See my Till example, or the Dijkstra example - some may be in the
> context itself.
>
> > It seems you're looking for some kind of compromise, but which parts
> would you keep static? Data classes only? Static typing is a PITL
> thing, you'd be using it everywhere expect in the code that matters
> "in the large".
>
> I want to keep as much static as I can. I like that about Java, and
> the tool support I get with it. But the argument about merged-
> interfaces in the Dijkstra example showed that sometimes you need to
> have the ability to be dynamic. In those cases, and only those cases,
> I want to leverage the dynamic capability.
>
> Mixing languages is an interesting idea, and I will consider it some
> more. The problem at the moment is that I don't think there are any
> tools which let you mix code within a project. E.g. in the IDE of my
> choice, can I create a class in a Java project, and instantiate it in
> a Groovy project? I'll look into it some more, as that could be a
> viable option... What I cannot do is write Groovy and Java in the
> same source file!
True. It's not an ideal solution.
> I've just been reading more about Groovy...
>
> Interestingly, MOP in Groovy is similar to what I just added to Java:
> http://groovy.codehaus.org/api/groovy/lang/MetaObjectProtocol.html#get
> M
> etaMethod%28java.lang.String,%20java.lang.Object[]%29
>
> That method is pretty much what I added to Java, without me knowing
> someone else had done it in Groovy... So what I have done is not as
> dumb as it looks. Great minds think alike ;-)
No, it's not dumb by any means. But even very clever ideas can lead to quite unsatisfying results.
> This forum is about the evolution of a DCI. I wasn't suggesting you
> take what I did and go to production with it. I was experimenting
> with what you'd have to do to Java to make it work. If we didn't
> experiment, we'd still be living in caves ;-)
Right, I think I got that part wrong.
Stefan
> Did you have a chance yet to check out OT/J? I would assume that it covers at least some of that ground?
Yes, I've looked at it pretty thoroughly. I'm not taking it much further because it isn't quite the right computational model. Identity is a crucial component of most type systems, and it is one of the three most important components of an OO type system. The identity model in OT/J isn't what I'm looking for.
> No, it's not dumb by any means. But even very clever ideas can lead to quite unsatisfying results.
And vice versa sometimes, too.
Trygve Reenskaug mailto: try...@ifi.uio.no
Morgedalsvn. 5A http://folk.uio.no/trygver/
N-0378 Oslo Tel: (+47) 22 49 57 27
Norway
> You add methods within a context, and that makes sense within that
> context. To leave those methods hanging on to the object after the
> context is complete is dangerous, because the objects from the
> context's interaction may go off in their directions and the context
> is missing in which they can interact. Why give the programmer the
> chance to do something stupid?
A good type-checking system would make it unnecessary to actually remove the methods. Such a type system could also accommodate name collisions that occur from injecting different roles over time.
This is, indeed, quite an elaborate type system, but I don't see anything about it that is intractable.
There are tradeoffs between making such a type-checking system dynamic or static.
It should be as simple as:
myFlyingPlane.fly();
If you write a compiler to generate byte code which does something
similar to what I did tonight, you'd have DCI in Java.
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of James O. Coplien [jcop...@gmail.com]
Sent: Friday, September 30, 2011 19:03
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
On Sep 30, 2011, at 5:20 , Wenig, Stefan wrote:
dynamic is a variable type, so whether you're able to access dynamic members depends on the variable type, not on the object type. This has at least two important consequences:
- it works for statically typed objects too, you just don't have to know the static type up-front.
- if you don't use 'dynamic' as a variable type, you still get static type safety for static members (it seems that in Scala, i.e. renaming a static method will reroute all calls to the dynamic interface, which seems wrong to me.
Interesting choice. I wonder what the rationale is.
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Friday, September 30, 2011 22:21
To: dci-evolution
Subject: Re: Roles in Eclipse Indigo
This is interesting:
http://squirrelsewer.blogspot.com/2011/02/scalas-upcoming-dynamic-capabilities.html
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Friday, September 30, 2011 23:20
To: dci-evolution
Subject: Re: Roles in Eclipse Indigo
I spent a few hours creating some really ugly code tonight.
But my argument is not that people won't use it, but that people who choose a static language will not want to use it in the heart of their systems.
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Friday, September 30, 2011 23:30
To: dci-evolution
Subject: Re: Roles in Eclipse Indigo
On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
Where would you see such a split in the context of DCI? Your own C#/dynamic samples where very short, so I could be totally wrong. But I'm expecting a DCI app in this style to consist almost exclusively of dynamic calls, so I don't see the advantage of using a static language anymore.
But a static language with a type system that can handle DCI would be very interesting!
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of rune funch [funchs...@gmail.com]
Sent: Saturday, October 01, 2011 07:05
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
> On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
then just make the role interfaces 'private' for a) or 'internal' for b) and you're fine.
(Bad news is that we need to rewrite the assemblies to do this, for technical reasons all interfaces must be 'public' otherwise)
So even a library like re-mix can get pretty close to this goal. For a native DCI language, it would be much easier. Visibility is a well understood concept.
I see a few conceptual challenges for a perfect static DCI language, but I really do think that this one here is actually easy.
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of James O. Coplien [jcop...@gmail.com]
Sent: Saturday, October 01, 2011 13:23
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
On Sep 30, 2011, at 4:41 , Ant Kutschera wrote:
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Saturday, October 01, 2011 23:30
To: dci-evolution
Subject: Re: Roles in Eclipse Indigo
On Oct 1, 7:36 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
As for 'null', we've learned to live with it, but it's not perfect.
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
Stefan
________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Saturday, October 01, 2011 23:43
To: dci-evolution
Subject: Re: Roles in Eclipse Indigo
A few weeks ago, I tried to argue that type declarations were
> So, for cases where you cannot tell if an object is playing a role or
> Not
If it's bound to a role it is if it's not bound it's not a) a
roleplayer and will therefor not be bound or b) part of the context
and will therefor not be bound. My point is it's possible up front to
say whether an object identified by an identifier in any line of code
is a RolePlayer or not. Or do you see this otherwise?
> Sure, there's a lot of interesting combinations. But some just don't make any sense. E.g., you don't want to code an app in some fancy new FullOO language and then write your queries in LINQ,
This I don't understand why wouldn't I want Linq in a fullOO language?
> Where would you see such a split in the context of DCI? Your own C#/dynamic samples where very short, so I could be totally wrong.
> But I'm expecting a DCI app in this style to consist almost exclusively of dynamic calls, so I don't see the advantage of using a static language anymore.
As I recall the example I was simply trying to show that you could
have implicit role contracts in C#. If that's the example you're
talking about you don't need more than one line of dynamically typed
code for each binding operation. What you loose is limited to one area
of the code namely the part where the context picks the RolePlayers.
Since the context is responsible for picking the objects there's low
risk of a type mismatch and since it's in a very limited part of the
code that I in general would expect to have few paths it's also cheap
to have a high path coverage in you testing. The rest of the code ie
non context code and role methods is statically typed
> But a static language with a type system that can handle DCI would be very interesting!
I'd say that Linq is an example of how it could be done
If you had a context with a role method called select then you be able
to decorate any object with of a given type with a locally scoped
method called select.
I'm not saying that you can build DCI on top of Linq (maybe you can
maybe not) I'm saying that the methodology methods are found for the
query translation can be used to build DCI. There's even rules about
resolving name conflicts. The pattern based approach can be used to
both support implicit but verifiable role contracts. In n new type
system I'd prefer to solve them differently though the same approach
can also be used to create a method invocation translation when the
method is invoked on a role and not a local. That translation would
take care of naming conflicts as well (as is the case in Linq where
it's however reversed compared to DCI)
What you loose is limited to one area
of the code namely the part where the context picks the RolePlayers.
Since the context is responsible for picking the objects there's low
risk of a type mismatch and since it's in a very limited part of the
code that I in general would expect to have few paths it's also cheap
to have a high path coverage in you testing. The rest of the code ie
non context code and role methods is statically typed
I think Context and Role code should be in the same file.
To have to think explicitly about things called roles and objects as part of any single mental model violates the way I look at DCI. I want DCI to add role behavior to an object — one set of roles to one object at a time — and to thereafter think only in terms of objects, in terms of their methodless role names. To bring in the name of the type of the methodful role (got that?) doesn't help. It's clutter.
I suppose some software-engineering minded person — the same kind of person who created Hungarian notation — would laud the explicitness. But it's below my type horizon. Most of the time I want to call an integer an integer. If the language forces me to explicitly differentiate between signed-ness and length of integers every time I use one, it's superfluous. Those attributes are indeed part of the type. And I can see the need for explicit qualification to disambiguate method name collisions (horrors! this should not happen within a Context, anyhow — that violates quite a number of Kent Beck's design patterns). Otherwise, it violates Occam's Razor for me.
On Oct 2, 6:23 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:And there's still the problem with contexts playing roles, you didn't answer that one.Yeah, I'm still thinking about it. I'll turn it round, and ask for a concrete example of when a context plays a role. Not just code, but the use case too please. I am struggling with the concept of why a context should be a role player. I know Trygve and James have also mentioned this in the past.
> Same namespace as in something like a Java package: yes.
> Same physical file? Not convinced.
>
> I don't mean to argue, but I don't think that's a keeper.
When the point is to keep the information in one place how are you
going to argue at all if the information is scattered amongst several
files?
> There might be reasons to separate Context and its Roles (that are integral part of one and just one Context) into different files, but I would be very interesting to see at least one _good_ reason to do that.
To me it's like trying to find an argument to why it might sometimes
be a good idea to declare members outside the class files. Roles are
nothing on their own.
I think it's probably related to whether or not a role is a type or
not. I don't think they are any more a type than a function is and to
some functions are types.
I'd say you and Feather need to discuss this in more depth, because
you seems to be pulling in opposite directions on this point.
In terms of long term evolution, we can wait for that answer.
In terms of here and now, my toy language and using DCI in Java, I'll
leave role namespaces in.
In my mind, a namespace containing just the context and role impls is
> In my mind, a namespace containing just the context and role impls is
> "one place".
It can be. But in the same sense, anything can be made to be "one place"— but only with tools. Namespaces are not a significant factor unless you have a rich tool empire to physically and contiguously render the namespace content in a single locus of attention.
I think the distinction is logical versus physical. As logical as our brains are, they are also physical, and have the 5 +/- 2 limitation. A logical grouping like namespaces is inadequate: you need a literal, physical grouping.
See Jef Raskin's discussion in his book, "Humane Interfaces" about the notion of a file — he approaches this issue from exactly the opposite end, in such an extreme way that it wraps around to be the same thing...
In a simplified world there'e to ways to appreciate code. You can appreciate it forwhat it does or how it does it. When you write code you can either write it declarative so that the what is clearer or you can write it imperative creating the need for the reader of the code to be able to analyse the code to figure out what it does. I find one of the most important aspects of DCI to be the readability of the code, including the ability to understand what it does without having to analyse the code.
In my mind, a namespace containing just the context and role impls is
"one place".
I can use keywords to give roles the correct visibility too.
Look at it this way: if the context contains several roles and all
that code results in a file with a thousand lines of code, that's not
good either. Sure the IDE could help, but do you really want to
prohibit people from using vi?
The keeper here is that roles are internal parts of contexts and the
two exist together. Nothing more.
I think it's probably related to whether or not a role is a type or not. I don't think they are any more a type than a function is and to some functions are types.
I don't find roles to have a type. Let alone a super type.
With no roles there's no context just bits.
It's not about accessability but readability.
Single Context with all its Roles would be definitely more convenient with plain text editor without special DCI tooling support.
> I'd say you and Feather need to discuss this in more depth, because
> you seems to be pulling in opposite directions on this point.
I don't know what Feather[s?] has to say about DCI.
If you really want to know what's going on, you should also write the name of the class of the object to which the role is bound. Why don't you do that? (I'm seeking an answer in the cognitive space rather than in the technical space.)
On Oct 3, 2011, at 9:15 , rune funch wrote:I think it's probably related to whether or not a role is a type or not. I don't think they are any more a type than a function is and to some functions are types.
Rune, I was taken aback earlier when you exclaimed:On Oct 2, 2011, at 9:05 , rune funch wrote:I don't find roles to have a type. Let alone a super type.
To me, a (methodful) role is the type of the object as accessed through the corresponding methodless role. Rune, you redeemed yourself later when you said (more or less):On Oct 3, 2011, at 10:33 , Rune Funch Søltoft wrote (more or less):With no roles there's no context just bits.
If you think of the concept of type in terms of its origins in mathematics around the semantics of set theory (classes were originally power sets), then a role is perfectly a type. The (methodful) role is the type of the object currently being operated on.
If a single context grows beyond 1000 lines of code I'd rather have its roles in individual files, packaged in a folder/namespace structure.
Whether a role is considered a type in a dynamic language is probably a much more academic question.
Stefan
On Mo, Okt 03, 2011 at 12:14:47, James O. Coplien wrote:
> Subject: Re: Roles in Eclipse Indigo
>
> I'm not up to it - I think one needs much more foundation in the
All true. Tooling restrictions aside, I would consider using partial classes for non-trivial nested types though. So instead of
class Outer {
class Inner {…}
…
}
in a single file, you’d get two files:
// Outer.cs:
partial class Outer {
…
}
// Outer.Inner.cs:
partial class Outer {
class Inner {…}
}
Still feels like a hack, but at least you don’t have files with 10.000s of lines… and several coders hacking at them concurrently.
For contexts and roles, I’d rather use folders and namespaces, but then I don’t get visibility restrictions within a context. And assemblies might be to heavyweight for many contexts.
// MyContext/Context.cs:
partial class MyContext {
private IRole1 role1;
someFacadeMethod() {…}
}
// MyContext/Role1.cs
partial class MyContext {
private interface IRole1 {…}
private class Role1 {
// role methods
}
}
(IRole1 and Role1 could also be split, as could a separate role contract interface if there should be one)
This reduces visibility of Role1 methods to a MyContext, and there’s no longer a need to dynamically add/remove methods to/from objects, no name conflicts… Still, a language with explicit DCI support could do much better.
Stefan
From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com]
On Behalf Of Rune Funch Søltoft
Sent: Monday, October 03, 2011 10:34 AM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.
-Rune
I hope a get a chance to look at it in more detail soon. How do you think it compares with Qi4j specifically? My take (after a very short look):
+ no name conflicts
+ no compile-time dependencies on a single list of roles per data class
- more verbose call syntax
- callRole will potentially fail for every single call (whereas Qi4j gives you a single type that combines all methods, and you only have to check when binding)
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Sunday, October 02, 2011 11:38 AM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>
> I've done it! Java is now fully DCI compliant, and in doing so
> supports Java's type checker as well as full IDE integration.
>
> Quote 1:
>
> On Sep 30, 5:32 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> > > > Have you solved the name scoping problems in your Java version?
> >
> > > I think so, see my previous post. Or perhaps I don't know which
> > > scoping problem you are talking about. You mean if a role player
> is
> > > playing two roles which have identical methods, which one gets
> called?
> > > Yes, that is solved, see previous comments.
> >
> > Found it, thanks. Problem is, you need to provide an optional
> parameter. If you omit this parameter, a conflict might arise at
> runtime, which is harder to find, plus you'll have to go back to every
> call and add the qualifier. At this point, the code might be compiled
> and out of your hands. (Assuming that composition occurs at deployment
> time or runtime.)
>
> Quote 2:
>
> On Oct 2, 11:01 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> > Yes, that's even better. Qualify the name of a role method with its
> role
> > name. Possibly also its Context name. This ties in with my new idea
> for
> > an "ideal" DCI method execution model. (In principle simple, but
> waiting
> > to get on the top of my stack to be written down)
> >
> > On 2011.09.30 23:28, Ant Kutschera wrote:
> > > I think the following might be even better:
> > >
> > > String s = domainObject#SomeRole.roleMethod(parameter1,
> parameter2);
>
>
> Stefan & Trygve, you are geniuses! When I read this, my first thought
> was "hey, ok, so we always need to fully qualify the role method we
> are calling".
>
> And in doing that, I have been able to make my little extension to
> Java provide full type safety and IDE integration!
>
> Last night I posted that the ideal role method call might look like
> this:
>
> //Listing 1
> String s = domainObject#SomeRole.roleMethod(parameter1,
> parameter2);
>
> (SomeRole only exists within a given Context, so there is no need to
> add that before the role name - the compiler wouldn't let me anyway,
> if the role was out of scope)
>
> So I was about a quarter way into adding that syntax to the OpenJKD
> compiler, when it struck me. Why not just do it like this:
>
> //Listing 2
> String s =
> myDomainObject.callRole(SomeRole.class).aRoleMethod(parameter1,
> parameter2);
>
> This syntax is a little uglier than my proposal with the "#" symbol,
> but it is far better than the syntax I proposed a few days ago, where
> the method to call is provided using a string. Does anyone have any
> suggestions as to how to make the utility method name "callRole"
> better?
>
> I've been able to update my framework so that I can write exactly that
> shown in listing 2, and it works great. The compiler is able to tell
> me if parameter types are wrong, code-complete offers me all the role
> methods on "SomeRole". The IDE can do refactoring, just like it can
> on any other class. The IDE can jump into role methods when I hit F3,
> just like it can on normal Java methods. It's perfect, from the type
> checking and IDE points of view.
>
> So, below is some test code to show how it all works.
>
> After that, is Dijkstra's Algorithm, implemented with the new
> framework, and again, semantically it is identical to that done in
> Ruby, syntactically it is a little different, but much better than
> what I posted a few days ago, because the compiler and tools help you
> in the same way they help you when you write normal Java code.
>
> Those interested in how I implemented this, check the third section
> below - there are three classes: DomainObject, Context, Role. The
> mechanisms are a little different than that described a few days ago.
> But importantly, context stacking is supported, as well as the ability
> to "unassign" roles when a context is finished with, so that those
> roles go out of scope.
>
> What do you think?
>
>
>
> ----- test code -----
>
> /** a domain class - dumb */
> class Cart extends DomainObject {
> private String name;
> private int numWheels;
> public Cart(String name, int numWheels){
> this.name = name;
> this.numWheels = numWheels;
> }
> }
>
> /** a context */
> class TravelContext extends Context {
>
> Date travelDate;
>
> /** a role */
> class Plane extends Role<Cart, TravelContext> {
> void fly(){
> self.setData("wingsExtended", true);
> log("I'm flying on the " + new SimpleDateFormat("dd
> MMM yyyy").format(context.travelDate) + " man!!");
> }
> }
>
> /** a role */
> class Car extends Role<Cart, TravelContext> {
> void drive(){
> log("Only driving now...");
> }
>
> void aMethodWithParameters(Integer a, Integer b){
> log("aMethodWithParameters called with " + a + " and "
> + b);
> }
>
> int aMethodWithReturnAndParameters(Integer a, Integer b){
> int result = a + b;
> log("aMethodWithReturnAndParameters called with " + a
> + " and " + b + ", returning " + result);
> return result;
> }
>
> int aMethodWithReturnButNoParameters(){
> int result = 69;
> log("aMethodWithReturnButNoParameters called,
> returning 69");
> return result;
> }
>
> void aMethodWithNoReturnAndNoParameters(){
> log("aMethodWithNoReturnAndNoParameters called");
> }
>
> }
>
> /** context constructor and execution
> * @param log */
> TravelContext(Cart myFlyingCar) throws ParseException {
>
> travelDate = new
> SimpleDateFormat("yyyyMMdd").parse("20111002");
>
> //lets check its not already playing the role in this
> context
> assertFalse(myFlyingCar.isPlayingRole(Car.class));
> assertFalse(myFlyingCar.isPlayingRole(Plane.class));
>
> bind(myFlyingCar, Car.class);
> bind(myFlyingCar, Plane.class);
>
> //lets check it is now playing the role in this context
> assertTrue(myFlyingCar.isPlayingRole(Car.class));
> assertTrue(myFlyingCar.isPlayingRole(Plane.class));
>
> log("My cool transport is called: " + myFlyingCar.name + "
> and it has " + myFlyingCar.numWheels + " wheels.");
>
> myFlyingCar.callRole(Car.class).drive();
>
> myFlyingCar.callRole(Plane.class).fly();
>
> log("Are the wings are now extended? " +
> myFlyingCar.getData("wingsExtended"));
>
> myFlyingCar.callRole(Car.class).aMethodWithParameters(5,
> 4);
>
> int n =
> myFlyingCar.callRole(Car.class).aMethodWithReturnAndParameters(6, 7);
> assertTrue(n == 13);
>
> n =
> myFlyingCar.callRole(Car.class).aMethodWithReturnButNoParameters();
> assertTrue(n == 69);
>
>
> myFlyingCar.callRole(Car.class).aMethodWithNoReturnAndNoParameters();
>
> //I can't write a test here to prove there is no object
> schizophrenia,
> //because there is only the single object and no
> wrappers!!
>
> cleanup(); // housekeeping related to context stacking
> }
>
> }
>
> @Test
> public void test() throws ParseException {
>
> log.clear();
>
> Cart myCart = new Cart("betty", 4);
>
> //create and run context in one step
> new TravelContext(myCart);
>
> assertTrue(myCart.getData("wingsExtended") == null); //after
> the context, the field no longer exists on the object
>
> System.out.println(log);
>
> assertEquals(16, log.size());
> int i = 0;
> assertEquals("My cool transport is called: betty and it has 4
> wheels.", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("Only driving now...", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("I'm flying on the 02 Okt 2011 man!!", log.get(i+
> +));
> assertEquals("\r\n", log.get(i++));
> assertEquals("Are the wings are now extended? true", log.get(i+
> +));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithParameters called with 5 and 4",
> log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithReturnAndParameters called with 6 and
> 7, returning 13", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithReturnButNoParameters called,
> returning 69", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithNoReturnAndNoParameters called",
> log.get(i++));
> assertEquals("\r\n", log.get(i++));
>
> assertFalse(myCart.isPlayingRole(Car.class));
> assertFalse(myCart.isPlayingRole(Plane.class));
> }
>
> }
>
> ----- djikstra solution -----
>
> package three;
> import java.util.ArrayList;
> import java.util.Collections;
> import java.util.HashMap;
> import java.util.List;
> import java.util.Map;
>
> import ch.maxant.dci.util2.Context;
> import ch.maxant.dci.util2.DomainObject;
> import ch.maxant.dci.util2.Role;
>
> /**
> * here, I have removed need to use a type declaration for a merged
> interface.
> * there is no merged interface. I only ever refer to objects by
> their actual data
> * type.
> *
> * Oh, and I have solved the object schizophrenia problem too.
> */
> public class Runner {
>
> ////////////////////////////////////////////////////////////////
>
> static class Pair {
> private Object a;
> private Object b;
>
> public Pair(Object a, Object b) {
> this.a = a;
> this.b = b;
> }
>
> @Override
> public int hashCode() {
> final int prime = 31;
> int result = 1;
> result = prime * result + ((a == null) ? 0 :
> a.hashCode());
> result = prime * result + ((b == null) ? 0 :
> b.hashCode());
> return result;
> }
>
> @Override
> public boolean equals(Object obj) {
> if (this == obj)
> return true;
> if (obj == null)
> return false;
> if (getClass() != obj.getClass())
> return false;
> Pair other = (Pair) obj;
> if (a == null) {
> if (other.a != null)
> return false;
> } else if (!a.equals(other.a))
> return false;
> if (b == null) {
> if (other.b != null)
> return false;
> } else if (!b.equals(other.b))
> return false;
> return true;
> }
>
> @Override
> public String toString() {
> return "Pair [" + a + ", " + b + "]";
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * use less than infinity, otherwise some of the calcs go wrong,
> * when we add tentative distances to actual distances and we end
> * up with negative numbers, because weve gone over max INT.
> */
> static final Integer INFINITY = Integer.MAX_VALUE - 10000;
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * "Map" as in cartography rather than Computer Science...
> *
> * Map is technically a role from the DCI perspective. The role
> * in this example is played by an object representing a
> particular
> * Manhattan geometry
> */
> static class CartographyMap extends Role<Geometry, Object> {
>
> Integer distance_between(Node a, Node b) {
> return self.getDistances().get(new Pair(a, b));
> }
>
> // These two functions presume always travelling
> // in a southern or easterly direction
>
> Node next_down_the_street_from(Node node) {
> return self.east_neighbor_of(node);
> }
>
> Node next_along_the_avenue_from(Node node) {
> return self.south_neighborOf(node);
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * There are four roles in the algorithm: CurrentIntersection
> (@current)
> * EastNeighbor, which lies DIRECTLY to the east of
> CurrentIntersection
> * (@east_neighbor) SouthernNeighbor, which is DIRECTLy to its
> south
> * (@south_neighbor) Destination, the target node (@destination)
> *
> * We also add a role of Map (@map) as the oracle for the geometry
> *
> * The algorithm is straight from Wikipedia:
> *
> * http://en.wikipedia.org/wiki/Dijkstra's_algorithm
> *
> * and reads directly from the distance method, below
> *
> * (use context type "Object" because this role is found in
> several contexts - we still need to think how to handle duplicate code
> in a better way...)
> */
> static class Distance_labeled_graph_node extends Role<Node,
> Object> {
>
> /*
> * NOTE: This role creates a new data member in the node into
> * which it is injected. An alernative implementation
> would
> * be to use a separate associative array
> */
>
> void set_tentative_distance_to(Integer x) {
> self.setData("tentative_distance", x);
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * Consider street corners on a Manhattan grid. We want to find
> the
> * minimal path from the most northeast city to the most
> * southeast city. Use Dijstra's algorithm
> *
> * Data class
> *
> * Note there is NO NEED to implement hashCode or equals!
> */
> static class Node extends DomainObject {
>
> private String name;
>
> public Node(String name) {
> this.name = name;
> }
>
> public String getName() {
> return name;
> }
>
> /** only done for debugging purposes */
> @Override
> public String toString() {
> return "Node[name=" + name + ", hashCode=" + hashCode() +
> "]";
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * This is the main Context for shortest path calculation
> */
> static class CalculateShortestPath extends Context {
>
> //These are handles to internal housekeeping arrays set up in
> initialize
>
> Map<Node, Boolean> unvisited = new HashMap<Node, Boolean>();
> Map<Node, Node> pathTo;
> Node east_neighbor;
> Node south_neighbor;
> List<Node> path;
> Geometry map;
> Node current;
> Node destination;
>
> // Initialization
>
> void rebind(Node origin_node, Geometry geometries){
> current = origin_node;
> map = geometries;
>
> bind(map, CartographyMap.class);
>
> bind(current, CurrentIntersection.class);
>
> east_neighbor = map.east_neighbor_of(origin_node);
>
> for(Node n : geometries.nodes()){
> bind(n, Distance_labeled_graph_node.class);
> }
>
> if(east_neighbor != null){
> bind(east_neighbor, Neighbor.class);
> }
>
> south_neighbor = map.south_neighborOf(origin_node);
>
> if(south_neighbor != null){
> bind(south_neighbor, Neighbor.class);
> }
> }
>
> /**
> * public initialize. It's overloaded so that the public
> version doesn't
> * have to pass a lot of crap; the initialize method takes
> care of
> * setting up internal data structures on the first
> invocation. On
> * recursion we override the defaults
> */
> public CalculateShortestPath(Node origin_node, Node
> target_node, Geometry geometries, List<Node> path_vector,
> Map<Node, Boolean> unvisited_hash, Map<Node, Node>
> pathto_hash) {
>
> destination = target_node;
>
> rebind(origin_node, geometries);
>
> // This has to come after rebind is done
> if (path_vector == null) {
>
> // This is the fundamental data structure for
> Dijkstra's algorithm,
> // called
> // "Q" in the Wikipedia description. It is a boolean
> hash that maps
> // a
> // node onto false or true according to whether it has
> been visited
> this.unvisited = new HashMap<Node, Boolean>();
>
> // These initializations are directly from the
> description of the
> // algorithm
> for (Node n : geometries.getNodes()) {
> this.unvisited.put(n, Boolean.TRUE);
>
> n.callRole(Distance_labeled_graph_node.class).set_tentative_distance_to
> (INFINITY);
> }
>
>
> current.callRole(Distance_labeled_graph_node.class).set_tentative_dista
> nce_to(0);
>
> this.unvisited.remove(origin_node);
>
> // The path array is kept in the outermost context and
> serves to
> // store the
> // return path. Each recurring context may add
> something to the
> // array along
> // the way. However, because of the nature of the
> algorithm,
> // individual
> // Context instances don't deliver "partial paths" as
> partial
> // answers.
> this.path = new ArrayList<Node>();
>
> // The pathTo map is a local associative array that
> remembers the
> // arrows between nodes through the array and erases
> them if we
> // re-label a node with a shorter distance
> this.pathTo = new HashMap<Node, Node>();
>
> } else {
>
> this.unvisited = unvisited_hash;
> this.path = path_vector;
> this.pathTo = pathto_hash;
> }
>
> execute();
> }
>
> class CurrentIntersection extends Role<Node,
> CalculateShortestPath> {
>
> List<Node> unvisited_neighbors() {
>
> //WATCHOUT: moved the access to data from the context,
> from outside this method,
> //to in inside it, otherwise we are introducing state
> to the role
> Map<Node, Boolean> unvisited = context.unvisited;
> Node south_neighbor = context.south_neighbor;
> Node east_neighbor = context.east_neighbor;
>
> List<Node> retval = new ArrayList<Node>();
> if (south_neighbor != null) {
> Boolean addIt = unvisited.get(south_neighbor);
> if (addIt == Boolean.TRUE) { //watch out, addIt
> can be null apparently
> retval.add(south_neighbor);
> }
> }
> if (east_neighbor != null) {
> Boolean addIt = unvisited.get(east_neighbor);
> if (addIt == Boolean.TRUE) { //watch out, addIt
> can be null apparently
> retval.add(east_neighbor);
> }
>
> }
> return retval;
> }
> }
>
> /**
> * This module serves to provide the methods both for the
> east_neighbor and south_neighbor roles
> */
> class Neighbor extends Role<Node, CalculateShortestPath> {
>
> boolean relable_node_as(Integer x) {
> if (x < (Integer)self.getData("tentative_distance")) {
>
>
> self.callRole(Distance_labeled_graph_node.class).set_tentative_distance
> _to(x);
> return true;
> } else {
> return false;
> }
> }
> }
>
> /**
> * This is the method that does the work. Called from
> initialize
> */
> public void execute() {
> // Calculate tentative distances of unvisited neighbors
> List<Node> unvisited_neighbors =
> current.callRole(CurrentIntersection.class).unvisited_neighbors();
> if (unvisited_neighbors != null) {
> for (Node neighbor : unvisited_neighbors) {
>
> Integer tentativeDistance = (Integer)
> current.getData("tentative_distance");
> Integer distanceBetween =
> map.callRole(CartographyMap.class).distance_between(current,
> neighbor);
> boolean relable_node_as =
> neighbor.callRole(Neighbor.class).relable_node_as(tentativeDistance +
> distanceBetween);
>
> if (relable_node_as) {
> pathTo.put(neighbor, current);
> }
> }
> }
>
> unvisited.remove(current);
>
> // Are we done?
>
> if (unvisited.size() == 0) {
> save_path(this.path);
> } else {
>
> // The next current node is the one with the least
> distance in the
> // unvisited set
>
> Node selection = nearest_unvisited_node_to_target();
>
> // Recur
> new CalculateShortestPath(selection, destination, map,
> path, unvisited, pathTo);
> }
> }
>
> Node nearest_unvisited_node_to_target() {
>
> int min = INFINITY;
> Node selection = null;
>
> for (Node intersection : unvisited.keySet()) {
> if (unvisited.get(intersection)) {
>
> if(intersection.getData("tentative_distance",
> Integer.class) <= min) {
>
> min =
> intersection.getData("tentative_distance", Integer.class);
> selection = intersection;
> }
> }
> }
> return selection;
> }
>
> /**
> * This method does a simple traversal of the data structures
> (following
> * pathTo) to build the directed traversal vector for the
> minimum path
> */
> void save_path(List<Node> pathVector) {
>
> Node node = destination;
> do {
> pathVector.add(node);
>
> node = pathTo.get(node);
>
> } while (node != null);
> }
>
> public List<Node> getPath() {
> return path;
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
>
> /**
> * This is the main Context for shortest distance calculation
> */
> static class CalculateShortestDistance extends Context {
>
> List<Node> path = new ArrayList<Node>();
> Geometry map;
> Node destination;
> Node current;
>
> //MAP ROLE: SEE COMMON CODE NEAR TOP
> //DISTANCE LABELED GRAPH NODE: SEE COMMON CODE NEAR TOP
>
> void rebind(Node origin_node, Geometry geometries){
> current = origin_node;
> destination = geometries.destination();
> map = geometries;
>
> bind(map, CartographyMap.class);
>
> for(Node node : map.nodes()){
> bind(node, Distance_labeled_graph_node.class);
> }
> }
>
> public CalculateShortestDistance(Node origin_node, Node
> target_node, Geometry geometries) {
>
> rebind(origin_node, geometries);
>
>
> this.current.callRole(Distance_labeled_graph_node.class).set_tentative_
> distance_to(0);
>
> this.path = new CalculateShortestPath(this.current,
> this.destination, geometries, null, null, null).getPath();
>
> cleanup(); //related to context stacking
> }
>
> public int distance() {
> int retval = 0;
> Node previous_node = null;
>
> List<Node> reversed = new ArrayList<Node>(path);
> Collections.reverse(reversed);
>
> for (Node node : reversed) {
>
> if (previous_node == null) {
> retval = 0;
> } else {
> retval +=
> this.map.callRole(CartographyMap.class).distance_between(previous_node,
> node);
> }
> previous_node = node;
> }
> return retval;
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> static abstract class Geometry extends DomainObject {
>
> List<Node> nodes;
> Node root;
> Node destination;
> Map<Pair, Integer> distances;
> Map<Node, Node> next_down_the_street_from = new HashMap<Node,
> Node>();
> Map<Node, Node> next_along_the_avenue_from = new HashMap<Node,
> Node>();
>
> public Node east_neighbor_of(Node a) {
> return next_down_the_street_from.get(a);
> }
>
> public Node south_neighborOf(Node a) {
> return next_along_the_avenue_from.get(a);
> }
>
> public Node root() {
> return root;
> }
>
> public Node destination() {
> return destination;
> }
>
> public List<Node> nodes() {
> return nodes;
> }
>
> public Node getRoot() {
> return root;
> }
>
> public Node getDestination() {
> return destination;
> }
>
> public List<Node> getNodes() {
> return nodes;
> }
>
> public Map<Pair, Integer> getDistances() {
> return distances;
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> static class ManhattanGeometry1 extends Geometry {
>
> public ManhattanGeometry1() {
> this.nodes = new ArrayList<Node>();
> this.distances = new HashMap<Pair, Integer>();
>
> String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
> "i" };
>
> for (int i = 0; i < 3; i++) {
> for (int j = 0; j < 3; j++) {
> this.nodes.add(new Node(names[(i * 3) + j]));
> }
> }
>
> // Aliases to help set up the grid. Grid is of Manhattan
> form:
> //
> // a - 2 - b - 3 - c
> // | | |
> // 1 2 1
> // | | |
> // d - 1 - e - 1 - f
> // | |
> // 2 4
> // | |
> // g - 1 - h - 2 - i
> //
> Node a = this.nodes.get(0);
> root = a;
> Node b = this.nodes.get(1);
> Node c = this.nodes.get(2);
> Node d = this.nodes.get(3);
> Node e = this.nodes.get(4);
> Node f = this.nodes.get(5);
> Node g = this.nodes.get(6);
> Node h = this.nodes.get(7);
> Node i = this.nodes.get(8);
> destination = i;
>
> for (int s = 0; s < 3; s++) {
> for (int t = 0; t < 3; t++) {
> this.distances.put(new Pair(nodes.get(s),
> nodes.get(t)), INFINITY);
> }
> }
>
> distances.put(new Pair(a, b), 2);
> distances.put(new Pair(b, c), 3);
> distances.put(new Pair(c, f), 1);
> distances.put(new Pair(f, i), 4);
> distances.put(new Pair(b, e), 2);
> distances.put(new Pair(e, f), 1);
> distances.put(new Pair(a, d), 1);
> distances.put(new Pair(d, g), 2);
> distances.put(new Pair(g, h), 1);
> distances.put(new Pair(h, i), 2);
> distances.put(new Pair(d, e), 1);
>
> distances = Collections.unmodifiableMap(distances);
>
> next_down_the_street_from.put(a, b);
> next_down_the_street_from.put(b, c);
> next_down_the_street_from.put(d, e);
> next_down_the_street_from.put(e, f);
> next_down_the_street_from.put(g, h);
> next_down_the_street_from.put(h, i);
> next_down_the_street_from = Collections
> .unmodifiableMap(next_down_the_street_from);
>
> next_along_the_avenue_from.put(a, d);
> next_along_the_avenue_from.put(b, e);
> next_along_the_avenue_from.put(c, f);
> next_along_the_avenue_from.put(d, g);
> next_along_the_avenue_from.put(f, i);
>
> next_along_the_avenue_from = Collections
> .unmodifiableMap(next_along_the_avenue_from);
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> static class ManhattanGeometry2 extends Geometry {
>
> public ManhattanGeometry2() {
> this.nodes = new ArrayList<Node>();
> this.distances = new HashMap<Pair, Integer>();
>
> String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
> "i", "j", "k" };
>
> for (int j = 0; j < 11; j++) {
> nodes.add(new Node(names[j]));
> }
>
> // Aliases to help set up the grid. Grid is of Manhattan
> form:
> //
> // a - 2 - b - 3 - c - 1 - j
> // | | | |
> // 1 2 1 |
> // | | | |
> // d - 1 - e - 1 - f 1
> // | | |
> // 2 4 |
> // | | |
> // g - 1 - h - 2 - i - 2 - k
> //
> Node a = nodes.get(0);
> root = a;
> Node b = nodes.get(1);
> Node c = nodes.get(2);
> Node d = nodes.get(3);
> Node e = nodes.get(4);
> Node f = nodes.get(5);
> Node g = nodes.get(6);
> Node h = nodes.get(7);
> Node i = nodes.get(8);
> Node j = nodes.get(9);
> Node k = nodes.get(10);
> destination = k;
>
> for (int s = 0; s < 11; s++) {
> for (int t = 0; t < 11; t++) {
> distances.put(new Pair(nodes.get(s),
> nodes.get(t)),
> INFINITY);
> }
> }
>
> distances.put(new Pair(a, b), 2);
> distances.put(new Pair(b, c), 3);
> distances.put(new Pair(c, f), 1);
> distances.put(new Pair(f, i), 4);
> distances.put(new Pair(b, e), 2);
> distances.put(new Pair(e, f), 1);
> distances.put(new Pair(a, d), 1);
> distances.put(new Pair(d, g), 2);
> distances.put(new Pair(g, h), 1);
> distances.put(new Pair(h, i), 2);
> distances.put(new Pair(d, e), 1);
> distances.put(new Pair(c, j), 1);
> distances.put(new Pair(j, k), 1);
> distances.put(new Pair(i, k), 2);
>
> distances = Collections.unmodifiableMap(distances);
>
> next_down_the_street_from.put(a, b);
> next_down_the_street_from.put(b, c);
> next_down_the_street_from.put(c, j);
> next_down_the_street_from.put(d, e);
> next_down_the_street_from.put(e, f);
> next_down_the_street_from.put(g, h);
> next_down_the_street_from.put(h, i);
> next_down_the_street_from.put(i, k);
>
> next_down_the_street_from = Collections
> .unmodifiableMap(next_down_the_street_from);
>
> next_along_the_avenue_from.put(a, d);
> next_along_the_avenue_from.put(b, e);
> next_along_the_avenue_from.put(c, f);
> next_along_the_avenue_from.put(d, g);
> next_along_the_avenue_from.put(f, i);
> next_along_the_avenue_from.put(j, k);
>
> next_along_the_avenue_from = Collections
> .unmodifiableMap(next_along_the_avenue_from);
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> /** Test drivers */
> public static void main(String[] args) {
>
> Geometry geometries = new ManhattanGeometry1();
>
> CalculateShortestPath path = new
> CalculateShortestPath(geometries.getRoot(),
> geometries.getDestination(), geometries, null, null,
> null);
>
> System.out.println("Path is: ");
> for (Node node : path.getPath()) {
> System.out.println(node.getName());
> }
>
> System.out.println("distance is "
> + new CalculateShortestDistance(geometries.getRoot(),
> geometries.getDestination(),
> geometries).distance());
>
> System.out.println();
>
> geometries = new ManhattanGeometry2();
>
> path = new CalculateShortestPath(geometries.getRoot(),
> geometries.getDestination(), geometries, null, null,
> null);
>
> System.out.println("Path is: ");
> Node last_node = null;
> for (Node node : path.getPath()) {
> if (last_node != null) {
> System.out.print(" - "
> + geometries.distances.get(new Pair(node,
> last_node))
> + " - ");
> }
> System.out.print(node.getName());
> last_node = node;
> }
>
> System.out.println();
> System.out.println("distance is "
> + new CalculateShortestDistance(geometries.getRoot(),
> geometries.getDestination(),
> geometries).distance());
> }
>
> }
>
> ---- output -----
>
> Path is:
> i
> h
> g
> d
> a
> distance is 6
>
> Path is:
> k - 1 - j - 1 - c - 3 - b - 2 - a
> distance is 7
>
>
> ----- framework impl -----
>
> /*
> * Copyright (c) 2011 Ant Kutschera
> *
> * This file is part of Ant Kutschera's blog.
> *
> * This is free software: you can redistribute it and/or modify
> * it under the terms of the Lesser GNU General Public License as
> published by
> * the Free Software Foundation, either version 3 of the License, or
> * (at your option) any later version.
> *
> * This software 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
> * Lesser GNU General Public License for more details.
> * You should have received a copy of the Lesser GNU General Public
> License
> * along with this software. If not, see <http://www.gnu.org/licenses/
> >.
> */
> package ch.maxant.dci.util2;
>
>
> /**
> * Here are some rules to consider when implementing roles:<br>
> * <br>
> * 1) roles must NEVER EVER use "this". Instead they should always
> use "self".
> * <br><br>
> * 2) role methods should NEVER EVER use primative types as parameter
> types. e.g. "int" as a parameter will not work.<br>
> * use Integer instead. e.g.: <br>
> * <br>
> * <code>void someRoleMethod(Integer anInt, String someString){...}
> </code><br>
> *
> * @param <S> the type or interface which domain objects playing this
> role must be. used to provide the correct type to the
> * field "self" which subclasses should use in the methods
> they provide. "this" should NEVER BE USED!
> * @param <C> the type of the context to which this role belongs.
> used to provide the correct type on the field "context" which
> * role implementations have access to.
> */
> public class Role<S, C> {
>
> /** the object playing this role */
> protected S self;
>
> /** the context owning this role */
> protected C context;
>
> //not sure this is needed. only if in a role impl, you type
> something like
> // "this.equals(that)" - but you shouldn't use "this" in a role
> impl, only "self"
> @Override
> public int hashCode() {
> return self.hashCode();
> }
>
> //not sure this is needed. only if in a role impl, you type
> something like
> // "this.equals(that)" - but you shouldn't use "this" in a role
> impl, only "self"
> @Override
> public boolean equals(Object obj) {
> if(self == null){
> if(obj == null){
> return true;
> }else{
> return false;
> }
> }else{
> return self.equals(obj);
> }
> }
>
> @SuppressWarnings("unchecked")
> public Role() {
> try {
> Context ctx = Helper.getContext();
> this.context = (C)ctx;
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> }
> }
>
> }
>
>
> /*
> * Copyright (c) 2011 Ant Kutschera
> *
> * This file is part of Ant Kutschera's blog.
> *
> * This is free software: you can redistribute it and/or modify
> * it under the terms of the Lesser GNU General Public License as
> published by
> * the Free Software Foundation, either version 3 of the License, or
> * (at your option) any later version.
> *
> * This software 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
> * Lesser GNU General Public License for more details.
> * You should have received a copy of the Lesser GNU General Public
> License
> * along with this software. If not, see <http://www.gnu.org/licenses/
> >.
> */
> package ch.maxant.dci.util2;
>
> import java.lang.reflect.Field;
> import java.util.HashMap;
> import java.util.HashSet;
> import java.util.Map;
> import java.util.Set;
> import java.util.Stack;
>
> import javax.annotation.Resource;
>
> /**
> * DCI contexts must inherit from this class
> */
> public abstract class Context {
>
> private static final ThreadLocal<Stack<Context>> contextStacks =
> new ThreadLocal<Stack<Context>>();
> static {
> contextStacks.set(new Stack<Context>());
> }
>
> /** resources, which will be injected into roles as required. */
> private Map<String, Object> resources = new HashMap<String,
> Object>();
>
> /** so that we can clean up, we keep a list of domain objects that
> are assigned roles */
> private Map<DomainObject, Set<Class<? extends Role<?,?>>>>
> domainObjects = new HashMap<DomainObject, Set<Class<? extends Role<?,?
> >>>>();
>
> public Context() {
> contextStacks.get().push(this);
> }
>
> /**
> * add a resource which can be injected into the role.
> * <br><br>
> * in the role, there may be a requirement to use say an {@link
> EntityManager}
> * in order to persist a new part of the domain model. the entity
> manager could theoretically
> * be passed to the role after its contruction, but the entity
> manager has nothing to do
> * with the users mental model - its a technical thing. so simply
> let it be injected, and available,
> * should it be required.
> * <br><br>
> * to use this, the current implementation looks for fields with
> the "name" you pass. any such
> * fields in either the role class, or any of its super classes,
> which are marked with {@link Resource}
> * get injected.
> */
> public void addResource(String name, Object resource){
> resources.put(name, resource);
> }
>
> /**
> * assign an object a role. if its already in that role, nothing
> happens!
> * @param object the object to play the role
> * @param roleClass the role to play
> */
> public void bind(DomainObject object, Class<? extends Role<?,?>>
> roleClass){
>
> Set<Class<? extends Role<?,?>>> roles =
> domainObjects.get(object);
> if(roles == null){
> roles = new HashSet<Class<? extends Role<?,?>>>();
> domainObjects.put(object, roles);
> }
> roles.add(roleClass);
> }
>
> /** MUST BE CALLED WHEN A CONTEXT IS FINSHED WITH */
> protected void cleanup(){
>
> //cleanup needs to pop contexts, so that the context is the
> previous one
>
> Stack<Context> contextStack = contextStacks.get();
> contextStack.pop();
>
> resources.clear();
> resources = null; //help the GC
>
> if(contextStack.isEmpty()){
> //clear all temp data on the object
> try {
> Field f =
> DomainObject.class.getDeclaredField("tempData");
> f.setAccessible(true);
> for(DomainObject o : domainObjects.keySet()){
> @SuppressWarnings("rawtypes")
> Map m = (Map)f.get(o);
> m.clear();
> }
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> }
> }
>
> domainObjects.clear();
> domainObjects = null; //help the GC
> }
>
> }
>
>
>
> /*
> * Copyright (c) 2011 Ant Kutschera
> *
> * This file is part of Ant Kutschera's blog.
> *
> * This is free software: you can redistribute it and/or modify
> * it under the terms of the Lesser GNU General Public License as
> published by
> * the Free Software Foundation, either version 3 of the License, or
> * (at your option) any later version.
> *
> * This software 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
> * Lesser GNU General Public License for more details.
> * You should have received a copy of the Lesser GNU General Public
> License
> * along with this software. If not, see <http://www.gnu.org/licenses/
> >.
> */
> package ch.maxant.dci.util2;
>
> import java.lang.annotation.Annotation;
> import java.lang.reflect.Constructor;
> import java.lang.reflect.Field;
> import java.lang.reflect.InvocationTargetException;
> import java.util.HashMap;
> import java.util.Map;
> import java.util.Set;
>
> import javax.annotation.Resource;
>
> import ch.maxant.dci.util2.Role;
>
> /**
> * domain classes should extend this
> */
> public class DomainObject {
>
> /** key = field name, value = role name */
> private Map<String, Object> tempData = new HashMap<String,
> Object>();
>
> /** lets you set extra data which you have added to this object,
> for the lifetime of oldest parent context. */
> public Object getData(String name){
> return tempData.get(name);
> }
>
> /** lets you set extra data which you have added to this object,
> for the lifetime of the context */
> @SuppressWarnings("unchecked")
> public <T> T getData(String name, Class<T> returnType) {
> return (T)getData(name);
> }
>
> /** lets you set extra data on this object, for the lifetime of
> the context */
> public void setData(String name, Object data){
> tempData.put(name, data);
> }
>
> /** to call a role method on a role, call this method first */
> @SuppressWarnings("unchecked")
> public <T> T callRole(Class<T> roleClass) {
>
> if(roleClass == null){
> throw new NullPointerException("roleClass must be
> supplied");
> }
>
> if(!Role.class.isAssignableFrom(roleClass)){
> throw new IllegalArgumentException("the roleClass
> parameter (" + roleClass + ") must be an instance of the class Role");
> }
>
> try{
> Context context = Helper.getContext();
>
> if(context == null){
> throw new IllegalStateException("role methods can only
> be called on objects where a valid context exists, and no context
> could be found. instantiate a class of type Context before calling
> this method.");
> }
>
> //
> // is it playing that role?
> //
> if(!isPlayingRole((Class<? extends Role<?,?>>)roleClass)){
> throw new IllegalStateException("the object " + this +
> " is not currently playing the role " + roleClass + " in the context "
> + context);
> }
>
> //
> // instantiate the role class
> //
>
> @SuppressWarnings("rawtypes")
> Constructor constructor = null;
> int numParams = 99;
> for(Constructor<?> c : roleClass.getDeclaredConstructors())
> {
> if(c.getParameterTypes() == null){
> numParams = 0;
> constructor = c;
> }else{
> if(c.getParameterTypes().length < numParams){
> numParams = c.getParameterTypes().length;
> constructor = c;
> }
>
> }
> }
>
> if(constructor == null){
> throw new RuntimeException("unable to find a
> constructor for role " + roleClass);
> }
> constructor.setAccessible(true);
> Role<?,?> roleInstance = (Role<?, ?>)
> constructor.newInstance(new Object[numParams]);
>
> //
> // inject self into role
> //
>
> Field f = Role.class.getDeclaredField("self");
> f.setAccessible(true);
> f.set(roleInstance, this);
>
> //
> // inject resources into role
> //
>
> f = Context.class.getDeclaredField("resources");
> f.setAccessible(true);
> Map<String, Object> resources = (Map<String, Object>)
> f.get(context);
> if(resources != null && !resources.isEmpty()){
> injectResources(roleInstance, roleInstance.getClass(),
> resources);
> }
>
> return (T)roleInstance;
>
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (InstantiationException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (InvocationTargetException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> }
> }
>
> private void injectResources(Object roleInstance,
> @SuppressWarnings("rawtypes") Class clazz, Map<String, Object>
> resources) {
> Field[] declaredFields = clazz.getDeclaredFields();
> Field[] otherFields = clazz.getFields();
> Field[] allFields = new Field[declaredFields.length +
> otherFields.length];
> System.arraycopy(declaredFields, 0, allFields, 0,
> declaredFields.length);
> System.arraycopy(otherFields, 0, allFields,
> declaredFields.length, otherFields.length);
>
> for (Field field : allFields) {
> Annotation a = field.getAnnotation(Resource.class);
> if (a != null) {
> String name = field.getName();
> Object resource = resources.get(name);
> if(resource == null){
> //check using the name defined in the annotation
> String aName = ((Resource)a).name();
> if(aName != null){
> name = aName;
> resource = resources.get(name);
> }
> }
> if(resource != null){
> field.setAccessible(true); //it may be private!
> try {
> field.set(roleInstance, resource);
> } catch (Exception e) {
> throw new RuntimeException("unable to set
> resource " + name + " in class " + roleInstance.getClass().getName(),
> e);
> }
> }
> }
> }
>
> // do it recursively, as roles may have super classes!
> if(clazz.getSuperclass() != null){
> injectResources(roleInstance, clazz.getSuperclass(),
> resources);
> }
>
> }
>
> public boolean isPlayingRole(Class<? extends Role<?,?>> roleClass)
> {
>
> try{
> Context ctx = Helper.getContext();
> if(ctx == null){
> return false;
> }
>
> Field f = Context.class.getDeclaredField("domainObjects");
> f.setAccessible(true);
> @SuppressWarnings("unchecked")
> Map<DomainObject, Set<Class<? extends Role<?,?>>>>
> domainObjects = (Map<DomainObject, Set<Class<? extends Role<?,?
> >>>>)f.get(ctx);
> Set<Class<? extends Role<?,?>>> roles =
> domainObjects.get(this);
> if(roles != null && !roles.isEmpty()){
> if(roles.contains(roleClass)){
> return true;
> }
> }
> return false;
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> }
> }
>
> }
But seriously, if you only have 50 LOC, why bother with DCI at all? None of this is visible from the outside, and from the inside, the complexity of 50 LOC is manageable no matter how you do it. I was thinking these samples are just for explanation, but contexts are expected to grow much larger in real life. Am I wrong here?
Stefan
Makes sense too. I guess it would be best to leave this decision to the programmer. Better decided on a by-case basis. Losing the capability to put large roles in separate files would just… suck.
Stefan
Stefan
From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com]
On Behalf Of Rune Funch Søltoft
Sent: Monday, October 03, 2011 10:34 AM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.
I really think your callRole-approach is both better if you'd use it today, and more interesting if you want to explore possible language improvements to Java.
Stefan
> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Tuesday, October 04, 2011 5:56 PM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>
The project I was on before this current had around 20 use cases was
written in 65k Lines of c++ code and was being reduced when I left.
Parts of it was pretty complex most was rather straight forward. Quite
a fraction of the code was low level hardware drivers and graphic
rendering and I think 25% of the code had anything to do with what the
system does.
When I left the project had been on going for 10 years with an average
of around 10 people on the software team. Admittedly wasn't the
highest performing team I've been on but again LOC is not a function
of time. We actually reduce the count from 88k to 65k during the two
years I was there _while_ adding three major use cases.
-Rune
Thanks, I just learned a new word! But just to be pernickety, there’s just one ‘r ‘ in it ;-)
(see? I used it in a sentence!)
BTW, I was talking about two different interfaces, IRole1 and an optional role contract interface. (You might even need a third one that derives from both, so you can call an object’s role player methods and role methods from a single strongly-typed variable. I think I once called them IRole1Methods, IRole1Contract, and IRole1 for the combination of both. The role player implicitly or explicitly implements IRole1Contract, the role mixin implements IRole1Methods, and IRole1 just derives from both. I guess that’s just what happens if you try and get creative in statically typed languages ;-))
Stefan
So would I. And I still didn’t get around to code it up using re-mix (guess the Dijkstra thing scares me a bit too ;-))
Unfortunately, I’m beginning to think that Qi4j can’t really work around the name conflict problem, because unlike the CLR, the JVM won’t let you implement 2 interface methods of the same name. That would be a killer. Can anybody confirm this?
Stefan
Thanks, I just learned a new word! But just to be pernickety, there’s just one ‘r ‘ in it ;-)
(see? I used it in a sentence!)
BTW, I was talking about two different interfaces, IRole1 and an optional role contract interface. (You might even need a third one that derives from both, so you can call an object’s role player methods and role methods from a single strongly-typed variable. I think I once called them IRole1Methods, IRole1Contract, and IRole1 for the combination of both. The role player implicitly or explicitly implements IRole1Contract, the role mixin implements IRole1Methods, and IRole1 just derives from both. I guess that’s just what happens if you try and get creative in statically typed languages ;-))
Stefan
From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com]
On Behalf Of ant.ku...@gmail.com
Sent: Tuesday, October 04, 2011 6:05 PM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
Just to be pernickerty Stefan, a role contract is the interface which domain object are required to have in order to play a role. IRole is the role interface, which is totally different.
----- Reply message -----
From: "Wenig, Stefan" <stefan...@rubicon.eu>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo
Stefan
From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com]
On Behalf Of Rune Funch Søltoft
Sent: Monday, October 03, 2011 10:34 AM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo
In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.