File Coverage

lib/UR/Namespace/Command/Update/ClassDiagram.pm
Criterion Covered Total %
statement 12 153 7.8
branch 0 64 0.0
condition 0 2 0.0
subroutine 4 10 40.0
pod 0 3 0.0
total 16 232 6.9


line stmt bran cond sub pod time code
1              
2             package UR::Namespace::Command::Update::ClassDiagram;
3              
4              
5              
6 1     1   23 use strict;
  1         2  
  1         29  
7 1     1   4 use warnings;
  1         1  
  1         30  
8 1     1   4 use UR;
  1         1  
  1         5  
9             our $VERSION = "0.46"; # UR $VERSION;
10              
11             UR::Object::Type->define(
12             class_name => __PACKAGE__,
13             is => 'UR::Namespace::Command::Base',
14             has => [
15             data_source => {type => 'String', doc => 'Which datasource to use', is_optional => 1},
16             depth => { type => 'Integer', doc => 'Max distance of related classes to include. Default is 1. 0 means show only the named class(es), -1 means to include everything', is_optional => 1},
17             file => { type => 'String', doc => 'Pathname of the Umlet (.uxf) file' },
18             show_attributes => { type => 'Boolean', is_optional => 1, default => 1, doc => 'Include class attributes in the diagram' },
19             show_methods => { type => 'Boolean', is_optional => 1, default => 0, doc => 'Include methods in the diagram (not implemented yet' },
20             include_ur_object => { type => 'Boolean', is_optional => 1, default => 0, doc => 'Include UR::Object and UR::Entity in the diagram (default = no)' },
21             initial_name => {
22             is_many => 1,
23             is_optional => 1,
24             shell_args_position => 1
25             }
26             ],
27             );
28              
29 0     0 0   sub sub_command_sort_position { 4 };
30              
31             sub help_brief {
32 0     0 0   "Update an Umlet diagram based on the current class definitions"
33             }
34              
35             sub help_detail {
36 0     0 0   return <
37             Creates a new Umlet diagram, or updates an existing diagram. Bare arguments
38             are taken as class names to include in the diagram. Other classes may be
39             included in the diagram based on their distance from the names classes
40             and the --depth parameter.
41              
42             If an existing file is being updated, the position of existing elements
43             will not change.
44              
45             EOS
46             }
47              
48             # The max X coord to use when placing boxes. After this, move down a line and go back to the left
49 1     1   4 use constant MAX_X_AUTO_POSITION => 800;
  1         1  
  1         1177  
