phenomenal 0.99.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Rakefile +1 -7
  2. data/lib/phenomenal.rb +10 -1
  3. data/lib/phenomenal/adaptation.rb +6 -8
  4. data/lib/phenomenal/conflict_policies.rb +2 -1
  5. data/lib/phenomenal/context.rb +36 -31
  6. data/lib/phenomenal/dsl.rb +14 -9
  7. data/lib/phenomenal/feature.rb +1 -0
  8. data/lib/phenomenal/manager.rb +10 -12
  9. data/lib/phenomenal/proc.rb +2 -2
  10. data/lib/phenomenal/relationships/context_relationships.rb +2 -0
  11. data/lib/phenomenal/relationships/dsl.rb +1 -0
  12. data/lib/phenomenal/relationships/feature_relationships.rb +2 -2
  13. data/lib/phenomenal/relationships/implication.rb +10 -4
  14. data/lib/phenomenal/relationships/relationship.rb +13 -0
  15. data/lib/phenomenal/relationships/relationships_manager.rb +1 -1
  16. data/lib/phenomenal/relationships/relationships_store.rb +13 -7
  17. data/lib/phenomenal/relationships/requirement.rb +5 -0
  18. data/lib/phenomenal/relationships/suggestion.rb +14 -6
  19. data/lib/phenomenal/version.rb +1 -1
  20. data/lib/phenomenal/viewer/dsl.rb +15 -0
  21. data/lib/phenomenal/viewer/graphical.rb +136 -0
  22. data/lib/phenomenal/viewer/textual.rb +36 -0
  23. data/spec/context_spec.rb +93 -27
  24. data/spec/dsl_spec.rb +0 -39
  25. data/spec/integration/adaptation_spec.rb +127 -0
  26. data/spec/integration/combined_contexts_spec.rb +48 -0
  27. data/spec/integration/composition_spec.rb +62 -0
  28. data/spec/integration/conflict_policy_spec.rb +143 -0
  29. data/spec/integration/open_context.rb +48 -0
  30. data/spec/{behavior → integration}/relationships_spec.rb +0 -6
  31. data/spec/manager_spec.rb +30 -19
  32. data/spec/relationships/context_relationships_spec.rb +23 -3
  33. data/spec/relationships/dsl_spec.rb +9 -3
  34. data/spec/relationships/feature_relationships_spec.rb +22 -3
  35. data/spec/relationships/relationship_spec.rb +13 -1
  36. data/spec/relationships/relationships_manager_spec.rb +0 -11
  37. data/spec/relationships/relationships_store_spec.rb +31 -7
  38. data/spec/spec_helper.rb +1 -0
  39. data/spec/test_classes.rb +3 -0
  40. metadata +16 -13
  41. data/spec/behavior/adaptation_spec.rb +0 -5
  42. data/spec/behavior/combined_contexts_spec.rb +0 -5
  43. data/spec/behavior/composition_spec.rb +0 -5
  44. data/spec/behavior/conflict_policy_spec.rb +0 -5
  45. data/spec/behavior/open_context.rb +0 -5
@@ -1,3 +1,5 @@
1
+ # Define the methods that can be called by a feature to
2
+ # define relationships
1
3
  module Phenomenal::FeatureRelationships
2
4
  attr_accessor :relationships
3
5
 
@@ -20,7 +22,6 @@ module Phenomenal::FeatureRelationships
20
22
  private
21
23
  def add_relationship(source,targets,type)
22
24
  targets[:on]=Array.new.push(targets[:on]) if !targets[:on].is_a?(Array)
23
-
24
25
  if targets[:on].nil?
