Module: Jinx::Introspector

Includes:
Propertied
Included in:
Metadata
Defined in:
lib/jinx/metadata/introspector.rb

Overview

Meta-data mix-in to infer attribute meta-data from Java properties.

Instance Attribute Summary

Attributes included from Propertied

#attributes, #defaults

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Propertied

#add_alternate_key_attribute, #add_attribute, #add_attribute_aliases, #add_attribute_default, #add_attribute_defaults, #add_mandatory_attribute, #add_mandatory_attributes, #add_primary_key_attribute, #add_property, #add_restriction, #add_secondary_key_attribute, #alias_attribute, #alias_standard_attribute_hash, #all_key_attributes, #alternate_key_attributes, #append_ancestor_enum, #attribute_filter, #collect_mandatory_attributes, #collection_attribute?, #compose_property, #create_nonjava_property, #default_mandatory_local_attributes, #dependent_attributes, #dependent_properties, #domain_attribute?, #domain_attributes, #domain_properties, #each_property, #independent_attributes, #init_property_classifiers, #introspected?, #java_attributes, #mandatory_attributes, #mandatory_owner_attribute, #most_specific_domain_attribute, #nondomain_attribute?, #nondomain_attributes, #nondomain_java_attributes, #nonowner_attributes, #offset_attribute, #primary_key_attributes, #properties, #property, #property_defined?, #property_hash, #property_path, #qualify_attribute, #qualify_property, #register_property_alias, #remove_attribute, #secondary_key_attributes, #secondary_key_non_owner_domain_attributes, #set_alternate_key_attributes, #set_attribute_type, #set_primary_key_attributes, #set_secondary_key_attributes, #standard_attribute, #unidirectional_dependent_attributes

Class Method Details

.ensure_introspected(klass) ⇒ Object

Introspects the given class, if necessary. Some member of the class hierarchy must first be introspected.

Parameters:

  • klass (Class)

    the class to introspect if necessary

Raises:

  • (NoSuchMethodError)

    if the class or an ancestor of the class is not introspected



18
19
20
21
22
23
24
25
26
# File 'lib/jinx/metadata/introspector.rb', line 18

def self.ensure_introspected(klass)
  return if klass < Resource and klass.introspected?
  sc = klass.superclass
  ensure_introspected(sc) unless sc == Java::java.lang.Object
  logger.debug { "Introspecting the fetched object class #{klass}..." }
  # Resolving the class name in the context of the domain module
  # introspects the class.
  sc.domain_module.const_get(klass.name.demodulize)
end

Instance Method Details

#add_attribute_value_initializerObject

Augments the introspected class new method as follows:

  • Adds an optional attribute=>value constructor parameter.

  • Calls the Resource#post_initialize method after initialization.



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/jinx/metadata/introspector.rb', line 31

def add_attribute_value_initializer
  class << self
    def new(opts=nil)
      obj = super()
      obj.post_initialize
      obj.merge_attributes(opts) if opts
      obj
    end
  end
  logger.debug { "#{self} is extended with an optional {attribute=>value} constructor parameter." }
end

#add_java_property(pd) ⇒ Property (private)

Makes a standard attribute for the given property descriptor. Adds a camelized Java-like alias to the standard attribute.

Parameters:

  • the (Java::PropertyDescriptor)

    introspected property descriptor

Returns:



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/jinx/metadata/introspector.rb', line 165

def add_java_property(pd)
  # make the attribute metadata
  prop = create_java_property(pd)
  add_property(prop)
  # the property name is an alias for the standard attribute
  pa = prop.attribute
  # the Java property name as an attribute symbol
  ja = pd.name.to_sym
  delegate_to_property(ja, prop) unless prop.reader == ja
  prop
end

#alias_property_accessors(property) ⇒ Object (private)

Aliases the given Ruby property reader and writer to its underlying Java property reader and writer, resp.

Parameters:

  • property (Property)

    the property to alias



152
153
154
155
156
157
158
# File 'lib/jinx/metadata/introspector.rb', line 152

def alias_property_accessors(property)
  # the Java reader and writer accessor method symbols
  jra, jwa = property.java_accessors
  # strip the Java reader and writer is/get/set prefix and make a symbol
  alias_method(property.reader, jra)
  alias_method(property.writer, jwa)
end

#create_java_property(pd) ⇒ Property (private)

Returns the new property.

Parameters:

  • the (Java::PropertyDescriptor)

    introspected property descriptor

Returns:



179
180
181
# File 'lib/jinx/metadata/introspector.rb', line 179

def create_java_property(pd)
  JavaProperty.new(pd, self)
end

#define_java_property(pd) ⇒ Object (private)

Defines the Java property attribute and standard attribute methods, e.g. study_protocol and studyProtocol. A boolean attribute is provisioned with an additional reader alias, e.g. available? for is_available.

