phenomenal 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/LICENSE +27 -0
  2. data/README +3 -0
  3. data/Rakefile +11 -0
  4. data/lib/phenomenal.rb +24 -0
  5. data/lib/phenomenal/adaptation.rb +79 -0
  6. data/lib/phenomenal/conflict_policies.rb +20 -0
  7. data/lib/phenomenal/context.rb +269 -0
  8. data/lib/phenomenal/dsl.rb +119 -0
  9. data/lib/phenomenal/feature.rb +8 -0
  10. data/lib/phenomenal/logger.rb +34 -0
  11. data/lib/phenomenal/manager.rb +317 -0
  12. data/lib/phenomenal/proc.rb +34 -0
  13. data/lib/phenomenal/relationships/context_relationships.rb +22 -0
  14. data/lib/phenomenal/relationships/dsl.rb +18 -0
  15. data/lib/phenomenal/relationships/feature_relationships.rb +42 -0
  16. data/lib/phenomenal/relationships/implication.rb +35 -0
  17. data/lib/phenomenal/relationships/relationship.rb +42 -0
  18. data/lib/phenomenal/relationships/relationships_manager.rb +63 -0
  19. data/lib/phenomenal/relationships/relationships_store.rb +73 -0
  20. data/lib/phenomenal/relationships/requirement.rb +26 -0
  21. data/lib/phenomenal/relationships/suggestion.rb +41 -0
  22. data/lib/phenomenal/version.rb +3 -0
  23. data/spec/adaptation_spec.rb +64 -0
  24. data/spec/behavior/adaptation_spec.rb +5 -0
  25. data/spec/behavior/combined_contexts_spec.rb +5 -0
  26. data/spec/behavior/composition_spec.rb +5 -0
  27. data/spec/behavior/conflict_policy_spec.rb +5 -0
  28. data/spec/behavior/open_context.rb +5 -0
  29. data/spec/behavior/relationships_spec.rb +249 -0
  30. data/spec/context_spec.rb +268 -0
  31. data/spec/dsl_spec.rb +181 -0
  32. data/spec/feature_spec.rb +5 -0
  33. data/spec/manager_spec.rb +84 -0
  34. data/spec/proc_spec.rb +20 -0
  35. data/spec/relationships/context_relationships_spec.rb +13 -0
  36. data/spec/relationships/dsl_spec.rb +13 -0
  37. data/spec/relationships/feature_relationships_spec.rb +13 -0
  38. data/spec/relationships/relationship_spec.rb +31 -0
  39. data/spec/relationships/relationships_manager_spec.rb +15 -0
  40. data/spec/relationships/relationships_store_spec.rb +19 -0
  41. data/spec/spec_helper.rb +18 -0
  42. data/spec/test_classes.rb +111 -0
  43. metadata +124 -0
