patapizza/COP-Ruby

View on GitHub
cop/context.rb

Summary

Maintainability
A
2 hrs
Test Coverage
#!/usr/bin/env ruby

$: << ".."

require "cop/context_manager"
require "cop/context_adaptation"

class Context

  @@default = nil
  @@stack = Array.new

  class << self
    attr_writer :stack
  end

  attr_writer :manager
  attr_accessor :adaptations

  def initialize
    @count = 0
    @adaptations = Array.new
  end

  def self.default(*args)
    return @@default = args.first unless args.size == 0
    if @@default.nil?
      @@default = self.new
      @@default.activate
    end
    @@default
  end

  def self.stack
    @@stack
  end

  def self.named(name)
    c = self.new
    c.name= name
    c
  end

  def self.proceed
    ca = @@stack.last
    raise Exception, "proceed should only be called from adapted methods" if ca.nil?
    adaptations = self.default.manager.adaptations.select do |adaptation|
      adaptation.adapts_class? ca.adapted_class, ca.adapted_selector
    end
    raise Exception, "no adaptation found" if adaptations.empty?
    meth = @@default.manager.adaptation_chain(ca.adapted_class, ca.adapted_selector)[1].adapted_implementation
    if meth.is_a? Proc
      meth.call(meth.parameters)
    else
      meth.bind(ca.adapted_class.class_eval("self.new")).call(meth.parameters)
    end
  end

  def activation_age
    self.manager.context_activation_age(self)
  end

  def activate
    self.manager.signal_activation_request(self)
    self.activate_adaptations if @count == 0
    @count += 1
    self
  end

  def deactivate
    self.deactivate_adaptations if @count == 1
    @count -= 1 unless @count == 0
    self
  end

  def active?
    @count > 0
  end

  def name
    self.manager.directory[self]
  end

  def name=(new_name)
    new_name.nil? ? self.manager.directory.delete(self) : self.manager.directory[self] = new_name
  end

  def manager
    return @manager unless @manager.nil?
    return @manager = Context.default.manager unless self == Context.default
    @manager = ContextManager.new
    self.name= "default"
    @manager
  end

  def discard
    self.manager.discard_context(self)
    Context.default(nil) if self == Context.default
    @adaptations.each do |adaptation|
      self.remove_existing_adaptation(adaptation)
    end
  end

  def to_s
    (self.name.nil? ? "anonymous" : "#{self.name}") + " context"
  end

  def adapt_class(a_class, a_selector, a_method)
    begin
      method = a_class.instance_method(a_selector)
      rescue NameError
        raise Exception, "can't adapt inexistent method #{a_selector.to_s} in #{a_method.to_s}"
    end
    default = ContextAdaptation.in_context(Context.default, a_class, a_selector, method)
    Context.default.add_adaptation(default) { :preserve }
    adaptation = ContextAdaptation.in_context(self, a_class, a_selector, a_method)
    self.add_adaptation(adaptation) { raise Exception, "an adaptation already exists for #{self.to_s}" }
  end

  def add_adaptation(context_adaptation, &block)
    existing = @adaptations.index do |adaptation|
      adaptation.same_target? context_adaptation
    end
    if existing.nil?
      self.add_inexistent_adaptation(context_adaptation)
      return self
    end
    existing = @adaptations[existing]
    action = yield
    if action == :overwrite
      self.remove_existing_adaptation(existing)
      self.add_inexistent_adaptation(context_adaptation)
    else
      raise Exception, "unknown overriding action #{action.to_s}" unless action == :preserve
    end
  end

  def add_inexistent_adaptation(context_adaptation)
    raise Exception, "can't add foreign adaptation" unless self == context_adaptation.context
    @adaptations.push(context_adaptation)
    self.manager.activate_adaptation(context_adaptation) if self.active?
  end

  def remove_existing_adaptation(context_adaptation)
    raise Exception, "can't remove foreign adaptation" unless self == context_adaptation.context
    self.manager.deactivate_adaptation(context_adaptation) if self.active?
    raise Exception, "can't remove adaptation" if @adaptations.delete(context_adaptation).nil?
  end

  def activate_adaptations
    @adaptations.each do |adaptation|
      begin
        self.manager.activate_adaptation(adaptation)
    rescue Exception
      self.rollback_adaptations
      raise Exception, $!
      end
    end
  end

  def deactivate_adaptations
    @adaptations.each do |adaptation|
      self.manager.deactivate_adaptation(adaptation)
    end
  end

  def rollback_adaptations
    deployed = self.manager.adaptations.select do |adaptation|
      adaptation.context == self
    end
    deployed.each do |adaptation|
      self.manager.deactivate_adaptation(adaptation)
    end
  end

end