A standard attribute which differs from the property attribute delegates to the property attribute, e.g. study_protocol delegates to studyProtocol rather than aliasing setStudyProtocol. Redefining these methods results in a call to the redefined method. This contrasts with a Ruby alias, where each attribute alias is bound to the respective attribute reader or writer.

Parameters:

  • the (Java::PropertyDescriptor)

    introspected property descriptor



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/jinx/metadata/introspector.rb', line 87

def define_java_property(pd)
  if transient?(pd) then
    logger.debug { "Ignoring #{name.demodulize} transient attribute #{pd.name}." }
    return
  end
  # the standard underscore lower-case attributes
  prop = add_java_property(pd)
  # delegate the standard attribute accessors to the attribute accessors
  alias_property_accessors(prop)
  # add special wrappers
  wrap_java_property(prop)
  # create Ruby alias for boolean, e.g. alias :empty? for :empty
  if pd.property_type.name[/\w+$/].downcase == 'boolean' then
    # Strip the leading is_, if any, before appending a question mark.
    aliaz = prop.to_s[/^(is_)?(\w+)/, 2] << '?'
    delegate_to_property(aliaz, prop)
  end
end

#delegate_to_property(aliaz, property) ⇒ Object (private)

Defines methods aliaz and aliaz= which calls the standard attribute and attribute= accessor methods, resp. Calling rather than aliasing the attribute accessor allows the aliaz accessor to reflect a change to the attribute accessor.



187
188
189
190
191
192
193
# File 'lib/jinx/metadata/introspector.rb', line 187

def delegate_to_property(aliaz, property)
  ra, wa = property.accessors
  if aliaz == ra then raise MetadataError.new("Cannot delegate #{self} #{aliaz} to itself.") end
  define_method(aliaz) { send(ra) }
  define_method("#{aliaz}=".to_sym) { |value| send(wa, value) }
  register_property_alias(aliaz, property.attribute)
end

#introspectObject

Defines the Java attribute access methods, e.g. study_protocol and studyProtocol. A boolean attribute is provisioned with an additional reader alias, e.g. available? for is_available.

Each Java property attribute delegates to the Java attribute getter and setter. Each standard attribute delegates to the Java property attribute. Redefining these methods results in a call to the redefined method. This contrasts with a Ruby alias, where the alias remains bound to the original method body.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/jinx/metadata/introspector.rb', line 52

def introspect
  # Set up the attribute data structures; delegates to Propertied.
  init_property_classifiers
  logger.debug { "Introspecting #{qp} metadata..." }
  # check for method conflicts
  conflicts = instance_methods(false) & Resource.instance_methods(false)
  unless conflicts.empty? then
    logger.warn("#{self} methods conflict with #{Resource} methods: #{conflicts.qp}")
  end
  # If this is a Java class rather than interface, then define the Java property
  # attributes.
  if Class === self then
    # the Java attributes defined by this class with both a read and a write method
    pds = property_descriptors(false)
    # Define the standard Java attribute methods.
    pds.each { |pd| define_java_property(pd) }
  end
  logger.debug { "Introspection of #{qp} metadata complete." }
  self
end

#wrap_java_date_property(property) ⇒ Object (private)

Adds a Java-Ruby Date filter to the given Date property Ruby access methods. The reader returns a Ruby date. The writer sets a Java date.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/jinx/metadata/introspector.rb', line 130

def wrap_java_date_property(property)
  ra, wa = property.accessors
  jra, jwa = property.java_accessors
  
  # filter the attribute reader
  define_method(ra) do
    value = send(jra)
    Java::JavaUtil::Date === value ? value.to_ruby_date : value
  end
  
  # filter the attribute writer
  define_method(wa) do |value|
    value = Java::JavaUtil::Date.from_ruby_date(value) if ::Date === value
    send(jwa, value)
  end

  logger.debug { "Filtered #{qp} #{ra} and #{wa} methods with Java Date <-> Ruby Date converter." }
end

#wrap_java_property(property) ⇒ Object (private)

Adds a filter to the given property access methods if it is a String or Date.



107
108
109
110
111
112
113
114
# File 'lib/jinx/metadata/introspector.rb', line 107

def wrap_java_property(property)
  pd = property.property_descriptor
  if pd.property_type == Java::JavaLang::String.java_class then
    wrap_java_string_property(property)
  elsif pd.property_type == Java::JavaUtil::Date.java_class then
    wrap_java_date_property(property)
  end
end

#wrap_java_string_property(property) ⇒ Object (private)

Adds a number -> string filter to the given String property Ruby access methods.



117
118
119
120
121
122
123
124
125
126
# File 'lib/jinx/metadata/introspector.rb', line 117

def wrap_java_string_property(property)
  ra, wa = property.accessors
  jra, jwa = property.java_accessors
  # filter the attribute writer
  define_method(wa) do |value|
    stdval = Math.numeric?(value) ? value.to_s : value
    send(jwa, stdval)
  end
  logger.debug { "Filtered #{qp} #{wa} method with non-String -> String converter." }
end