devdaily home | apple | java | perl | unix | directory | blog

What this is

This file is included in the DevDaily.com "Ruby Source Code Warehouse" project. The intent of this project is to help you "Learn Ruby by Example" TM.

Other links

The source code

# access+.rb, a module implementing Eiffel-esque access methods
# Copyright (C) 2003  Jamis Buck (jgb3@email.byu.edu)
#
# access+.rb is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# access+.rb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with access+.rb; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# =========================================================================

# Enhance Class to help us along.
class Class

  # Return true if the given class is either the same as this class, or a
  # a superclass of this class.
  def descendant_of?( klass )
    return true if self == klass
    return ( superclass.nil? ? false : superclass.descendant_of?( klass ) )
  end
end

# Enhance Module.  Methods allowing the Eiffel 'feature' feature to be
# implemented are added.
class Module

  # Indicate that the given method is only accessable to the given classes,
  # and to this class.  If the classes list is empty, then it defaults to :Object.
  def access( method, *classes )
    classes = classes.collect { |id| id == :Any ? :Object : ( id == :None ? :NilClass : id ) }
    classes = [ :Object ] if classes.length < 1

    return if classes.length == 1 && classes.first == :Object

    real_method_name = "__real_#{method}"

    s = "alias :#{real_method_name} :#{method}\n"
    s << "def #{method}(*args)\n"

    err_msg = ""
    s << "  c = caller\n"

    condition = "c != #{self}"

    if classes.length == 1 && classes.first == :NilClass
      classes = []
      err_msg = "'#{method}' is not accessible"
    else
      err_msg = "'#{method}' is only accessible to instances of " + classes.join( ", " )
    end

    s << "  err_msg = \"" << err_msg << "\"\n"

    classes.each do |klass|
      condition << " && " if condition.length > 0
      condition << "!c.descendant_of?( #{klass} )"
    end

    s << "  raise NoMethodError, " <<
         "\"inaccessible method '#{method}' called from \#\{c} for \#\{self}\ " <<
         "(\" + err_msg + \")\" if " <<
         condition << "\n"

    if method.id2name =~ /=$/
      s << "  self.#{real_method_name}( args.first )\n"
    else
      s << "  self.#{real_method_name}( *args )\n"
    end

    s << "end"

    @method_define_disabled = true
    module_eval s
    @method_define_disabled = false
  end

  # Indicate that subsequent methods are only accessible to descendents of the
  # given classes.  If the classes list is empty, it defaults to :Any.
  def feature( *classes )
    @__current_classes = classes
  end

  # Make 'allow' an alias for 'feature', since 'feature' won't be easily understandable
  # unless you're an Eiffel programmer.
  alias :allow :feature

  # Override method_added, so that as methods are added, their access levels are modified.
  def method_added( id )
    return if id.id2name == "method_added"
    return if @method_define_disabled
    access( id, *( @__current_classes || [ :Object ] ) )
  end
end

# Get the Class of the caller that invoked the current method.  This is thread-safe,
# so that multiple threads can safely call this method and get the correct answer.
def caller
  Thread.current[:callstack] ||= []
  ( Thread.current[:callstack][-3] || Object )
end

# Set the trace function so that we can detect when methods are invoked and when they
# are exited.  Unfortunately, this little hack is the only way (currently) in which
# the call stack can be inspected.
set_trace_func proc { |event,file,line,obj,bind,klass|
  if event == "call"
    Thread.current[:callstack] ||= []
    Thread.current[:callstack].push klass
  elsif event == "return"
    Thread.current[:callstack].pop
  end
}




Copyright 1998-2008 Alvin Alexander
All Rights Reserved.
 
devdaily.com is based in louisville, kentucky, and this web site is hosted by godaddy.com