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 +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 [![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
|
-
|
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
|