/* * 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.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import org.apache.pig.backend.executionengine.ExecException; import org.apache.pig.data.Tuple; import org.apache.pig.data.DataByteArray; import org.jruby.Ruby; import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyEnumerator; import org.jruby.RubyModule; import org.jruby.RubyObject; import org.jruby.RubySymbol; import org.jruby.RubyString; import org.jruby.RubyFixnum; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.javasupport.JavaUtil; import org.jruby.javasupport.JavaObject; /** * This class presents a native Ruby object for interacting with and manipulating * DataByteArray objects. For more information on what the annotations mean, see * {@link RubyDataBag}. */ @JRubyClass(name="DataByteArray") public class RubyDataByteArray extends RubyObject { private static final long serialVersionUID = 1L; private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new RubyDataByteArray(runtime, klass); } }; /** * Registers the DataByteArray class with the Ruby runtime. * * @param runtime an instance of the Ruby runtime * @return a RubyClass object with metadata about the registered class */ public static RubyClass define(Ruby runtime) { RubyClass result = runtime.defineClass("DataByteArray", runtime.getObject(), ALLOCATOR); result.kindOf = new RubyModule.KindOf() { public boolean isKindOf(IRubyObject obj, RubyModule type) { return obj instanceof RubyDataByteArray; } }; result.defineAnnotatedMethods(RubyDataByteArray.class); return result; } private DataByteArray internalDBA; /** * This constructor encapsulated a null DataByteArray. * * @param ruby an instance of the ruby runtime * @param rc an instance of the class object with meatadata */ protected RubyDataByteArray(final Ruby ruby, RubyClass rc) { super(ruby,rc); internalDBA = new DataByteArray(); } /** * Given a DataByteArray, this constructor creates a new one which copies the underlying bytes. * * @param ruby an instance of the ruby runtime * @param rc an instance of the class object with meatadata * @param dba a DataByteArray to copy then encapsulate */ protected RubyDataByteArray(final Ruby ruby, RubyClass rc, DataByteArray dba) { this(ruby, rc); byte[] buf1 = dba.get(); byte[] buf2 = new byte[buf1.length]; System.arraycopy(buf1, 0, buf2, 0, buf1.length); internalDBA = new DataByteArray(buf2); } /** * This constructor makes a RubyDataByteArray whose underlying bytes are a concatenation * of the given bytes. The new DataByteArray will not point to the original bytes. * * @param ruby an instance of the ruby runtime * @param rc an instance of the class object with meatadata * @param dba1 first DataByteArray in the concatentation * @param dba2 second DAtaByteArray whose bytes will be concatenated to the first's */ protected RubyDataByteArray(final Ruby ruby, RubyClass rc, DataByteArray dba1, DataByteArray dba2) { super(ruby, rc); internalDBA = new DataByteArray(dba1, dba2); } /** * This constructor creates a new DataByteArray with a reference to the provided bytes. * * @param ruby an instance of the ruby runtime * @param rc an instance of the class object with meatadata * @param buf a byte array to encapsulate directly */ protected RubyDataByteArray(final Ruby ruby, RubyClass rc, byte[] buf) { super(ruby,rc); internalDBA = new DataByteArray(buf); } public DataByteArray getDBA() { return internalDBA; } /** * This is the default initializer, which returns an empty DataByteArray. * * @return the initialized RubyDataByteArray */ @JRubyMethod @SuppressWarnings("deprecation") public RubyDataByteArray initialize() { internalDBA = new DataByteArray(); return this; } /** * Given a String or a set of bytes[], initializes the encapsulated DataByteArray * using {@link DataByteArray#set}. In the case of a DataByteArray, will copy * the underlying bytes. * * @param arg a value to set the encapsulated DataByteArray to. A DataByteArray will be copied, whereas a byte array will be encapsulated directly, and a string's bits will be used per {@link DataByteArray#set}. * @return the initialized RubyDataByteArray */ @JRubyMethod public RubyDataByteArray initialize(IRubyObject arg) { if (arg instanceof RubyString) { internalDBA.set(arg.toString()); } else if (arg instanceof RubyDataByteArray) { byte[] buf1 = ((RubyDataByteArray)arg).getDBA().get(); byte[] buf2 = new byte[buf1.length]; System.arraycopy(buf1, 0, buf2, 0, buf1.length); internalDBA = new DataByteArray(buf2); } else { internalDBA.set((byte[])arg.toJava(byte[].class)); } return this; } /** * Given two RubyDataByteArrays, initializes the encapsulated DataByteArray * to be a concatentation of the copied bits of the first and second. * * @param arg1 the first RubyDataByteArray whose bits will be used * @param arg2 the second RubyDataByteArray whose bits will be concatenated after the first's * @return the initialized RubyDataBytearray */ @JRubyMethod public RubyDataByteArray initialize(IRubyObject arg1, IRubyObject arg2) { if (arg1 instanceof RubyDataByteArray && arg2 instanceof RubyDataByteArray) { internalDBA = new DataByteArray(((RubyDataByteArray)arg1).getDBA(), ((RubyDataByteArray)arg2).getDBA()); } else { throw new IllegalArgumentException("Invalid arguments passed to DataByteArray"); } return this; } /** * This calls to the append function of the underlying DataByteArray. This accepts * the same types that set accepts. * * @param context the context the method is being executed in * @param arg a value to append to the encpasulated DataByteArray. In the case of a * RubyDataByteArray, the bytes will be copied and appended; in the case * of a String, the bits will be added; in the case of a byte array, * the bytes will be appended directly. */ @JRubyMethod(name = {"add!", "append"}) public void append(ThreadContext context, IRubyObject arg) { Ruby runtime = context.getRuntime(); RubyDataByteArray toAppend = new RubyDataByteArray(runtime, runtime.getClass("DataByteArray")).initialize(arg); internalDBA.append(toAppend.getDBA()); } /** * This calls to the static method compare of DataByteArray. Given two DataByteArrays, it will call it * on the underlying bytes. * * @param context the context the method is being executed in * @param self an class which contains metadata on the calling class (required for static Ruby methods) * @param arg1 a RubyDataByteArray or byte array to compare * @param arg2 a RubyDataByteArray or byte array to compare * @return the Fixnum result of comparing arg1 and arg2's bytes */ @JRubyMethod public static RubyFixnum compare(ThreadContext context, IRubyObject self, IRubyObject arg1, IRubyObject arg2) { byte[] buf1, buf2; if (arg1 instanceof RubyDataByteArray) { buf1 = ((RubyDataByteArray)arg1).getDBA().get(); } else { buf1 = (byte[])arg1.toJava(byte[].class); } if (arg2 instanceof RubyDataByteArray) { buf2 = ((RubyDataByteArray)arg2).getDBA().get(); } else { buf2 = (byte[])arg2.toJava(byte[].class); } return RubyFixnum.newFixnum(context.getRuntime(), DataByteArray.compare(buf1, buf2)); } /** * This calls the compareTo method of the encapsulated DataByteArray. * * @param context the context the method is being executed in * @param arg a RubyDataByteArray or byte array to compare to the * encapsulated DataByteArray * @return the result of compareTo on the encapsulated DataByteArray and arg */ @JRubyMethod public RubyFixnum compareTo(ThreadContext context, IRubyObject arg) { int compResult; if (arg instanceof RubyDataByteArray) { compResult = internalDBA.compareTo(((RubyDataByteArray)arg).getDBA()); } else { compResult = internalDBA.compareTo(new DataByteArray((byte[])arg.toJava(byte[].class))); } return RubyFixnum.newFixnum(context.getRuntime(), compResult); } /** * Overrides equality leveraging DataByteArray's equality. * * @param context the context the method is being executed in * @param arg a RubyDataByteArray against which to test equality * @return true if they are equal, false otherwise */ @JRubyMethod(name = {"eql?", "=="}) public RubyBoolean equals(ThreadContext context, IRubyObject arg) { Ruby runtime = context.getRuntime(); if (arg instanceof RubyDataByteArray) { return RubyBoolean.newBoolean(runtime, internalDBA.equals(((RubyDataByteArray)arg).getDBA())); } else { return runtime.getFalse(); } } /** * Overrides ruby's hash leveraging DataByteArray's hash. * * @param context the context the method is being executed in * @return the encapsulated DataByteArray's hashCode() */ @JRubyMethod public IRubyObject hash(ThreadContext context) { return RubyFixnum.newFixnum(context.getRuntime(), internalDBA.hashCode()); } /** * This method calls the set method of the underlying DataByteArray with one exception: * if given a RubyDataByteArray, it will set the encapsulated DataByteArray to be equal. * * @param arg an object to set the encapsulated DataByteArray's bits to. In the case of * a RubyString or byte array, the underlying bytes will be sit directly. In * the case of a RubyDataByteArray, the encapsulated DataByteArray will be set * equal to arg. */ @JRubyMethod public void set(IRubyObject arg) { if (arg instanceof RubyDataByteArray) { internalDBA = ((RubyDataByteArray)arg).getDBA(); } else if (arg instanceof RubyString) { internalDBA.set(arg.toString()); } else { internalDBA.set((byte[])arg.toJava(byte[].class)); } } /** * @param context the context the method is being executed in * @return the size of the encapsulated DataByteArray */ @JRubyMethod(name = {"size", "length"}) public RubyFixnum size(ThreadContext context) { return RubyFixnum.newFixnum(context.getRuntime(), internalDBA.size()); } /** * This method accepts as an argument anything that could be passed to set (ie a * RubyDataByteArray, RubyString, or byte array). It then adds those values together. * * @param context the context the method is being executed in * @param arg any argument that can validly initialize a RubyDataByteArray * @return a <u>new</u> RubyDataByteArray that is the concatentation of * the encapsulated DataByteArray and arg */ @JRubyMethod(name = "+") public IRubyObject plus(ThreadContext context, IRubyObject arg) { Ruby runtime = context.getRuntime(); RubyDataByteArray toAdd = new RubyDataByteArray(runtime, runtime.getClass("DataByteArray")).initialize(arg); return new RubyDataByteArray(runtime, runtime.getClass("DataByteArray"), internalDBA, toAdd.getDBA()); } /** * @param context the context the method is being executed in * @return the string representation of the encapsulated DataByteArray */ @JRubyMethod(name = {"to_s", "inspect"}) public IRubyObject toString(ThreadContext context) { return RubyString.newString(context.getRuntime(), internalDBA.toString()); } }