@@ -0,0 +1,8 @@
1
+ class Phenomenal::Feature < Phenomenal::Context
2
+ include Phenomenal::FeatureRelationships
3
+
4
+ def initialize(name=nil,manager=nil)
5
+ super(name,manager)
6
+ initialize_relationships
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ require 'logger'
2
+ require 'singleton'
3
+
4
+ class Phenomenal::Error < StandardError; end
5
+
6
+ class Phenomenal::Logger
7
+ attr_accessor :logger
8
+ include Singleton
9
+
10
+ def info(msg)
11
+ logger.info(msg)
12
+ end
13
+
14
+ def debug(msg)
15
+ logger.debug(msg)
16
+ end
17
+
18
+ def warn(msg)
19
+ logger.warn(msg)
20
+ end
21
+
22
+ def error(msg)
23
+ raise(Phenomenal::Error, msg)
24
+ end
25
+
26
+ private
27
+ def initialize
28
+ self.logger = Logger.new(STDOUT)
29
+ self.logger.level = Logger::DEBUG
30
+ self.logger.datetime_format = "%Y-%m-%d - %H:%M:%S"
31
+ end
32
+ end
33
+
34
+
@@ -0,0 +1,317 @@
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
+ contexts[context]=context
24
+ end
25
+
26
+ # Unregister a context (forget)
27
+ def unregister_context(context)
28
+ if context==default_context && !contexts.size==1
29
+ Phenomenal::Logger.instance.error(
30
+ "Default context can only be forgotten when alone"
31
+ )
32
+ else
33
+ contexts.delete(context)
34
+
35
+ # Forgot combined contexts
36
+ combined_contexts.delete(context)
37
+ if shared_contexts[context]
38
+ shared_contexts[context].each do |c|
39
+ c.forget
40
+ end
41
+ end
42
+
43
+ # Restore default context
44
+ init_default() if context==default_context
45
+ end
46
+ end
47
+
48
+ # Register a new adaptation for a registered context
49
+ def register_adaptation(adaptation)
50
+ default_adaptation = default_context.adaptations.find do|i|
51
+ i.concern?(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
52
+ end
53
+ if adaptation.context!=default_context && !default_adaptation
54
+ save_default_adaptation(adaptation.klass, adaptation.method_name,adaptation.instance_adaptation?)
55
+ end
56
+ activate_adaptation(adaptation) if adaptation.context.active?
57
+ end
58
+
59
+ # Unregister an adaptation for a registered context
60
+ def unregister_adaptation(adaptation)
61
+ deactivate_adaptation(adaptation) if adaptation.context.active?
62
+ end
63
+
64
+ # Activate the context 'context' and deploy the related adaptation
65
+ def activate_context(context)
66
+ begin
67
+ # Relationships managment
68
+ rmanager.activate_relationships(context) if context.just_activated?
69
+
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_adapatation(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
+
118
+ next_adaptation = adaptations_stack[calling_adaptation_index+1]
119
+
120
+ next_adaptation.bind(instance,*args, &block)
121
+ end
122
+
123
+ # Change the conflict resolution policy.
124
+ # These can be ones from the ConflictPolicies module or other ones
125
+ # Other one should return -1 or +1 following the resolution order
126
+ def change_conflict_policy (&block)
127
+ self.class.class_eval{define_method(:conflict_policy,&block)}
128
+ end
129
+
130
+ # Return the corresponding context or raise an error if the context isn't
131
+ # currently registered.
132
+ # The 'context' parameter can be either a reference to a context instance or
133
+ # a Symbol with the name of a named (not anonymous) context.
134
+ def find_context(context, *contexts)
135
+ if contexts.length==0
136
+ find_simple_context(context)
137
+ else #Combined contexts
138
+ contexts.insert(0,context)
139
+ find_combined_context(contexts)
140
+ end
141
+ end
142
+
143
+ # Check wether context 'context' exist in the context manager
144
+ # Context can be either the context name or the context instance itself
145
+ # Return the context if found, or nil otherwise
146
+ def context_defined?(context, *contexts)
147
+ c=nil
148
+ begin
149
+ c = find_context(context,*contexts)
150
+ rescue Phenomenal::Error
151
+ return nil
152
+ end
153
+ return c
154
+ end
155
+ # ==== Private methods ==== #
156
+ private
157
+ def find_simple_context(context)
158
+ find=nil
159
+ if !context.kind_of?(Phenomenal::Context)
160
+ a = contexts.find{|k,v| v.name==context}
161
+ if a
162
+ find = a[1]
163
+ end
164
+ else
165
+ find = context if contexts.has_key?(context)
166
+ end
167
+ if find
168
+ find
169
+ else
170
+ Phenomenal::Logger.instance.error(
171
+ "Unknown context #{context}"
172
+ )
173
+ end
174
+ end
175
+
176
+ def find_combined_context(contexts)
177
+ list=Array.new
178
+ contexts.each do |c|
179
+ # Use the object instance if already available
180
+ # otherwise use the symbol name
181
+ c = find_simple_context(c) if context_defined?(c)
182
+ if shared_contexts[c]==nil
183
+ list.clear
184
+ break
185
+ elsif list.length==0
186
+ # clone otherwise list.clear empty shared_contexts[c]
187
+ list=shared_contexts[c].clone
188
+ else
189
+ list=shared_contexts[c].find_all{|i| list.include?(i) }
190
+ end
191
+ end
192
+ if list.length==0
193
+ Phenomenal::Logger.instance.error(
194
+ "Unknown combined context #{contexts}"
195
+ )
196
+ elsif list.length==1
197
+ return list.first
198
+ else
199
+ Phenomenal::Logger.instance.error(
200
+ "Multiple definition of combined context #{contexts}"
201
+ )
202
+ end
203
+ end
204
+
205
+ # Activate the adaptation and redeploy the adaptations to take the new one
206
+ # one in account
207
+ def activate_adaptation(adaptation)
208
+ if !active_adaptations.include?(adaptation)
209
+ active_adaptations.push(adaptation)
210
+ end
211
+ redeploy_adaptation(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
212
+ end
213
+
214
+ # Deactivate the adaptation and redeploy the adaptations if necessary
215
+ def deactivate_adaptation(adaptation)
216
+ active_adaptations.delete(adaptation)
217
+ if deployed_adaptations.include?(adaptation)
218
+ deployed_adaptations.delete(adaptation)
219
+ redeploy_adaptation(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
220
+ end
221
+ end
222
+
223
+ # Redeploy the adaptations concerning klass.method_name according to the
224
+ # conflict policy
225
+ def redeploy_adaptation(klass, method_name,instance)
226
+ to_deploy = resolve_conflict(klass,method_name,instance)
227
+ # Do nothing when to_deploy==nil to break at default context deactivation
228
+ if !deployed_adaptations.include?(to_deploy) && to_deploy!=nil
229
+ deploy_adaptation(to_deploy)
230
+ end
231
+ end
232
+
233
+ # Deploy the adaptation
234
+ def deploy_adaptation(adaptation)
235
+ to_undeploy = deployed_adaptations.find do |i|
236
+ i.concern?(adaptation.klass,adaptation.method_name,adaptation.instance_adaptation?)
237
+ end
238
+ if to_undeploy!=adaptation # if new adaptation
239
+ deployed_adaptations.delete(to_undeploy)
240
+ deployed_adaptations.push(adaptation)
241
+ adaptation.deploy
242
+ end
243
+ end
244
+
245
+ # Save the default adaptation of a method, ie: the initial method
246
+ def save_default_adaptation(klass, method_name,instance)
247
+ if instance
248
+ method = klass.instance_method(method_name)
249
+ else
250
+ method = klass.method(method_name)
251
+ end
252
+ adaptation = default_context.add_adaptation(klass,method_name,instance,method)
253
+ end
254
+
255
+ # Return the adaptation that math the calling_stack, on the basis of the
256
+ # file and the line number --> proceed is always called under an
257
+ # adaptation definition
258
+ def find_adapatation(calling_stack)
259
+ source = calling_stack[0]
260
+ source_info = source.scan(/(.+\.rb):(\d+)/)[0]
261
+ call_file = source_info[0]
262
+ call_line = source_info[1].to_i
263
+ i = 0
264
+ match = nil
265
+ relevants = active_adaptations.select{ |i| i.src_file == call_file }
266
+ # Sort by src_line DESC
267
+ relevants.sort!{ |a,b| b.src_line <=> a.src_line }
268
+ relevants.each do |adaptation|
269
+ if adaptation.src_line <= call_line # Find first matching line
270
+ match = adaptation
271
+ break
272
+ end
273
+ end
274
+
275
+ if match==nil
276
+ Phenomenal::Logger.instance.error(
277
+ "Inexistant adaptation for proceed call at #{call_file}:#{call_line}"
278
+ )
279
+ end
280
+ match
281
+ end
282
+
283
+ # Return the best adaptation according to the resolution policy
284
+ def resolve_conflict(klass,method_name,instance)
285
+ sorted_adaptations_for(klass,method_name,instance).first
286
+ end
287
+
288
+ # Return the adaptations for a particular method sorted with the
289
+ # conflict policy
290
+ def sorted_adaptations_for(klass,method_name,instance)
291
+ relevant_adaptations =
292
+ active_adaptations.find_all { |i| i.concern?(klass, method_name,instance) }
293
+ relevant_adaptations.sort!{|a,b| conflict_policy(a,b)}
294
+ end
295
+
296
+ # Resolution policy
297
+ def conflict_policy(adaptation1, adaptation2)
298
+ age_conflict_policy(adaptation1, adaptation2)
299
+ end
300
+
301
+ # Set the default context
302
+ def init_default
303
+ self.default_context= Phenomenal::Feature.new(nil,self)
304
+ self.default_context.activate
305
+ end
306
+
307
+ # Private constructor because this is a singleton object
308
+ def initialize
309
+ @contexts = Hash.new
310
+ @deployed_adaptations = Array.new
311
+ @active_adaptations = Array.new
312
+ @combined_contexts = Hash.new
313
+ @shared_contexts = Hash.new
314
+ @rmanager = Phenomenal::RelationshipsManager.instance
315
+ init_default()
316
+ end
317
+ end
@@ -0,0 +1,34 @@
1
+ # Add methods to Proc classe
2
+ class Proc
3
+ # Define a bind method on Proc object,
4
+ # This allow to execute a Proc as it was an instance method of reciever
5
+ # --> used for composition of instance methods adaptations
6
+ # Src: http://www.ruby-forum.com/topic/173699
7
+ def phenomenal_bind(receiver)
8
+ block, time = self, Time.now
9
+ (class << receiver; self end).class_eval do
10
+ method_name = "__bind_#{time.to_i}_#{time.usec}-#{rand(100000)}"
11
+ define_method(method_name, &block)
12
+ method = instance_method(method_name)
13
+ remove_method(method_name)
14
+ method
15
+ end.bind(receiver)
16
+ end
17
+
18
+ # Define a bind_class method on Proc object,
19
+ # This allow to execute a Proc as it was an class method of klass
20
+ # --> used for composition of class methods adaptations
21
+ def phenomenal_class_bind(klass)
22
+ block, time = self, Time.now
23
+ method_name = "__bind_#{time.to_i}_#{time.usec}-#{rand(100000)}"
24
+ method = nil
25
+ klass.instance_eval do
26
+ define_singleton_method(method_name, &block)
27
+ method = method(method_name)
28
+ (class << self; self end).instance_eval do
29
+ remove_method(method_name)
30
+ end
31
+ end
32
+ method
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module Phenomenal::ContextRelationships
2
+ def requires(context,*contexts)
3
+ contexts = contexts.push(context)
4
+ contexts.each do |target|
5
+ self.parent_feature.requirements_for(self,{:on=>target})
6
+ end
7
+ end
8
+
9
+ def implies(context,*contexts)
10
+ contexts = contexts.push(context)
11
+ contexts.each do |target|
12
+ self.parent_feature.implications_for(self,{:on=>target})
13
+ end
14
+ end
15
+
16
+ def suggests(context,*contexts)
17
+ contexts = contexts.push(context)
18
+ contexts.each do |target|
19
+ self.parent_feature.suggestions_for(self,{:on=>target})
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module Phenomenal::DSL
2
+ def self.define_relationships(klass)
3
+ klass.class_eval do
4
+ # Requirements
5
+ def requirements_for(source,targets)
6
+ Phenomenal::Manager.instance.default_context.requirements_for(source,targets)
7
+ end
8
+ # Implications
9
+ def implications_for(source,targets)
10
+ Phenomenal::Manager.instance.default_context.implications_for(source,targets)
11
+ end
12
+ # Suggestions
13
+ def suggestions_for(source,targets)
14
+ Phenomenal::Manager.instance.default_context.suggestions_for(source,targets)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ module Phenomenal::FeatureRelationships
2
+ attr_accessor :relationships
3
+
4
+ def initialize_relationships
5
+ @relationships = Array.new
6
+ end
7
+
8
+ def requirements_for(source,targets)
9
+ add_relationship(source,targets,Phenomenal::Requirement)
10
+ end
11
+
12
+ def implications_for(source,targets)
13
+ add_relationship(source,targets,Phenomenal::Implication)
14
+ end
15
+
16
+ def suggestions_for(source,targets)
17
+ add_relationship(source,targets,Phenomenal::Suggestion)
18
+ end
19
+
20
+ private
21
+ def add_relationship(source,targets,type)
22
+ targets[:on]=Array.new.push(targets[:on]) if !targets[:on].is_a?(Array)
23
+
24
+ if targets[:on].nil?
25
+ Phenomenal::Logger.instance.error(
26
+ "Invalid relationship, missing target context"
27
+ )
28
+ end
29
+ targets[:on].each do |target|
30
+ r = type.new(source,target,self)
31
+ if relationships.find{|o| o==r}.nil?
32
+ relationships.push(r)
33
+ if self.active?
34
+ r.refresh
35
+ manager.rmanager.relationships.add(r)
36
+ r.activate_feature
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end