# = annotation.rb
#
# == Copyright (c) 2005 Thomas Sawyer
#
#   Ruby License
#
#   This module is free software. You may use, modify, and/or redistribute this
#   software under the same terms as Ruby.
#
#   This program 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.
#
# == History
#
# LOG 2006-11-07 trans  Created this ultra-concise version of annotations.
#
# == Notes
#
# TODO Might be nice to have a default object of annotation, eg. the next
# method defined, like how +desc+ annotates a rake +task+.
#
# TODO The ann(x).name notation is kind of nice. Would like to add that
# back-in if reasonable. Basically this require heritage to be an OpenObject
# rather than just a hash.
#
# == Authors & Contributors
#
# * Thomas Sawyer

# Author::    Thomas Sawyer
# Copyright:: Copyright (c) 2005 Thomas Sawyer
# License::   Ruby License

require 'facets/core/hash/to_h'
require 'facets/core/hash/rekey'
require 'facets/core/hash/op_add'

# = Annotation
#
# Annotations allows you to annontate objects, including methods with arbitrary
# "metadata". These annotations don't do anything in themselves. They are
# merely comments. But you can put them to use. For instance an attribute
# validator might check for an annotation called :valid and test against it.
#
# Annotation is an OpenObject, and is used across the board for keeping annotations.
#
# Annotation class serves for both simple and inherited cases depending on whether
# a base class is given.
#
# == Synopsis
#
#   class X
#     attr :a
#     ann :@a, :valid => lambda{ |x| x.is_a?(Integer) }
#
#     def validate
#       instance_variables.each { |iv|
#         if validator = self.class.ann(iv)[:valid]
#           value = instance_variable_get(iv)
#           unless validator.call(vale)
#             raise "Invalid value #{value} for #{iv}"
#           end
#         end
#       }
#     end
#
#   end

#--
# By using a global veriable rather the definining a class instance variable
# for each class/module, it is possible to quicky scan all annotations for the
# entire system. To do the same without this would require scanning through
# the ObjectSpace. (Still which is better?)
#
#$annotations = Hash.new { |h,k| h[k] = {} }
#++

class Module

  def annotations
    #$annotations[self]
    @annotations ||= {}
  end

  def heritage(ref)
    ref = ref.to_sym
    ancestors.inject({}) { |memo, ancestor|
      ancestor.annotations[ref] ||= {}
      ancestor.annotations[ref] + memo
    }
  end

  # Set or read annotations.

  def ann( ref, keys_or_class=nil, keys=nil )
    return heritage(ref) unless keys_or_class or keys

    if Class === keys_or_class
      keys ||= {}
      keys[:class] = keys_or_class
    else
      keys = keys_or_class
    end

    if Hash === keys
      ref = ref.to_sym
      annotations[ref] ||= {}
      annotations[ref].update(keys.rekey)
    else
      key = keys.to_sym
      heritage(ref)[key]
    end
  end

  # To change an annotation's value in place for a given class or module
  # it first must be duplicated, otherwise the change may effect annotations
  # in the class or module's ancestors.

  def ann!( ref, keys_or_class=nil, keys=nil )
    #return heritage(ref) unless keys_or_class or keys
    return annotations[ref] unless keys_or_class or keys

    if Class === keys_or_class
      keys ||= {}
      keys[:class] = keys_or_class
    else
      keys = keys_or_class
    end

    if Hash === keys
      ref = ref.to_sym
      annotations[ref] ||= {}
      annotations[ref].update(keys.rekey)
    else
      key = keys.to_sym
      annotations[ref][key] = heritage(ref)[key].dup
    end
  end

end


#  _____         _
# |_   _|__  ___| |_
#   | |/ _ \/ __| __|
#   | |  __/\__ \ |_
#   |_|\___||___/\__|
#

=begin test

  require 'test/unit'

  class TestAnnotation1 < Test::Unit::TestCase
    class X
      def x1 ; end
      ann :x1, :a=>1
      ann :x1, :b=>2
    end

    def test_1_01
      assert_equal( X.ann(:x1,:a), X.ann(:x1,:a) )
      assert_equal( X.ann(:x1,:a).object_id, X.ann(:x1,:a).object_id )
    end
    def test_1_02
      X.ann :x1, :a => 2
      assert_equal( 2, X.ann(:x1,:a) )
    end
  end

  class TestAnnotation2 < Test::Unit::TestCase
    class X
      def x1 ; end
      ann :x1, :a=>1
      ann :x1, :b=>2
    end
    class Y < X ; end

    def test_2_01
      assert_equal( Y.ann(:x1,:a), Y.ann(:x1,:a) )
      assert_equal( Y.ann(:x1,:a).object_id, Y.ann(:x1,:a).object_id )
    end
    def test_2_02
      assert_equal( 1, Y.ann(:x1,:a) )
      assert_equal( 2, Y.ann(:x1,:b) )
    end
    def test_2_03
      Y.ann :x1,:a => 2
      assert_equal( 2, Y.ann(:x1,:a) )
      assert_equal( 2, Y.ann(:x1,:b) )
    end
  end

  class TestAnnotation3 < Test::Unit::TestCase
    class X
      ann :foo, Integer
    end
    class Y < X
      ann :foo, String
    end

    def test_3_01
      assert_equal( Integer, X.ann(:foo, :class) )
    end
    def test_3_02
      assert_equal( String, Y.ann(:foo, :class) )
    end
  end

  class TestAnnotation4 < Test::Unit::TestCase
    class X
      ann :foo, :doc => "hello"
      ann :foo, :bar => []
    end
    class Y < X
      ann :foo, :class => String, :doc => "bye"
    end

    def test_4_01
      assert_equal( "hello", X.ann(:foo,:doc) )
    end
    def test_4_02
      assert_equal( X.ann(:foo), { :doc => "hello", :bar => [] } )
    end
    def test_4_03
      X.ann(:foo,:bar) << "1"
      assert_equal( ["1"], X.ann(:foo,:bar) )
    end
    def test_4_04
      assert_equal( "bye", Y.ann(:foo,:doc) )
    end
    def test_4_05
      #assert_equal( nil, Y.ann(:foo,:bar) )
      assert_equal( ["1"], Y.ann(:foo,:bar) )
    end
    def test_4_06
      Y.ann(:foo, :doc => "cap")
      assert_equal( "cap", Y.ann(:foo, :doc)  )
    end
    def test_4_07
      Y.ann!(:foo,:bar) << "2"
      assert_equal( ["1", "2"], Y.ann(:foo,:bar) )
      assert_equal( ["1", "2"], Y.ann(:foo,:bar) )
      assert_equal( ["1"], X.ann(:foo,:bar) )
    end
  end

=end

