phenomenal 0.9.0

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.
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
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (C) 2011 Thibault Poncelet <thibault.poncelet@student.uclouvain.be>
2
+ Copyright (C) 2011 Loic Vigneron <loic.vigneron@student.uclouvain.be>
3
+
4
+ This program is free software; you can redistribute it and/or modify
5
+ it under the terms of the GNU General Public License as published by
6
+ the Free Software Foundation; either version 2 of the License, or
7
+ (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License
15
+ along with this program; if not, write to the Free Software
16
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
+
18
+ ========================
19
+
20
+ Master thesis : The phenomenal gem
21
+ Prof : Kim Mens
22
+ Year : 2011-2012
23
+ Students names,sections :
24
+ - Poncelet Thibault (SINF22MS)
25
+ - Vigneron Loic (SINF22MS)
26
+ Start date : May 1, 2011
27
+
data/README ADDED
@@ -0,0 +1,3 @@
1
+ Phenomenal Gem is a context-oriented framework implemented in Ruby that allows context-oriented programming in Ruby and Ruby on Rails applications.
2
+
3
+ See www.phenomenal-gem.com for more details
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
9
+
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new('spec')
data/lib/phenomenal.rb ADDED
@@ -0,0 +1,24 @@
1
+ # Load the gem files in the system
2
+ module Phenomenal end
3
+ #Relationships
4
+ require_relative "./phenomenal/relationships/context_relationships.rb"
5
+ require_relative "./phenomenal/relationships/feature_relationships.rb"
6
+ require_relative "./phenomenal/relationships/relationships_store.rb"
7
+ require_relative "./phenomenal/relationships/relationships_manager.rb"
8
+ require_relative "./phenomenal/relationships/relationship.rb"
9
+ require_relative "./phenomenal/relationships/requirement.rb"
10
+ require_relative "./phenomenal/relationships/implication.rb"
11
+ require_relative "./phenomenal/relationships/suggestion.rb"
12
+
13
+ # Core
14
+ require_relative "./phenomenal/adaptation.rb"
15
+ require_relative "./phenomenal/conflict_policies.rb"
16
+ require_relative "./phenomenal/context.rb"
17
+ require_relative "./phenomenal/feature.rb"
18
+ require_relative "./phenomenal/logger.rb"
19
+ require_relative "./phenomenal/manager.rb"
20
+ require_relative "./phenomenal/proc.rb"
21
+
22
+ # DSL
23
+ require_relative "./phenomenal/relationships/dsl.rb"
24
+ require_relative "./phenomenal/dsl.rb"
@@ -0,0 +1,79 @@
1
+ # Represent a method adaptation for a particular context
2
+ class Phenomenal::Adaptation
3
+ attr_accessor :context, :klass, :method_name, :implementation, :src_file,
4
+ :src_line,:instance_adaptation
5
+
6
+ def initialize(context,klass, method_name,instance_adapatation,implementation)
7
+ @context = context
8
+ @klass = klass
9
+ @method_name = method_name
10
+ @implementation = implementation
11
+ @instance_adaptation=instance_adapatation
12
+
13
+ check_validity
14
+ # Save the source location if any, this is used to find again the adaptation
15
+ # in a ctxt_proceed call. It always exists except for method directly
16
+ # implemented in C -> Not a problem because these one never use ctxt_proceed
17
+ source_location = implementation.source_location
18
+ if source_location
19
+ @src_file = implementation.source_location[0]
20
+ @src_line = implementation.source_location[1]
21
+ end
22
+ end
23
+
24
+ # Deploy actually the adaptation in the target class by overriding the current
25
+ # implementation
26
+ def deploy
27
+ method_name = self.method_name
28
+ implementation = self.implementation
29
+ if instance_adaptation?
30
+ klass.class_eval { define_method(method_name, implementation) }
31
+ else
32
+ klass.define_singleton_method(method_name,implementation)
33
+ end
34
+ end
35
+
36
+ # IMPROVE check for better implementation
37
+ # Bind the implementation corresponding to this adaptation to 'instance' when
38
+ # instance_method or to implementation klass when class method
39
+ def bind(instance,*args,&block)
40
+ if instance_adaptation?
41
+ if implementation.class==Proc
42
+ implementation.phenomenal_bind(instance).call(*args,&block)
43
+ else
44
+ implementation.bind(instance).call(*args,&block)
45
+ end
46
+ else
47
+ if implementation.class==Proc
48
+ implementation.phenomenal_class_bind(klass).call(*args,&block)
49
+ else
50
+ implementation.call(*args,&block)
51
+ end
52
+ end
53
+ end
54
+
55
+ #True if the adaptation concern the class n_klass and method n_method
56
+ def concern?(klass,method_name,instance_adaptation)
57
+ self.klass==klass &&
58
+ self.method_name==method_name &&
59
+ self.instance_adaptation==instance_adaptation
60
+ end
61
+
62
+ alias_method :instance_adaptation?, :instance_adaptation
63
+
64
+ # String representation
65
+ def to_s
66
+ ":#{context.name} => #{klass.name}.#{method_name} :: #{src_file}:#{src_line}"
67
+ end
68
+
69
+ private
70
+ def check_validity
71
+ if klass.instance_methods.include?(method_name) && !instance_adaptation? ||
72
+ !klass.instance_methods.include?(method_name) && instance_adaptation?
73
+ Phenomenal::Logger.instance.error(
74
+ "Illegal adaptation for context: #{context}" +
75
+ " for #{klass.name}.#{method_name}, type mismatch"
76
+ )
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,20 @@
1
+ # Context conflict resolution policies
2
+ module Phenomenal::ConflictPolicies
3
+ # Prefer not default adaptation, error if two not default ones
4
+ def no_resolution_conflict_policy(adaptation1,adaptation2)
5
+ if adaptation1.context==default_context()
6
+ 1
7
+ elsif adaptation2.context.name==default_context()
8
+ -1
9
+ else #Fail if two non default adaptations
10
+ Phenomenal::Logger.instance.error(
11
+ "Error: Illegal duplicate adapation of #{adaptation1.to_s}"
12
+ )
13
+ end
14
+ end
15
+
16
+ # Age based conflict resolution
17
+ def age_conflict_policy(adaptation1, adaptation2)
18
+ adaptation1.context.age <=> adaptation2.context.age
19
+ end
20
+ end
@@ -0,0 +1,269 @@
1
+ # Represent a first class context
2
+ class Phenomenal::Context
3
+ include Phenomenal::ContextRelationships
4
+ @@total_activations = 0
5
+ def self.create(context,*contexts,nested,closest_feature,&block)
6
+ manager = Phenomenal::Manager.instance
7
+ contexts.insert(0,context)
8
+ if contexts.length==1
9
+ if !manager.context_defined?(context)
10
+ context = self.new(context)
11
+ else
12
+ context = manager.find_context(context)
13
+ if !context.instance_of?(self)
14
+ Phenomenal::Logger.instance.error(
15
+ "Only #{self.name} can be used with this keyword"
16
+ )
17
+ end
18
+ end
19
+ else #Combined contexts
20
+ if !manager.context_defined?(*contexts) # New combined context
21
+ context = self.new
22
+ context.parent=closest_feature # Set parent
23
+ instances = Array.new
24
+ first = contexts.first
25
+ contexts.each do |c|
26
+ # Use the object instance if already available
27
+ # otherwise create it
28
+ if manager.context_defined?(c)
29
+ c = manager.find_context(c)
30
+ if !nested && c!=first && !c.instance_of?(self)
31
+ Phenomenal::Logger.instance.error(
32
+ "Only #{self.name} can be used with this keyword"
33
+ )
34
+ end
35
+ else
36
+ c = self.new(c)
37
+ end
38
+ instances.push(c)
39
+ manager.shared_contexts[c]= Array.new if !manager.shared_contexts[c]
40
+ manager.shared_contexts[c].push(context)
41
+ end
42
+ manager.combined_contexts[context] = instances
43
+ else
44
+ context = manager.find_context(*contexts)
45
+ end
46
+ end
47
+ context.add_adaptations(&block)
48
+ context
49
+ end
50
+
51
+ attr_accessor :activation_age, :activation_frequency, :adaptations,
52
+ :activation_count, :parent,:forgotten
53
+ attr_reader :manager,:name
54
+
55
+ def initialize(name=nil, manager=nil)
56
+ @manager = manager || Phenomenal::Manager.instance
57
+ @name = name
58
+ @activation_age = 0
59
+ @activation_count = 0
60
+ @adaptations = Array.new
61
+ @manager.register_context(self)
62
+ @parent=nil
63
+ @forgotten=false
64
+ end
65
+
66
+ # Unregister the context from the context manager,
67
+ # This context shoudn't be used after.
68
+ # The context has to be inactive before being forgetted
69
+ # TODO handle relationships references
70
+ def forget
71
+ if active?
72
+ Phenomenal::Logger.instance.error(
73
+ "Active context cannot be forgotten"
74
+ )
75
+ else
76
+ manager.unregister_context(self)
77
+ forgotten=true
78
+ end
79
+ end
80
+
81
+ # Add a new method adaptation to the context
82
+ # Return the adaptation just created
83
+ def add_adaptation(klass, method_name,instance,umeth=nil, &implementation)
84
+ if umeth
85
+ implementation = umeth
86
+ instance = klass.instance_methods.include?(method_name)
87
+ end
88
+ if adaptations.find{ |i| i.concern?(klass,method_name,instance) }
89
+ Phenomenal::Logger.instance.error(
90
+ "Error: Illegal duplicated adaptation in context: #{self} for " +
91
+ "#{klass.name}:#{method_name}"
92
+ )
93
+ else
94
+ if klass.instance_methods.include?(method_name) && instance
95
+ method = klass.instance_method(method_name)
96
+ elsif klass.methods.include?(method_name) && !instance
97
+ method = klass.method(method_name)
98
+ else
99
+ Phenomenal::Logger.instance.error(
100
+ "Error: Illegal adaptation for context #{self},a method with "+
101
+ "name: #{method_name} should exist in class #{klass.name} to "+
102
+ "be adapted"
103
+ )
104
+ end
105
+ if method.arity != implementation.arity
106
+ Phenomenal::Logger.instance.error(
107
+ "Error: Illegal adaptation for context #{self},the adaptation "+
108
+ "have to keep the original method arity for method: " +
109
+ "#{klass.name}.#{method_name}: (#{method.arity} instead of " +
110
+ "#{implementation.arity})"
111
+ )
112
+ end
113
+
114
+ adaptation = Phenomenal::Adaptation.new(
115
+ self, klass, method_name,instance, implementation
116
+ )
117
+ adaptations.push(adaptation)
118
+ manager.register_adaptation(adaptation)
119
+ adaptation
120
+ end
121
+ end
122
+
123
+ # Catch nested context calls and transform them in nested contexts creation
124
+ def context(context,*contexts,&block)
125
+ check_validity
126
+ Phenomenal::Context.create(self,context,*contexts,true,self,&block)
127
+ end
128
+ alias_method :phen_context,:context
129
+
130
+ # Catch nested feature calls and transform them in nested contexts creation
131
+ def feature(feature,*features, &block)
132
+ check_validity
133
+ Phenomenal::Feature.create(self,feature,*features,true,self,&block)
134
+ end
135
+ alias_method :phen_feature,:feature
136
+
137
+ # Add multiple adaptations at definition time
138
+ def add_adaptations(&block)
139
+ instance_eval(&block) if block
140
+ end
141
+
142
+ # Set the current adapted class for the next adapt calls
143
+ def adaptations_for(klass)
144
+ @current_adapted_class = klass
145
+ end
146
+
147
+ # Adapt a method for @current_adapted_class
148
+ def adapt(method,&block)
149
+ add_adaptation(@current_adapted_class,method,true,&block)
150
+ end
151
+
152
+ # Adapt a method for @current_adapted_class
153
+ def adapt_klass(method,&block)
154
+ add_adaptation(@current_adapted_class,method,false,&block)
155
+ end
156
+
157
+ # Remove a method adaptation from the context
158
+ def remove_adaptation(klass,method_name,instance)
159
+ adaptation_index =
160
+ adaptations.find_index{ |i| i.concern?(klass, method_name,instance) }
161
+ if !adaptation_index
162
+ Phenomenal::Logger.instance.error(
163
+ "Error: Illegal deleting of an inexistent adaptation in context: " +
164
+ "#{self} for #{klass.name}.#{method_name})"
165
+ )
166
+ end
167
+
168
+ adaptation = adaptations.delete_at(adaptation_index)
169
+ manager.unregister_adaptation(adaptation)
170
+ end
171
+
172
+ # Activate the context
173
+ def activate
174
+ check_validity
175
+ @@total_activations +=1
176
+ self.activation_age =@@total_activations
177
+ self.activation_count = self.activation_count+1
178
+ manager.activate_context(self)
179
+ self
180
+ end
181
+
182
+ # Deactivate the context
183
+ def deactivate(caller_context=nil)
184
+ check_validity
185
+ was_active = active?
186
+ if self.activation_count>0
187
+ #Deactivation
188
+ self.activation_count = self.activation_count-1
189
+ end
190
+ if was_active && !active?
191
+ manager.deactivate_context(self)
192
+ end
193
+ self
194
+ end
195
+
196
+ # True if the context is active
197
+ def active?
198
+ activation_count>0
199
+ end
200
+
201
+ # True if the context has just became active
202
+ def just_activated?
203
+ activation_count==1
204
+ end
205
+
206
+ # True if the context is anonymous
207
+ def anonymous?
208
+ name.nil?
209
+ end
210
+
211
+ # Return the activation age of the context:
212
+ # The age counter minus the age counter when the context was activated
213
+ # for the last time
214
+ def age
215
+ @@total_activations-activation_age
216
+ end
217
+
218
+ # Return context informations:
219
+ # - Name
220
+ # - List of the adaptations
221
+ # - Active state
222
+ # - Activation age
223
+ # - Activation count
224
+ def information
225
+ {
226
+ :name=>name,
227
+ :adaptations=>adaptations,
228
+ :active=>active?,
229
+ :age=>age,
230
+ :activation_count=>activation_count,
231
+ :type=>self.class.name
232
+ }
233
+ end
234
+
235
+ # Return the closest parent feature of the context
236
+ def parent_feature
237
+ p = parent
238
+ while p!=nil && !p.is_a?(Phenomenal::Feature) do
239
+ p=p.parent
240
+ end
241
+ if p.nil?
242
+ manager.default_context
243
+ else
244
+ p
245
+ end
246
+ end
247
+
248
+ # String representation of the context
249
+ def to_s
250
+ if name
251
+ name.to_s
252
+ elsif self==manager.default_context
253
+ "'Default context'"
254
+ elsif manager.combined_contexts[self]
255
+ "'Combined context : #{manager.combined_contexts[self].flatten}'"
256
+ else
257
+ "'Anonymous context'"
258
+ end
259
+ end
260
+
261
+ private
262
+ def check_validity
263
+ if forgotten
264
+ Phenomenal::Logger.instance.error(
265
+ "Action not allowed anymore when context has been forgotten"
266
+ )
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,119 @@
1
+ # Define the DSL methods
2
+ module Phenomenal::DSL
3
+ #Override included hook method
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ #Define Context
7
+ def phen_define_context(name=nil,priority=nil)
8
+ Phenomenal::Context.new(name,priority)
9
+ end
10
+ # Define context with adaptations
11
+ def phen_context(context,*contexts,&block)
12
+ Phenomenal::Context.create(context,*contexts,false,nil,&block)
13
+ end
14
+ Phenomenal::DSL.phen_alias(:context,klass)
15
+
16
+ # Define context with adaptations
17
+ def phen_feature(context,*contexts,&block)
18
+ Phenomenal::Feature.create(context,*contexts,false,nil,&block)
19
+ end
20
+ Phenomenal::DSL.phen_alias(:feature,klass)
21
+
22
+
23
+ # Forget Context
24
+ def phen_forget_context(context)
25
+ Phenomenal::Manager.instance.find_context(context).forget
26
+ end
27
+
28
+ # Add adaptation
29
+ def phen_add_adaptation(context,klass, method_name, &implementation)
30
+ Phenomenal::Manager.instance.find_context(context).add_adaptation(
31
+ klass, method_name,true, &implementation
32
+ )
33
+ end
34
+
35
+ def phen_add_class_adaptation(context,klass, method_name, &implementation)
36
+ Phenomenal::Manager.instance.find_context(context).add_adaptation(
37
+ klass, method_name,false, &implementation
38
+ )
39
+ end
40
+
41
+ # Remove Adaptation
42
+ def phen_remove_adaptation(context,klass,method_name)
43
+ Phenomenal::Manager.instance.find_context(context).remove_adaptation(
44
+ klass,method_name,true
45
+ )
46
+ end
47
+
48
+ def phen_remove_class_adaptation(context,klass,method_name)
49
+ Phenomenal::Manager.instance.find_context(context).remove_adaptation(
50
+ klass,method_name,false
51
+ )
52
+ end
53
+
54
+ # Activate Context
55
+ def phen_activate_context(context)
56
+ Phenomenal::Manager.instance.find_context(context).activate
57
+ end
58
+ Phenomenal::DSL.phen_alias(:activate_context,klass)
59
+
60
+ # Deactivate Context
61
+ def phen_deactivate_context(context)
62
+ Phenomenal::Manager.instance.find_context(context).deactivate
63
+ end
64
+ Phenomenal::DSL.phen_alias(:deactivate_context,klass)
65
+
66
+ # Context is active?
67
+ def phen_context_active?(context)
68
+ Phenomenal::Manager.instance.find_context(context).active?
69
+ end
70
+
71
+ # Context informations
72
+ def phen_context_information(context)
73
+ Phenomenal::Manager.instance.find_context(context).information
74
+ end
75
+
76
+ # Default Context
77
+ def phen_default_context
78
+ Phenomenal::Manager.instance.default_context
79
+ end
80
+
81
+ # Defined context registered in the manager
82
+ def phen_defined_contexts
83
+ Phenomenal::Manager.instance.contexts.values
84
+ end
85
+
86
+ # Proceed
87
+ def phen_proceed(*args,&block)
88
+ Phenomenal::Manager.instance.proceed(caller,self,*args,&block)
89
+ end
90
+ Phenomenal::DSL.phen_alias(:proceed,klass)
91
+
92
+ # Change conflict resolution policy (for the proceed call)
93
+ def phen_change_conflict_policy(&block)
94
+ Phenomenal::Manager.instance.change_conflict_policy(&block)
95
+ end
96
+ end
97
+ # Add relationships specific DSL
98
+ define_relationships(klass)
99
+ end
100
+
101
+ private
102
+ def self.phen_alias(method,klass)
103
+ if Kernel.respond_to? method
104
+ Phenomenal::Logger.instance.warn(
105
+ "The Phenomenal DSL keyword #{method} wasn't defined, use"+
106
+ " phen_#{method} instead"
107
+ )
108
+ else
109
+ klass.class_eval do
110
+ alias_method method, "phen_#{method}"
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ #Load the phenomenal primitives as top level methods
117
+ module Kernel
118
+ include Phenomenal::DSL
119
+ end