Enum.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2010, 2011 Wayne Meissner
  15.  *
  16.  * Alternatively, the contents of this file may be used under the terms of
  17.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  18.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  19.  * in which case the provisions of the GPL or the LGPL are applicable instead
  20.  * of those above. If you wish to allow use of your version of this file only
  21.  * under the terms of either the GPL or the LGPL, and not to allow others to
  22.  * use your version of this file under the terms of the EPL, indicate your
  23.  * decision by deleting the provisions above and replace them with the notice
  24.  * and other provisions required by the GPL or the LGPL. If you do not delete
  25.  * the provisions above, a recipient may use your version of this file under
  26.  * the terms of any one of the EPL, the GPL or the LGPL.
  27.  ***** END LICENSE BLOCK *****/

  28. package org.jruby.ext.ffi;

  29. import java.util.IdentityHashMap;
  30. import java.util.Map;
  31. import java.util.concurrent.ConcurrentHashMap;
  32. import org.jruby.anno.JRubyClass;
  33. import org.jruby.anno.JRubyMethod;
  34. import org.jruby.Ruby;
  35. import org.jruby.RubyArray;
  36. import org.jruby.RubyClass;
  37. import org.jruby.RubyFixnum;
  38. import org.jruby.RubyHash;
  39. import org.jruby.RubyInteger;
  40. import org.jruby.RubyModule;
  41. import org.jruby.RubyObject;
  42. import org.jruby.RubySymbol;
  43. import org.jruby.runtime.builtin.IRubyObject;
  44. import org.jruby.runtime.ObjectAllocator;
  45. import org.jruby.runtime.ThreadContext;
  46. import org.jruby.runtime.Visibility;

  47. /**
  48.  * Represents a C enum
  49.  */
  50. @JRubyClass(name="FFI::Enum", parent="Object")
  51. public final class Enum extends RubyObject {
  52.     private IRubyObject nativeType;
  53.     private final RubyHash kv_map;
  54.     private volatile IRubyObject tag;

  55.     private volatile Map<RubySymbol, RubyInteger> symbolToValue = new IdentityHashMap<RubySymbol, RubyInteger>();
  56.     private volatile ConcurrentHashMap<Long, RubySymbol> valueToSymbol = new ConcurrentHashMap<Long, RubySymbol>();

  57.     public static RubyClass createEnumClass(Ruby runtime, RubyModule ffiModule) {
  58.         RubyClass enumClass = ffiModule.defineClassUnder("Enum", runtime.getObject(),
  59.                 Allocator.INSTANCE);
  60.         enumClass.defineAnnotatedMethods(Enum.class);
  61.         enumClass.defineAnnotatedConstants(Enum.class);
  62.         enumClass.includeModule(ffiModule.getConstant("DataConverter"));
  63.        
  64.         return enumClass;
  65.     }

  66.     private static final class Allocator implements ObjectAllocator {
  67.         private static final ObjectAllocator INSTANCE = new Allocator();

  68.         public final IRubyObject allocate(Ruby runtime, RubyClass klass) {
  69.             return new Enum(runtime, klass);
  70.         }
  71.     }

  72.     private Enum(Ruby runtime, RubyClass klass) {
  73.         super(runtime, klass);
  74.         kv_map = RubyHash.newHash(runtime);
  75.         tag = runtime.getNil();
  76.     }

  77.     @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
  78.     public final IRubyObject initialize(ThreadContext context, IRubyObject arg) {
  79.         return initialize(context, null, null, arg);
  80.     }

  81.     @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
  82.     public final IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
  83.         if (arg0 instanceof org.jruby.ext.ffi.Type)
  84.             return initialize(context, arg0, arg1, null);

  85.         if (arg1.isNil())
  86.             return initialize(context, null, arg0, null);

  87.         // Handles bad args and tag, values case.
  88.         return initialize(context, null, arg0, arg1);
  89.     }

  90.     @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
  91.     public final IRubyObject initialize(ThreadContext context, IRubyObject type, IRubyObject values, IRubyObject tag) {
  92.         int offset = 0;
  93.         if (type instanceof org.jruby.ext.ffi.Type) {
  94.             nativeType = type;
  95.         } else {
  96.             if (!(type == null || type.isNil()))
  97.                 throw context.runtime.newTypeError(type, context.runtime.getModule("FFI").getClass("Type"));

  98.             nativeType = context.runtime.getModule("FFI").getClass("Type").getConstant("INT");
  99.         }

  100.         if (!(tag == null || tag.isNil() || tag instanceof RubySymbol))
  101.             throw context.runtime.newTypeError(tag, context.runtime.getSymbol());

  102.         this.tag = tag;

  103.         if (!(values instanceof RubyArray)) {
  104.             throw context.runtime.newTypeError(values, context.runtime.getArray());
  105.         }

  106.         RubyArray ary = (RubyArray) values;

  107.         Map<RubySymbol, RubyInteger> s2v = new IdentityHashMap<RubySymbol, RubyInteger>();
  108.         IRubyObject prevConstant = null;
  109.         long nextValue = 0;

  110.         for (int i = 0; i < ary.size(); i++) {
  111.             IRubyObject v = ary.entry(i);

  112.             if (v instanceof RubySymbol) {
  113.                 s2v.put((RubySymbol) v, RubyFixnum.newFixnum(context.runtime, nextValue));
  114.                 prevConstant = v;
  115.                 nextValue++;

  116.             } else if (v instanceof RubyInteger) {
  117.                 if (prevConstant == null) {
  118.                     throw context.runtime.newArgumentError("invalid enum sequence - no symbol for value "
  119.                             + v);
  120.                 }
  121.                 s2v.put((RubySymbol) prevConstant, (RubyFixnum) v);
  122.                 nextValue = ((RubyInteger) v).getLongValue() + 1;

  123.             } else {
  124.                 throw context.runtime.newTypeError(v, context.runtime.getSymbol());
  125.             }
  126.         }

  127.         symbolToValue = new IdentityHashMap<RubySymbol, RubyInteger>(s2v);
  128.         valueToSymbol = new ConcurrentHashMap<Long, RubySymbol>(symbolToValue.size());
  129.         for (Map.Entry<RubySymbol, RubyInteger> e : symbolToValue.entrySet()) {
  130.             kv_map.fastASet(e.getKey(), e.getValue());
  131.             valueToSymbol.put(e.getValue().getLongValue(), e.getKey());
  132.         }

  133.         return this;
  134.     }

  135.     @JRubyMethod(name = { "[]", "find" })
  136.     public final IRubyObject find(ThreadContext context, IRubyObject query) {
  137.         if (query instanceof RubySymbol) {
  138.             IRubyObject value = kv_map.fastARef(query);
  139.             return value != null ? value : context.runtime.getNil();

  140.         } else if (query instanceof RubyInteger) {
  141.             RubySymbol symbol = valueToSymbol.get((Long)((RubyInteger) query).getLongValue());
  142.             return symbol != null ? symbol : context.runtime.getNil();

  143.         } else {
  144.             return context.runtime.getNil();
  145.         }
  146.     }

  147.     @JRubyMethod(name = { "symbol_map", "to_h", "to_hash" })
  148.     public final IRubyObject symbol_map(ThreadContext context) {
  149.         return kv_map.dup(context);
  150.     }

  151.     @JRubyMethod(name = { "symbols" })
  152.     public final IRubyObject symbols(ThreadContext context) {
  153.         return kv_map.keys();
  154.     }

  155.     @JRubyMethod(name = { "tag" })
  156.     public final IRubyObject tag(ThreadContext context) {
  157.         return tag;
  158.     }

  159.     @JRubyMethod(name = "native_type")
  160.     public final IRubyObject native_type(ThreadContext context) {
  161.         return nativeType;
  162.     }

  163.     @JRubyMethod(name = "to_native")
  164.     public final IRubyObject to_native(ThreadContext context, IRubyObject name, IRubyObject ctx) {
  165.         RubyInteger value;

  166.         if (name instanceof RubyFixnum) {
  167.             return name;

  168.         } else if (name instanceof RubySymbol && (value = symbolToValue.get(name)) != null) {
  169.             return value;

  170.         } else if (name instanceof RubyInteger) {
  171.             return name;

  172.         } else if (name.respondsTo("to_int")) {

  173.             return name.convertToInteger();

  174.         } else {
  175.             throw name.getRuntime().newArgumentError("invalid enum value, " + name.inspect());
  176.         }
  177.     }

  178.     @JRubyMethod(name = "from_native")
  179.     public final IRubyObject from_native(ThreadContext context, IRubyObject value, IRubyObject ctx) {

  180.         RubySymbol sym;

  181.         if (value instanceof RubyInteger && (sym = valueToSymbol.get((Long)((RubyInteger) value).getLongValue())) != null) {
  182.             return sym;
  183.         }

  184.         return value;
  185.     }

  186.     @JRubyMethod(name = "reference_required?")
  187.     public IRubyObject reference_required_p(ThreadContext context) {
  188.         return context.runtime.getFalse();
  189.     }
  190. }