Class: Class

Inherits:
Object show all
Defined in:
lib/jinx/import/java.rb,
lib/jinx/helpers/class.rb

Constant Summary collapse

OBJ_INST_MTHDS =
Object.instance_methods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.to_ruby(class_or_name) ⇒ Class

Returns the Ruby class for the given class, as follows:

  • If the given class is already a Ruby class, then return the class.

  • If the class argument is a Java class or a Java class name, then the Ruby class is the JRuby wrapper for the Java class.

Parameters:

  • class_or_name (Class, String)

    the class or class name

Returns:

  • (Class)

    the corresponding Ruby class



217
218
219
220
221
222
223
# File 'lib/jinx/import/java.rb', line 217

def self.to_ruby(class_or_name)
  case class_or_name
    when Class then class_or_name
    when String then eval to_ruby_name(class_or_name)
    else to_ruby(class_or_name.name)
  end
end

.to_ruby_name(jname) ⇒ String (private)

Returns the JRuby class or module name.

Examples:

Java.to_ruby_class_name('com.test.Sample') #=> Java::ComTest::Sample

Parameters:

  • jname (String)

    the fully-qualified Java class or interface name

Returns:

  • (String)

    the JRuby class or module name



322
323
324
325
326
327
328
# File 'lib/jinx/import/java.rb', line 322

def self.to_ruby_name(jname)
  path = jname.split('.')
  return "Java::#{jname}" if path.size == 1
  cname = path[-1]
  pkg = path[0...-1].map { |s| s.capitalize_first }.join
  "Java::#{pkg}::#{cname}"
end

Instance Method Details

#abstract?Boolean

Returns whether this is a wrapper for an abstract Java class.

Returns:

  • (Boolean)

    whether this is a wrapper for an abstract Java class



226
227
228
# File 'lib/jinx/import/java.rb', line 226

def abstract?
  java_class? and Java::JavaLangReflect::Modifier.isAbstract(java_class.modifiers)
end

#alias_attribute(aliaz, attribute) ⇒ Object (private)

Creates an alias for each accessor method of the given attribute.

Examples:

class Person
 attr_reader :social_security_number
 attr_accessor :postal_code
 alias_attribute(:ssn, :social_security_number)
 alias_attribute(:zip_code, :postal_code)
end
Person.method_defined?(:ssn) #=> true
Person.method_defined?(:ssn=) #=> false
Person.method_defined?(:zip_code) #=> true
Person.method_defined?(:zip_code=) #=> true


30
31
32
33
34
# File 'lib/jinx/helpers/class.rb', line 30

def alias_attribute(aliaz, attribute)
  alias_method(aliaz, attribute) if method_defined?(attribute)
  writer = "#{attribute}=".to_sym
  alias_method("#{aliaz}=".to_sym, writer) if method_defined?(writer)
end

#attr_create_on_demand_accessor(symbol) {|obj| ... } ⇒ Object (private)

Defines an instance variable accessor attribute whose reader calls the block given to this method to create a new instance variable on demand, if necessary.

For example, the declaration

class AlertHandler
  attr_create_on_demand_accessor(:pings) { Array.new }
end

is equivalent to:

class AlertHandler
  attr_writer :pings
  def pings
    instance_variable_defined?(@pings) ? @pings : Array.new
  end
end

This method is useful either as a short-hand for the create-on-demand idiom as shown in the example above, or when it is desirable to dynamically add a mix-in attribute to a class at runtime whose name is not known when the class is defined.

Examples:

class AlertHandler
  def self.handle(alert)
    attr_create_on_demand_accessor(alert) { AlertQueue.new }
  end
end
...
AlertHandler.handle(:pings)
AlertHandler.new.pings #=> empty AlertQueue

Parameters:

  • symbol (Symbol)

    the attribute to define

Yields:

  • (obj)

    factory to create the new attribute value for the given instance

Yield Parameters:

  • obj

    the class instance for which the attribute will be set



