Coverage Report - datafu.pig.util.SimpleEvalFunc
 
Classes in this File Line Coverage Branch Coverage Complexity
SimpleEvalFunc
52%
18/34
55%
10/18
5.25
 
 1  
 /*
 2  
  * Copyright 2010 LinkedIn, Inc
 3  
  * 
 4  
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 5  
  * use this file except in compliance with the License. You may obtain a copy of
 6  
  * the License at
 7  
  * 
 8  
  * http://www.apache.org/licenses/LICENSE-2.0
 9  
  * 
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12  
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13  
  * License for the specific language governing permissions and limitations under
 14  
  * the License.
 15  
  */
 16  
  
 17  
 package datafu.pig.util;
 18  
 
 19  
 import java.io.IOException;
 20  
 import java.lang.reflect.Method;
 21  
 import java.lang.reflect.Type;
 22  
 
 23  
 import org.apache.pig.EvalFunc;
 24  
 import org.apache.pig.data.Tuple;
 25  
 
 26  
 /**
 27  
   Uses reflection to makes writing simple wrapper Pig UDFs easier.
 28  
 
 29  
   For example, writing a simple string trimming UDF might look like
 30  
   this:
 31  
   <pre>
 32  
   {@code
 33  
 package datafu.pig.util;
 34  
 
 35  
 import java.io.IOException;
 36  
 
 37  
 import org.apache.pig.EvalFunc;
 38  
 import org.apache.pig.data.Tuple;
 39  
 import org.apache.pig.impl.util.WrappedIOException;
 40  
 
 41  
 public class TRIM extends EvalFunc<String> 
 42  
 {
 43  
   public String exec(Tuple input) throws IOException 
 44  
   {
 45  
     if (input.size() != 1)
 46  
       throw new IllegalArgumentException("requires a parameter");
 47  
 
 48  
     try {
 49  
       Object o = input.get(0);
 50  
       if (!(o instanceof String))
 51  
         throw new IllegalArgumentException("expected a string");
 52  
 
 53  
       String str = (String)o;
 54  
       return (str == null) ? null : str.trim();
 55  
     } 
 56  
     catch (Exception e) {
 57  
       throw WrappedIOException.wrap("error...", e);
 58  
     }
 59  
   }
 60  
 }
 61  
   }
 62  
   </pre>
 63  
   There is a lot of boilerplate to check the number of arguments and
 64  
   the parameter types in the tuple.
 65  
 
 66  
   Instead, with this class, you can derive from SimpleEvalFunc and
 67  
   create a <code>call()</code> method (not exec!), just specifying the
 68  
   arguments as a regular function. The class handles all the argument
 69  
   checking and exception wrapping for you. So your code would be:
 70  
   <pre>
 71  
   {@code
 72  
 package datafu.pig.util;
 73  
 
 74  
 public class TRIM2 extends SimpleEvalFunc<String> 
 75  
 {
 76  
   public String call(String s)
 77  
   {
 78  
     return (s != null) ? s.trim() : null;
 79  
   }
 80  
 }
 81  
   }
 82  
   </pre>
 83  
 
 84  
   An example of this UDF in action with Pig:
 85  
   <pre>
 86  
   {@code
 87  
 grunt> a = load 'test' as (x:chararray, y:chararray); dump a;
 88  
   (1 , 2)
 89  
 grunt> b = foreach a generate TRIM2(x); dump b;
 90  
   (1)
 91  
 grunt> c = foreach a generate TRIM2((int)x); dump c;
 92  
   datafu.pig.util.TRIM2(java.lang.String): argument type 
 93  
   mismatch [#1]; expected java.lang.String, got java.lang.Integer
 94  
 grunt> d = foreach a generate TRIM2(x, y); dump d;
 95  
   datafu.pig.util.TRIM2(java.lang.String): got 2 arguments, 
 96  
   expected 1.
 97  
 }
 98  
   </pre>
 99  
 
 100  
 */
 101  
 
 102  
 public abstract class SimpleEvalFunc<T> extends EvalFunc<T>
 103  
 {
 104  
   // TODO Add support for other UDF types (e.g., FilterFunc)
 105  
   // TODO Algebraic EvalFuncs 
 106  
   
 107  22904
   Method m = null;
 108  
 
 109  
   public SimpleEvalFunc()
 110  22904
   {
 111  582206
     for (Method method : this.getClass().getMethods()) {
 112  559302
       if (method.getName() == "call")
 113  22904
         m = method;
 114  
     }
 115  22904
     if (m == null)
 116  0
       throw new IllegalArgumentException(String.format("%s: couldn't find call() method in UDF.", getClass().getName()));
 117  22904
   }
 118  
 
 119  
   // Pig can't get the return type via reflection (as getReturnType normally tries to do), so give it a hand 
 120  
   @Override
 121  
   public Type getReturnType() 
 122  
   {
 123  1543
     return m.getReturnType();
 124  
   }
 125  
 
 126  
   private String _method_signature() 
 127  
   {
 128  0
     StringBuilder sb = new StringBuilder(getClass().getName());
 129  0
     Class<?> pvec[] = m.getParameterTypes();
 130  
 
 131  0
     sb.append("(");
 132  0
     for (int i=0; i < pvec.length; i++) {
 133  0
       if (i > 0)
 134  0
         sb.append(", ");
 135  0
       sb.append(String.format("%s", pvec[i].getName()));
 136  
     }
 137  0
     sb.append(")");
 138  
 
 139  0
     return sb.toString();
 140  
   }
 141  
  
 142  
   @Override
 143  
   @SuppressWarnings("unchecked")
 144  
   public T exec(Tuple input) throws IOException
 145  
   {
 146  159
     Class pvec[] = m.getParameterTypes();
 147  
 
 148  159
     if (input == null || input.size() == 0)
 149  0
       return null;
 150  
     
 151  
     // check right number of arguments
 152  159
     if (input.size() != pvec.length) 
 153  0
       throw new IOException(String.format("%s: got %d arguments, expected %d.", _method_signature(), input.size(), pvec.length));
 154  
 
 155  
     // pull and check argument types
 156  159
     Object[] args = new Object[input.size()];
 157  441
     for (int i=0; i < pvec.length; i++) {
 158  282
       Object o = input.get(i);
 159  
       try {
 160  282
         o = pvec[i].cast(o);
 161  
       }
 162  0
       catch (ClassCastException e) {
 163  0
         throw new IOException(String.format("%s: argument type mismatch [#%d]; expected %s, got %s", _method_signature(), i+1,
 164  
               pvec[i].getName(), o.getClass().getName()));
 165  282
       }
 166  282
       args[i] = o;
 167  
     }
 168  
 
 169  
     try {
 170  159
       return (T) m.invoke(this, args);
 171  
     }
 172  0
     catch (Exception e) {
 173  0
         throw new IOException(String.format("%s: caught exception processing input.", _method_signature()), e);
 174  
     }
 175  
   }
 176  
 }
 177