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 +1 -1
- data/lib/phenomenal.rb +7 -2
- data/lib/phenomenal/context.rb +50 -31
- data/lib/phenomenal/manager/adaptations_management.rb +134 -0
- data/lib/phenomenal/{conflict_policies.rb → manager/conflict_policies.rb} +12 -1
- data/lib/phenomenal/manager/contexts_management.rb +171 -0
- data/lib/phenomenal/manager/manager.rb +29 -0
- data/lib/phenomenal/relationships/context_relationships.rb +10 -5
- data/lib/phenomenal/relationships/feature_relationships.rb +12 -5
- data/lib/phenomenal/relationships/relationships_store.rb +15 -20
- data/lib/phenomenal/version.rb +1 -1
- data/lib/phenomenal/viewer/graphical.rb +80 -62
- data/spec/integration/conflict_policy_spec.rb +1 -1
- data/spec/spec_helper.rb +8 -0
- metadata +6 -4
- data/lib/phenomenal/manager.rb +0 -316
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Phenomenal Gem
|
1
|
+
Phenomenal Gem [](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
|
-
|
17
|
+
|
18
18
|
require_relative "./phenomenal/context.rb"
|
19
19
|
require_relative "./phenomenal/feature.rb"
|
20
20
|
require_relative "./phenomenal/logger.rb"
|
21
|
-
|
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"
|
data/lib/phenomenal/context.rb
CHANGED
@@ -7,12 +7,24 @@ class Phenomenal::Context
|
|
7
7
|
:activation_count, :parent, :forgotten
|
8
8
|
attr_reader :manager,:name
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
contexts
|
13
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
41
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
51
|
+
array_for(@sources,context).concat(array_for(@targets,context))
|
57
52
|
end
|
58
53
|
|
59
54
|
private
|
60
|
-
#
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
72
|
-
rel =
|
66
|
+
def array_for(contexts,context)
|
67
|
+
rel = contexts[context]
|
73
68
|
if rel.nil?
|
74
69
|
Array.new
|
75
70
|
else
|
data/lib/phenomenal/version.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
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(
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
83
|
-
|
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
|
106
|
-
current_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
|
109
|
-
|
120
|
+
elsif feature_nodes[context.parent_feature].nil?
|
121
|
+
add_node_for(context.parent_feature)
|
110
122
|
else
|
111
|
-
current_graph=
|
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
|
-
|
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
|
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
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: phenomenal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.1.
|
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-
|
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
|
data/lib/phenomenal/manager.rb
DELETED
@@ -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
|