94
95
96
97
98
99
100
101
102
# File 'lib/jinx/helpers/class.rb', line 94

def attr_create_on_demand_accessor(symbol)
  attr_writer(symbol)
  wtr = "#{symbol}=".to_sym
  iv = "@#{symbol}".to_sym
  # the attribute reader creates a new proxy on demand
  define_method(symbol) do
    instance_variable_defined?(iv) ? instance_variable_get(iv) : send(wtr, yield(self))
  end
end

#class_hierarchyObject

Returns an Enumerable on this class and its ancestors.



5
6
7
# File 'lib/jinx/helpers/class.rb', line 5

def class_hierarchy
  @class__hierarchy ||= Enumerable::Enumerator.new(self, :each_class_in_hierarchy)
end

#each_class_in_hierarchy {|klass| ... } ⇒ Object (private)

Enumerates each class in the hierarchy.

Yields:

  • (klass)

    the enumeration block

Yield Parameters:

  • klass (Class)

    the class in the hierarchy



108
109
110
111
112
113
114
# File 'lib/jinx/helpers/class.rb', line 108

def each_class_in_hierarchy
  current = self
  until current.nil?
    yield current
    current = current.superclass
  end
end

#java_class?Boolean

Returns whether this is a Java wrapper class.

Returns:

  • (Boolean)


206
207
208
# File 'lib/jinx/import/java.rb', line 206

def java_class?
  method_defined?(:java_class)
end

#offset_attr_accessor(hash, offset = nil) ⇒ Object (private)

Creates new accessor methods for each method => original hash entry. The new method offsets the existing Number original attribute value by the given offset (default -1).

Examples:

class OneBased
  attr_accessor :index
  offset_attr_accessor :zero_based_index => :index
end

Parameters:

  • hash ({Symbol => Symbol})

    the offset => original method hash

  • offset (Integer, nil) (defaults to: nil)

    the offset amount (default is -1)



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/jinx/helpers/class.rb', line 47

def offset_attr_accessor(hash, offset=nil)
  offset ||= -1
  hash.each do |method, original|
    define_method(method) { value = send(original); value + offset if value } if method_defined?(original)
    original_writer = "#{original}=".to_sym
    if method_defined?(original_writer) then
      define_method("#{method}=".to_sym) do |value|
        adjusted = value - offset if value
        send(original_writer, adjusted)
      end
    end
  end
end

#property_descriptors(hierarchy = true) ⇒ Object

Returns this class’s readable and writable Java PropertyDescriptors, or an empty Array if none. If the hierarchy flag is set to false, then only this class’s properties will be introspected.



244
245
246
247
248
# File 'lib/jinx/import/java.rb', line 244

def property_descriptors(hierarchy=true)
  return Array::EMPTY_ARRAY  unless java_class?
  info = hierarchy ? Java::JavaBeans::Introspector.getBeanInfo(java_class) : Java::JavaBeans::Introspector.getBeanInfo(java_class, java_class.superclass)
  info.propertyDescriptors.select { |pd| pd.write_method and property_read_method(pd) }
end

#property_read_method(pd) ⇒ Symbol

Returns the property descriptor pd introspected or discovered Java read Method.

Returns:

  • (Symbol)

    the property descriptor pd introspected or discovered Java read Method

Technology idiosyncracy:

  • Java

    java.lang.Boolean isname is not introspected as a read method, since the type must be primitive, i.e. boolean isname. The work-around is to explicitly look for the isname method in the case of a java.lang.Boolean property rather than rely on the Java introspector.



306
307
308
309
310
311
312
# File 'lib/jinx/import/java.rb', line 306

def property_read_method(pd)
  return pd.read_method if pd.read_method
  return unless pd.get_property_type == Java::JavaLang::Boolean.java_class
  rdr = java_class.java_method("is#{pd.name.capitalize_first}") rescue nil
  logger.debug { "Discovered #{qp} #{pd.name} property non-introspected reader method #{rdr.name}." } if rdr
  rdr
