1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc.  All rights reserved.
3# https://developers.google.com/protocol-buffers/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9#     * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#     * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15#     * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31require 'forwardable'
32
33#
34# This class makes RepeatedField act (almost-) like a Ruby Array.
35# It has convenience methods that extend the core C or Java based
36# methods.
37#
38# This is a best-effort to mirror Array behavior.  Two comments:
39#  1) patches always welcome :)
40#  2) if performance is an issue, feel free to rewrite the method
41#     in jruby and C.  The source code has plenty of examples
42#
43# KNOWN ISSUES
44#   - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'`
45#   - #concat should return the orig array
46#   - #push should accept multiple arguments and push them all at the same time
47#
48module Google
49  module Protobuf
50    class RepeatedField
51      extend Forwardable
52
53      # methods defined in C or Java:
54      #   +
55      #   [], at
56      #   []=
57      #   concat
58      #   clear
59      #   dup, clone
60      #   each
61      #   push, <<
62      #   replace
63      #   length, size
64      #   ==
65      #   to_ary, to_a
66      #   also all enumerable
67      #
68      # NOTE:  using delegators rather than method_missing to make the
69      #        relationship explicit instead of implicit
70      def_delegators :to_ary,
71        :&, :*, :-, :'<=>',
72        :assoc, :bsearch, :bsearch_index, :combination, :compact, :count,
73        :cycle, :dig, :drop, :drop_while, :eql?, :fetch, :find_index, :flatten,
74        :include?, :index, :inspect, :join,
75        :pack, :permutation, :product, :pretty_print, :pretty_print_cycle,
76        :rassoc, :repeated_combination, :repeated_permutation, :reverse,
77        :rindex, :rotate, :sample, :shuffle, :shelljoin, :slice,
78        :to_s, :transpose, :uniq, :|
79
80
81      def first(n=nil)
82        n ? self[0..n] : self[0]
83      end
84
85
86      def last(n=nil)
87        n ? self[(self.size-n-1)..-1] : self[-1]
88      end
89
90
91      def pop(n=nil)
92        if n
93          results = []
94          n.times{ results << pop_one }
95          return results
96        else
97          return pop_one
98        end
99      end
100
101
102      def empty?
103        self.size == 0
104      end
105
106      # array aliases into enumerable
107      alias_method :each_index, :each_with_index
108      alias_method :slice, :[]
109      alias_method :values_at, :select
110      alias_method :map, :collect
111
112
113      class << self
114        def define_array_wrapper_method(method_name)
115          define_method(method_name) do |*args, &block|
116            arr = self.to_a
117            result = arr.send(method_name, *args)
118            self.replace(arr)
119            return result if result
120            return block ? block.call : result
121          end
122        end
123        private :define_array_wrapper_method
124
125
126        def define_array_wrapper_with_result_method(method_name)
127          define_method(method_name) do |*args, &block|
128            # result can be an Enumerator, Array, or nil
129            # Enumerator can sometimes be returned if a block is an optional argument and it is not passed in
130            # nil usually specifies that no change was made
131            result = self.to_a.send(method_name, *args, &block)
132            if result
133              new_arr = result.to_a
134              self.replace(new_arr)
135              if result.is_a?(Enumerator)
136                # generate a fresh enum; rewinding the exiting one, in Ruby 2.2, will
137                # reset the enum with the same length, but all the #next calls will
138                # return nil
139                result = new_arr.to_enum
140                # generate a wrapper enum so any changes which occur by a chained
141                # enum can be captured
142                ie = ProxyingEnumerator.new(self, result)
143                result = ie.to_enum
144              end
145            end
146            result
147          end
148        end
149        private :define_array_wrapper_with_result_method
150      end
151
152
153      %w(delete delete_at delete_if shift slice! unshift).each do |method_name|
154        define_array_wrapper_method(method_name)
155      end
156
157
158      %w(collect! compact! fill flatten! insert reverse!
159        rotate! select! shuffle! sort! sort_by! uniq!).each do |method_name|
160        define_array_wrapper_with_result_method(method_name)
161      end
162      alias_method :keep_if, :select!
163      alias_method :map!, :collect!
164      alias_method :reject!, :delete_if
165
166
167      # propagates changes made by user of enumerator back to the original repeated field.
168      # This only applies in cases where the calling function which created the enumerator,
169      # such as #sort!, modifies itself rather than a new array, such as #sort
170      class ProxyingEnumerator < Struct.new(:repeated_field, :external_enumerator)
171        def each(*args, &block)
172          results = []
173          external_enumerator.each_with_index do |val, i|
174            result = yield(val)
175            results << result
176            #nil means no change occured from yield; usually occurs when #to_a is called
177            if result
178              repeated_field[i] = result if result != val
179            end
180          end
181          results
182        end
183      end
184
185
186    end
187  end
188end
189