50              
51             sub execute {
52 0     0     my $self = shift;
53              
54 0           my $params = shift;
55            
56             #$DB::single = 1;
57 0           my $namespace = $self->namespace_name;
58 0           eval "use $namespace";
59 0 0         if ($@) {
60 0           $self->error_message("Failed to load module for $namespace: $@");
61 0           return;
62             }
63              
64 0           my @initial_name_list = $self->initial_name;
65              
66 0           my $diagram;
67 0 0         if (-f $params->{'file'}) {
68 0 0         $params->{'depth'} = 0 unless (exists $params->{'depth'}); # Default is just update what's there
69 0           $diagram = UR::Object::Umlet::Diagram->create_from_file($params->{'file'});
70 0           push @initial_name_list, map { $_->subject_id } UR::Object::Umlet::Class->get(diagram_name => $diagram->name);
  0            
71             } else {
72 0 0         $params->{'depth'} = 1 unless exists($params->{'depth'});
73 0           $diagram = UR::Object::Umlet::Diagram->create(name => $params->{'file'});
74             }
75              
76             # FIXME this can get removed when attribute defaults work correctly
77 0 0         unless (exists $params->{'show_attributes'}) {
78 0           $self->show_attributes(1);
79             }
80            
81 0           my @involved_classes;
82 0           foreach my $class_name ( @initial_name_list ) {
83 0           push @involved_classes, UR::Object::Type->get(class_name => $class_name);
84             }
85              
86             push @involved_classes, $self->_get_related_classes_via_inheritance(
87             names => \@initial_name_list,
88 0           depth => $params->{'depth'},
89             );
90              
91             push @involved_classes, $self->_get_related_classes_via_properties(
92             #names => [ map { $_->class_name } @involved_classes ],
93             names => \@initial_name_list,
94 0           depth => $params->{'depth'},
95             );
96 0           my %involved_class_names = map { $_->class_name => $_ } @involved_classes;
  0            
97              
98             # The initial placement, and how much to move over for the next box
99 0           my($x_coord, $y_coord, $x_inc, $y_inc) = (20,20,40,40);
100 0 0         my @objs = sort { $b->y <=> $a->y or $b->x <=> $a->x } UR::Object::Umlet::Class->get();
  0            
101 0 0         if (@objs) {
102 0           my $maxobj = $objs[0];
103 0           $x_coord = $maxobj->x + $maxobj->width + $x_inc;
104 0           $y_coord = $maxobj->y + $maxobj->height + $y_inc;
105             }
106            
107              
108             # First, place all the classes
109 0           my @all_boxes = UR::Object::Umlet::Class->get( diagram_name => $diagram->name );
110 0           foreach my $class ( values %involved_class_names ) {
111 0           my $umlet_class = UR::Object::Umlet::Class->get(diagram_name => $diagram->name,
112             subject_id => $class->class_name);
113 0           my $created = 0;
114 0 0         unless ($umlet_class) {
115 0           $created = 1;
116 0           $umlet_class = UR::Object::Umlet::Class->create( diagram_name => $diagram->name,
117             subject_id => $class->class_name,
118             label => $class->class_name,
119             x => $x_coord,
120             y => $y_coord,
121             );
122             # add the attributes
123 0 0         if ($self->show_attributes) {
124 0   0       my $attributes = $umlet_class->attributes || [];
125 0           my %attributes_already_in_diagram = map { $_->{'name'} => 1 } @{ $attributes };
  0            
  0            
126 0           my %id_properties = map { $_ => 1 } $class->id_property_names;
  0            
127            
128 0           my $line_count = scalar @$attributes;
129 0           foreach my $property_name ( $class->direct_property_names ) {
130 0 0         next if $attributes_already_in_diagram{$property_name};
131 0           $line_count++;
132 0           my $property = UR::Object::Property->get(class_name => $class->class_name, property_name => $property_name);
133 0 0         push @$attributes, { is_id => $id_properties{$property_name} ? '+' : ' ',
134             name => $property_name,
135             type => $property->data_type,
136             line => $line_count,
137             };
138             }
139 0           $umlet_class->attributes($attributes);
140             }
141              
142 0 0         if ($self->show_methods) {
143             # Not implemented yet
144             # Use the same module the schemabrowser uses to get that info
145             }
146              
147             # Make sure this box dosen't overlap other boxes
148 0           while(my $overlapped = $umlet_class->is_overlapping(@all_boxes) ) {
149 0 0         if ($umlet_class->x > MAX_X_AUTO_POSITION) {
150 0           $umlet_class->x(20);
151 0           $umlet_class->y( $umlet_class->y + $y_inc);
152             } else {
153 0           $umlet_class->x( $overlapped->x + $overlapped->width + $x_inc );
154             }
155             }
156            
157 0           push @all_boxes, $umlet_class;
158             }
159              
160 0 0         if ($created) {
161 0           $x_coord = $umlet_class->x + $umlet_class->width + $x_inc;
162 0 0         if ($x_coord > MAX_X_AUTO_POSITION) {
163 0           $x_coord = 20;
164 0           $y_coord += $y_inc;
165             }
166             }
167             }
168              
169             # Next, connect the classes together
170 0           foreach my $class ( values %involved_class_names ) {
171 0 0         my @properties = grep { $_->is_delegated and $_->data_type} $class->all_property_metas();
  0            
172 0           foreach my $property ( @properties ) {
173              
174 0 0         next unless (exists $involved_class_names{$property->data_type});
175              
176 0           my @property_links = eval { $property->get_property_name_pairs_for_join };
  0            
177 0 0         next unless @property_links;
178              
179 0           my $id_by = join(':', map { $_->[0] } @property_links);
  0            
180 0           my $their_id_by = join (':', map { $_->[1] } @property_links);
  0            
181              
182 0           my $umlet_relation = UR::Object::Umlet::Relation->get( diagram_name => $diagram->name,
183             from_entity_name => $property->class_name,
184             to_entity_name => $property->data_type,
185             from_attribute_name => $id_by,
186             to_attribute_name => $their_id_by,
187             );
188 0 0         unless ($umlet_relation) {
189 0           $umlet_relation = UR::Object::Umlet::Relation->create( diagram_name => $diagram->name,
190             relation_type => '<-',
191             from_entity_name => $property->class_name,
192             to_entity_name => $property->data_type,
193             from_attribute_name => $id_by,
194             to_attribute_name => $their_id_by,
195             );
196 0 0         unless ($umlet_relation->connect_entity_attributes()) {
197             # This didn't link to anything on the diagram
198 0           $umlet_relation->delete;
199             }
200             }
201              
202             }
203              
204 0           foreach my $parent_class_name ( @{ $class->is } ) {
  0            
205 0 0         next unless ($involved_class_names{$parent_class_name});
206              
207 0           my $umlet_relation = UR::Object::Umlet::Relation->get( diagram_name => $diagram->name,
208             from_entity_name => $class->class_name,
209             to_entity_name => $parent_class_name,
210             );
211 0 0         unless ($umlet_relation) {
212 0           $umlet_relation = UR::Object::Umlet::Relation->create( diagram_name => $diagram->name,
213             relation_type => '<<-',
214             from_entity_name => $class->class_name,
215             to_entity_name => $parent_class_name,
216             );
217 0           $umlet_relation->connect_entities();
218             }
219             }
220             }
221              
222 0           $diagram->save_to_file($params->{'file'});
223              
224 0           1;
225             }
226              
227              
228              
229             sub _get_related_classes_via_properties {
230 0     0     my($self, %params) = @_;
231              
232 0 0         return unless (@{$params{'names'}});
  0            
233 0 0         return unless $params{'depth'};
234              
235             # Make sure the named classes are loaded
236 0           foreach ( @{ $params{'names'} } ) {
  0            
237 0           eval { $_->class };
  0            
238             }
239              
240             # Get everything linked to the named things
241 0           my @related_names = grep { eval { $_->class } }
  0            
242             #grep { $_ }
243 0           map { $_->data_type }
244 0           map { UR::Object::Property->get(class_name => $_ ) }
245 0           @{ $params{'names'}};
  0            
246 0           push @related_names, grep { eval { $_->class } }
  0            
247             #grep { $_ }
248 0           map { $_->class_name }
249 0           map { UR::Object::Property->get(data_type => $_ ) }
250 0           @{ $params{'names'}};
  0            
251 0 0         return unless @related_names;
252              
253 0           my @objs = map { UR::Object::Type->get(class_name => $_) } @related_names;
  0            
254              
255             #my @related_names = grep { $_ } map { $_->$related_param } $related_class->get($item_param => $params{'names'});
256             #push @related_names, grep { $_ } map { $_->$item_param } $related_class->get($related_param => $params{'names'});
257             #return unless @related_names;
258             #
259             # my @objs = $item_class->get($item_param => \@related_names);
260              
261 0 0         unless ($self->include_ur_object) {
262             # Prune out UR::Object and UR::Entity
263 0 0         @objs = grep { $_->class_name ne 'UR::Object' and $_->class_name ne 'UR::Entity' } @objs;
  0            
264             }
265              
266             # make a recursive call to get the related objects by name
267 0           return ( @objs, $self->_get_related_classes_via_properties( %params, names => \@related_names, depth => --$params{'depth'}) );
268             }
269            
270             sub _get_related_classes_via_inheritance {
271 0     0     my($self,%params) = @_;
272              
273 0 0         return unless (@{$params{'names'}});
  0            
274 0 0         return unless $params{'depth'};
275              
276 0           my @related_class_names;
277 0           foreach my $class_name ( @{ $params{'names'} } ) {
  0            
278             # get the class loaded
279 0           eval { $class_name->class };
  0            
280 0 0         if ($@) {
281 0           $self->warning_message("Problem loading class $class_name: $@");
282 0           next;
283             }
284              
285             # Get this class' parents
286             #push @related_class_names, $class_name->parent_classes;
287 0           push @related_class_names, @{ $class_name->__meta__->is };
  0            
288             }
289              
290 0           my @objs = map { $_->__meta__ } @related_class_names;
  0            
291              
292 0 0         unless ($self->include_ur_object) {
293             # Prune out UR::Object and UR::Entity
294 0 0         @objs = grep { $_->class_name ne 'UR::Object' and $_->class_name ne 'UR::Entity' } @objs;
  0            
295             }
296              
297             # make a recursive call to get their parents
298             return ( @objs,
299             $self->_get_related_classes_via_inheritance( %params,
300             names => \@related_class_names,
301 0           depth => --$params{'depth'},
302             )
303             );
304            
305             }
306              
307              
308             1;
309