phenomenal 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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