ruby-uml 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ require 'observer'
2
+
3
+ class TempSensor
4
+ include Observable
5
+
6
+ def run(start = 26)
7
+ start.upto(27) do |temp|
8
+ changed
9
+ notify_observers temp
10
+ end
11
+ end
12
+ end
13
+
14
+ class TempAlarm
15
+
16
+ def initialize(sensor)
17
+ sensor.add_observer self
18
+ end
19
+
20
+ def update(temp)
21
+ $stderr.puts temp if temp > 26
22
+ temp
23
+ end
24
+
25
+ end
@@ -0,0 +1,24 @@
1
+ require 'observer'
2
+
3
+ class TempSensor
4
+ include Observable
5
+
6
+ def run(start = 26)
7
+ start.upto(27) do |temp|
8
+ changed
9
+ notify_observers temp
10
+ end
11
+ end
12
+ end
13
+
14
+ class TempAlarm
15
+
16
+ def initialize(sensor)
17
+ sensor.add_observer self
18
+ end
19
+
20
+ def update(temp)
21
+ temp
22
+ end
23
+
24
+ end
@@ -0,0 +1,208 @@
1
+ require 'uml/lowlevel_backtracer'
2
+ require 'uml/class_diagram_helper'
3
+
4
+ module UML
5
+
6
+ # Generates dot representation out of program execution.
7
+ #
8
+ # Starts with information gathering as soon as it is created.
9
+ #
10
+ # TODO Parser in to_dot who checks double directed association and substitutes with undirected.
11
+ #
12
+ # TODO When cluster_packages is true, module is printed as subgraph and node.
13
+ class ClassDiagram
14
+ include Graphviz
15
+
16
+ # Configuration options:
17
+ # show_private_methods:: If +true+, includes private instance methods in Nodes.
18
+ #
19
+ # Default is +false+.
20
+ # show_protected_methods:: If +true+, includes protected instance methods in Nodes.
21
+ #
22
+ # Default is +false+.
23
+ # show_public_methods:: If +true+, includes public instance methods in Nodes.
24
+ #
25
+ # Default is +true+.
26
+ # cluster_packages:: If +true+, namespaces are clustered into a package-like representation.
27
+ #
28
+ # Graphviz can't plot UML-package with tab, so there is just the package name in
29
+ # the top-left corner of a box. Defaults to +false+.
30
+ # exclude:: Array which contains information for classes to exclude from graph.
31
+ #
32
+ # Can contain regular expressions, strings, symbols, or Constants.
33
+ #
34
+ # Defaults to empty Array.
35
+ # include:: Array which contains information for desired classes.
36
+ #
37
+ # Can contain regular expressions, strings, symbols, or Constants.
38
+ #
39
+ # Defaults to empty Array.
40
+ def initialize(config = {})
41
+ @config = {
42
+ :show_private_methods => false,
43
+ :show_protected_methods => false,
44
+ :show_public_methods => true,
45
+ :cluster_packages => false,
46
+ :exclude => [],
47
+ :include => []
48
+ }.update(config)
49
+
50
+ @graph = Graph.new('digraph', 'class_diagram')
51
+ @graph.default_node_attributes[:shape] = 'record'
52
+ @graph.default_graph_attributes[:labelloc] = 't'
53
+ @graph.default_graph_attributes[:labeljust] = 'l'
54
+
55
+ @tracer = LowlevelBacktracer.instance
56
+ @tracer.add_observer self
57
+ end
58
+
59
+ # Returns a dot representation of gathered information
60
+ def to_dot
61
+ @graph.to_dot
62
+ end
63
+
64
+ # Manually add a klass with its included modules and superclasses to graph.
65
+ #
66
+ # klass has to be accepted by configuration options.
67
+ def include(klass)
68
+ generate_node_with_supers(inheritance(klass))
69
+ end
70
+
71
+ def update(event, tracer) # :nodoc:
72
+ return if event != :call
73
+
74
+ right = tracer.call_stack[-1]
75
+ return if not accepted(right[:klass])
76
+
77
+ left = called_by_interesting(tracer.call_stack)
78
+
79
+ include(right[:klass])
80
+ if left
81
+ include(left[:klass])
82
+ @graph.edges[[left[:klass], right[:klass]]] ||= Dependency.new(left[:klass], right[:klass]) if left[:klass] != right[:klass]
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # Creates parent subgraphs and returns needed one if package clustering is activated.
89
+ # If not, returns just toplevel graph.
90
+ def get_graph(klass)
91
+ return @graph if not @config[:cluster_packages]
92
+ result = @graph
93
+ namespaces = klass.to_s.split('::')
94
+ object = namespaces.pop
95
+ namespaces.each do |namespace|
96
+ unless result.subgraphs[namespace]
97
+ result.subgraphs[namespace] = Graph.new('subgraph', "cluster_#{namespace}")
98
+ result.subgraphs[namespace].default_graph_attributes[:label] = namespace
99
+ end
100
+ result = result.subgraphs[namespace]
101
+ end
102
+ return result
103
+ end
104
+
105
+ # When package clustering is activated, node labels contain just name of module.
106
+ # If not, the full qualified name is returned.
107
+ def get_nodelabel(klass)
108
+ @config[:cluster_packages] ? klass.to_s.split('::').last : klass
109
+ end
110
+
111
+ def called_by_interesting(stack)
112
+ stack.reverse_each do |element|
113
+ next if element == stack.last
114
+ return element if accepted(element[:klass])
115
+ end
116
+ nil
117
+ end
118
+
119
+ # Checks if klass is wanted or not according to +:include+ and +:exclude+ configuration options.
120
+ # If +:include+ is empty, everything is accepted at first place.
121
+ # Exclusions are checked at second.
122
+ def accepted(klass)
123
+ (@config[:include].empty? ? true : test_inclusion(@config[:include], klass)) and not test_inclusion(@config[:exclude], klass)
124
+ end
125
+
126
+ # Is called by accepted.
127
+ # Makes it possible to use regular expressions, constants, strings or symbols
128
+ # in +:include+ and +:exclude+ configuration options.
129
+ def test_inclusion(array, klass)
130
+ array.each do |element|
131
+ case element
132
+ when Regexp
133
+ return true if element =~ klass.to_s
134
+ when String, Symbol
135
+ return true if element.to_s == klass.to_s
136
+ else
137
+ return true if element == klass
138
+ end
139
+ end
140
+ false
141
+ end
142
+
143
+ def generate_public_method_hash(object)
144
+ object.public_instance_methods(false).inject({}) do |result, m|
145
+ result[m] = '+'
146
+ result
147
+ end
148
+ end
149
+
150
+ def generate_protected_method_hash(object)
151
+ object.protected_instance_methods(false).inject({}) do |result, m|
152
+ result[m] = '#'
153
+ result
154
+ end
155
+ end
156
+
157
+ def generate_private_method_hash(object)
158
+ object.private_instance_methods(false).inject({}) do |result, m|
159
+ result[m] = '-'
160
+ result
161
+ end
162
+ end
163
+
164
+ # Add or update a node in graph with desired informations.
165
+ # Node is integrated in right graph, according to +:cluster_packages+
166
+ def generate_node(klass)
167
+ target_graph = get_graph(klass)
168
+ target_graph.nodes[klass] ||= ClassNode.new(klass, get_nodelabel(klass))
169
+ target_graph.nodes[klass].methods.update(generate_public_method_hash(klass)) if @config[:show_public_methods]
170
+ target_graph.nodes[klass].methods.update(generate_protected_method_hash(klass)) if @config[:show_protected_methods]
171
+ target_graph.nodes[klass].methods.update(generate_private_method_hash(klass)) if @config[:show_private_methods]
172
+ end
173
+
174
+ # Uses hash from +inheritance+ to generate needed nodes with generalization edges
175
+ def generate_node_with_supers(hash, subclass = nil)
176
+ hash.each do |key, value|
177
+ generate_node(key)
178
+ generate_node_with_supers(value, key)
179
+
180
+ # Generalization always has high priority. That means an existing edge at
181
+ # [subclass, key] is overwritten.
182
+ # Edges are always integrated in top graph.
183
+ @graph.edges[[subclass, key]] = Generalization.new(subclass, key) if subclass
184
+ end
185
+ end
186
+
187
+ # Returns recursive hash with included modules and superclasses.
188
+ # Objects that are not accepted by configuration options and their predecessors
189
+ # are omitted.
190
+ # Modules are included once in deepest object that included it.
191
+ def inheritance(klass, excludes = [])
192
+ result = {}
193
+ if klass and (accepted(klass) and not excludes.include?(klass))
194
+
195
+ result[klass] = klass.respond_to?(:superclass) ? inheritance(klass.superclass, excludes) : {}
196
+ excludes << klass
197
+
198
+ klass.included_modules.each do |mod|
199
+ result[klass].update(inheritance(mod, excludes))
200
+ excludes << mod
201
+ end
202
+
203
+ end
204
+ result
205
+ end
206
+
207
+ end
208
+ end
@@ -0,0 +1,61 @@
1
+ require 'uml/graphviz_helper'
2
+
3
+ module UML
4
+ module Graphviz
5
+
6
+ class ClassNode < Node
7
+
8
+ attr_accessor :methods
9
+
10
+ def initialize(name, title)
11
+ super(name)
12
+ @title = title
13
+ # key = method_symbol, value = visibility
14
+ @methods = {}
15
+ end
16
+
17
+ def to_dot(indent = 0)
18
+ @attributes[:label] = label
19
+ super
20
+ end
21
+
22
+ private
23
+
24
+ def label
25
+ result = "{#{@title}"
26
+ result << '|' if not @methods.empty?
27
+ @methods.each do |symbol, visibility|
28
+ result << "#{visibility} #{escape_symbol(symbol)}()\\l"
29
+ end
30
+ result << '}'
31
+ end
32
+
33
+ def escape_symbol(symbol)
34
+ symbol.gsub(/</, '\<').gsub(/>/, '\>').gsub(/\|/, '\|')
35
+ end
36
+ end
37
+
38
+ class Generalization < Edge
39
+ def initialize(tail, head)
40
+ super
41
+ @attributes[:arrowhead] = 'onormal'
42
+ end
43
+ end
44
+
45
+ # aka directed association
46
+ class Dependency < Edge
47
+ def initialize(tail, head)
48
+ super
49
+ @attributes[:arrowhead] = 'vee'
50
+ end
51
+ end
52
+
53
+ class Association < Edge
54
+ def initialize(tail, head)
55
+ super
56
+ @attributes[:arrowhead] = 'none'
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,90 @@
1
+ module UML
2
+ module Graphviz
3
+
4
+ class Attributes < Hash
5
+ def to_dot
6
+ return '' if empty?
7
+ result = inject([]) do |tmp, value|
8
+ tmp << "#{value[0]} = \"#{value[1]}\""
9
+ end
10
+ '[' + result.join(', ') + ']'
11
+ end
12
+ end
13
+
14
+ class Graph
15
+
16
+ attr_accessor :subgraphs, :nodes, :edges
17
+ attr_reader :default_graph_attributes,
18
+ :default_node_attributes,
19
+ :default_edge_attributes
20
+
21
+ def initialize(type, name)
22
+ @name = name
23
+ @type = type
24
+ @subgraphs = {}
25
+ @nodes = {}
26
+ @edges = {}
27
+ @default_graph_attributes = Attributes.new
28
+ @default_node_attributes = Attributes.new
29
+ @default_edge_attributes = Attributes.new
30
+ end
31
+
32
+ def to_dot(indent = 0)
33
+ result = ''
34
+ result << ' ' * indent << "#{@type} #{@name} {\n"
35
+ result << ' ' * (indent + 1) << "graph #{@default_graph_attributes.to_dot};\n" unless @default_graph_attributes.empty?
36
+ result << ' ' * (indent + 1) << "node #{@default_node_attributes.to_dot};\n" unless @default_node_attributes.empty?
37
+ result << ' ' * (indent + 1) << "edge #{@default_edge_attributes.to_dot};\n" unless @default_edge_attributes.empty?
38
+ result << collection_to_dot(@subgraphs.values, indent)
39
+ result << collection_to_dot(@nodes.values, indent)
40
+ result << collection_to_dot(@edges.values, indent)
41
+ result << ' ' * indent << '}'
42
+ end
43
+
44
+ def name_to_id(object)
45
+ self.class.name_to_id object
46
+ end
47
+
48
+ def self.name_to_id(object)
49
+ '_' << object.to_s.gsub(/::/, '_')
50
+ end
51
+
52
+ private
53
+ def collection_to_dot(collection, indent)
54
+ collection.inject([]) { |result, element| result << "#{element.to_dot(indent + 1)}\n" }.to_s
55
+ end
56
+ end
57
+
58
+ class Node
59
+
60
+ attr_reader :attributes, :name
61
+
62
+ def initialize(name)
63
+ @name = name
64
+ @attributes = Attributes.new
65
+ end
66
+
67
+ def to_dot(indent = 0)
68
+ "#{' ' * indent}#{Graph::name_to_id(name)} #{@attributes.to_dot};"
69
+ end
70
+
71
+ end
72
+
73
+ class Edge
74
+
75
+ attr_reader :attributes
76
+
77
+ def initialize(tail, head)
78
+ @tail = tail
79
+ @head = head
80
+ @attributes = Attributes.new
81
+ end
82
+
83
+ def to_dot(indent = 0)
84
+ "#{' ' * indent}#{Graph::name_to_id(@tail)} -> #{Graph::name_to_id(@head)} #{@attributes.to_dot};"
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,73 @@
1
+ require 'observer'
2
+ require 'aspectr'
3
+ require 'uml/method_helper'
4
+
5
+ module UML
6
+
7
+ class HighlevelBacktracer < AspectR::Aspect
8
+ include Observable
9
+ include MethodHelper
10
+
11
+ # +call_stack+ is an array with hashes as elements.
12
+ # Each element represents a call to a wrapped method with topmost element at last.
13
+ #
14
+ # Element has following keys as symbols:
15
+ # args:: holds an array of the arguments to method
16
+ # object:: reference to receiver of method
17
+ # method_symbol:: symbol of called method
18
+ # real_object:: the real receiver of method in inheritance tree
19
+ # returns:: return value of method. Only valid if method returned already
20
+ attr_reader :call_stack
21
+
22
+ def initialize
23
+
24
+ @call_stack = []
25
+ end
26
+
27
+ # Wrap given methods of class or instance.
28
+ #
29
+ # each wrapped method will notify observers with <tt>(symbol, self)</tt>
30
+ # where symbol can be +:call+ or +:return+
31
+ def include(klass, *methods)
32
+ methods.each do |method_symbol|
33
+ add_advice klass, PRE, method_symbol, :before
34
+ add_advice klass, POST, method_symbol, :after
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def before(method_string, object, return_value, *args)
41
+ method_symbol = method_string.to_sym
42
+
43
+ actual_information = {
44
+ :args => args,
45
+ :object => object,
46
+ :method_symbol => method_symbol,
47
+ :real_object => get_real_receiver_of_method(object, method_symbol)
48
+ }
49
+
50
+ @call_stack.push actual_information
51
+
52
+ changed
53
+ notify_observers :call, self
54
+
55
+ end
56
+
57
+ def after(method_string, object, return_value, *args)
58
+ method_symbol = method_string.to_sym
59
+
60
+ actual_information = {
61
+ :returns => return_value
62
+ }
63
+
64
+ @call_stack.last.update actual_information
65
+
66
+ changed
67
+ notify_observers :return, self
68
+
69
+ @call_stack.pop
70
+ end
71
+
72
+ end # class Backtracer
73
+ end # module UML