phenomenal 1.1.0 → 1.1.1

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.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Phenomenal Gem
1
+ Phenomenal Gem [![Build Status](https://secure.travis-ci.org/phenomenal/phenomenal.png)](http://travis-ci.org/phenomenal/phenomenal)
2
2
  ===
3
3
  Phenomenal Gem is a context-oriented framework implemented in Ruby that allows context-oriented programming in Ruby and Ruby on Rails applications.
4
4
 
data/lib/phenomenal.rb CHANGED
@@ -14,11 +14,16 @@ require_relative "./phenomenal/relationships/suggestion.rb"
14
14
 
15
15
  # Core
16
16
  require_relative "./phenomenal/adaptation.rb"
17
- require_relative "./phenomenal/conflict_policies.rb"
17
+
18
18
  require_relative "./phenomenal/context.rb"
19
19
  require_relative "./phenomenal/feature.rb"
20
20
  require_relative "./phenomenal/logger.rb"
21
- require_relative "./phenomenal/manager.rb"
21
+
22
+ #Manager
23
+ require_relative "./phenomenal/manager/adaptations_management.rb"
24
+ require_relative "./phenomenal/manager/contexts_management.rb"
25
+ require_relative "./phenomenal/manager/conflict_policies.rb"
26
+ require_relative "./phenomenal/manager/manager.rb"
22
27
 
23
28
  # Viewer
24
29
  require_relative "./phenomenal/viewer/graphical.rb"
@@ -7,12 +7,24 @@ class Phenomenal::Context
7
7
  :activation_count, :parent, :forgotten
8
8
  attr_reader :manager,:name
9
9
 
10
- def self.create(context,*contexts,nested,closest_feature,&block)
11
- manager = Phenomenal::Manager.instance
12
- contexts.insert(0,context)
13
- if contexts.length==1
10
+ # Class metods
11
+ class << self
12
+ def create(context,*contexts,nested,closest_feature,&block)
13
+ manager = Phenomenal::Manager.instance
14
+ contexts.insert(0,context)
15
+ if contexts.length==1
16
+ context = find_or_create_simple_context(manager,context)
17
+ else #Combined contexts
18
+ context = find_or_create_combined_context(manager,contexts,nested,closest_feature)
19
+ end
20
+ context.add_adaptations(&block)
21
+ context
22
+ end
23
+
24
+ private
25
+ def find_or_create_simple_context(manager,context)
14
26
  if !manager.context_defined?(context)
15
- context = self.new(context)
27
+ self.new(context)
16
28
  else
17
29
  context = manager.find_context(context)
18
30
  if !context.instance_of?(self)
@@ -20,31 +32,13 @@ class Phenomenal::Context
20
32
  "Only #{self.name} can be used with this keyword."
21
33
  )
22
34
  end
35
+ context
23
36
  end
24
- else #Combined contexts
37
+ end
38
+
39
+ def find_or_create_combined_context(manager,contexts,nested,closest_feature)
25
40
  if !manager.context_defined?(*contexts) # New combined context
26
- context = self.new
27
- context.parent=closest_feature # Set parent
28
- instances = Array.new
29
- first = contexts.first
30
- contexts.each do |c|
31
- # Use the object instance if already available
32
- # otherwise create it
33
- if manager.context_defined?(c)
34
- c = manager.find_context(c)
35
- if !nested && c!=first && !c.instance_of?(self)
36
- Phenomenal::Logger.instance.error(
37
- "Only #{self.name} can be used with this keyword."
38
- )
39
- end
40
- else
41
- c = self.new(c)
42
- end
43
- instances.push(c)
44
- manager.shared_contexts[c]= Array.new if !manager.shared_contexts[c]
45
- manager.shared_contexts[c].push(context)
46
- end
47
- manager.combined_contexts[context] = instances
41
+ context = create_combined_context(manager,contexts,nested,closest_feature)
48
42
  else
49
43
  context = manager.find_context(*contexts)
50
44
  if !context.instance_of?(self)
@@ -53,11 +47,37 @@ class Phenomenal::Context
53
47
  )
54
48
  end
55
49
  end
50
+ context
51
+ end
52
+
53
+ def create_combined_context(manager,contexts,nested,closest_feature)
54
+ context = self.new
55
+ context.parent=closest_feature # Set parent
56
+ instances = Array.new
57
+ first = contexts.first
58
+ contexts.each do |c|
59
+ # Use the object instance if already available
60
+ # otherwise create it
61
+ if manager.context_defined?(c)
62
+ c = manager.find_context(c)
63
+ if !nested && c!=first && !c.instance_of?(self)
64
+ Phenomenal::Logger.instance.error(
65
+ "Only #{self.name} can be used with this keyword."
66
+ )
67
+ end
68
+ else
69
+ c = self.new(c)
70
+ end
71
+ instances.push(c)
72
+ manager.shared_contexts[c]= Array.new if !manager.shared_contexts[c]
73
+ manager.shared_contexts[c].push(context)
74
+ end
75
+ manager.combined_contexts[context] = instances
76
+ context
56
77
  end
57
- context.add_adaptations(&block)
58
- context
59
78
  end
60
79
 
80
+ # Instance methods
61
81
  def initialize(name=nil, manager=nil)
