cadove 0.1
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.
- data/CHANGELOG +3 -0
- data/README +34 -0
- data/lib/cadove.rb +92 -0
- data/lib/cadove/models.rb +5 -0
- data/lib/cadove/models/XMI/document.rb +143 -0
- data/lib/cadove/models/analytics/comparison.rb +155 -0
- data/lib/cadove/models/helpers.rb +7 -0
- data/lib/cadove/models/indifferent_access.rb +149 -0
- data/lib/cadove/models/java/code.rb +108 -0
- data/lib/cadove/models/java/javalib/qdox-1.10.jar +0 -0
- data/lib/cadove/models/reports/reports.rb +60 -0
- data/spec/cadove_spec.rb +18 -0
- data/spec/java_stubs/org/foo/Animal.java +3 -0
- data/spec/java_stubs/org/foo/Cat.java +3 -0
- data/spec/java_stubs/org/foo/Dog.java +18 -0
- data/spec/models/XMI/document_spec.rb +127 -0
- data/spec/models/analytics/comparison_spec.rb +105 -0
- data/spec/models/java/code_spec.rb +48 -0
- data/spec/spec_helper.rb +1 -0
- metadata +82 -0
data/CHANGELOG
ADDED
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
caDOVe (Domain Object Verifier)
|
2
|
+
===============================
|
3
|
+
|
4
|
+
About
|
5
|
+
-----
|
6
|
+
caDOVe helps with checking if a Java code base and a UML model
|
7
|
+
of the domain objects are in sync. This is especially useful for
|
8
|
+
verifying the silver level compatibility requirement,
|
9
|
+
"bla bla bla bla bla".
|
10
|
+
|
11
|
+
Currently...
|
12
|
+
- only Hibernate annotated Java classes.
|
13
|
+
- only XMI representations of UML are supported.
|
14
|
+
|
15
|
+
Installation
|
16
|
+
------------
|
17
|
+
1. Install the RJB rubygem (http://rjb.rubyforge.org/)
|
18
|
+
2. Set JAVA_HOME in your environment
|
19
|
+
|
20
|
+
Usage Example
|
21
|
+
-------------
|
22
|
+
require 'cadove'
|
23
|
+
|
24
|
+
CaDOVe.configure do
|
25
|
+
left 'nbia.xmi'
|
26
|
+
right 'nbia/domain'
|
27
|
+
|
28
|
+
ignore_classes %w(Boolean Date Double Integer Long String)
|
29
|
+
|
30
|
+
matching_classes(
|
31
|
+
:Image => :CTImage
|
32
|
+
)
|
33
|
+
end.class_comparison.attribute_comparison
|
34
|
+
|
data/lib/cadove.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/cadove/models'
|
2
|
+
|
3
|
+
module CaDOVe
|
4
|
+
class << self
|
5
|
+
attr_writer :left, :right, :ignore_classes, :matching_classes
|
6
|
+
|
7
|
+
def configure(&block)
|
8
|
+
Configurator.module_eval &block
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
###### CONFIGURATION PROPERTY ACCESSORS
|
13
|
+
%w(left right).each do |attribute|
|
14
|
+
required_accessor = <<-EOS
|
15
|
+
def #{attribute}
|
16
|
+
raise ConfigurationError.new("Bcsec::#{attribute} not set") unless @#{attribute}
|
17
|
+
@#{attribute}
|
18
|
+
end
|
19
|
+
EOS
|
20
|
+
class_eval(required_accessor)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ignore_classes
|
24
|
+
@ignore_classes ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def matching_classes
|
28
|
+
@matching_clases ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def class_comparison
|
32
|
+
Reports::ClassReport.new(comparison, left_type, right_type).display
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def attribute_comparison
|
37
|
+
Reports::AttributeReport.new(comparison, left_type, right_type).display
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def left_representation
|
43
|
+
@left_representation ||= xmi?(left) ? XMI::XMIDocument.new(left) : Java::JavaSourceTree.new(left)
|
44
|
+
end
|
45
|
+
|
46
|
+
def right_representation
|
47
|
+
@right_representation ||= xmi?(right) ? XMI::XMIDocument.new(right) : Java::JavaSourceTree.new(right)
|
48
|
+
end
|
49
|
+
|
50
|
+
def comparison
|
51
|
+
@comparison ||= Analytics::Comparison.new(
|
52
|
+
left_representation.hashify,
|
53
|
+
right_representation.hashify,
|
54
|
+
:ignore_classes => @ignore_classes,
|
55
|
+
:matching_classes => @matching_classes
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def xmi?(path)
|
60
|
+
path =~ /.*\.xmi$/i
|
61
|
+
end
|
62
|
+
|
63
|
+
%w(left right).each do |side|
|
64
|
+
type_accessor = <<-EOS
|
65
|
+
def #{side}_type
|
66
|
+
xmi?(#{side}) ? :xmi : :java
|
67
|
+
end
|
68
|
+
EOS
|
69
|
+
class_eval(type_accessor)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module Configurator
|
74
|
+
class << self
|
75
|
+
#########
|
76
|
+
protected
|
77
|
+
|
78
|
+
SIMPLE_TERMS = %w(left right ignore_classes matching_classes)
|
79
|
+
|
80
|
+
# DSL terms for simple top-level attributes
|
81
|
+
SIMPLE_TERMS.each do |attribute|
|
82
|
+
simple_setter = <<-EOS
|
83
|
+
def #{attribute}(value)
|
84
|
+
CaDOVe.#{attribute} = value
|
85
|
+
end
|
86
|
+
EOS
|
87
|
+
class_eval(simple_setter)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
class ConfigurationError < Exception; end
|
92
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require File.dirname(__FILE__) + '/../helpers'
|
3
|
+
|
4
|
+
module CaDOVe
|
5
|
+
module XMI
|
6
|
+
class XMIDocument
|
7
|
+
def initialize(input, opts = {})
|
8
|
+
opts[:input_type] ||= :file_path
|
9
|
+
val =
|
10
|
+
if opts[:input_type] == :text
|
11
|
+
input
|
12
|
+
elsif opts[:input_type] == :file_path
|
13
|
+
File.new(input)
|
14
|
+
end
|
15
|
+
|
16
|
+
@xml_doc = REXML::Document.new(val)
|
17
|
+
end
|
18
|
+
|
19
|
+
def classes
|
20
|
+
package = UMLPackage.logical_model(@xml_doc) || UMLPackage.all(@xml_doc).first
|
21
|
+
raise "No packages found in the XMI document" unless package
|
22
|
+
@classes ||= package.classes
|
23
|
+
end
|
24
|
+
|
25
|
+
def hashify
|
26
|
+
all = {}
|
27
|
+
classes.each do |clazz|
|
28
|
+
all.merge!(clazz.hashify)
|
29
|
+
end
|
30
|
+
all
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class UMLPackage
|
35
|
+
def initialize(xml)
|
36
|
+
@xml = xml
|
37
|
+
@name = name
|
38
|
+
end
|
39
|
+
|
40
|
+
# def self.logical_model(xml)
|
41
|
+
# packages = all(xml)
|
42
|
+
# logical = packages.size == 1 ? packages : packages.select{|p| p.name =~ /Logical Model/i}
|
43
|
+
# logical.first
|
44
|
+
# end
|
45
|
+
|
46
|
+
def self.all(xml)
|
47
|
+
xml.elements.collect('.//UML:Package') do |p|
|
48
|
+
new(p)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.method_missing(name, *args)
|
53
|
+
package_name = name.to_s.gsub(/_/, ' ')
|
54
|
+
all(args[0]).select{|p| p.name =~ /#{package_name}/i}.first
|
55
|
+
end
|
56
|
+
|
57
|
+
def name
|
58
|
+
@name ||= @xml.attributes['name']
|
59
|
+
end
|
60
|
+
|
61
|
+
def classes
|
62
|
+
UMLClass.all(@xml)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class UMLClass
|
67
|
+
def initialize(xml)
|
68
|
+
@xml = xml
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.all(xml)
|
72
|
+
xml.elements.collect('.//UML:Class') do |c|
|
73
|
+
new(c)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def name
|
78
|
+
@name ||= @xml.attributes['name']
|
79
|
+
end
|
80
|
+
|
81
|
+
def hashify
|
82
|
+
field_and_type = attributes.collect{|f| [f.name, f.type] }
|
83
|
+
{"#{name}" => field_and_type}
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
@attributes ||= UMLAttribute.all(@xml)
|
88
|
+
end
|
89
|
+
|
90
|
+
def associations
|
91
|
+
@associations ||= UMLAssociation.all(@xml, name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class UMLAttribute
|
96
|
+
include CaDOVe::Helpers
|
97
|
+
|
98
|
+
def initialize(xml)
|
99
|
+
@xml = xml
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.all(class_element)
|
103
|
+
class_element.elements.collect('.//UML:Attribute') do |a|
|
104
|
+
new(a)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def name
|
109
|
+
@name ||= @xml.attributes['name']
|
110
|
+
end
|
111
|
+
|
112
|
+
def type
|
113
|
+
@type ||= remove_package_name(REXML::XPath.first(@xml, ".//UML:TaggedValue[@tag='type']").attributes['value'])
|
114
|
+
end
|
115
|
+
|
116
|
+
def key
|
117
|
+
"#{name} (#{type})"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class UMLAssociation
|
122
|
+
def initialize(xml)
|
123
|
+
@xml = xml
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.all(class_element, class_name)
|
127
|
+
class_element.root.elements.collect("//UML:Association") do |a|
|
128
|
+
if REXML::XPath.first(a, ".//UML:TaggedValue[@tag='ea_sourceName']") && REXML::XPath.first(a, ".//UML:TaggedValue[@tag='ea_sourceName']").attributes['value'] == class_name
|
129
|
+
new(a)
|
130
|
+
end
|
131
|
+
end.compact
|
132
|
+
end
|
133
|
+
|
134
|
+
def name
|
135
|
+
REXML::XPath.first(@xml, ".//UML:TaggedValue[@tag='ea_targetName']").attributes['value']
|
136
|
+
end
|
137
|
+
|
138
|
+
def key
|
139
|
+
"#{name} (#{type})"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module CaDOVe
|
2
|
+
module Analytics
|
3
|
+
class Comparison
|
4
|
+
def initialize(left, right, opts={})
|
5
|
+
@left = left
|
6
|
+
@right = right
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def classes
|
11
|
+
@classes ||= ClassMatch.all(@left, @right, @opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def intersecting_classes
|
15
|
+
@intersecting_classes ||= classes.select{|c| c.intersecting_class? }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ClassMatch
|
20
|
+
def initialize(key, left_attributes, right_attributes)
|
21
|
+
@key = key
|
22
|
+
@left_attributes = left_attributes
|
23
|
+
@right_attributes = right_attributes
|
24
|
+
end
|
25
|
+
attr_reader :key
|
26
|
+
|
27
|
+
# TODO: Clean this mess up
|
28
|
+
def self.all(left, right, opts={})
|
29
|
+
opts ||= {}
|
30
|
+
ignore_classes = opts[:ignore_classes] || []
|
31
|
+
matching_classes = opts[:matching_classes] || {}
|
32
|
+
|
33
|
+
left_keys = left.keys.reject{|k| ignore_classes.include?(k)}
|
34
|
+
right_keys = right.keys.reject{|k| ignore_classes.include?(k)}
|
35
|
+
ClassMatchKey.align(left_keys, right_keys, matching_classes).collect do |key|
|
36
|
+
ClassMatch.new(
|
37
|
+
key,
|
38
|
+
left["#{key.left}"],
|
39
|
+
right["#{key.right}"]
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def attributes
|
45
|
+
@attributes ||= AttributeMatch.all(@left_attributes, @right_attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
def left_match?
|
49
|
+
!@left_attributes.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
def right_match?
|
53
|
+
!@right_attributes.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
def intersecting_class?
|
57
|
+
left_match? && right_match?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class ClassMatchKey
|
62
|
+
def initialize(left, right)
|
63
|
+
@left = left
|
64
|
+
@right = right
|
65
|
+
end
|
66
|
+
attr_reader :left, :right
|
67
|
+
|
68
|
+
def self.align(left_keys, right_keys, override={})
|
69
|
+
override = override.with_indifferent_access
|
70
|
+
|
71
|
+
matches = []
|
72
|
+
left_keys.each do |left_key|
|
73
|
+
found =
|
74
|
+
if m = override_match(left_key, right_keys, override)
|
75
|
+
right_keys.delete(m)
|
76
|
+
elsif right_keys.include?(left_key)
|
77
|
+
right_keys.delete(left_key)
|
78
|
+
end
|
79
|
+
|
80
|
+
matches << new(left_key, found)
|
81
|
+
end
|
82
|
+
|
83
|
+
right_keys.each do |right_key|
|
84
|
+
matches << new(nil, right_key)
|
85
|
+
end
|
86
|
+
|
87
|
+
matches.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.override_match(left_key, right_keys, override={})
|
91
|
+
if override.has_key?(left_key) && right_keys.include?(override[left_key].to_s)
|
92
|
+
override[left_key].to_s
|
93
|
+
elsif override.invert.has_key?(left_key) && right_keys.include?(override.invert[left_key].to_s)
|
94
|
+
override.invert[left_key].to_s
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def <=>(other)
|
99
|
+
display <=> other.display
|
100
|
+
end
|
101
|
+
|
102
|
+
def display
|
103
|
+
return if left.nil? && right.nil?
|
104
|
+
|
105
|
+
if left && right.nil?
|
106
|
+
left
|
107
|
+
elsif right && left.nil?
|
108
|
+
right
|
109
|
+
elsif left == right
|
110
|
+
left
|
111
|
+
elsif left != right
|
112
|
+
"#{left} => #{right}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class AttributeMatch
|
118
|
+
def initialize(name, type, left_match, right_match)
|
119
|
+
@name = name
|
120
|
+
@type = type
|
121
|
+
@left_match = left_match
|
122
|
+
@right_match = right_match
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.all(left_attributes, right_attributes)
|
126
|
+
combined = (left_attributes | right_attributes).sort{|a,b|a[0] <=> b[0]} # Sort by name
|
127
|
+
|
128
|
+
combined.collect do |name, type|
|
129
|
+
AttributeMatch.new(
|
130
|
+
name,
|
131
|
+
type,
|
132
|
+
left_attributes.include?( [name, type] ),
|
133
|
+
right_attributes.include?( [name, type] )
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def key
|
139
|
+
[@name, @type]
|
140
|
+
end
|
141
|
+
|
142
|
+
def left_match?
|
143
|
+
@left_match
|
144
|
+
end
|
145
|
+
|
146
|
+
def right_match?
|
147
|
+
@right_match
|
148
|
+
end
|
149
|
+
|
150
|
+
def display
|
151
|
+
"#{@name} (#{@type})"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# This class has dubious semantics and we only have it so that
|
2
|
+
# people can write params[:key] instead of params['key']
|
3
|
+
# and they get the same value for both keys.
|
4
|
+
|
5
|
+
class HashWithIndifferentAccess < Hash
|
6
|
+
def initialize(constructor = {})
|
7
|
+
if constructor.is_a?(Hash)
|
8
|
+
super()
|
9
|
+
update(constructor)
|
10
|
+
else
|
11
|
+
super(constructor)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def default(key = nil)
|
16
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
17
|
+
self[key]
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
24
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
25
|
+
|
26
|
+
# Assigns a new value to the hash:
|
27
|
+
#
|
28
|
+
# hash = HashWithIndifferentAccess.new
|
29
|
+
# hash[:key] = "value"
|
30
|
+
#
|
31
|
+
def []=(key, value)
|
32
|
+
regular_writer(convert_key(key), convert_value(value))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Updates the instantized hash with values from the second:
|
36
|
+
#
|
37
|
+
# hash_1 = HashWithIndifferentAccess.new
|
38
|
+
# hash_1[:key] = "value"
|
39
|
+
#
|
40
|
+
# hash_2 = HashWithIndifferentAccess.new
|
41
|
+
# hash_2[:key] = "New Value!"
|
42
|
+
#
|
43
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
44
|
+
#
|
45
|
+
def update(other_hash)
|
46
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :merge!, :update
|
51
|
+
|
52
|
+
# Checks the hash for a key matching the argument passed in:
|
53
|
+
#
|
54
|
+
# hash = HashWithIndifferentAccess.new
|
55
|
+
# hash["key"] = "value"
|
56
|
+
# hash.key? :key # => true
|
57
|
+
# hash.key? "key" # => true
|
58
|
+
#
|
59
|
+
def key?(key)
|
60
|
+
super(convert_key(key))
|
61
|
+
end
|
62
|
+
|
63
|
+
alias_method :include?, :key?
|
64
|
+
alias_method :has_key?, :key?
|
65
|
+
alias_method :member?, :key?
|
66
|
+
|
67
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
68
|
+
def fetch(key, *extras)
|
69
|
+
super(convert_key(key), *extras)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns an array of the values at the specified indices:
|
73
|
+
#
|
74
|
+
# hash = HashWithIndifferentAccess.new
|
75
|
+
# hash[:a] = "x"
|
76
|
+
# hash[:b] = "y"
|
77
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
78
|
+
#
|
79
|
+
def values_at(*indices)
|
80
|
+
indices.collect {|key| self[convert_key(key)]}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an exact copy of the hash.
|
84
|
+
def dup
|
85
|
+
HashWithIndifferentAccess.new(self)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
89
|
+
# Does not overwrite the existing hash.
|
90
|
+
def merge(hash)
|
91
|
+
self.dup.update(hash)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
|
95
|
+
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
|
96
|
+
def reverse_merge(other_hash)
|
97
|
+
super other_hash.with_indifferent_access
|
98
|
+
end
|
99
|
+
|
100
|
+
def invert
|
101
|
+
super.with_indifferent_access
|
102
|
+
end
|
103
|
+
|
104
|
+
# Removes a specified key from the hash.
|
105
|
+
def delete(key)
|
106
|
+
super(convert_key(key))
|
107
|
+
end
|
108
|
+
|
109
|
+
def stringify_keys!; self end
|
110
|
+
def symbolize_keys!; self end
|
111
|
+
def to_options!; self end
|
112
|
+
|
113
|
+
# Convert to a Hash with String keys.
|
114
|
+
def to_hash
|
115
|
+
Hash.new(default).merge(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
def convert_key(key)
|
120
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
121
|
+
end
|
122
|
+
|
123
|
+
def convert_value(value)
|
124
|
+
case value
|
125
|
+
when Hash
|
126
|
+
value.with_indifferent_access
|
127
|
+
when Array
|
128
|
+
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
|
129
|
+
else
|
130
|
+
value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module CaDOVe #:nodoc:
|
136
|
+
module CoreExtensions #:nodoc:
|
137
|
+
module Hash #:nodoc:
|
138
|
+
module IndifferentAccess #:nodoc:
|
139
|
+
def with_indifferent_access
|
140
|
+
hash = HashWithIndifferentAccess.new(self)
|
141
|
+
hash.default = self.default
|
142
|
+
hash
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
Hash.send(:include, CaDOVe::CoreExtensions::Hash::IndifferentAccess)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'rjb'
|
2
|
+
require File.dirname(__FILE__) + '/../helpers'
|
3
|
+
|
4
|
+
module CaDOVe
|
5
|
+
module Java
|
6
|
+
Rjb::load(File.dirname(__FILE__) + '/javalib/qdox-1.10.jar')
|
7
|
+
|
8
|
+
class JavaSourceTree
|
9
|
+
def initialize(directory)
|
10
|
+
file = Rjb::import('java.io.File').
|
11
|
+
new_with_sig('Ljava.lang.String;', directory)
|
12
|
+
|
13
|
+
builder = Rjb::import('com.thoughtworks.qdox.JavaDocBuilder').new
|
14
|
+
builder._invoke('addSourceTree', 'Ljava.io.File;', file)
|
15
|
+
|
16
|
+
@sources = builder.getSources
|
17
|
+
end
|
18
|
+
|
19
|
+
def classes
|
20
|
+
JavaClass.all(@sources)
|
21
|
+
end
|
22
|
+
|
23
|
+
def hashify
|
24
|
+
all = {}
|
25
|
+
classes.each do |clazz|
|
26
|
+
all.merge!(clazz.hashify)
|
27
|
+
end
|
28
|
+
all
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class JavaClass
|
33
|
+
def initialize(clazz)
|
34
|
+
@clazz = clazz
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.all(sources, including_abstract=false)
|
38
|
+
classes = sources.collect { |s| s.getClasses }.flatten
|
39
|
+
results = classes.collect { |c| new(c) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
@name ||= @clazz.getName
|
44
|
+
end
|
45
|
+
|
46
|
+
def hashify
|
47
|
+
field_and_type = fields.collect{|f| [f.name, f.type] }
|
48
|
+
{"#{name}" => field_and_type}
|
49
|
+
end
|
50
|
+
|
51
|
+
def fields
|
52
|
+
JavaField.all_non_associated(@clazz)
|
53
|
+
end
|
54
|
+
alias_method :attributes, :fields
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
class JavaField
|
59
|
+
include CaDOVe::Helpers
|
60
|
+
def initialize(field)
|
61
|
+
@field = field
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.all(clazz)
|
65
|
+
clazz.getFields.collect do |field|
|
66
|
+
new(field)
|
67
|
+
end.reject{|f| is_constant?(f.name) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.all_non_associated(clazz)
|
71
|
+
all(clazz).select{|f| is_simple_type(f.type) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.is_simple_type(class_name)
|
75
|
+
java_lang_and_other = %w(Boolean Byte Character Double Float Integer Long Math Number Short String Date Timestamp)
|
76
|
+
primitives = %w(boolean byte char double float integer long short)
|
77
|
+
result |= java_lang_and_other.include?(class_name)
|
78
|
+
result |= primitives.include?(class_name)
|
79
|
+
result
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.is_constant?(name)
|
83
|
+
name =~ /^([A-Z]|_)+$/
|
84
|
+
end
|
85
|
+
|
86
|
+
def name
|
87
|
+
@name ||= @field.getName
|
88
|
+
end
|
89
|
+
|
90
|
+
def type
|
91
|
+
@type ||= remove_package_name(@field.getType.getValue)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(other)
|
95
|
+
return true if self.equal?(other)
|
96
|
+
return false if self.class != Java::JavaMethod
|
97
|
+
|
98
|
+
if (self.name != other.name)
|
99
|
+
return false
|
100
|
+
elsif (self.type != other.type)
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
|
104
|
+
true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
Binary file
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# TODO: Move these into a plain text file
|
2
|
+
module CaDOVe
|
3
|
+
module Reports
|
4
|
+
class ClassReport
|
5
|
+
def initialize(comparison, left_type, right_type)
|
6
|
+
@comparison = comparison
|
7
|
+
@left_type = left_type
|
8
|
+
@right_type = right_type
|
9
|
+
end
|
10
|
+
|
11
|
+
def display
|
12
|
+
puts Time.new.strftime('%b %d, %Y %H:%M:%S %Z')
|
13
|
+
puts "\n"
|
14
|
+
puts " #{header(@left_type)} | #{header(@right_type)} | Class Name"
|
15
|
+
puts "--------------------------------"
|
16
|
+
@comparison.classes.each do |clazz|
|
17
|
+
puts " #{clazz.left_match? ? 'X' : ' '} | #{clazz.right_match? ? 'X' : ' '} | #{clazz.key.display}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def header(name)
|
22
|
+
name.to_s.upcase
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class AttributeReport
|
27
|
+
def initialize(comparison, left_type, right_type)
|
28
|
+
@comparison = comparison
|
29
|
+
@left_type = left_type
|
30
|
+
@right_type = right_type
|
31
|
+
end
|
32
|
+
|
33
|
+
def display
|
34
|
+
puts Time.new.strftime('%b %d, %Y %H:%M:%S %Z')
|
35
|
+
puts "\n"
|
36
|
+
|
37
|
+
puts "########################"
|
38
|
+
@comparison.intersecting_classes.each do |clazz|
|
39
|
+
unless clazz.attributes.empty?
|
40
|
+
puts ""
|
41
|
+
puts "#{clazz.key.display}"
|
42
|
+
puts ""
|
43
|
+
puts " #{header(@left_type)} | #{header(@right_type)} | Attributes"
|
44
|
+
puts "--------------------------------------"
|
45
|
+
|
46
|
+
clazz.attributes.each do |a|
|
47
|
+
puts " #{a.left_match? ? 'X' : ' '} | #{a.right_match? ? 'X' : ' '} | #{a.display}"
|
48
|
+
end
|
49
|
+
puts ""
|
50
|
+
puts "########################"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def header(name)
|
56
|
+
name.to_s.upcase
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/cadove_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe CaDOVe do
|
4
|
+
it "should configure the left and right properties" do
|
5
|
+
CaDOVe.configure do
|
6
|
+
left 'hello'
|
7
|
+
right 'world'
|
8
|
+
end
|
9
|
+
|
10
|
+
CaDOVe.left.should == 'hello'
|
11
|
+
CaDOVe.right.should == 'world'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should throws an exception if a required configuration option is accessed without being set" do
|
15
|
+
CaDOVe.left = nil
|
16
|
+
lambda { CaDOVe.left }.should raise_error(CaDOVe::ConfigurationError)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
package org.foo;
|
2
|
+
|
3
|
+
class Dog {
|
4
|
+
String color;
|
5
|
+
boolean rabid;
|
6
|
+
Father father;
|
7
|
+
List<Enemies> enemies;
|
8
|
+
|
9
|
+
public String getColor(){}
|
10
|
+
public boolean isRabid(){}
|
11
|
+
|
12
|
+
public Father getFather() {}
|
13
|
+
public List<Enemies> getEnemies() {}
|
14
|
+
|
15
|
+
public String setIgnoreSetters(){}
|
16
|
+
private String getIgnorePrivateMethods(){}
|
17
|
+
protected String getIgnoreProtectedMethods(){}
|
18
|
+
}
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module CaDOVe::XMI
|
4
|
+
describe UMLClass do
|
5
|
+
before(:each) do
|
6
|
+
@xmi =
|
7
|
+
xmi_document do
|
8
|
+
uml_package('Data Model') do
|
9
|
+
uml_class('DontFindMe')
|
10
|
+
end +
|
11
|
+
uml_package('Logical Model') do
|
12
|
+
uml_class('Dog') do
|
13
|
+
uml_attribute('color', 'String') +
|
14
|
+
uml_attribute('rabid', 'boolean')
|
15
|
+
end +
|
16
|
+
uml_class('Cat')
|
17
|
+
end +
|
18
|
+
|
19
|
+
uml_association('Dog', 'Friend') +
|
20
|
+
uml_association('Dog', 'Enemy')
|
21
|
+
end
|
22
|
+
|
23
|
+
@doc = XMIDocument.new(@xmi, :input_type => :text)
|
24
|
+
@dog = @doc.classes.select{ |c| c.name == 'Dog' }.first
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have two classes in the document" do
|
28
|
+
classes = @doc.classes
|
29
|
+
@doc.should have(2).classes
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should include the class Cat and Dog" do
|
33
|
+
names = @doc.classes.collect { |c| c.name }
|
34
|
+
names.should include("Cat")
|
35
|
+
names.should include("Dog")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have the correct attributes for Dog" do
|
39
|
+
@dog.should have(2).attributes
|
40
|
+
names = @dog.attributes.collect { |c| [c.name, c.type] }
|
41
|
+
names.should include( %w(color String) )
|
42
|
+
names.should include( %w(rabid boolean) )
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should have the correct correct associations for Dog" do
|
46
|
+
@dog.should have(2).associations
|
47
|
+
names = @dog.associations.collect { |c| c.name }
|
48
|
+
names.should include('Friend')
|
49
|
+
names.should include('Enemy')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the only model available if only one exists" do
|
53
|
+
single_pkg_xmi =
|
54
|
+
xmi_document do
|
55
|
+
uml_package('My Package') do
|
56
|
+
uml_class('Foo')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
single_pkg_doc = XMIDocument.new(single_pkg_xmi, :input_type => :text)
|
61
|
+
single_pkg_doc.should have(1).classes
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should hashify dog" do
|
65
|
+
doggy_hash = @dog.hashify
|
66
|
+
doggy_hash['Dog'].should include( %w(color String) )
|
67
|
+
doggy_hash['Dog'].should include( %w(rabid boolean) )
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should hashify the document tree" do
|
71
|
+
@doc.hashify.keys.should include('Cat')
|
72
|
+
@doc.hashify.keys.should include('Dog')
|
73
|
+
|
74
|
+
@doc.hashify['Dog'].should include( %w(color String) )
|
75
|
+
@doc.hashify['Dog'].should include( %w(rabid boolean) )
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def xmi_document(&block)
|
80
|
+
<<-XMI
|
81
|
+
<xmi:XMI xmlns:UML="http://schema.omg.org/spec/UML/2.0" xmlns:xmi="http://schema.omg.org/spec/XMI/2.1">
|
82
|
+
#{yield if block_given?}
|
83
|
+
</xmi:XMI>
|
84
|
+
XMI
|
85
|
+
end
|
86
|
+
|
87
|
+
def uml_package(name, &block)
|
88
|
+
<<-XMI
|
89
|
+
<UML:Package name="#{name}">
|
90
|
+
#{yield if block_given?}
|
91
|
+
</UML:Package>
|
92
|
+
XMI
|
93
|
+
end
|
94
|
+
|
95
|
+
def uml_class(name, &block)
|
96
|
+
<<-XMI
|
97
|
+
<UML:Class name='#{name}'>
|
98
|
+
#{yield if block_given?}
|
99
|
+
</UML:Class>
|
100
|
+
XMI
|
101
|
+
end
|
102
|
+
|
103
|
+
def uml_attribute(name, type)
|
104
|
+
<<-XMI
|
105
|
+
<UML:Attribute name='#{name}' visibility='private'>
|
106
|
+
<UML:ModelElement.taggedValue>
|
107
|
+
<UML:TaggedValue tag='foo' value='bar'/>
|
108
|
+
<UML:TaggedValue tag='type' value='#{type}'/>
|
109
|
+
<UML:TaggedValue tag='derived' value='0'/>
|
110
|
+
</UML:ModelElement.taggedValue>
|
111
|
+
</UML:Attribute>
|
112
|
+
XMI
|
113
|
+
end
|
114
|
+
|
115
|
+
def uml_association(src, dst)
|
116
|
+
<<-XMI
|
117
|
+
<UML:Association visibility='public'>
|
118
|
+
<UML:ModelElement.taggedValue>
|
119
|
+
<UML:TaggedValue tag='style' value='3'/>
|
120
|
+
<UML:TaggedValue tag='ea_sourceName' value='#{src}'/>
|
121
|
+
<UML:TaggedValue tag='ea_targetName' value='#{dst}'/>
|
122
|
+
</UML:ModelElement.taggedValue>
|
123
|
+
</UML:Association>
|
124
|
+
XMI
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module CaDOVe::Analytics
|
4
|
+
describe Comparison do
|
5
|
+
before(:each) do
|
6
|
+
@left = {
|
7
|
+
'Dog' => [
|
8
|
+
%w(name String),
|
9
|
+
%w(rabid boolean)
|
10
|
+
]
|
11
|
+
}
|
12
|
+
|
13
|
+
@right = {
|
14
|
+
'Dog' => [
|
15
|
+
%w(name String),
|
16
|
+
%w(color String)
|
17
|
+
],
|
18
|
+
'Rabbit' => [ %w(color String) ]
|
19
|
+
}
|
20
|
+
|
21
|
+
@comparison = Comparison.new(@left, @right)
|
22
|
+
@dog = @comparison.classes.select{|c| c.key.left == 'Dog' }.first
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should have two class" do
|
26
|
+
@comparison.should have(2).classes
|
27
|
+
@comparison.classes.collect{|c| [c.key.left, c.key.right]}.should == [
|
28
|
+
['Dog', 'Dog'],
|
29
|
+
[nil, 'Rabbit']
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have a left and right match" do
|
34
|
+
@dog.should be_left_match
|
35
|
+
@dog.should be_right_match
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it "should have one intersecting class" do
|
40
|
+
@comparison.should have(1).intersecting_classes
|
41
|
+
@comparison.intersecting_classes.collect{|c| [c.key.left, c.key.right]}.should == [['Dog', 'Dog']]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have a dog match with 3 attributes" do
|
45
|
+
keys = @dog.attributes.collect{|a| a.key}
|
46
|
+
keys.should include( %w(name String) )
|
47
|
+
keys.should include( %w(rabid boolean))
|
48
|
+
keys.should include( %w(color String) )
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have a dog match having an 'name' attribute having a left and right match" do
|
52
|
+
name = @dog.attributes.select{|a| a.key[0] == 'name'}.first
|
53
|
+
name.should be_left_match
|
54
|
+
name.should be_right_match
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have a dog match having an 'rabid' attribute having only a left match" do
|
58
|
+
name = @dog.attributes.select{|a| a.key[0] == 'rabid'}.first
|
59
|
+
name.should be_left_match
|
60
|
+
name.should_not be_right_match
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should ignore the Rabbit class" do
|
64
|
+
ignore_on = Comparison.new(@left, @right, :ignore_classes => %w(Rabbit) )
|
65
|
+
ignore_on.should have(1).classes
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should manually match 'Rabbit' to 'Hare'" do
|
69
|
+
@left['Hare'] = [
|
70
|
+
%w(color String)
|
71
|
+
]
|
72
|
+
|
73
|
+
manual_matching = Comparison.new(@left, @right, :matching_classes => {:Hare => :Rabbit})
|
74
|
+
manual_matching.should have(2).intersecting_classes
|
75
|
+
names = manual_matching.intersecting_classes.collect{|c| [c.key.left, c.key.right]}
|
76
|
+
names.should include(['Hare', 'Rabbit'])
|
77
|
+
names.should include(['Dog', 'Dog'])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should manually match 'Rabbit' to 'Hare' with inverse matching class" do
|
81
|
+
@left['Hare'] = [
|
82
|
+
%w(color String)
|
83
|
+
]
|
84
|
+
|
85
|
+
manual_matching = Comparison.new(@left, @right, :matching_classes => {:Rabbit => :Hare})
|
86
|
+
manual_matching.should have(2).intersecting_classes
|
87
|
+
names = manual_matching.intersecting_classes.collect{|c| [c.key.left, c.key.right]}
|
88
|
+
names.should include(['Hare', 'Rabbit'])
|
89
|
+
names.should include(['Dog', 'Dog'])
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should align keys" do
|
93
|
+
aligned = ClassMatchKey.align(['a', 'b'], ['b', 'c'], :a => :c).collect{|k| [k.left, k.right]}
|
94
|
+
aligned.size.should be(2)
|
95
|
+
aligned.should include(['b', 'b'])
|
96
|
+
aligned.should include(['a', 'c'])
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should sort keys" do
|
100
|
+
keys = [ClassMatchKey.new('b', 'b'), ClassMatchKey.new('a', 'a')].sort
|
101
|
+
keys[0].left.should == 'a'
|
102
|
+
keys[1].left.should == 'b'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module CaDOVe::Java
|
4
|
+
describe JavaSourceTree do
|
5
|
+
before(:each) do
|
6
|
+
@code = JavaSourceTree.new(
|
7
|
+
File.dirname(__FILE__) + '/../../java_stubs'
|
8
|
+
)
|
9
|
+
|
10
|
+
@dog = @code.classes.select { |c| c.name == "Dog" }.first
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have three classes in the document" do
|
14
|
+
@code.should have(3).classes
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should include only classes Animal, Cat and Dog" do
|
18
|
+
names = @code.classes.collect { |c| c.name }
|
19
|
+
names.should include("Animal")
|
20
|
+
names.should include("Cat")
|
21
|
+
names.should include("Dog")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have the two fields" do
|
25
|
+
@dog.should have(2).fields
|
26
|
+
fields = @dog.fields.collect{ |f| [f.name, f.type] }
|
27
|
+
fields.should include( %w(color String) )
|
28
|
+
fields.should include( %w(rabid boolean) )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "should hashify Dog" do
|
33
|
+
doggy_hash = @dog.hashify
|
34
|
+
doggy_hash['Dog'].should include( %w(color String) )
|
35
|
+
doggy_hash['Dog'].should include( %w(rabid boolean) )
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should hashify the source tree" do
|
39
|
+
@code.hashify.keys.should include('Cat')
|
40
|
+
@code.hashify.keys.should include('Dog')
|
41
|
+
|
42
|
+
@code.hashify['Dog'].should include( %w(color String) )
|
43
|
+
@code.hashify['Dog'].should include( %w(rabid boolean) )
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have only associations"
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/cadove'
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cadove
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Dzak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-11 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rjb
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email:
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/cadove/models/analytics/comparison.rb
|
35
|
+
- lib/cadove/models/helpers.rb
|
36
|
+
- lib/cadove/models/indifferent_access.rb
|
37
|
+
- lib/cadove/models/java/code.rb
|
38
|
+
- lib/cadove/models/java/javalib/qdox-1.10.jar
|
39
|
+
- lib/cadove/models/reports/reports.rb
|
40
|
+
- lib/cadove/models/XMI/document.rb
|
41
|
+
- lib/cadove/models.rb
|
42
|
+
- lib/cadove.rb
|
43
|
+
- CHANGELOG
|
44
|
+
- README
|
45
|
+
- spec/cadove_spec.rb
|
46
|
+
- spec/java_stubs/org/foo/Animal.java
|
47
|
+
- spec/java_stubs/org/foo/Cat.java
|
48
|
+
- spec/java_stubs/org/foo/Dog.java
|
49
|
+
- spec/models/analytics/comparison_spec.rb
|
50
|
+
- spec/models/java/code_spec.rb
|
51
|
+
- spec/models/XMI/document_spec.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage:
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Domain Object Verifier compares Java domain objects to their corresponding XMI representations.
|
81
|
+
test_files: []
|
82
|
+
|