phenomenal 0.99.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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