62
82
  @manager = manager || Phenomenal::Manager.instance
63
83
  @name = name
@@ -94,7 +114,6 @@ class Phenomenal::Context
94
114
  end
95
115
  if umeth
96
116
  implementation = umeth
97
- instance = klass.instance_methods.include?(method_name)
98
117
  end
99
118
  if adaptations.find{ |i| i.concern?(klass,method_name,instance) }
100
119
  Phenomenal::Logger.instance.error(
@@ -0,0 +1,134 @@
1
+ module Phenomenal::AdaptationsManagement
2
+ attr_accessor :active_adaptations, :deployed_adaptations
3
+
4
+ # Register a new adaptation for a registered context
5
+ def register_adaptation(adaptation)
6
+ default_adaptation = default_context.adaptations.find do|i|
7
+ i.concern?(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
8
+ end
9
+ if adaptation.context!=default_context && !default_adaptation
10
+ save_default_adaptation(adaptation.klass, adaptation.method_name,adaptation.instance_adaptation?)
11
+ end
12
+ activate_adaptation(adaptation) if adaptation.context.active?
13
+ end
14
+
15
+ # Unregister an adaptation for a registered context
16
+ def unregister_adaptation(adaptation)
17
+ deactivate_adaptation(adaptation) if adaptation.context.active?
18
+ end
19
+
20
+ # Call the old implementation of the method 'caller.caller_method'
21
+ def proceed(calling_stack,instance,*args,&block)
22
+ calling_adaptation = find_adaptation(calling_stack)
23
+ # IMPROVE Problems will appears if proceed called in a file where
24
+ # adaptations are defined but not in one of them=> how to check?
25
+ # IMPROVE Problems will also appears if two adaptations are defined on the same
26
+ # line using the ';' some check needed at add_adaptation ?
27
+ adaptations_stack = sorted_adaptations_for(calling_adaptation.klass,
28
+ calling_adaptation.method_name,calling_adaptation.instance_adaptation?)
29
+ calling_adaptation_index = adaptations_stack.find_index(calling_adaptation)
30
+ next_adaptation = adaptations_stack[calling_adaptation_index+1]
31
+ next_adaptation.bind(instance,*args, &block)
32
+ end
33
+
34
+ private
35
+ # Activate the adaptation and redeploy the adaptations to take the new one
36
+ # one in account
37
+ def activate_adaptation(adaptation)
38
+ if !active_adaptations.include?(adaptation)
39
+ active_adaptations.push(adaptation)
40
+ end
41
+ redeploy_adaptation(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
42
+ end
43
+
44
+ # Deactivate the adaptation and redeploy the adaptations if necessary
45
+ def deactivate_adaptation(adaptation)
46
+ active_adaptations.delete(adaptation)
47
+ if deployed_adaptations.include?(adaptation)
48
+ deployed_adaptations.delete(adaptation)
49
+ redeploy_adaptation(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
50
+ end
51
+ end
52
+
53
+ # Redeploy the adaptations concerning klass.method_name according to the
54
+ # conflict policy
55
+ def redeploy_adaptation(klass, method_name,instance)
56
+ to_deploy = resolve_conflict(klass,method_name,instance)
57
+ # Do nothing when to_deploy==nil to break at default context deactivation
58
+ if !deployed_adaptations.include?(to_deploy) && to_deploy!=nil
59
+ deploy_adaptation(to_deploy)
60
+ end
61
+ end
62
+
63
+ # Deploy the adaptation
64
+ def deploy_adaptation(adaptation)
65
+ to_undeploy = deployed_adaptations.find do |i|
66
+ i.concern?(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
67
+ end
68
+ if to_undeploy!=adaptation # if new adaptation
69
+ deployed_adaptations.delete(to_undeploy)
70
+ deployed_adaptations.push(adaptation)
71
+ adaptation.deploy
72
+ end
73
+ end
74
+
75
+ # Save the default adaptation of a method, ie: the initial method
76
+ def save_default_adaptation(klass, method_name,instance)
77
+ if instance
78
+ method = klass.instance_method(method_name)
79
+ else
80
+ method = klass.method(method_name)
81
+ end
82
+ adaptation = default_context.add_adaptation(klass,method_name,instance,method)
83
+ end
84
+
85
+ # Return the adaptation that math the calling_stack, on the basis of the
86
+ # file and the line number --> proceed is always called under an
87
+ # adaptation definition
88
+ def find_adaptation(calling_stack)
89
+ call_file,call_line = parse_stack(calling_stack)
90
+ match = nil
91
+ relevant_adaptations(call_file).each do |adaptation|
92
+ if adaptation.src_line <= call_line # Find first matching line
93
+ match = adaptation
94
+ break
95
+ end
96
+ end
97
+
98
+ if match==nil
99
+ Phenomenal::Logger.instance.error(
100
+ "Inexistant adaptation for proceed call at #{call_file}:#{call_line}"
101
+ )
102
+ end
103
+ match
104
+ end
105
+
106
+ # Parse calling stack to find the calling line and file
107
+ def parse_stack(calling_stack)
108
+ source = calling_stack[0]
109
+ source_info = source.scan(/(.+\.rb):(\d+)/)[0]
110
+ call_file = source_info[0]
111
+ call_line = source_info[1].to_i
112
+ [call_file,call_line]
113
+ end
114
+
115
+ # Gets the relevants adaptations for a file in DESC order of line number
116
+ def relevant_adaptations(call_file)
117
+ relevants = active_adaptations.select{ |i| i.src_file == call_file }
118
+ # Sort by src_line DESC order
119
+ relevants.sort!{ |a,b| b.src_line <=> a.src_line }
120
+ end
121
+
122
+ # Return the best adaptation according to the resolution policy
123
+ def resolve_conflict(klass,method_name,instance)
124
+ sorted_adaptations_for(klass,method_name,instance).first
125
+ end
126
+
127
+ # Return the adaptations for a particular method sorted with the
128
+ # conflict policy
129
+ def sorted_adaptations_for(klass,method_name,instance)
130
+ relevants =
131
+ active_adaptations.find_all { |i| i.concern?(klass, method_name,instance) }
132
+ relevants.sort!{|a,b| conflict_policy(a.context,b.context)}
133
+ end
134
+ end
@@ -1,6 +1,5 @@
1
1
  # Context conflict resolution policies
2
2
  module Phenomenal::ConflictPolicies
3
-
4
3
  # Prefer not default adaptation, error if two not default ones
5
4
  def no_resolution_conflict_policy(context1,context2)
6
5
  if context1==default_context()
@@ -18,4 +17,16 @@ module Phenomenal::ConflictPolicies
18
17
  def age_conflict_policy(context1, context2)
19
18
  context1.age <=> context2.age
20
19
  end
20
+
21
+ # Resolution policy
22
+ def conflict_policy(context1, context2)
23
+ age_conflict_policy(context1, context2)
24
+ end
25
+
26
+ # Change the conflict resolution policy.
27
+ # These can be ones from the ConflictPolicies module or other ones
28
+ # Other one should return -1 or +1 following the resolution order
29
+ def change_conflict_policy (&block)
30
+ self.class.class_eval{define_method(:conflict_policy,&block)}
31
+ end
21
32
  end
@@ -0,0 +1,171 @@
1
+ module Phenomenal::ContextsManagement
2
+ attr_accessor :contexts, :default_context, :combined_contexts, :shared_contexts
3
+
4
+ # Register a new context
5
+ def register_context(context)
6
+ if context_defined?(context)
7
+ Phenomenal::Logger.instance.error(
8
+ "The context #{context} is already registered"
9
+ )
10
+ end
11
+ if context.name && context_defined?(context.name)
12
+ Phenomenal::Logger.instance.error(
13
+ "There is already a context with name: #{context.name}." +
14
+ " If you want to have named context it has to be a globally unique name"
15
+ )
16
+ end
17
+ # Update the relationships that concern this context
18
+ rmanager.update_relationships_references(context)
19
+ # Store the context at its ID
20
+ contexts[context]=context
21
+ end
22
+
23
+ # Unregister a context (forget)
24
+ def unregister_context(context)
25
+ if context==default_context && contexts.size>1
26
+ Phenomenal::Logger.instance.error(
27
+ "Default context can only be forgotten when alone"
28
+ )
29
+ else
30
+ contexts.delete(context)
31
+ unregister_combined_contexts(context)
32
+ # Restore default context
33
+ init_default() if context==default_context
34
+ end
35
+ end
36
+
37
+
38
+
39
+ # Activate the context 'context' and deploy the related adaptation
40
+ def activate_context(context)
41
+ begin
42
+ # Relationships managment
43
+ rmanager.activate_relationships(context) if context.just_activated?
44
+ # Activation of adaptations
45
+ context.adaptations.each{ |i| activate_adaptation(i) }
46
+ # Activate combined contexts
47
+ activate_combined_contexts(context)
48
+ rescue Phenomenal::Error
49
+ context.deactivate # rollback the deployed adaptations
50
+ raise # throw up the exception
51
+ end
52
+ end
53
+
54
+ # Deactivate the adaptations (undeploy if needed)
55
+ def deactivate_context(context)
56
+ #Relationships managment
57
+ rmanager.deactivate_relationships(context)
58
+ #Adaptations deactivation
59
+ context.adaptations.each do |i|
60
+ deactivate_adaptation(i)
61
+ end
62
+ deactivate_combined_contexts(context)
63
+ end
64
+
65
+ # Return the corresponding context (or combined context) or raise an error
66
+ # if the context isn't currently registered.
67
+ # The 'context' parameter can be either a reference to a context instance or
68
+ # a Symbol with the name of a named (not anonymous) context.
69
+ def find_context(context, *contexts)
70
+ if contexts.length==0
71
+ find_simple_context(context)
72
+ else #Combined contexts
73
+ contexts.insert(0,context)
74
+ find_combined_context(contexts)
75
+ end
76
+ end
77
+
78
+ # Check wether context 'context' (or combined context) exist in the context manager
79
+ # Context can be either the context name or the context instance itself
80
+ # Return the context if found, or nil otherwise
81
+ def context_defined?(context, *contexts)
82
+ c=nil
83
+ begin
84
+ c = find_context(context,*contexts)
85
+ rescue Phenomenal::Error
86
+ return nil
87
+ end
88
+ return c
89
+ end
90
+
91
+ private
92
+ def unregister_combined_contexts(context)
93
+ # Forgot combined contexts
94
+ combined_contexts.delete(context)
95
+ if shared_contexts[context]
96
+ shared_contexts[context].each do |c|
97
+ c.forget
98
+ end
99
+ end
100
+ end
101
+
102
+ def activate_combined_contexts(context)
103
+ if shared_contexts[context]
104
+ shared_contexts[context].each do |combined_context|
105
+ need_activation=true
106
+ combined_contexts[combined_context].each do |shared_context|
107
+ need_activation=false if !shared_context.active?
108
+ end
109
+ combined_context.activate if need_activation
110
+ end
111
+ end
112
+ end
113
+
114
+ def deactivate_combined_contexts(context)
115
+ if shared_contexts[context]
116
+ shared_contexts[context].each do |combined_context|
117
+ while combined_context.active? do
118
+ combined_context.deactivate
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def find_simple_context(context)
125
+ find=nil
126
+ if !context.kind_of?(Phenomenal::Context)
127
+ a = contexts.find{|k,v| v.name==context}
128
+ if a
129
+ find = a[1]
130
+ end
131
+ else
132
+ find = context if contexts.has_key?(context)
133
+ end
134
+ if find
135
+ find
136
+ else
137
+ Phenomenal::Logger.instance.error(
138
+ "Unknown context #{context}"
139
+ )
140
+ end
141
+ end
142
+
143
+ def find_combined_context(contexts)
144
+ list=Array.new
145
+ contexts.each do |c|
146
+ # Use the object instance if already available
147
+ # otherwise use the symbol name
148
+ c = find_simple_context(c) if context_defined?(c)
149
+ if shared_contexts[c]==nil
150
+ list.clear
151
+ break
152
+ elsif list.length==0
153
+ # clone otherwise list.clear empty shared_contexts[c]
154
+ list=shared_contexts[c].clone
155
+ else
156
+ list=shared_contexts[c].find_all{|i| list.include?(i) }
157
+ end
158
+ end
159
+ if list.length==0
160
+ Phenomenal::Logger.instance.error(
161
+ "Unknown combined context #{contexts}"
162
+ )
163
+ elsif list.length==1
164
+ return list.first
165
+ else
166
+ Phenomenal::Logger.instance.error(
167
+ "Multiple definition of combined context #{contexts}"
168
+ )
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,29 @@
1
+ require 'singleton'
2
+ # This class manage the different contexts in the system and their interactions
3
+ class Phenomenal::Manager
4
+ include Singleton
5
+ include Phenomenal::ConflictPolicies
6
+ include Phenomenal::AdaptationsManagement
7
+ include Phenomenal::ContextsManagement
8
+
9
+ attr_accessor :rmanager
10
+
11
+ # PRIVATE METHODS
12
+ private
13
+ # Set the default context
14
+ def init_default
15
+ self.default_context= Phenomenal::Feature.new(nil,self)
16
+ self.default_context.activate
17
+ end
18
+
19
+ # Private constructor because this is a singleton object
20
+ def initialize
21
+ @contexts = Hash.new
22
+ @deployed_adaptations = Array.new
23
+ @active_adaptations = Array.new
24
+ @combined_contexts = Hash.new
25
+ @shared_contexts = Hash.new
26
+ @rmanager = Phenomenal::RelationshipsManager.instance
27
+ init_default()
28
+ end
29
+ end
@@ -2,23 +2,28 @@
2
2
  # define relationships
3
3
  module Phenomenal::ContextRelationships
4
4
  def requires(context,*contexts)
5
- contexts = contexts.push(context)
6
- contexts.each do |target|
5
+ set_context_relationship(context,contexts) do |target|
7
6
  self.parent_feature.requirements_for(self,{:on=>target})
8
7
  end
9
8
  end
10
9
 
11
10
  def implies(context,*contexts)
12
- contexts = contexts.push(context)
13
- contexts.each do |target|
11
+ set_context_relationship(context,contexts) do |target|
14
12
  self.parent_feature.implications_for(self,{:on=>target})
15
13
  end
16
14
  end
17
15
 
18
16
  def suggests(context,*contexts)
17
+ set_context_relationship(context,contexts) do |target|
18
+ self.parent_feature.suggestions_for(self,{:on=>target})
19
+ end
20
+ end
21
+
22
+ private
23
+ def set_context_relationship(context,contexts)
19
24
  contexts = contexts.push(context)
20
25
  contexts.each do |target|
21
- self.parent_feature.suggestions_for(self,{:on=>target})
26
+ yield(target)
22
27
  end
23
28
  end
24
29
  end
@@ -23,6 +23,7 @@ module Phenomenal::FeatureRelationships
23
23
  alias_method :phen_suggestions_for,:suggestions_for
24
24
 
25
25
  private
26
+ # Create the new relationships and add them to the runtime system
26
27
  def add_relationship(source,targets,type)
27
28
  targets[:on]=Array.new.push(targets[:on]) if !targets[:on].is_a?(Array)
28
29
  if targets[:on].nil?
@@ -34,12 +35,18 @@ module Phenomenal::FeatureRelationships
34
35
  r = type.new(source,target,self)
35
36
  if relationships.find{|o| o==r}.nil?
36
37
  relationships.push(r)
37
- if self.active?
38
- r.refresh
39
- manager.rmanager.relationships.add(r)
40
- r.activate_feature
41
- end
38
+ set_relationship(r)
42
39
  end
43
40
  end
44
41
  end
42
+
43
+ # Refresh the references (replace symbol by actual object reference)
44
+ # And activate the relationship if the associated feature is already active
45
+ def set_relationship(relationship)
46
+ if self.active?
47
+ relationship.refresh
48
+ manager.rmanager.relationships.add(relationship)
49
+ relationship.activate_feature
50
+ end
51
+ end
45
52
  end
@@ -37,39 +37,34 @@ class Phenomenal::RelationshipsStore
37
37
  # Do nothing when anonymous, references are already valid
38
38
  return if context.anonymous?
39
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
+ set_references(@sources,context) do
41
+ relationship.source=context
45
42
  end
46
43
  # Update targets
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)
44
+ set_references(@sources,context) do
45
+ relationship.target=context
52
46
  end
53
47
  end
54
48
 
49
+ # Return all relationships for 'context'
55
50
  def get_for(context)
56
- get_for_source(context).concat(get_for_target(context))
51
+ array_for(@sources,context).concat(array_for(@targets,context))
57
52
  end
58
53
 
59
54
  private
60
- # Return an array of relationships
61
- def get_for_source(source)
62
- rel = @sources[source]
63
- if rel.nil?
64
- Array.new
65
- else
66
- rel
55
+ # Set the references for 'context' (according to 'block')
56
+ def set_references(contexts,context,&block)
57
+ if !contexts[context.name].nil?
58
+ contexts[context.name].each do |relationship|
59
+ yield
60
+ end
61
+ contexts[context]=contexts.delete(context.name)
67
62
  end
68
63
  end
69
64
 
70
65
  # Return an array of relationships
71
- def get_for_target(target)
72
- rel = @targets[target]
66
+ def array_for(contexts,context)
67
+ rel = contexts[context]
73
68
  if rel.nil?
74
69
  Array.new
75
70
  else
@@ -1,3 +1,3 @@
1
1
  module Phenomenal
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -14,8 +14,8 @@ class Phenomenal::Viewer::Graphical
14
14
  def initialize(destination_file)
15
15
  if !@@graphviz
16
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"
17
+ "The 'ruby-graphviz' gem isn't available. Please install it to generate graphic visualisations\n"+
18
+ " Otherwise use the text version: phen_textual_view"
19
19
  )
20
20
  end
21
21
 
@@ -29,16 +29,10 @@ class Phenomenal::Viewer::Graphical
29
29
  end
30
30
 
31
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
32
+ set_options()
39
33
  # Add nodes to the graph
40
34
  self.manager.contexts.each do |key,context|
41
- if not self.feature_nodes.include?(context) and not @context_nodes.include?(context)
35
+ if !feature_nodes.include?(context) && !context_nodes.include?(context)
42
36
  add_node_for(context)
43
37
  end
44
38
  end
@@ -46,71 +40,99 @@ class Phenomenal::Viewer::Graphical
46
40
  self.manager.contexts.each do |key,context|
47
41
  add_edges_for(context)
48
42
  end
49
- self.main_graph.output( :png => self.destination_file )
43
+ self.main_graph.output(:png => destination_file)
50
44
  end
51
45
 
52
46
  private
47
+ def set_options
48
+ # Create main graph
49
+ self.main_graph = GraphViz::new("")
50
+ # Default options
51
+ self.main_graph[:compound] = "true"
52
+ self.main_graph.edge[:lhead] = ""
53
+ self.main_graph.edge[:ltail] = ""
54
+ self.main_graph.edge[:fontsize] = 10.0
55
+ end
56
+
53
57
  def add_edges_for(context)
54
58
  if context.is_a?(Phenomenal::Feature)
55
59
  context.relationships.each do |relationship|
56
60
  # Get source and destionation node
57
- ltail=""
58
- lhead=""
59
61
  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
62
+ source_node,ltail = node(relationship.source)
63
+ target_node,lhead = node(relationship.target)
64
+ # Get graph container
65
+ graph = graph_container(relationship)
80
66
  # 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
67
+ edge = graph.add_edges(source_node,target_node,:ltail=>ltail,:lhead=>lhead)
68
+ # Define edge type
69
+ set_edge(context,edge,relationship)
99
70
  end
100
71
  end
101
72
  end
102
73
 
74
+ def node(feature)
75
+ if feature_nodes.include?(feature)
76
+ [r_feature_nodes[feature],"cluster_#{feature.to_s}"]
77
+ else
78
+ [context_nodes[feature],""]
79
+ end
80
+ end
81
+
82
+ def graph_container(relationship)
83
+ s_parent_feature=relationship.source.parent_feature
84
+ t_parent_feature=relationship.target.parent_feature
85
+ if s_parent_feature==t_parent_feature && s_parent_feature!=manager.default_context
86
+ feature_nodes[relationship.source.parent_feature]
87
+ else
88
+ main_graph
89
+ end
90
+ end
91
+
92
+ def set_edge(context,edge,relationship)
93
+ # Define edge label
94
+ if context!=manager.default_context
95
+ edge[:label]=context.to_s
96
+ end
97
+ # Define edge color
98
+ if rmanager.relationships.include?(relationship)
99
+ edge[:color]="red"
100
+ end
101
+ # Define arrow type
102
+ if relationship.is_a?(Phenomenal::Implication)
103
+ edge[:arrowhead]="normal"
104
+ elsif relationship.is_a?(Phenomenal::Suggestion)
105
+ edge[:arrowhead]="empty"
106
+ elsif relationship.is_a?(Phenomenal::Requirement)
107
+ edge[:arrowhead]="inv"
108
+ else
109
+ Phenomenal::Logger.instance.error(
110
+ "This relationship hasn't been defined yet in the graphical viewer"
111
+ )
112
+ end
113
+ end
114
+
103
115
  def add_node_for(context)
104
116
  # 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
117
+ if feature_nodes[context.parent_feature].nil? && context==manager.default_context
118
+ current_graph=main_graph
107
119
  # 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)
120
+ elsif feature_nodes[context.parent_feature].nil?
121
+ add_node_for(context.parent_feature)
110
122
  else
111
- current_graph=self.feature_nodes[context.parent_feature]
123
+ current_graph=feature_nodes[context.parent_feature]
112
124
  end
113
125
  # Add node
126
+ node = new_node_for(context,current_graph)
127
+ # Define node color
128
+ if context.active?
129
+ node[:color]="red"
130
+ else
131
+ node[:color]="black"
132
+ end
133
+ end
134
+
135
+ def new_node_for(context,current_graph)
114
136
  if context.is_a?(Phenomenal::Feature)
115
137
  node=current_graph.add_graph("cluster_#{context.to_s}")
116
138
  node[:label]="#{context.to_s}"
@@ -122,15 +144,11 @@ class Phenomenal::Viewer::Graphical
122
144
  fr[:fixedsize]=true
123
145
  self.feature_nodes[context]=node
124
146
  self.r_feature_nodes[context]=fr
147
+ node
125
148
  else
126
149
  node=current_graph.add_nodes(context.to_s)
127
150
  self.context_nodes[context]=node
128
151
  end
129
- # Define node color
130
- if context.active?
131
- node[:color]="red"
132
- else
133
- node[:color]="black"
134
- end
152
+ node
135
153
  end
136
154
  end
@@ -107,7 +107,7 @@ describe "Conflict policies" do
107
107
  phone.advertise(call).should=="ringtone"
108
108
  end
109
109
 
110
- it "should PPPPPPPPPPPPPPPPPPPP" do
110
+ it "should proceed adaptations in the rigth order" do
111
111
  phen_change_conflict_policy { |a,b| age_conflict_policy(a,b) }
112
112
  context(:level1)
113
113
  phen_add_adaptation(:level1,TestClass,:print) do |arg|
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,11 @@
1
+ require 'simplecov'
2
+ if ENV["COVERAGE"]
3
+ SimpleCov.start do
4
+ add_filter "/spec/"
5
+ add_filter "/lib/phenomenal/viewer"
6
+ end
7
+ end
8
+
1
9
  require 'rspec'
2
10
  require "phenomenal"
3
11
  require "test_classes.rb"
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: phenomenal
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.1.0
5
+ version: 1.1.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Loic Vigneron - Thibault Poncelet
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-04-17 00:00:00 Z
13
+ date: 2012-05-09 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -32,9 +32,7 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
 
34
34
  files:
35
- - lib/phenomenal/manager.rb
36
35
  - lib/phenomenal/feature.rb
37
- - lib/phenomenal/conflict_policies.rb
38
36
  - lib/phenomenal/viewer/graphical.rb
39
37
  - lib/phenomenal/viewer/textual.rb
40
38
  - lib/phenomenal/viewer/dsl.rb
@@ -50,6 +48,10 @@ files:
50
48
  - lib/phenomenal/relationships/suggestion.rb
51
49
  - lib/phenomenal/relationships/feature_relationships.rb
52
50
  - lib/phenomenal/context.rb
51
+ - lib/phenomenal/manager/manager.rb
52
+ - lib/phenomenal/manager/conflict_policies.rb
53
+ - lib/phenomenal/manager/adaptations_management.rb
54
+ - lib/phenomenal/manager/contexts_management.rb
53
55
  - lib/phenomenal/logger.rb
54
56
  - lib/phenomenal/adaptation.rb
55
57
  - lib/phenomenal.rb
@@ -1,316 +0,0 @@
1
- require 'singleton'
2
- # This class manage the different contexts in the system and their interactions
3
- class Phenomenal::Manager
4
- include Singleton
5
- include Phenomenal::ConflictPolicies
6
-
7
- attr_accessor :active_adaptations, :deployed_adaptations, :contexts,
8
- :default_context, :combined_contexts, :shared_contexts, :rmanager
9
-
10
- # Register a new context
11
- def register_context(context)
12
- if context_defined?(context)
13
- Phenomenal::Logger.instance.error(
14
- "The context #{context} is already registered"
15
- )
16
- end
17
- if context.name && context_defined?(context.name)
18
- Phenomenal::Logger.instance.error(
19
- "There is already a context with name: #{context.name}." +
20
- " If you want to have named context it has to be a globally unique name"
21
- )
22
- end
23
- # Update the relationships that concern this context
24
- rmanager.update_relationships_references(context)
25
- # Store the context at its ID
26
- contexts[context]=context
27
- end
28
-
29
- # Unregister a context (forget)
30
- def unregister_context(context)
31
- if context==default_context && contexts.size>1
32
- Phenomenal::Logger.instance.error(
33
- "Default context can only be forgotten when alone"
34
- )
35
- else
36
- contexts.delete(context)
37
- # Forgot combined contexts
38
- combined_contexts.delete(context)
39
- if shared_contexts[context]
40
- shared_contexts[context].each do |c|
41
- c.forget
42
- end
43
- end
44
- # Restore default context
45
- init_default() if context==default_context
46
- end
47
- end
48
-
49
- # Register a new adaptation for a registered context
50
- def register_adaptation(adaptation)
51
- default_adaptation = default_context.adaptations.find do|i|
52
- i.concern?(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
53
- end
54
- if adaptation.context!=default_context && !default_adaptation
55
- save_default_adaptation(adaptation.klass, adaptation.method_name,adaptation.instance_adaptation?)
56
- end
57
- activate_adaptation(adaptation) if adaptation.context.active?
58
- end
59
-
60
- # Unregister an adaptation for a registered context
61
- def unregister_adaptation(adaptation)
62
- deactivate_adaptation(adaptation) if adaptation.context.active?
63
- end
64
-
65
- # Activate the context 'context' and deploy the related adaptation
66
- def activate_context(context)
67
- begin
68
- # Relationships managment
69
- rmanager.activate_relationships(context) if context.just_activated?
70
- # Activation of adaptations
71
- context.adaptations.each{ |i| activate_adaptation(i) }
72
- #puts "activation of #{context}"
73
- if shared_contexts[context]
74
- #puts "trigger activation of #{shared_contexts[context].first.information}"
75
- shared_contexts[context].each do |combined_context|
76
- need_activation=true
77
- combined_contexts[combined_context].each do |shared_context|
78
- need_activation=false if !shared_context.active?
79
- end
80
- combined_context.activate if need_activation
81
- end
82
- end
83
-
84
- rescue Phenomenal::Error
85
- context.deactivate # rollback the deployed adaptations
86
- raise # throw up the exception
87
- end
88
- end
89
-
90
- # Deactivate the adaptations (undeploy if needed)
91
- def deactivate_context(context)
92
- #Relationships managment
93
- rmanager.deactivate_relationships(context)
94
- #Adaptations deactivation
95
- context.adaptations.each do |i|
96
- deactivate_adaptation(i)
97
- end
98
- if shared_contexts[context]
99
- shared_contexts[context].each do |combined_context|
100
- while combined_context.active? do
101
- combined_context.deactivate
102
- end
103
- end
104
- end
105
- end
106
-
107
- # Call the old implementation of the method 'caller.caller_method'
108
- def proceed(calling_stack,instance,*args,&block)
109
- calling_adaptation = find_adaptation(calling_stack)
110
- # IMPROVE Problems will appears if proceed called in a file where
111
- # adaptations are defined but not in one of them=> how to check?
112
- # IMPROVE Problems will also appears if two adaptations are defined on the same
113
- # line using the ';' some check needed at add_adaptation ?
114
- adaptations_stack = sorted_adaptations_for(calling_adaptation.klass,
115
- calling_adaptation.method_name,calling_adaptation.instance_adaptation?)
116
- calling_adaptation_index = adaptations_stack.find_index(calling_adaptation)
117
- next_adaptation = adaptations_stack[calling_adaptation_index+1]
118
- next_adaptation.bind(instance,*args, &block)
119
- end
120
-
121
- # Change the conflict resolution policy.
122
- # These can be ones from the ConflictPolicies module or other ones
123
- # Other one should return -1 or +1 following the resolution order
124
- def change_conflict_policy (&block)
125
- self.class.class_eval{define_method(:conflict_policy,&block)}
126
- end
127
-
128
- # Return the corresponding context (or combined context) or raise an error
129
- # if the context isn't currently registered.
130
- # The 'context' parameter can be either a reference to a context instance or
131
- # a Symbol with the name of a named (not anonymous) context.
132
- def find_context(context, *contexts)
133
- if contexts.length==0
134
- find_simple_context(context)
135
- else #Combined contexts
136
- contexts.insert(0,context)
137
- find_combined_context(contexts)
138
- end
139
- end
140
-
141
- # Check wether context 'context' (or combined context) exist in the context manager
142
- # Context can be either the context name or the context instance itself
143
- # Return the context if found, or nil otherwise
144
- def context_defined?(context, *contexts)
145
- c=nil
146
- begin
147
- c = find_context(context,*contexts)
148
- rescue Phenomenal::Error
149
- return nil
150
- end
151
- return c
152
- end
153
-
154
-
155
- # Resolution policy
156
- def conflict_policy(context1, context2)
157
- age_conflict_policy(context1, context2)
158
- end
159
-
160
- private
161
- def find_simple_context(context)
162
- find=nil
163
- if !context.kind_of?(Phenomenal::Context)
164
- a = contexts.find{|k,v| v.name==context}
165
- if a
166
- find = a[1]
167
- end
168
- else
169
- find = context if contexts.has_key?(context)
170
- end
171
- if find
172
- find
173
- else
174
- Phenomenal::Logger.instance.error(
175
- "Unknown context #{context}"
176
- )
177
- end
178
- end
179
-
180
- def find_combined_context(contexts)
181
- list=Array.new
182
- contexts.each do |c|
183
- # Use the object instance if already available
184
- # otherwise use the symbol name
185
- c = find_simple_context(c) if context_defined?(c)
186
- if shared_contexts[c]==nil
187
- list.clear
188
- break
189
- elsif list.length==0
190
- # clone otherwise list.clear empty shared_contexts[c]
191
- list=shared_contexts[c].clone
192
- else
193
- list=shared_contexts[c].find_all{|i| list.include?(i) }
194
- end
195
- end
196
- if list.length==0
197
- Phenomenal::Logger.instance.error(
198
- "Unknown combined context #{contexts}"
199
- )
200
- elsif list.length==1
201
- return list.first
202
- else
203
- Phenomenal::Logger.instance.error(
204
- "Multiple definition of combined context #{contexts}"
205
- )
206
- end
207
- end
208
-
209
- # Activate the adaptation and redeploy the adaptations to take the new one
210
- # one in account
211
- def activate_adaptation(adaptation)
212
- if !active_adaptations.include?(adaptation)
213
- active_adaptations.push(adaptation)
214
- end
215
- redeploy_adaptation(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
216
- end
217
-
218
- # Deactivate the adaptation and redeploy the adaptations if necessary
219
- def deactivate_adaptation(adaptation)
220
- active_adaptations.delete(adaptation)
221
- if deployed_adaptations.include?(adaptation)
222
- deployed_adaptations.delete(adaptation)
223
- redeploy_adaptation(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
224
- end
225
- end
226
-
227
- # Redeploy the adaptations concerning klass.method_name according to the
228
- # conflict policy
229
- def redeploy_adaptation(klass, method_name,instance)
230
- to_deploy = resolve_conflict(klass,method_name,instance)
231
- # Do nothing when to_deploy==nil to break at default context deactivation
232
- if !deployed_adaptations.include?(to_deploy) && to_deploy!=nil
233
- deploy_adaptation(to_deploy)
234
- end
235
- end
236
-
237
- # Deploy the adaptation
238
- def deploy_adaptation(adaptation)
239
- to_undeploy = deployed_adaptations.find do |i|
240
- i.concern?(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
241
- end
242
- if to_undeploy!=adaptation # if new adaptation
243
- deployed_adaptations.delete(to_undeploy)
244
- deployed_adaptations.push(adaptation)
245
- adaptation.deploy
246
- end
247
- end
248
-
249
- # Save the default adaptation of a method, ie: the initial method
250
- def save_default_adaptation(klass, method_name,instance)
251
- if instance
252
- method = klass.instance_method(method_name)
253
- else
254
- method = klass.method(method_name)
255
- end
256
- adaptation = default_context.add_adaptation(klass,method_name,instance,method)
257
- end
258
-
259
- # Return the adaptation that math the calling_stack, on the basis of the
260
- # file and the line number --> proceed is always called under an
261
- # adaptation definition
262
- def find_adaptation(calling_stack)
263
- source = calling_stack[0]
264
- source_info = source.scan(/(.+\.rb):(\d+)/)[0]
265
- call_file = source_info[0]
266
- call_line = source_info[1].to_i
267
- i = 0
268
- match = nil
269
- relevants = active_adaptations.select{ |i| i.src_file == call_file }
270
- # Sort by src_line DESC
271
- relevants.sort!{ |a,b| b.src_line <=> a.src_line }
272
- relevants.each do |adaptation|
273
- if adaptation.src_line <= call_line # Find first matching line
274
- match = adaptation
275
- break
276
- end
277
- end
278
-
279
- if match==nil
280
- Phenomenal::Logger.instance.error(
281
- "Inexistant adaptation for proceed call at #{call_file}:#{call_line}"
282
- )
283
- end
284
- match
285
- end
286
-
287
- # Return the best adaptation according to the resolution policy
288
- def resolve_conflict(klass,method_name,instance)
289
- sorted_adaptations_for(klass,method_name,instance).first
290
- end
291
-
292
- # Return the adaptations for a particular method sorted with the
293
- # conflict policy
294
- def sorted_adaptations_for(klass,method_name,instance)
295
- relevant_adaptations =
296
- active_adaptations.find_all { |i| i.concern?(klass, method_name,instance) }
297
- relevant_adaptations.sort!{|a,b| conflict_policy(a.context,b.context)}
298
- end
299
-
300
- # Set the default context
301
- def init_default
302
- self.default_context= Phenomenal::Feature.new(nil,self)
303
- self.default_context.activate
304
- end
305
-
306
- # Private constructor because this is a singleton object
307
- def initialize
308
- @contexts = Hash.new
309
- @deployed_adaptations = Array.new
310
- @active_adaptations = Array.new
311
- @combined_contexts = Hash.new
312
- @shared_contexts = Hash.new
313
- @rmanager = Phenomenal::RelationshipsManager.instance
314
- init_default()
315
- end
316
- end