diff options
Diffstat (limited to 'ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java')
-rw-r--r-- | ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java | 434 |
1 files changed, 0 insertions, 434 deletions
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java deleted file mode 100644 index 2d4c03b5..00000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 Google Inc. All rights reserved. - * https://developers.google.com/protocol-buffers/ - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.protobuf.jruby; - -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.MapEntry; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.internal.runtime.methods.DynamicMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; -import org.jruby.util.ByteList; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@JRubyClass(name = "Map", include = "Enumerable") -public class RubyMap extends RubyObject { - public static void createRubyMap(Ruby runtime) { - RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cMap = protobuf.defineClassUnder("Map", runtime.getObject(), new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) { - return new RubyMap(ruby, rubyClass); - } - }); - cMap.includeModule(runtime.getEnumerable()); - cMap.defineAnnotatedMethods(RubyMap.class); - } - - public RubyMap(Ruby ruby, RubyClass rubyClass) { - super(ruby, rubyClass); - } - - /* - * call-seq: - * Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) - * => new map - * - * Allocates a new Map container. This constructor may be called with 2, 3, or 4 - * arguments. The first two arguments are always present and are symbols (taking - * on the same values as field-type symbols in message descriptors) that - * indicate the type of the map key and value fields. - * - * The supported key types are: :int32, :int64, :uint32, :uint64, :bool, - * :string, :bytes. - * - * The supported value types are: :int32, :int64, :uint32, :uint64, :bool, - * :string, :bytes, :enum, :message. - * - * The third argument, value_typeclass, must be present if value_type is :enum - * or :message. As in RepeatedField#new, this argument must be a message class - * (for :message) or enum module (for :enum). - * - * The last argument, if present, provides initial content for map. Note that - * this may be an ordinary Ruby hashmap or another Map instance with identical - * key and value types. Also note that this argument may be present whether or - * not value_typeclass is present (and it is unambiguously separate from - * value_typeclass because value_typeclass's presence is strictly determined by - * value_type). The contents of this initial hashmap or Map instance are - * shallow-copied into the new Map: the original map is unmodified, but - * references to underlying objects will be shared if the value type is a - * message type. - */ - - @JRubyMethod(required = 2, optional = 2) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - this.table = new HashMap<IRubyObject, IRubyObject>(); - this.keyType = Utils.rubyToFieldType(args[0]); - this.valueType = Utils.rubyToFieldType(args[1]); - - switch(keyType) { - case INT32: - case INT64: - case UINT32: - case UINT64: - case BOOL: - case STRING: - case BYTES: - // These are OK. - break; - default: - throw context.runtime.newArgumentError("Invalid key type for map."); - } - - int initValueArg = 2; - if (needTypeclass(this.valueType) && args.length > 2) { - this.valueTypeClass = args[2]; - Utils.validateTypeClass(context, this.valueType, this.valueTypeClass); - initValueArg = 3; - } else { - this.valueTypeClass = context.runtime.getNilClass(); - } - - // Table value type is always UINT64: this ensures enough space to store the - // native_slot value. - if (args.length > initValueArg) { - mergeIntoSelf(context, args[initValueArg]); - } - return this; - } - - /* - * call-seq: - * Map.[]=(key, value) => value - * - * Inserts or overwrites the value at the given key with the given new value. - * Throws an exception if the key type is incorrect. Returns the new value that - * was just inserted. - */ - @JRubyMethod(name = "[]=") - public IRubyObject indexSet(ThreadContext context, IRubyObject key, IRubyObject value) { - Utils.checkType(context, keyType, key, (RubyModule) valueTypeClass); - Utils.checkType(context, valueType, value, (RubyModule) valueTypeClass); - IRubyObject symbol; - if (valueType == Descriptors.FieldDescriptor.Type.ENUM && - Utils.isRubyNum(value) && - ! (symbol = RubyEnum.lookup(context, valueTypeClass, value)).isNil()) { - value = symbol; - } - this.table.put(key, value); - return value; - } - - /* - * call-seq: - * Map.[](key) => value - * - * Accesses the element at the given key. Throws an exception if the key type is - * incorrect. Returns nil when the key is not present in the map. - */ - @JRubyMethod(name = "[]") - public IRubyObject index(ThreadContext context, IRubyObject key) { - if (table.containsKey(key)) - return this.table.get(key); - return context.runtime.getNil(); - } - - /* - * call-seq: - * Map.==(other) => boolean - * - * Compares this map to another. Maps are equal if they have identical key sets, - * and for each key, the values in both maps compare equal. Elements are - * compared as per normal Ruby semantics, by calling their :== methods (or - * performing a more efficient comparison for primitive types). - * - * Maps with dissimilar key types or value types/typeclasses are never equal, - * even if value comparison (for example, between integers and floats) would - * have otherwise indicated that every element has equal value. - */ - @JRubyMethod(name = "==") - public IRubyObject eq(ThreadContext context, IRubyObject _other) { - if (_other instanceof RubyHash) - return toHash(context).op_equal(context, _other); - RubyMap other = (RubyMap) _other; - if (this == other) return context.runtime.getTrue(); - if (!typeCompatible(other) || this.table.size() != other.table.size()) - return context.runtime.getFalse(); - for (IRubyObject key : table.keySet()) { - if (! other.table.containsKey(key)) - return context.runtime.getFalse(); - if (! other.table.get(key).equals(table.get(key))) - return context.runtime.getFalse(); - } - return context.runtime.getTrue(); - } - - /* - * call-seq: - * Map.inspect => string - * - * Returns a string representing this map's elements. It will be formatted as - * "{key => value, key => value, ...}", with each key and value string - * representation computed by its own #inspect method. - */ - @JRubyMethod - public IRubyObject inspect() { - return toHash(getRuntime().getCurrentContext()).inspect(); - } - - /* - * call-seq: - * Map.hash => hash_value - * - * Returns a hash value based on this map's contents. - */ - @JRubyMethod - public IRubyObject hash(ThreadContext context) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - for (IRubyObject key : table.keySet()) { - digest.update((byte) key.hashCode()); - digest.update((byte) table.get(key).hashCode()); - } - return context.runtime.newString(new ByteList(digest.digest())); - } catch (NoSuchAlgorithmException ignore) { - return context.runtime.newFixnum(System.identityHashCode(table)); - } - } - - /* - * call-seq: - * Map.keys => [list_of_keys] - * - * Returns the list of keys contained in the map, in unspecified order. - */ - @JRubyMethod - public IRubyObject keys(ThreadContext context) { - return RubyArray.newArray(context.runtime, table.keySet()); - } - - /* - * call-seq: - * Map.values => [list_of_values] - * - * Returns the list of values contained in the map, in unspecified order. - */ - @JRubyMethod - public IRubyObject values(ThreadContext context) { - return RubyArray.newArray(context.runtime, table.values()); - } - - /* - * call-seq: - * Map.clear - * - * Removes all entries from the map. - */ - @JRubyMethod - public IRubyObject clear(ThreadContext context) { - table.clear(); - return context.runtime.getNil(); - } - - /* - * call-seq: - * Map.each(&block) - * - * Invokes &block on each |key, value| pair in the map, in unspecified order. - * Note that Map also includes Enumerable; map thus acts like a normal Ruby - * sequence. - */ - @JRubyMethod - public IRubyObject each(ThreadContext context, Block block) { - for (IRubyObject key : table.keySet()) { - block.yieldSpecific(context, key, table.get(key)); - } - return context.runtime.getNil(); - } - - /* - * call-seq: - * Map.delete(key) => old_value - * - * Deletes the value at the given key, if any, returning either the old value or - * nil if none was present. Throws an exception if the key is of the wrong type. - */ - @JRubyMethod - public IRubyObject delete(ThreadContext context, IRubyObject key) { - return table.remove(key); - } - - /* - * call-seq: - * Map.has_key?(key) => bool - * - * Returns true if the given key is present in the map. Throws an exception if - * the key has the wrong type. - */ - @JRubyMethod(name = "has_key?") - public IRubyObject hasKey(ThreadContext context, IRubyObject key) { - return this.table.containsKey(key) ? context.runtime.getTrue() : context.runtime.getFalse(); - } - - /* - * call-seq: - * Map.length - * - * Returns the number of entries (key-value pairs) in the map. - */ - @JRubyMethod - public IRubyObject length(ThreadContext context) { - return context.runtime.newFixnum(this.table.size()); - } - - /* - * call-seq: - * Map.dup => new_map - * - * Duplicates this map with a shallow copy. References to all non-primitive - * element objects (e.g., submessages) are shared. - */ - @JRubyMethod - public IRubyObject dup(ThreadContext context) { - RubyMap newMap = newThisType(context); - for (Map.Entry<IRubyObject, IRubyObject> entry : table.entrySet()) { - newMap.table.put(entry.getKey(), entry.getValue()); - } - return newMap; - } - - @JRubyMethod(name = {"to_h", "to_hash"}) - public RubyHash toHash(ThreadContext context) { - return RubyHash.newHash(context.runtime, table, context.runtime.getNil()); - } - - // Used by Google::Protobuf.deep_copy but not exposed directly. - protected IRubyObject deepCopy(ThreadContext context) { - RubyMap newMap = newThisType(context); - switch (valueType) { - case MESSAGE: - for (IRubyObject key : table.keySet()) { - RubyMessage message = (RubyMessage) table.get(key); - newMap.table.put(key.dup(), message.deepCopy(context)); - } - break; - default: - for (IRubyObject key : table.keySet()) { - newMap.table.put(key.dup(), table.get(key).dup()); - } - } - return newMap; - } - - protected List<DynamicMessage> build(ThreadContext context, RubyDescriptor descriptor) { - List<DynamicMessage> list = new ArrayList<DynamicMessage>(); - RubyClass rubyClass = (RubyClass) descriptor.msgclass(context); - Descriptors.FieldDescriptor keyField = descriptor.lookup("key").getFieldDef(); - Descriptors.FieldDescriptor valueField = descriptor.lookup("value").getFieldDef(); - for (IRubyObject key : table.keySet()) { - RubyMessage mapMessage = (RubyMessage) rubyClass.newInstance(context, Block.NULL_BLOCK); - mapMessage.setField(context, keyField, key); - mapMessage.setField(context, valueField, table.get(key)); - list.add(mapMessage.build(context)); - } - return list; - } - - protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) { - if (hashmap instanceof RubyHash) { - ((RubyHash) hashmap).visitAll(new RubyHash.Visitor() { - @Override - public void visit(IRubyObject key, IRubyObject val) { - indexSet(context, key, val); - } - }); - } else if (hashmap instanceof RubyMap) { - RubyMap other = (RubyMap) hashmap; - if (!typeCompatible(other)) { - throw context.runtime.newTypeError("Attempt to merge Map with mismatching types"); - } - } else { - throw context.runtime.newTypeError("Unknown type merging into Map"); - } - return this; - } - - protected boolean typeCompatible(RubyMap other) { - return this.keyType == other.keyType && - this.valueType == other.valueType && - this.valueTypeClass == other.valueTypeClass; - } - - private RubyMap newThisType(ThreadContext context) { - RubyMap newMap; - if (needTypeclass(valueType)) { - newMap = (RubyMap) metaClass.newInstance(context, - Utils.fieldTypeToRuby(context, keyType), - Utils.fieldTypeToRuby(context, valueType), - valueTypeClass, Block.NULL_BLOCK); - } else { - newMap = (RubyMap) metaClass.newInstance(context, - Utils.fieldTypeToRuby(context, keyType), - Utils.fieldTypeToRuby(context, valueType), - Block.NULL_BLOCK); - } - newMap.table = new HashMap<IRubyObject, IRubyObject>(); - return newMap; - } - - private boolean needTypeclass(Descriptors.FieldDescriptor.Type type) { - switch(type) { - case MESSAGE: - case ENUM: - return true; - default: - return false; - } - } - - private Descriptors.FieldDescriptor.Type keyType; - private Descriptors.FieldDescriptor.Type valueType; - private IRubyObject valueTypeClass; - private Map<IRubyObject, IRubyObject> table; -} |