jaxb2ruby 0.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require "erb"
2
+
3
+ module JAXB2Ruby
4
+ class Template # :nodoc:
5
+ HEADING=<<HEAD
6
+ #
7
+ # Auto-generated by jaxb2ruby v<%= VERSION %> on <%= Time.now %>
8
+ # https://github.com/sshaw/jaxb2ruby
9
+ #
10
+ HEAD
11
+
12
+ PATHS = Hash[
13
+ Dir[File.expand_path(__FILE__ + "/../../templates/*.erb")].map do |path|
14
+ [File.basename(path, ".erb"), path]
15
+ end
16
+ ]
17
+
18
+ DEFAULT = PATHS["roxml"]
19
+
20
+ def initialize(name = nil)
21
+ # If it's not a named template assume it's a path
22
+ path = PATHS[name] || name || DEFAULT
23
+ @__erb = ERB.new(HEADING + File.read(path), nil, "<>%-")
24
+ rescue => e
25
+ raise Error, "cannot load class template: #{e}"
26
+ end
27
+
28
+ def build(klass)
29
+ @class = klass
30
+ @__erb.result(binding)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,90 @@
1
+ module JAXB2Ruby
2
+ class TypeUtil # :nodoc:
3
+ # Only includes types that aren't annotated with @XmlSchemaType
4
+ JAVA_TO_SCHEMA = {
5
+ "java.lang.Boolean" => "boolean",
6
+ "boolean" => "boolean",
7
+ "byte" => "byte",
8
+ # byte[]
9
+ "[B" => "base64Binary",
10
+ "double" => "double",
11
+ "float" => "float",
12
+ "java.lang.Integer" => "int",
13
+ "int" => "int",
14
+ "java.lang.Object" => "anySimpleType",
15
+ "java.lang.String" => "string",
16
+ "java.math.BigDecimal" => "decimal",
17
+ "java.math.BigInteger" => "int",
18
+ "javax.xml.datatype.Duration" => "duration",
19
+ "javax.xml.datatype.XMLGregorianCalendar" => "dateTime",
20
+ #"javax.xml.namespace.QName" => "NOTATION"
21
+ "javax.xml.namespace.QName" => "QName",
22
+ "java.lang.Long" => "long",
23
+ "long" => "long",
24
+ "short" => "short"
25
+ }
26
+
27
+ SCHEMA_TO_RUBY = {
28
+ "ID" => :ID,
29
+ "IDREF" => :IDREF,
30
+ "Name" => "String",
31
+ "NCName" => "String",
32
+ "NMTOKEN" => "String",
33
+ "anySimpleType" => "Object",
34
+ "anyType" => "Object",
35
+ "anyURI" => "String",
36
+ "base64Binary" => "String",
37
+ "boolean" => :boolean,
38
+ "byte" => "Integer",
39
+ "date" => "Date",
40
+ "dateTime" => "DateTime",
41
+ "decimal" => "Float", # BigDecimal
42
+ "double" => "Float",
43
+ "duration" => "String",
44
+ "float" => "Float",
45
+ "gDay" => "String",
46
+ "gMonth" => "String",
47
+ "gMonthDay" => "String",
48
+ "gYear" => "String",
49
+ "gYearMonth" => "String",
50
+ "hexBinary" => "String",
51
+ "int" => "Integer",
52
+ "integer" => "Integer",
53
+ "long" => "Integer",
54
+ "language" => "String",
55
+ "nonNegativeInteger" => "Integer",
56
+ "nonPositiveInteger" => "Integer",
57
+ "normalizedString" => "String",
58
+ "positiveInteger" => "Integer",
59
+ "QName" => "String",
60
+ "short" => "Integer",
61
+ "string" => "String",
62
+ "time" => "Time",
63
+ "token" => "String",
64
+ "unsignedByte" => "Integer",
65
+ "unsignedInt" => "Integer",
66
+ "unsignedLong" => "Integer",
67
+ "unsignedShort" => "Integer"
68
+ }
69
+
70
+ def initialize(schema2ruby)
71
+ @schema2ruby = SCHEMA_TO_RUBY.merge(schema2ruby || {})
72
+ end
73
+
74
+ def schema_ruby_types
75
+ @schema_types ||= SCHEMA_TO_RUBY.values.uniq
76
+ end
77
+
78
+ def java2schema(klass)
79
+ JAVA_TO_SCHEMA[klass]
80
+ end
81
+
82
+ def schema2ruby(type)
83
+ @schema2ruby[type]
84
+ end
85
+
86
+ def java2ruby(klass)
87
+ schema2ruby(java2schema(klass))
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module JAXB2Ruby
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,59 @@
1
+ require "tmpdir"
2
+ require "cocaine"
3
+
4
+ module JAXB2Ruby
5
+ class XJC # :nodoc:
6
+ CONFIG = File.join(File.dirname(__FILE__), "config.xjb")
7
+
8
+ # https://github.com/thoughtbot/cocaine/issues/24
9
+ Cocaine::CommandLine.runner = Cocaine::CommandLine::BackticksRunner.new
10
+
11
+ def initialize(schema, options = {})
12
+ @schema = schema
13
+ @options = options
14
+ setup_tmpdirs
15
+ end
16
+
17
+ def execute
18
+ xjc
19
+ javac
20
+ @classes
21
+ end
22
+
23
+ private
24
+ def setup_tmpdirs
25
+ @tmproot = Dir.mktmpdir
26
+ @classes = File.join(@tmproot, "classes")
27
+ @sources = File.join(@tmproot, "source")
28
+ [@classes, @sources].each { |dir| Dir.mkdir(dir) }
29
+ rescue IOErorr, SystemCallError => e
30
+ raise Error, "error creating temp directories: #{e}"
31
+ end
32
+
33
+ def xjc
34
+ options = @schema.end_with?(".wsdl") || @options[:wsdl] ? "-wsdl " : ""
35
+ options << "-extension -npa -d :sources :schema -b :config"
36
+ options << @options[:jvm].map { |opt| " -J#{opt}" }.join(" ") if @options[:jvm]
37
+ line = Cocaine::CommandLine.new("xjc", options)
38
+ line.run(:schema => @schema, :sources => @sources, :config => CONFIG)
39
+ rescue Cocaine::ExitStatusError => e
40
+ raise Error, "xjc execution failed: #{e}"
41
+ rescue Cocaine::CommandNotFoundError => e
42
+ raise command_not_found("xjc")
43
+ end
44
+
45
+ def javac
46
+ files = Dir[ File.join(@sources, "**/*.java") ]
47
+ line = Cocaine::CommandLine.new("javac", "-d :classes :files")
48
+ line.run(:classes => @classes, :files => files)
49
+ rescue Cocaine::ExitStatusError => e
50
+ raise Error, "javac execution failed: #{e}"
51
+ rescue Cocaine::CommandNotFoundError => e
52
+ raise command_not_found("javac")
53
+ end
54
+
55
+ def command_not_found(cmd)
56
+ Error.new("#{cmd} command not found, is it in your PATH enviornment variable?")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,39 @@
1
+ <%- require "templates/util/happymapper" -%>
2
+
3
+ require "happymapper"
4
+ <%- @class.requires.each do |mod| -%>
5
+ require "<%= mod %>"
6
+ <%- end -%>
7
+
8
+ <%= @class.module.map { |mod| "module #{mod}" }.join " " %>
9
+ <%= @class.outter_class.map { |klass| "class #{klass}" }.join "; " %>
10
+
11
+ class <%= @class.basename %> <%= "< #{@class.superclass}" if @class.superclass %>
12
+ include HappyMapper
13
+
14
+ <%- [@class.element].concat(@class.element.children).map { |e| e.namespace }.compact.uniq.each do |ns| -%>
15
+ register_namespace "<%= ns.prefix %>", "<%= ns %>"
16
+ <%- end -%>
17
+
18
+ <%- if @class.element.root? && @class.element.namespace -%>
19
+ namespace "<%= @class.element.namespace.prefix %>"
20
+ <%- end -%>
21
+
22
+ tag "<%= @class.element.local_name %>"
23
+
24
+ <%- @class.element.children.each do |e| -%>
25
+ <% next if e.type == :ID || e.type == :IDREF %>
26
+ <%= accessor_method(e) %> :<%= e.accessor %>, <%= type_name(e) %>, :tag => "<%= e.local_name %>", :namespace => <%= namespace(e.namespace) %>
27
+ <%- end -%>
28
+
29
+ <%- if @class.element.text? -%>
30
+ content :content
31
+ <%- end -%>
32
+
33
+ <% @class.element.attributes.each do |attr| %>
34
+ attribute :<%= attr.accessor %>, <%= type_name(attr) %>, :tag => "<%= attr.local_name %>", :namespace => <%= namespace(attr.namespace) %>
35
+ <% end %>
36
+ end
37
+
38
+ <%= @class.outter_class.map { "end" }.join " " %>
39
+ <%= @class.module.map { "end" }.join " " %>
@@ -0,0 +1,42 @@
1
+ <%- require "templates/util/roxml" -%>
2
+
3
+ require "roxml"
4
+ <%- @class.requires.each do |mod| -%>
5
+ require "<%= mod %>"
6
+ <%- end -%>
7
+
8
+ <%= @class.module.map { |mod| "module #{mod}" }.join " " %>
9
+ <%= @class.outter_class.map { |klass| "class #{klass}" }.join "; " %>
10
+
11
+ class <%= @class.basename %> <%= "< #{@class.superclass}" if @class.superclass %>
12
+ include ROXML
13
+
14
+ <%= namespace_map(@class) %>
15
+
16
+ xml_name "<%= @class.element.name %>"
17
+
18
+ <%- @class.element.children.each do |e| -%>
19
+ <% if e.array? %>
20
+ xml_accessor <%= accessor_name(e) %>, :as => <%= type_name(e) %>, :from => "<%= e.name %>", :required => <%= e.required? %>
21
+ <%- elsif unsupported_type?(e.type) -%>
22
+ xml_accessor <%= accessor_name(e) %>, :from => "<%= e.name %>", :required => <%= e.required? %>
23
+ <%- else -%>
24
+ xml_accessor <%= accessor_name(e) %>, :as => <%= type_name(e) %>, :from => "<%= e.name %>", :required => <%= e.required? %>
25
+ <%- end -%>
26
+ <%- end -%>
27
+
28
+ <%- if @class.element.text? -%>
29
+ xml_accessor :content, :from => ".", :required => <%= @class.element.required? %>
30
+ <%- end -%>
31
+
32
+ <% @class.element.attributes.each do |attr| %>
33
+ <%- if unsupported_type?(attr.type) -%>
34
+ xml_accessor <%= accessor_name(attr) %>, :from => "@<%= attr.name %>", :required => <%= attr.required? %>
35
+ <% else %>
36
+ xml_accessor <%= accessor_name(attr) %>, :as => <%= type_name(attr) %>, :from => "@<%= attr.name %>", :required => <%= attr.required? %>
37
+ <% end %>
38
+ <% end %>
39
+ end
40
+
41
+ <%= @class.outter_class.map { "end" }.join " " %>
42
+ <%= @class.module.map { "end" }.join " " %>
@@ -0,0 +1,44 @@
1
+ <%- accessor = lambda { |node| node.accessor.tr("?", "") }; order = lambda { |nodes| nodes.sort_by { |n| n.name } } %>
2
+ <%- @class.requires.each do |mod| -%>
3
+ require "<%= mod %>"
4
+ <%- end -%>
5
+
6
+ <%= @class.module.map { |mod| "module #{mod}" }.join " " %>
7
+
8
+ <%= @class.outter_class.map { |klass| "class #{klass}" }.join "; " %>
9
+
10
+ class <%= @class.basename %> <%= "< #{@class.superclass}" if @class.superclass %>
11
+ <%- if @class.element.children.any? %>
12
+ # Elements
13
+ <%- order[@class.element.children].each do |attr| -%>
14
+ attr_accessor :<%= accessor[attr] %>
15
+ <%- end -%>
16
+ <% end %>
17
+
18
+ <%- if @class.element.attributes.any? %>
19
+ # Attributes
20
+ <%- order[@class.element.attributes].each do |attr| -%>
21
+ attr_accessor :<%= accessor[attr] %>
22
+ <%- end -%>
23
+ <%- end -%>
24
+
25
+ <%- nodes = order[@class.element.children + @class.element.attributes] %>
26
+ def initialize(attributes = nil)
27
+ attributes ||= {}
28
+ <% nodes.each do |node| %>
29
+ <%- if node.respond_to?(:array?) && node.array? -%>
30
+ @<%= accessor[node] %> = Array(attributes[:<%= accessor[node] %>]).dup unless attributes[:<%= accessor[node] %>].nil? <%# should these always ret an array? %>
31
+ <%- else -%>
32
+ @<%= accessor[node] %> = attributes[:<%= accessor[node] %>]
33
+ <%- end -%>
34
+ <%- end -%>
35
+ end
36
+
37
+ def inspect
38
+ sprintf "#<%%s:0x%x <%= nodes.map { |node| "@#{accessor[node]}=%s" }.join(", ") %>>", self.class.name, object_id, <%= nodes.map { |node| "@#{accessor[node]}.inspect" }.join(", ") %>
39
+ end
40
+ end
41
+
42
+ <%= @class.outter_class.map { "end" }.join " " %>
43
+
44
+ <%= @class.module.map { "end" }.join " " %>
@@ -0,0 +1,26 @@
1
+ def accessor_method(node)
2
+ if node.array?
3
+ "has_many"
4
+ elsif node.type.to_s.include?("::") # Class with namespace?
5
+ "has_one"
6
+ else
7
+ "element"
8
+ end
9
+ end
10
+
11
+ # HappyMapper quirk: we need to return nil if there's no namespace, otherwise the parent element's
12
+ # namespace will be used in XPath searches
13
+ def namespace(ns)
14
+ ns.blank? ? "nil" : %|"#{ns.prefix}"|
15
+ end
16
+
17
+ def type_name(node)
18
+ case node.type
19
+ when :boolean
20
+ "Boolean"
21
+ when "Object"
22
+ "String"
23
+ else
24
+ node.type
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ UNSUPPORTED_TYPES = [:ID, :IDREF, :boolean, "String", "Object"]
2
+
3
+ def unsupported_type?(type)
4
+ UNSUPPORTED_TYPES.include?(type)
5
+ end
6
+
7
+ def namespace_map(klass)
8
+ ns = [klass.element].concat(klass.element.children).map { |e| e.namespace }.compact.uniq
9
+ return unless ns.any?
10
+
11
+ map = "xml_namespaces "
12
+ map << ns.map { |e| sprintf('"%s" => "%s"', e.prefix, e) }.join(", ")
13
+ end
14
+
15
+ def type_name(node)
16
+ name = unsupported_type?(node.type) ? "" : node.type.dup
17
+ # May be an attribute node
18
+ if node.respond_to?(:array?) && node.array?
19
+ name.prepend "["
20
+ name.concat "]"
21
+ end
22
+ name
23
+ end
24
+
25
+ def accessor_name(node)
26
+ name = ":#{node.accessor}"
27
+ name << "?" if node.type == :boolean
28
+ name
29
+ end
@@ -0,0 +1,213 @@
1
+ require "spec_helper"
2
+ require "jaxb2ruby/type_util"
3
+
4
+ describe JAXB2Ruby::Converter do
5
+ it "creates ruby classes" do
6
+ classes = convert("address")
7
+ classes.size.must_equal(2)
8
+
9
+ hash = class_hash(classes)
10
+ hash["Address"].must_be_instance_of(JAXB2Ruby::RubyClass)
11
+ hash["Address"].name.must_equal("Com::Example::Address")
12
+ hash["Address"].module.must_equal("Com::Example")
13
+ hash["Address"].basename.must_equal("Address")
14
+ hash["Address"].outter_class.must_be_nil
15
+ hash["Address"].superclass.must_be_nil
16
+
17
+ hash["Recipient"].must_be_instance_of(JAXB2Ruby::RubyClass)
18
+ hash["Recipient"].name.must_equal("Com::Example::Recipient")
19
+ hash["Recipient"].module.must_equal("Com::Example")
20
+ hash["Recipient"].basename.must_equal("Recipient")
21
+ hash["Recipient"].superclass.must_be_nil
22
+ hash["Recipient"].outter_class.must_be_nil
23
+ end
24
+
25
+ it "creates inner classes from complex anonymous types" do
26
+ hash = class_hash(convert("types"))
27
+ hash["NestedClass"].must_be_instance_of(JAXB2Ruby::RubyClass)
28
+ hash["NestedClass"].name.must_equal("Com::Example::Types::Types::NestedClass")
29
+ hash["NestedClass"].module.must_equal("Com::Example::Types")
30
+ hash["NestedClass"].outter_class.must_equal("Types")
31
+ hash["NestedClass"].basename.must_equal("NestedClass")
32
+ end
33
+
34
+ it "creates superclasses from complex extension bases" do
35
+ hash = class_hash(convert("types"))
36
+ hash["TextSubType"].must_be_instance_of(JAXB2Ruby::RubyClass)
37
+ hash["TextSubType"].name.must_equal("Com::Example::Types::TextSubType")
38
+ hash["TextSubType"].superclass.must_equal("Com::Example::Types::TextType")
39
+ end
40
+
41
+ it "creates an element for each class" do
42
+ classes = convert("address")
43
+
44
+ hash = class_hash(classes)
45
+ hash["Address"].element.must_be_instance_of(JAXB2Ruby::Element)
46
+ hash["Address"].element.name.must_match(/\Ans\d+:Address\z/)
47
+ hash["Address"].element.local_name.must_equal("Address")
48
+ hash["Address"].element.namespace.must_equal("http://example.com")
49
+
50
+ hash["Recipient"].element.must_be_instance_of(JAXB2Ruby::Element)
51
+ hash["Recipient"].element.local_name.must_equal("Recipient")
52
+ hash["Recipient"].element.name.must_match(/\Ans\d+:Recipient\z/)
53
+ end
54
+
55
+ it "creates the right child elements for each class' element" do
56
+ classes = convert("address")
57
+ hash = class_hash(classes)
58
+
59
+ elements = class_hash(hash["Address"].element.children)
60
+ elements.size.must_equal(5)
61
+ %w[House Street Town County Country].each do |name|
62
+ elements[name].must_be_instance_of(JAXB2Ruby::Element)
63
+ elements[name].accessor.must_equal(name.underscore)
64
+ elements[name].type.must_equal("String")
65
+ end
66
+
67
+ elements = class_hash(hash["Recipient"].element.children)
68
+ elements.size.must_equal(2)
69
+ %w[FirstName LastName].each do |name|
70
+ elements[name].must_be_instance_of(JAXB2Ruby::Element)
71
+ elements[name].accessor.must_equal(name.underscore)
72
+ elements[name].type.must_equal("String")
73
+ end
74
+ end
75
+
76
+ it "creates the right attributes for each class" do
77
+ classes = class_hash(convert("address"))
78
+ classes["Address"].element.attributes.size.must_equal(2)
79
+
80
+ hash = class_hash(classes["Address"].element.attributes)
81
+ attr = hash["PostCode"]
82
+ attr.name.must_equal("PostCode")
83
+ attr.local_name.must_equal("PostCode")
84
+ attr.accessor.must_equal("post_code")
85
+ attr.type.must_equal("String")
86
+
87
+ attr = hash["State"]
88
+ attr.name.must_equal("State")
89
+ attr.local_name.must_equal("State")
90
+ attr.accessor.must_equal("state_code")
91
+ attr.type.must_equal("String")
92
+
93
+ classes["Recipient"].element.attributes.must_be_empty
94
+ end
95
+
96
+ it "detects classes that are a root xml element" do
97
+ classes = class_hash(convert("types"))
98
+ classes["Types"].element.root?.must_equal(true)
99
+ classes["NestedClass"].element.root?.must_equal(false)
100
+ end
101
+
102
+ it "detects types that are nillable" do
103
+ classes = class_hash(convert("types"))
104
+ nodes = node_hash(classes["Types"].element)
105
+ nodes["nillable"].nillable?.must_equal(true)
106
+ nodes["element"].nillable?.must_equal(false)
107
+ end
108
+
109
+ it "detects classes that are arrays" do
110
+ classes = class_hash(convert("types"))
111
+ nodes = node_hash(classes["Types"].element)
112
+ nodes["idrefs"].array?.must_equal(true)
113
+ nodes["idrefs"].type.must_equal("Object")
114
+
115
+ nodes["anyType"].must_be_instance_of(JAXB2Ruby::Element)
116
+ nodes["anyType"].array?.must_equal(false)
117
+ end
118
+
119
+ it "detects classes that contain text nodes" do
120
+ classes = class_hash(convert("types"))
121
+ classes["Types"].element.text?.must_equal(false)
122
+ classes["TextType"].element.text?.must_equal(true)
123
+ end
124
+
125
+ it "detects elements that are required" do
126
+ classes = class_hash(convert("address"))
127
+ required = node_hash(classes["Recipient"].element).select { |_, v| v.required? }
128
+ required.size.must_equal(2)
129
+ required.must_include("FirstName")
130
+ required.must_include("LastName")
131
+
132
+ required = node_hash(classes["Address"].element).select { |_, v| v.required? }
133
+ required.size.must_equal(4)
134
+ %w[House Street Town PostCode].each { |attr| required.must_include(attr) }
135
+ end
136
+
137
+ it "detects attributes that are required" do
138
+ classes = class_hash(convert("address"))
139
+ required = classes["Recipient"].element.attributes.select { |_, v| v.required? }
140
+ required.must_be_empty
141
+
142
+ required = class_hash(classes["Address"].element.attributes).select { |_, v| v.required? }
143
+ required.size.must_equal(1)
144
+ required.first.must_include("PostCode")
145
+ end
146
+
147
+ it "detects element defaults" do
148
+ classes = class_hash(convert("address"))
149
+ defaults = class_hash(classes["Recipient"].element.children).reject { |_, v| v.default.nil? }
150
+ defaults.must_be_empty
151
+
152
+ defaults = class_hash(classes["Address"].element.children).reject { |_, v| v.default.nil? }
153
+ defaults.size.must_equal(1)
154
+ defaults.must_include("Country")
155
+ defaults["Country"].default.must_equal("US")
156
+ end
157
+
158
+ it "detects attribute defaults" do
159
+ skip "No all XJC implementations support attribute defaults... but we do"
160
+ end
161
+
162
+ describe "given a namespace to module mapping" do
163
+ let(:mod) { "A::Namespace" }
164
+ let(:nsmap) { { "http://example.com" => mod } }
165
+
166
+ it "converts elements in the given namespace to the classes in the given module" do
167
+ hash = class_hash(convert("address", :namespace => nsmap))
168
+ hash["Address"].must_be_instance_of(JAXB2Ruby::RubyClass)
169
+ hash["Address"].name.must_equal("#{mod}::Address")
170
+ hash["Address"].module.must_equal(mod)
171
+ hash["Address"].basename.must_equal("Address")
172
+ end
173
+ end
174
+
175
+ describe "given an XML Schema type to Ruby type mapping" do
176
+ let(:typemap) { {
177
+ "anySimpleType" => "My::Type",
178
+ "boolean" => "TrueClass"
179
+ } }
180
+
181
+ # describe "elements without a mapping" do
182
+ # it "does not convert them" do
183
+ # end
184
+ # end
185
+
186
+ describe "elements with a mapping" do
187
+ it "converts them to the given classes" do
188
+ classes = class_hash(convert("types", :typemap => typemap))
189
+ nodes = node_hash(classes["Types"].element)
190
+ typemap.each do |xsd, ruby|
191
+ nodes[xsd].must_be_instance_of(JAXB2Ruby::Element)
192
+ nodes[xsd].type.must_equal(ruby)
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "XML schema to ruby data type mapping" do
199
+ let(:nodes) {
200
+ classes = class_hash(convert("types"))
201
+ node_hash(classes["Types"].element)
202
+ }
203
+
204
+ # TODO: nillablePrimitiveArray
205
+ JAXB2Ruby::TypeUtil::SCHEMA_TO_RUBY.each do |xsd, ruby|
206
+ it "maps the schema type #{xsd} to the ruby type #{ruby}" do
207
+ # xsd type is also the accessor name
208
+ nodes[xsd].must_be_instance_of(JAXB2Ruby::Element)
209
+ nodes[xsd].type.must_equal(ruby)
210
+ end
211
+ end
212
+ end
213
+ end