end

#redefine_method(method) {|old_method| ... } ⇒ Symbol (private)

Redefines method using the given block. The block argument is a new alias for the old method. The block creates a proc which implements the new method body.

Examples:

redefine_method(:ssn) { |omth| lambda { send(omth).delete('-').to_i } }

Parameters:

  • method (Symbol)

    the method to redefine

Yields:

  • (old_method)

    the redefinition Proc

Yield Parameters:

  • old_method (Symbol)

    the method being redefined

Returns:

  • (Symbol)

    an alias to the old method implementation



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/jinx/helpers/class.rb', line 125

def redefine_method(method)
  # make a new alias id method__base for the existing method.
  # disambiguate with a counter suffix if necessary.
  counter = 2
  # make a valid alias base
  old, eq = /^([^=]*)(=)?$/.match(method.to_s).captures
  old.tr!('|', 'or')
  old.tr!('&', 'and')
  old.tr!('+', 'plus')
  old.tr!('*', 'mult')
  old.tr!('/', 'div')
  old.gsub!(/[^\w]/, 'op')
  base = "redefined__#{old}"
  old_id = "#{base}#{eq}".to_sym
  while method_defined?(old_id)
    base = "#{base}#{counter}"
    old_id = "#{base}#{eq}".to_sym
    counter = counter + 1
  end
  alias_method(old_id, method)
  body = yield old_id
  define_method(method, body)
  old_id
end

#transient?(pd) ⇒ Boolean

Returns whether the given PropertyDescriptor pd corresponds to a transient field in this class, or nil if there is no such field.

Returns:

  • (Boolean)


231
232
233
234
235
236
237
238
239
# File 'lib/jinx/import/java.rb', line 231

def transient?(pd)
  begin
    field = java_class.declared_field(pd.name)
  rescue Exception
    # should occur only if a property is not a field; not an error
    return
  end
  Java::JavaLangReflect::Modifier.isTransient(field.modifiers) if field
end

#unocclude_reserved_method(pd) ⇒ Object

Redefines the reserved method corresponding to the given Java property descriptor back to the Object implementation, if necessary. If both this class and Object define a method with the given property name, then a new method is defined with the same body as the previous method. Returns the new method symbol, or nil if the property name is not an occluded Object instance method.

This method undoes the JRuby clobbering of Object methods by Java property method wrappers. The method is renamed as follows:

  • id is changed to :identifier

  • type is prefixed by the underscore subject class name, e.g. Specimen.type => :specimen_type, If the property name is type and the subject class name ends in ‘Type’, then the property symbol is the underscore subject class name, e.g. HistologicType.type => :histologic_type.

Raises ArgumentError if symbol is not an Object method.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/jinx/import/java.rb', line 265

def unocclude_reserved_method(pd)
  oldname = pd.name.underscore
  return unless OBJ_INST_MTHDS.include?(oldname)
  oldsym = oldname.to_sym
  undeprecated = case oldsym
    when :id then :object_id
    when :type then :class
    else oldsym
  end
  rsvd_mth = Object.instance_method(undeprecated)
  base = self.qp.underscore
  newname = if oldname == 'id' then
    'identifier'
  elsif base[-oldname.length..-1] == oldname then
    base
  else
    "#{base}_#{oldname}"
  end
  newsym = newname.to_sym
  rdr = property_read_method(pd).name.to_sym
  alias_method(newsym, rdr)
  # alias the writers
  wtr = pd.write_method.name.to_sym
  alias_method("#{newsym}=".to_sym, wtr)
  # alias a camel-case Java-style method if necessary
  altname = newname.camelize
  unless altname == newname then
    alias_method(altname.to_sym, oldsym)
    alias_method("#{altname}=".to_sym, wtr)
  end
  # restore the old method to Object
  define_method(oldsym) { |*args| rsvd_mth.bind(self).call(*args) }
  newsym
end