godobject/bit_set

View on GitHub
lib/god_object/bit_set/configuration.rb

Summary

Maintainability
A
25 mins
Test Coverage
# encoding: UTF-8
=begin
Copyright GodObject Team <dev@godobject.net>, 2012-2016

This file is part of BitSet.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
=end

module GodObject
  module BitSet

    # A Configuration defines the digits of a BitSet. Additionally it can hold
    # information on how to represent the digits in a String representation.
    class Configuration

      # @return [String] the default String representation for enabled digits
      UNNAMED_ENABLED  = '1'.freeze

      # @return [String] the default String representation for disabled digits
      UNNAMED_DISABLED = '0'.freeze

      # @return [String] the default String representation for disabled digits
      #   which have a custom enabled representation
      NAMED_DISABLED   = '-'.freeze

      class << self

        # @overload build(configuration)
        #   Returns an existing instance of GodObject::BitSet::Configuration.
        #   @param [GodObject::BitSet::Configuration] configuration an already
        #     existing Configuration
        #   @return [GodObject::BitSet::Configuration] the same Configuration object
        #
        # @overload build(digits)
        #   Returns a new Configuration object with given attributes.
        #   @param [Array<Symbol>] digits a list of digit names
        #   @return [GodObject::PosixMode::Mode] a new Configuration object
        #
        # @overload build(enabled_representations_by_digits)
        #   Returns a new Configuration object with given attributes.
        #   @param [Hash<Symbol => String>] enabled_representations_by_digits
        #     digit names mapped to their enabled character representations
        #   @return [GodObject::PosixMode::Mode] a new Configuration object
        #
        # @overload build(representations_by_digits)
        #   Returns a new Configuration object with given attributes.
        #   @param [Hash<Symbol => Array<String>>] representations_by_digits
        #     digit names mapped to their enabled and disabled character
        #     representations
        #   @return [GodObject::PosixMode::Mode] a new Configuration object
        def build(configuration)
          if configuration.is_a?(Configuration)
            configuration
          else
            new(configuration)
          end
        end

      end

      # @return [Range<Integer>] the Range in which an Integer representation of a
      #   BitSet with this Configuration can be.
      attr_reader :valid_range

      # Initializes a new BitSet::Configuration
      # 
      # @overload initialize(digits)
      #   @param [Array<Symbol>] digits a list of digit names
      #
      # @overload initialize(enabled_representations_by_digits)
      #   @param [Hash<Symbol => String>] enabled_representations_by_digits
      #     digit names mapped to their enabled character representations
      #
      # @overload initialize(representations_by_digits)
      #   @param [Hash<Symbol => Array<String>>] representations_by_digits
      #     digit names mapped to their enabled and disabled character
      #     representations
      def initialize(configuration)
        @digits   = {}
        @enabled  = {}
        @disabled = {}

        configuration.each do |digit, display|
          digit = digit.to_sym

          @digits[digit] = nil

          case
          when display.respond_to?(:all?) && display.all?{|s| s.respond_to?(:to_str) && s.to_str.length == 1}
            @enabled[digit]  = display.first.to_str.dup.freeze
            @disabled[digit] = display.last.to_str.dup.freeze
          when display.respond_to?(:to_str) && display.to_str.length == 1
            @enabled[digit]  = display.to_str.dup.freeze
            @disabled[digit] = NAMED_DISABLED
          when display.nil?
            @enabled[digit]  = UNNAMED_ENABLED
            @disabled[digit] = UNNAMED_DISABLED
          else
            raise ArgumentError, 'Invalid configuration'
          end
        end

        raise ArgumentError, 'At least one digit must be configured' if digits.count < 1

        @digits.keys.reverse.each_with_index{|digit, index| @digits[digit] = 2 ** index }

        @valid_range = Range.new(0, (@digits.values.first * 2) - 1).freeze

        @unique_characters = !@enabled.values.dup.uniq!
      end

      # Answers if all digits have unique enabled representations.
      #
      # @return [true, false] true if all digits have unique enabled
      #   representations, false otherwise
      def unique_characters?
        @unique_characters
      end

      # @attribute digits [readonly]
      # @return [Array<Symbol>] an ordered list of all configured digit names
      def digits
        @digits.keys
      end

      # @param [Integer, Array<Symbol>] state either the octal state of the
      #   BitSet or a list of enabled digits
      # @return [GodObject::BitSet] a new BitSet object with the current
      #   configuration
      def new(*state)
        BitSet.new(*state, self)
      end

      # @return [Integer] the Integer representation of a BitSet where
      #   only the given digit is enabled.
      def binary_position(digit)
        @digits[find_digit(digit)]
      end

      # @param [Symbol, Integer] digit the name or index of the digit
      # @return [String] the String representation for the given digit when
      #   it is disabled
      def disabled_character(digit)
        @disabled[find_digit(digit)]
      end

      # @param [Symbol, Integer] digit the name or index of the digit
      # @return [String] the String representation for the given digit when
      #   it is enabled
      def enabled_character(digit)
        @enabled[find_digit(digit)]
      end

      # @param [Symbol, Integer] index_or_digit the name or index of the digit
      # @return [Symbol] the digit's name
      def find_digit(index_or_digit)
        case
        when index_or_digit.respond_to?(:to_sym)
          digit = index_or_digit.to_sym

          raise ArgumentError, "Invalid digit name (#{index_or_digit})" unless @digits.keys.include?(digit)
        else
          digit = @digits.keys[index_or_digit.to_int]
        end

        raise ArgumentError, "Invalid index or digit (#{index_or_digit})" unless digit

        digit
      end

      # Answers if another object is equal.
      #
      # Equality is defined as having the same ordered list of digits.
      #
      # @param [Object] other an object to be checked for equality
      # @return [true, false] true if the object is considered equal, false
      #   otherwise
      def ==(other)
        digits == other.digits
      rescue NoMethodError
        false
      end

      # Answers if another object is equal and of the same type family.
      #
      # @see GodObject::Configuration#==
      # @param [Object] other an object to be checked for equality
      # @return [true, false] true if the object is considered equal and of
      #   the same type familiy, false otherwise
      def eql?(other)
        self == other && other.kind_of?(self.class)
      end

      # @return (see Hash#hash) identity hash for hash table usage
      def hash
        @digits.hash
      end

    end

  end
end