class Police::DataFlow::ProxyBase

Base class for labeled objects replacements.

Attributes

__police_classes__[RW]

The classes of the labels supported by the proxy class.

@private This is a Police::DataFlow implementation detail. Do not read it directly.

__police_class__[R]

The actual class of a proxy object.

This is necessary because the class method for a proxy object must return the proxied object’s class.

@private Use the {Police::DataFlow} API instead of reading this attribute directly.

__police_labels__[R]

The {Police::DataFlow::Label} instances attached to the proxied object.

This is a Hash whose keys are the __id__s of the Class objects for the labels’ classes. The values are sets of labels that are instances of the corresponding label classes. Sets are implemented as Hashes mapping the label instances to boolean true values.

@private Use the {Police::DataFlow} API instead of reading this attribute directly.

__police_proxied__[R]

The object being proxied by this object.

@private Use the {Police::DataFlow} API instead of reading this attribute directly.

__police_stickies__[R]

Attached {Police::DataFlow::Label} instances whose classes are sticky.

@private This is a proxying implementation detail and should not be relied on.

Public Class Methods

new(proxied, proxy_class, label_set) click to toggle source

Creates a proxied object.

@param [Object] proxied the object that will receive messages sent to the

newly created proxy

@param [Class<Police::DataFlow::ProxyBase>] proxy_class the

{Police::DataFlow::ProxyBase} subclass being instantiated; Object
instances can call Object#class to get to their class, but BasicObject
instances don't have this luxury

@param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the

set of all labels that will be held by the object's proxy
# File lib/police/dataflow/proxy_base.rb, line 49
def initialize(proxied, proxy_class, label_set)
  @__police_proxied__ = proxied
  @__police_labels__ = label_set

  # Cache the sticky labels to speed up certain computations.
  @__police_stickies__ = {}
  @__police_labels__.each do |class_id, instance_set|
    label_class = instance_set.first.first.class
    next unless label_class.sticky?
    # NOTE: the set of instances is intentionally shared with
    #       @__police_labels__ so updates to the former are automatically
    #       reflected in the stickies cache
    @__police_stickies__[class_id] = instance_set
  end

  # Holds the object's class, because Object#class is not available.
  @__police_class__ = proxy_class
end

Public Instance Methods

method_missing(name, *args) { |*yield_args| ... } click to toggle source

Handles method calls to the proxied object.

Whenever possible, proxy methods are created on-the-fly, so that future calls to the same method will be faster.

# File lib/police/dataflow/proxy_base.rb, line 72
def method_missing(name, *args, &block)
  # Build a fast path for future method calls, if possible.
  respond_to_missing? name, true

  # NOTE: going out of our way to use the fast path methods, because they
  #       handle native method argument unwrapping correctly
  if @__police_class__.method_defined?(name) ||
      @__police_class__.private_method_defined?(name)
    return __send__(name, *args, &block)
  end

  if block
    return_value = @__police_proxied__.__send__ name, *args do |*yield_args|
      # Yielded values filtering.
      @__police_labels__.each do |_, label_hash|
        label_class = label_hash.first.first.class
        if hook = label_class.yield_args_hook(name)
          label_hash.each do |label, _|
            yield_args = label.__send__ hook, self, yield_args, *args
          end
        elsif label_class.sticky?
          label_hash.each do |label, _|
            yield_args.map! { |arg| ::Police::DataFlow.label arg, label }
          end
        end
      end

      yield_return = yield(*yield_args)
      # TODO(pwnall): consider adding a yield value filter
      next yield_return
    end
  else
    return_value = @__police_proxied__.__send__ name, *args
  end

  # Return value filtering.
  @__police_labels__.each do |_, label_hash|
    label_class = label_hash.first.first.class
    if hook = label_hash.first.first.class.return_hook(name)
      label_hash.each do |label, _|
        return_value = label.__send__ hook, return_value, self, *args
      end
    elsif label_class.sticky?
      label_hash.each do |label, _|
        return_value = ::Police::DataFlow.label return_value, label
      end
    end
  end
  return return_value
end
respond_to_missing?(name, include_private) click to toggle source

Called when Object#respond_to? returns false.

# File lib/police/dataflow/proxy_base.rb, line 124
def respond_to_missing?(name, include_private)
  return false unless @__police_proxied__.respond_to? name, include_private

  # A method on the proxied object doesn't have a corresponding proxy.
  # Fix this by creating all possible proxies.

  # NOTE: this approach is cheaper than creating proxies one by one, because
  #       it plays nice with method caches

  ::Police::DataFlow::Proxying.add_instance_methods @__police_class__,
                                                    @__police_proxied__.class

  # NOTE: we don't want to create unnecessary singleton classes
  # target_methods = @__police_proxied__.singleton_methods true
  # unless target_methods.empty?
  #  ::Police::DataFlow::Proxying.add_singleton_methods self,
  #      @__police_proxied__, target_methods
  # end

  true
end