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
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