25
26
  Phenomenal::Logger.instance.error(
26
27
  "Invalid relationship, missing target context"
@@ -37,6 +38,5 @@ module Phenomenal::FeatureRelationships
37
38
  end
38
39
  end
39
40
  end
40
-
41
41
  end
42
42
  end
@@ -1,3 +1,4 @@
1
+ # Define the behavior of the Implication relationship
1
2
  class Phenomenal::Implication < Phenomenal::Relationship
2
3
  attr_accessor :activation_counter
3
4
  def initialize(source,target,feature)
@@ -8,28 +9,33 @@ class Phenomenal::Implication < Phenomenal::Relationship
8
9
  def activate_feature
9
10
  if source.active?
10
11
  target.activate
11
- activation_counter+=1
12
+ self.activation_counter+=1
12
13
  end
13
14
  end
14
15
 
15
16
  def deactivate_feature
16
17
  if activation_counter>0
17
18
  target.deactivate
18
- activation_coutner-=1
19
+ self.activation_counter-=1
19
20
  end
20
21
  end
21
22
 
22
23
  def activate_context(context)
23
24
  if source==context
24
25
  target.activate
26
+ self.activation_counter+=1
25
27
  end
26
28
  end
27
29
 
28
30
  def deactivate_context(context)
29
- if source==context
31
+ if source==context && activation_counter>0
30
32
  target.deactivate
31
- else
33
+ self.activation_counter-=1
34
+ elsif activation_counter>0
32
35
  source.deactivate
36
+ self.activation_counter-=1
37
+ else
38
+ # Nothing to do
33
39
  end
34
40
  end
35
41
  end
@@ -1,3 +1,4 @@
1
+ # Define a first class relationship
1
2
  class Phenomenal::Relationship
2
3
  attr_accessor :source,:target,:manager,:feature
3
4
 
@@ -24,15 +25,27 @@ class Phenomenal::Relationship
24
25
  self.target=t if !t.nil?
25
26
  end
26
27
 
28
+ # Must be redifined for each type of relation
29
+ # if necessary
30
+ # Called when a context is activated
27
31
  def activate_context(context)
28
32
  end
29
33
 
34
+ # Must be redifined for each type of relation
35
+ # if necessary
36
+ # Called when a context is deactivated
30
37
  def deactivate_context(context)
31
38
  end
32
39
 
40
+ # Must be redifined for each type of relation
41
+ # if necessary
42
+ # Called when a feature is activated
33
43
  def activate_feature
34
44
  end
35
45
 
46
+ # Must be redifined for each type of relation
47
+ # if necessary
48
+ # Called when a feature is deactivated
36
49
  def deactivate_feature
37
50
  end
38
51
 
@@ -1,5 +1,5 @@
1
1
  require 'singleton'
2
-
2
+ # This class manage the different relatiohsips in the system between contexts
3
3
  class Phenomenal::RelationshipsManager
4
4
  include Singleton
5
5
 
@@ -1,3 +1,5 @@
1
+ # Define the class where all the actives relationships are
2
+ # efficiently stored
1
3
  class Phenomenal::RelationshipsStore
2
4
  attr_accessor :sources, :targets
3
5
 
@@ -34,16 +36,20 @@ class Phenomenal::RelationshipsStore
34
36
  def update_references(context)
35
37
  # Do nothing when anonymous, references are already valid
36
38
  return if context.anonymous?
37
- # Update sources
38
- @sources[context.name].each do |relationship|
39
- relationship.source=context
39
+ # Update sources
40
+ if not @sources[context.name].nil?
41
+ @sources[context.name].each do |relationship|
42
+ relationship.source=context
43
+ end
44
+ @sources[context]=@source.delete(context.name)
40
45
  end
41
- @sources[context]=@source.delete(context.name)
42
46
  # Update targets
43
- @targets[context.name].each do |relationship|
44
- relationship.target=context
47
+ if not @targets[context.name].nil?
48
+ @targets[context.name].each do |relationship|
49
+ relationship.target=context
50
+ end
51
+ @targets[context]=@targets.delete(context.name)
45
52
  end
46
- @targets[context]=@targets.delete(context.name)
47
53
  end
48
54
 
49
55
  def get_for(context)
@@ -1,8 +1,13 @@
1
+ # Define the behavior of the Requirement relationship
1
2
  class Phenomenal::Requirement < Phenomenal::Relationship
3
+
2
4
  def activate_feature
3
5
  check_requirement
4
6
  end
5
7
 
8
+ def deactivate_feature
9
+ end
10
+
6
11
  def activate_context(context)
7
12
  if(source==context)
8
13
  check_requirement
@@ -1,3 +1,4 @@
1
+ # Define the behavior of the Suggestion relationship
1
2
  class Phenomenal::Suggestion < Phenomenal::Relationship
2
3
  attr_accessor :activation_counter
3
4
 
@@ -20,22 +21,29 @@ class Phenomenal::Suggestion < Phenomenal::Relationship
20
21
  begin
21
22
  if activation_counter>0
22
23
  target.deactivate
23
- self.activation_coutner-=1
24
+ self.activation_counter-=1
24
25
  end
25
26
  rescue
26
27
  end
27
28
  end
28
29
 
29
-
30
30
  def activate_context(context)
31
- if source==context
32
- target.activate
31
+ begin
32
+ if source==context
33
+ target.activate
34
+ self.activation_counter+=1
35
+ end
36
+ rescue
33
37
  end
34
38
  end
35
39
 
36
40
  def deactivate_context(context)
37
- if source==context
38
- target.deactivate
41
+ begin
42
+ if source==context && activation_counter>0
43
+ target.deactivate
44
+ self.activation_counter-=1
45
+ end
46
+ rescue
39
47
  end
40
48
  end
41
49
  end
@@ -1,3 +1,3 @@
1
1
  module Phenomenal
2
- VERSION = "0.99.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,15 @@
1
+ module Phenomenal::DSL
2
+ def self.define_viewers(klass)
3
+ klass.class_eval do
4
+ # Graphical
5
+ def phen_graphical_view(file="view.png")
6
+ Phenomenal::Viewer::Graphical.new(file).generate
7
+ end
8
+
9
+ #Textual
10
+ def phen_textual_view
11
+ Phenomenal::Viewer::Textual.new.generate
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,136 @@
1
+ # Define the way to generate a representation of the system
2
+ # using the graphical library graphviz
3
+ class Phenomenal::Viewer::Graphical
4
+ begin
5
+ require "graphviz"
6
+ @@graphviz=true
7
+ rescue LoadError
8
+ @@graphviz=false
9
+ end
10
+
11
+ attr_reader :manager, :rmanager
12
+ attr_accessor :main_graph, :feature_nodes, :r_feature_nodes, :context_nodes, :destination_file
13
+
14
+ def initialize(destination_file)
15
+ if !@@graphviz
16
+ Phenomenal::Logger.instance.error(
17
+ "The 'ruby-graphviz' gem isn't available. Please install it to generate graphic visualitations\n"+
18
+ " Otherwise use the text version"
19
+ )
20
+ end
21
+
22
+ @manager=Phenomenal::Manager.instance
23
+ @rmanager=Phenomenal::RelationshipsManager.instance
24
+ @destination_file=destination_file
25
+ @main_graph=nil
26
+ @feature_nodes={}
27
+ @r_feature_nodes={}
28
+ @context_nodes={}
29
+ end
30
+
31
+ def generate()
32
+ # Create main graph
33
+ self.main_graph = GraphViz::new("")
34
+ # Default options
35
+ self.main_graph[:compound] = "true"
36
+ self.main_graph.edge[:lhead] = ""
37
+ self.main_graph.edge[:ltail] = ""
38
+ self.main_graph.edge[:fontsize]=10.0
39
+ # Add nodes to the graph
40
+ self.manager.contexts.each do |key,context|
41
+ if not self.feature_nodes.include?(context) and not @context_nodes.include?(context)
42
+ add_node_for(context)
43
+ end
44
+ end
45
+ # Create a relationship links
46
+ self.manager.contexts.each do |key,context|
47
+ add_edges_for(context)
48
+ end
49
+ self.main_graph.output( :png => self.destination_file )
50
+ end
51
+
52
+ private
53
+ def add_edges_for(context)
54
+ if context.is_a?(Phenomenal::Feature)
55
+ context.relationships.each do |relationship|
56
+ # Get source and destionation node
57
+ ltail=""
58
+ lhead=""
59
+ relationship.refresh
60
+ if self.feature_nodes.include?(relationship.source)
61
+ source_node=self.r_feature_nodes[relationship.source]
62
+ ltail="cluster_#{relationship.source.to_s}"
63
+ else
64
+ source_node=self.context_nodes[relationship.source]
65
+ end
66
+ if self.feature_nodes.include?(relationship.target)
67
+ target_node=self.r_feature_nodes[relationship.target]
68
+ lhead="cluster_#{relationship.target.to_s}"
69
+ else
70
+ target_node=self.context_nodes[relationship.target]
71
+ end
72
+ # Define graph container
73
+ s_parent_feature=relationship.source.parent_feature
74
+ t_parent_feature=relationship.target.parent_feature
75
+ if s_parent_feature==t_parent_feature && s_parent_feature!=self.manager.default_context
76
+ graph=self.feature_nodes[relationship.source.parent_feature]
77
+ else
78
+ graph=self.main_graph
79
+ end
80
+ # Add edge
81
+ edge=graph.add_edges(source_node,target_node,:ltail=>ltail,:lhead=>lhead)
82
+ # Define edge label
83
+ if context!=self.manager.default_context
84
+ edge[:label]=context.to_s
85
+ end
86
+ # Define edge color
87
+ if self.rmanager.relationships.include?(relationship)
88
+ edge[:color]="red"
89
+ end
90
+ # Define arrow type
91
+ if relationship.is_a?(Phenomenal::Implication)
92
+ edge[:arrowhead]="normal"
93
+ elsif relationship.is_a?(Phenomenal::Suggestion)
94
+ edge[:arrowhead]="empty"
95
+ elsif relationship.is_a?(Phenomenal::Requirement)
96
+ edge[:arrowhead]="inv"
97
+ else
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ def add_node_for(context)
104
+ # The default context is the first to be added to the main graph
105
+ if self.feature_nodes[context.parent_feature].nil? and context==self.manager.default_context
106
+ current_graph=self.main_graph
107
+ # Always add the parent_feature before the contexts inside
108
+ elsif @feature_nodes[context.parent_feature].nil?
109
+ self.add_node_for(context.parent_feature)
110
+ else
111
+ current_graph=self.feature_nodes[context.parent_feature]
112
+ end
113
+ # Add node
114
+ if context.is_a?(Phenomenal::Feature)
115
+ node=current_graph.add_graph("cluster_#{context.to_s}")
116
+ node[:label]="#{context.to_s}"
117
+ # Add hidden node for feature relationship
118
+ fr=node.add_nodes("#{context.to_s}_relationship")
119
+ fr[:style]="invis"
120
+ fr[:height]=0.02
121
+ fr[:width]=0.02
122
+ fr[:fixedsize]=true
123
+ self.feature_nodes[context]=node
124
+ self.r_feature_nodes[context]=fr
125
+ else
126
+ node=current_graph.add_nodes(context.to_s)
127
+ self.context_nodes[context]=node
128
+ end
129
+ # Define node color
130
+ if context.active?
131
+ node[:color]="red"
132
+ else
133
+ node[:color]="black"
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,36 @@
1
+ # Define the way to generate a textual representation of the system
2
+ class Phenomenal::Viewer::Textual
3
+ attr_reader :manager, :rmanager
4
+
5
+ def initialize()
6
+ @manager=Phenomenal::Manager.instance
7
+ @rmanager=Phenomenal::RelationshipsManager.instance
8
+ end
9
+
10
+ def generate()
11
+ str=""
12
+ offset=" "
13
+ self.manager.contexts.each do |key,context|
14
+ if context.is_a?(Phenomenal::Feature)
15
+ type="Feature"
16
+ str=str+"#{type}: #{context.to_s} \n"
17
+ context.relationships.each do |relationship|
18
+ if relationship.is_a?(Phenomenal::Implication)
19
+ relation="=>"
20
+ elsif relationship.is_a?(Phenomenal::Suggestion)
21
+ relation="->"
22
+ elsif relationship.is_a?(Phenomenal::Requirement)
23
+ relation="=<"
24
+ else
25
+ relation="??"
26
+ end
27
+ str=str+"#{offset} #{relationship.source.to_s} #{relation} #{relationship.target.to_s} \n"
28
+ end
29
+ else
30
+ type="Context"
31
+ str=str+"#{type}: #{context.to_s} \n"
32
+ end
33
+ end
34
+ str
35
+ end
36
+ end
data/spec/context_spec.rb CHANGED
@@ -5,6 +5,7 @@ describe Phenomenal::Context do
5
5
  before :each do
6
6
  @context = Phenomenal::Context.new(:test)
7
7
  @context2 = Phenomenal::Context.new(:test2)
8
+ @manager = Phenomenal::Manager.instance
8
9
  end
9
10
 
10
11
  after :each do
@@ -34,7 +35,9 @@ describe Phenomenal::Context do
34
35
  end
35
36
 
36
37
  it "should be anonymous if the name wasn't set" do
37
- Phenomenal::Context.new.name.should be_nil
38
+ context = Phenomenal::Context.new
39
+ context.name.should be_nil
40
+ force_forget_context(context)
38
41
  end
39
42
 
40
43
  it "should be anonymous if it is the default context" do
@@ -42,24 +45,76 @@ describe Phenomenal::Context do
42
45
  end
43
46
  end
44
47
 
45
- describe ".create" do
46
- pending "TODO"
47
- end
48
-
49
48
  describe "#forget" do
50
- pending "TODO"
49
+ it "should raise an error if the context is active" do
50
+ @context.activate
51
+ expect{@context.forget}.to raise_error Phenomenal::Error
52
+ end
53
+
54
+ it "should unregister the context from the manager" do
55
+ @context.forget
56
+ @manager.contexts[@context].should be_nil
57
+ @context = Phenomenal::Context.new(:test)
58
+ end
59
+
60
+ it "should raise an error if used after being forgotted" do
61
+ @context.forget
62
+ expect{@context.activate}.to raise_error Phenomenal::Error
63
+ @context = Phenomenal::Context.new(:test)
64
+ end
51
65
  end
52
66
 
53
67
  describe "#add_adaptation" do
54
- pending "TODO"
68
+ it "should save the default behavior in the default context" do
69
+ @context.add_adaptation(TestString, :size,true) do
70
+ 42
71
+ end
72
+ a = phen_default_context.adaptations.find{|a| a.concern?(TestString,:size,true)}
73
+ a.bind(TestString.new("1234")).should==4
74
+ end
75
+
76
+ it "should activate the adaptation if the context is active"do
77
+ string = TestString.new("1234")
78
+ string.size.should==4
79
+ @context.activate
80
+ string.size.should==4
81
+ @context.add_adaptation(TestString, :size,true) do
82
+ 42
83
+ end
84
+ string.size.should==42
85
+ end
86
+
87
+ it "should not activate the adaptation if the context is inactive"do
88
+ string = TestString.new("1234")
89
+ string.size.should==4
90
+ @context.add_adaptation(TestString, :size,true) do
91
+ 42
92
+ end
93
+ string.size.should==4
94
+ end
55
95
  end
56
96
 
57
97
  describe "#remove_adaptation" do
58
- pending "TODO"
98
+ it "should deactivate the adaptation if the context is active" do
99
+ string = TestString.new("1234")
100
+ string.size.should==4
101
+ @context.activate
102
+ @context.add_adaptation(TestString, :size,true) do
103
+ 42
104
+ end
105
+ string.size.should==42
106
+ phen_remove_adaptation(:test, TestString, :size)
107
+ string.size.should==4
108
+ context(:test).active?.should be_true
109
+ end
59
110
  end
60
111
 
61
112
  describe "#context" do
62
- pending "TODO"
113
+ it "should create a combined context of itself and the argument context" do
114
+ c = phen_context(:test).context(:new_context)
115
+ phen_context(:test,:new_context).should==c
116
+ force_forget_context(:new_context)
117
+ end
63
118
 
64
119
  describe "#phen_context" do
65
120
  it "should be an alias of #context" do
@@ -69,7 +124,11 @@ describe Phenomenal::Context do
69
124
  end
70
125
 
71
126
  describe "#feature" do
72
- pending "TODO"
127
+ it "should create a combined context of itself and the feature" do
128
+ c = phen_context(:test).feature(:new_feature)
129
+ phen_context(:test).feature(:new_feature).should==c
130
+ force_forget_context(:new_feature)
131
+ end
73
132
 
74
133
  describe "#phen_feature" do
75
134
  it "should be an alias of #feature" do
@@ -79,19 +138,28 @@ describe Phenomenal::Context do
79
138
  end
80
139
 
81
140
  describe "#add_adaptations" do
82
- pending "TODO"
141
+ it "should be able to adapt multiple instance method" do
142
+ @context.should respond_to :add_adaptations
143
+ end
83
144
  end
84
145
 
85
146
  describe "#adapatations_for" do
86
- pending "TODO"
147
+ it "should set the current adapted class" do
148
+ @context.adaptations_for String
149
+ @context.instance_variable_get("@current_adapted_class").should == String
150
+ end
87
151
  end
88
152
 
89
153
  describe "#adapt" do
90
- pending "TODO"
154
+ it "should be able to adapt an instance method" do
155
+ @context.should respond_to :adapt
156
+ end
91
157
  end
92
158
 
93
- describe "#adapt_klass" do
94
- pending "TODO"
159
+ describe "#adapt_class" do
160
+ it "should be able to adapt a method" do
161
+ @context.should respond_to :adapt_class
162
+ end
95
163
  end
96
164
 
97
165
 
@@ -189,14 +257,6 @@ describe Phenomenal::Context do
189
257
  end
190
258
 
191
259
  describe "#anonymous?" do
192
- after do
193
- force_forget_context(@context3)
194
- end
195
- it "should be true when the context has no name" do
196
- @context3 = Phenomenal::Context.new
197
- @context3.anonymous?.should be_true
198
- end
199
-
200
260
  it "should be false when the context has a name" do
201
261
  @context.anonymous?.should be_false
202
262
  end
@@ -204,6 +264,12 @@ describe Phenomenal::Context do
204
264
  it "should be true for the default context" do
205
265
  Phenomenal::Manager.instance.default_context.anonymous?.should be_true
206
266
  end
267
+
268
+ it "should be true when the context has no name" do
269
+ @context3 = Phenomenal::Context.new
270
+ @context3.anonymous?.should be_true
271
+ force_forget_context(@context3)
272
+ end
207
273
  end
208
274
 
209
275
  describe "#information" do
@@ -235,13 +301,12 @@ describe Phenomenal::Context do
235
301
  @context.activate
236
302
  @context.information[:activation_count].should==1
237
303
  end
238
- after do
239
- force_forget_context(@feature)
240
- end
304
+
241
305
  it "should have a matching :type field" do
242
306
  @context.information[:type].should=="Phenomenal::Context"
243
307
  @feature = Phenomenal::Feature.new
244
308
  @feature.information[:type].should=="Phenomenal::Feature"
309
+ force_forget_context(@feature)
245
310
  end
246
311
 
247
312
 
@@ -260,8 +325,9 @@ describe Phenomenal::Context do
260
325
  end
261
326
  end
262
327
  c.parent_feature.should be f
263
- c.forget
328
+
264
329
  f.forget
330
+ context(:c).forget
265
331
  end
266
332
  end
267
333
  end