1 /* 2 * Protocol Buffers - Google's data interchange format 3 * Copyright 2014 Google Inc. All rights reserved. 4 * https://developers.google.com/protocol-buffers/ 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are 8 * met: 9 * 10 * * Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * * Redistributions in binary form must reproduce the above 13 * copyright notice, this list of conditions and the following disclaimer 14 * in the documentation and/or other materials provided with the 15 * distribution. 16 * * Neither the name of Google Inc. nor the names of its 17 * contributors may be used to endorse or promote products derived from 18 * this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.google.protobuf.jruby; 34 35 import com.google.protobuf.CodedInputStream; 36 import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; 37 import com.google.protobuf.Descriptors.EnumDescriptor; 38 import com.google.protobuf.Descriptors.EnumValueDescriptor; 39 import org.jruby.Ruby; 40 import org.jruby.RubyClass; 41 import org.jruby.RubyModule; 42 import org.jruby.RubyNumeric; 43 import org.jruby.RubyObject; 44 import org.jruby.anno.JRubyClass; 45 import org.jruby.anno.JRubyMethod; 46 import org.jruby.runtime.Block; 47 import org.jruby.runtime.ObjectAllocator; 48 import org.jruby.runtime.ThreadContext; 49 import org.jruby.runtime.builtin.IRubyObject; 50 51 @JRubyClass(name = "EnumDescriptor", include = "Enumerable") 52 public class RubyEnumDescriptor extends RubyObject { createRubyEnumDescriptor(Ruby runtime)53 public static void createRubyEnumDescriptor(Ruby runtime) { 54 RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); 55 RubyClass cEnumDescriptor = 56 mProtobuf.defineClassUnder( 57 "EnumDescriptor", 58 runtime.getObject(), 59 new ObjectAllocator() { 60 @Override 61 public IRubyObject allocate(Ruby runtime, RubyClass klazz) { 62 return new RubyEnumDescriptor(runtime, klazz); 63 } 64 }); 65 cEnumDescriptor.includeModule(runtime.getEnumerable()); 66 cEnumDescriptor.defineAnnotatedMethods(RubyEnumDescriptor.class); 67 } 68 RubyEnumDescriptor(Ruby runtime, RubyClass klazz)69 public RubyEnumDescriptor(Ruby runtime, RubyClass klazz) { 70 super(runtime, klazz); 71 } 72 73 /* 74 * call-seq: 75 * EnumDescriptor.name => name 76 * 77 * Returns the name of this enum type. 78 */ 79 @JRubyMethod(name = "name") getName(ThreadContext context)80 public IRubyObject getName(ThreadContext context) { 81 return this.name; 82 } 83 84 /* 85 * call-seq: 86 * EnumDescriptor.each(&block) 87 * 88 * Iterates over key => value mappings in this enum's definition, yielding to 89 * the block with (key, value) arguments for each one. 90 */ 91 @JRubyMethod each(ThreadContext context, Block block)92 public IRubyObject each(ThreadContext context, Block block) { 93 Ruby runtime = context.runtime; 94 for (EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) { 95 block.yield( 96 context, 97 runtime.newArray( 98 runtime.newSymbol(enumValueDescriptor.getName()), 99 runtime.newFixnum(enumValueDescriptor.getNumber()))); 100 } 101 return context.nil; 102 } 103 104 /* 105 * call-seq: 106 * EnumDescriptor.enummodule => module 107 * 108 * Returns the Ruby module corresponding to this enum type. Cannot be called 109 * until the enum descriptor has been added to a pool. 110 */ 111 @JRubyMethod enummodule(ThreadContext context)112 public IRubyObject enummodule(ThreadContext context) { 113 return module; 114 } 115 116 /* 117 * call-seq: 118 * EnumDescriptor.file_descriptor 119 * 120 * Returns the FileDescriptor object this enum belongs to. 121 */ 122 @JRubyMethod(name = "file_descriptor") getFileDescriptor(ThreadContext context)123 public IRubyObject getFileDescriptor(ThreadContext context) { 124 return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); 125 } 126 127 @JRubyMethod options(ThreadContext context)128 public IRubyObject options(ThreadContext context) { 129 RubyDescriptorPool pool = (RubyDescriptorPool) RubyDescriptorPool.generatedPool(null, null); 130 RubyDescriptor enumOptionsDescriptor = 131 (RubyDescriptor) 132 pool.lookup(context, context.runtime.newString("google.protobuf.EnumOptions")); 133 RubyClass enumOptionsClass = (RubyClass) enumOptionsDescriptor.msgclass(context); 134 RubyMessage msg = (RubyMessage) enumOptionsClass.newInstance(context, Block.NULL_BLOCK); 135 return msg.decodeBytes( 136 context, 137 msg, 138 CodedInputStream.newInstance( 139 descriptor.getOptions().toByteString().toByteArray()), /*freeze*/ 140 true); 141 } 142 isValidValue(ThreadContext context, IRubyObject value)143 public boolean isValidValue(ThreadContext context, IRubyObject value) { 144 EnumValueDescriptor enumValue; 145 146 if (Utils.isRubyNum(value)) { 147 enumValue = descriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); 148 } else { 149 enumValue = descriptor.findValueByName(value.asJavaString()); 150 } 151 152 return enumValue != null; 153 } 154 nameToNumber(ThreadContext context, IRubyObject name)155 protected IRubyObject nameToNumber(ThreadContext context, IRubyObject name) { 156 EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString()); 157 return value == null ? context.nil : context.runtime.newFixnum(value.getNumber()); 158 } 159 numberToName(ThreadContext context, IRubyObject number)160 protected IRubyObject numberToName(ThreadContext context, IRubyObject number) { 161 EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number)); 162 return value == null ? context.nil : context.runtime.newSymbol(value.getName()); 163 } 164 setDescriptor(ThreadContext context, EnumDescriptor descriptor)165 protected void setDescriptor(ThreadContext context, EnumDescriptor descriptor) { 166 this.descriptor = descriptor; 167 this.module = buildModuleFromDescriptor(context); 168 } 169 setName(IRubyObject name)170 protected void setName(IRubyObject name) { 171 this.name = name; 172 } 173 buildModuleFromDescriptor(ThreadContext context)174 private RubyModule buildModuleFromDescriptor(ThreadContext context) { 175 Ruby runtime = context.runtime; 176 177 RubyModule enumModule = RubyModule.newModule(runtime); 178 boolean defaultValueRequiredButNotFound = !descriptor.isClosed(); 179 for (EnumValueDescriptor value : descriptor.getValues()) { 180 String name = fixEnumConstantName(value.getName()); 181 // Make sure it's a valid constant name before trying to create it 182 int ch = name.codePointAt(0); 183 if (Character.isUpperCase(ch)) { 184 enumModule.defineConstant(name, runtime.newFixnum(value.getNumber())); 185 } else { 186 runtime 187 .getWarnings() 188 .warn( 189 "Enum value " 190 + name 191 + " does not start with an uppercase letter as is required for Ruby" 192 + " constants."); 193 } 194 if (value.getNumber() == 0) { 195 defaultValueRequiredButNotFound = false; 196 } 197 } 198 199 if (defaultValueRequiredButNotFound) { 200 throw Utils.createTypeError( 201 context, "Enum definition " + name + " does not contain a value for '0'"); 202 } 203 enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); 204 enumModule.defineAnnotatedMethods(RubyEnum.class); 205 return enumModule; 206 } 207 fixEnumConstantName(String name)208 private static String fixEnumConstantName(String name) { 209 if (name != null && name.length() > 0) { 210 int ch = name.codePointAt(0); 211 if (ch >= 'a' && ch <= 'z') { 212 // Protobuf enums can start with lowercase letters, while Ruby's constant should 213 // always start with uppercase letters. We tolerate this case by capitalizing 214 // the first character if possible. 215 return new StringBuilder() 216 .appendCodePoint(Character.toUpperCase(ch)) 217 .append(name.substring(1)) 218 .toString(); 219 } 220 } 221 return name; 222 } 223 224 private EnumDescriptor descriptor; 225 private EnumDescriptorProto.Builder builder; 226 private IRubyObject name; 227 private RubyModule module; 228 } 229