|
What this is
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