| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.pig.scripting.jruby; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.lang.reflect.Method; |
| |
| import org.apache.pig.backend.executionengine.ExecException; |
| import org.apache.pig.data.DataBag; |
| import org.apache.pig.data.DataByteArray; |
| import org.apache.pig.data.Tuple; |
| import org.apache.pig.data.TupleFactory; |
| import org.apache.pig.impl.logicalLayer.schema.Schema; |
| |
| import org.jruby.Ruby; |
| import org.jruby.RubyClass; |
| import org.jruby.runtime.load.Library; |
| import org.jruby.Ruby; |
| import org.jruby.RubyArray; |
| import org.jruby.RubyBignum; |
| import org.jruby.RubyBoolean; |
| import org.jruby.RubyFixnum; |
| import org.jruby.RubyFloat; |
| import org.jruby.RubyHash; |
| import org.jruby.RubyInteger; |
| import org.jruby.RubyNil; |
| import org.jruby.RubyString; |
| import org.jruby.RubySymbol; |
| import org.jruby.RubyEnumerator; |
| import org.jruby.runtime.builtin.IRubyObject; |
| |
| import com.google.common.collect.Maps; |
| |
| /** |
| * This class provides the ability to present to Ruby a library that was written in Java. |
| * In JRuby, there are two ways to present a library to ruby: one is to implement it in |
| * ruby as a module or class, and the other is to implement it in Java and then register |
| * it with the runtime. For the Pig datatypes we provide Ruby implementations for, |
| * it was easier to implement them in Java and provide a Ruby interface via the annotations |
| * that Jruby provides. |
| * |
| * Additionally, this class provides static object conversion functionality to and from Pig |
| * and JRuby. |
| */ |
| |
| public class PigJrubyLibrary implements Library { |
| /** |
| * This method is called from JRuby to register any classes in |
| * the library. |
| * |
| * @param runtime the current Ruby runtime |
| * @param wrap ignored |
| * @throws IOException |
| */ |
| @Override |
| public void load(Ruby runtime, boolean wrap) throws IOException { |
| RubyDataBag.define(runtime); |
| RubyDataByteArray.define(runtime); |
| RubySchema.define(runtime); |
| } |
| |
| private static final TupleFactory tupleFactory = TupleFactory.getInstance(); |
| |
| /** |
| * This method facilitates conversion from Ruby objects to Pig objects. This is |
| * a general class which detects the subclass and invokes the appropriate conversion |
| * routine. It will fail on an unsupported datatype. |
| * |
| * @param rbObject a Ruby object to convert |
| * @return the Pig analogue of the Ruby object |
| * @throws ExecException if rbObject is not of a known type that can be converted |
| */ |
| @SuppressWarnings("unchecked") |
| public static Object rubyToPig(IRubyObject rbObject) throws ExecException { |
| if (rbObject == null || rbObject instanceof RubyNil) { |
| return null; |
| } else if (rbObject instanceof RubyArray) { |
| return rubyToPig((RubyArray)rbObject); |
| } else if (rbObject instanceof RubyHash) { |
| return rubyToPig((RubyHash)rbObject); |
| } else if (rbObject instanceof RubyString) { |
| return rubyToPig((RubyString)rbObject); |
| } else if (rbObject instanceof RubyBignum) { |
| return rubyToPig((RubyBignum)rbObject); |
| } else if (rbObject instanceof RubyFixnum) { |
| return rubyToPig((RubyFixnum)rbObject); |
| } else if (rbObject instanceof RubyFloat) { |
| return rubyToPig((RubyFloat)rbObject); |
| } else if (rbObject instanceof RubyInteger) { |
| return rubyToPig((RubyInteger)rbObject); |
| } else if (rbObject instanceof RubyDataBag) { |
| return rubyToPig((RubyDataBag)rbObject); |
| } else if (rbObject instanceof RubyDataByteArray) { |
| return rubyToPig((RubyDataByteArray)rbObject); |
| } else if (rbObject instanceof RubySchema) { |
| return rubyToPig((RubySchema)rbObject); |
| } else if (rbObject instanceof RubyBoolean) { |
| return rubyToPig((RubyBoolean)rbObject); |
| } else { |
| throw new ExecException("Cannot cast into any pig supported type: " + rbObject.getClass().getName()); |
| } |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Tuple rubyToPig(RubyArray rbObject) throws ExecException { |
| Tuple out = tupleFactory.newTuple(rbObject.size()); |
| int i = 0; |
| for (IRubyObject arrayObj : rbObject.toJavaArray()) |
| out.set(i++, rubyToPig(arrayObj)); |
| return out; |
| } |
| |
| //TODO need to convert to output a String |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| @SuppressWarnings("unchecked") |
| public static Map<String, ?> rubyToPig(RubyHash rbObject) throws ExecException { |
| Map<String, Object> newMap = Maps.newHashMap(); |
| for (Map.Entry<Object, Object> entry : (Set<Map.Entry<Object, Object>>)rbObject.entrySet()) { |
| Object key = entry.getKey(); |
| |
| if (!(key instanceof String || key instanceof RubyString || key instanceof RubySymbol)) |
| throw new ExecException("Hash must have String or Symbol key. Was given: " + key.getClass().getName()); |
| |
| String keyStr = key.toString(); |
| if (entry.getValue() instanceof IRubyObject) { |
| newMap.put(keyStr, rubyToPig((IRubyObject)entry.getValue())); |
| } else { |
| newMap.put(keyStr, entry.getValue()); |
| } |
| } |
| return newMap; |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Boolean rubyToPig(RubyBoolean rbObject) { |
| return rbObject.isTrue(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Schema rubyToPig(RubySchema rbObject) { |
| return rbObject.getInternalSchema(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static String rubyToPig(RubyString rbObject) { |
| return rbObject.toString(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Long rubyToPig(RubyBignum rbObject) { |
| return rbObject.getLongValue(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Long rubyToPig(RubyFixnum rbObject) { |
| return rbObject.getLongValue(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Double rubyToPig(RubyFloat rbObject) { |
| return rbObject.getDoubleValue(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Integer rubyToPig(RubyInteger rbObject) { |
| return (Integer)rbObject.toJava(Integer.class); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static DataBag rubyToPig(RubyDataBag rbObject) { |
| return rbObject.getBag(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static DataByteArray rubyToPig(RubyDataByteArray rbObject) { |
| return rbObject.getDBA(); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param rbObject object to convert |
| * @return analogous Pig type |
| */ |
| public static Object rubyToPig(RubyNil rbObject) { |
| return null; |
| } |
| |
| /** |
| * This is the method which provides conversion from Pig to Ruby. In this case, an |
| * instance of the Ruby runtime is necessary. This method provides the general detection |
| * of the type and then calls the more specific conversion methods. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object the Pig object to convert to Ruby |
| * @return Ruby analogue of object |
| * @throws ExecException object is not a convertible Pig type |
| */ |
| @SuppressWarnings("unchecked") |
| public static IRubyObject pigToRuby(Ruby ruby, Object object) throws ExecException { |
| if (object == null) { |
| return ruby.getNil(); |
| } else if (object instanceof Tuple) { |
| return pigToRuby(ruby, (Tuple)object); |
| } else if (object instanceof DataBag) { |
| return pigToRuby(ruby, (DataBag)object); |
| } else if (object instanceof Map<?, ?>) { |
| return pigToRuby(ruby, (Map<String, ?>)object); |
| } else if (object instanceof DataByteArray) { |
| return pigToRuby(ruby, (DataByteArray)object); |
| } else if (object instanceof Schema) { |
| return pigToRuby(ruby, ((Schema)object)); |
| } else if (object instanceof String) { |
| return pigToRuby(ruby, (String)object); |
| } else if (object instanceof Integer) { |
| return pigToRuby(ruby, (Integer)object); |
| } else if (object instanceof Long) { |
| return pigToRuby(ruby, (Long)object); |
| } else if (object instanceof Float) { |
| return pigToRuby(ruby, (Float)object); |
| } else if (object instanceof Double) { |
| return pigToRuby(ruby, (Double)object); |
| } else if (object instanceof Boolean) { |
| return pigToRuby(ruby, (Boolean)object); |
| } else { |
| throw new ExecException("Object of unknown type " + object.getClass().getName() + " passed to pigToRuby"); |
| } |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| * @throws ExecException object contained an object that could not convert |
| */ |
| public static RubyArray pigToRuby(Ruby ruby, Tuple object) throws ExecException{ |
| RubyArray rubyArray = ruby.newArray(); |
| |
| for (Object o : object.getAll()) |
| rubyArray.add(pigToRuby(ruby, o)); |
| |
| return rubyArray; |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyBoolean pigToRuby(Ruby ruby, Boolean object) { |
| return RubyBoolean.newBoolean(ruby, object.booleanValue()); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyDataBag pigToRuby(Ruby ruby, DataBag object) { |
| return new RubyDataBag(ruby, ruby.getClass("DataBag"), object); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubySchema pigToRuby(Ruby ruby, Schema object) { |
| return new RubySchema(ruby, ruby.getClass("Schema"), object); |
| } |
| |
| /** |
| * A type specific conversion routine for Pig Maps. This only deals with maps |
| * with String keys, which is all that Pig supports. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object map to convert. In Pig, only maps with String keys are |
| * supported |
| * @return analogous Ruby type |
| * @throws ExecException object contains a key that can't be convert to a Ruby type |
| */ |
| public static <T> RubyHash pigToRuby(Ruby ruby, Map<T, ?> object) throws ExecException { |
| RubyHash newMap = RubyHash.newHash(ruby); |
| |
| boolean checkType = false; |
| |
| for (Map.Entry<T, ?> entry : object.entrySet()) { |
| T key = entry.getKey(); |
| if (!checkType) { |
| if (!(key instanceof String)) |
| throw new ExecException("pigToRuby only supports converting Maps with String keys"); |
| checkType = true; |
| } |
| newMap.put(key, pigToRuby(ruby, entry.getValue())); |
| } |
| |
| return newMap; |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyDataByteArray pigToRuby(Ruby ruby, DataByteArray object) { |
| return new RubyDataByteArray(ruby, ruby.getClass("DataByteArray"), object); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyString pigToRuby(Ruby ruby, String object) { |
| return ruby.newString(object.toString()); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyFixnum pigToRuby(Ruby ruby, Integer object) { |
| return RubyFixnum.newFixnum(ruby, object.longValue()); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyFixnum pigToRuby(Ruby ruby, Long object) { |
| return RubyFixnum.newFixnum(ruby, object); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyFloat pigToRuby(Ruby ruby, Float object) { |
| return RubyFloat.newFloat(ruby, object.doubleValue()); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| public static RubyFloat pigToRuby(Ruby ruby, Double object) { |
| return RubyFloat.newFloat(ruby, object); |
| } |
| |
| /** |
| * A type specific conversion routine. |
| * |
| * @param ruby the Ruby runtime to create objects in |
| * @param object object to convert |
| * @return analogous Ruby type |
| */ |
| private static final Method enumeratorizeMethod; |
| static { |
| try { |
| enumeratorizeMethod = RubyEnumerator.class.getDeclaredMethod("enumeratorize", Ruby.class, IRubyObject.class, String.class); |
| enumeratorizeMethod.setAccessible(true); |
| } catch (NoSuchMethodException e) { |
| throw new RuntimeException("Enumeratorize method not found", e); |
| } |
| } |
| |
| /** |
| * This is a hack to get around the fact that in JRuby 1.6.7, the enumeratorize method |
| * isn't public. In 1.7.0, it will be made public and this can be gotten rid of, but |
| * until then the Jruby API doesn't provide an easy way (or even a difficult way, really) |
| * to provide this functionality; thus, it felt much cleaner to use reflection to make |
| * public a method that will soon be public anyway instead of doing something much hairier. |
| * |
| * @param runtime the Ruby runtime to create objects in |
| * @param obj the Enumerable object to wrap |
| * @param name the name of the method that still needs a block (ie each or flatten) |
| * @return enumerator Ruby object wrapping the given Enumerable |
| */ |
| public static IRubyObject enumeratorize(Ruby runtime, IRubyObject obj, String name) { |
| try { |
| return (IRubyObject)enumeratorizeMethod.invoke(null, runtime, obj, name); |
| } catch (Exception e) { |
| throw new RuntimeException("Unable to properly enumeratorize", e); |
| } |
| } |
| } |