/* * 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.builtin; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pig.backend.executionengine.ExecException; import org.apache.pig.data.DataBag; import org.apache.pig.data.Tuple; import org.apache.pig.impl.PigContext; import org.apache.pig.impl.logicalLayer.FrontendException; import com.google.common.collect.Lists; import com.google.common.collect.Sets; public class Invoker<T> { private static final Log LOG = LogFactory.getLog(Invoker.class); private static final Class<?> DOUBLE_ARRAY_CLASS = new double[0].getClass(); private static final Class<?> INT_ARRAY_CLASS = new int[0].getClass(); private static final Class<?> FLOAT_ARRAY_CLASS = new float[0].getClass(); private static final Class<?> STRING_ARRAY_CLASS = new String[0].getClass(); private static final Class<?> LONG_ARRAY_CLASS = new long[0].getClass(); @SuppressWarnings("unchecked") private static final Set<Class<?>> ARRAY_CLASSES = Sets.newHashSet( DOUBLE_ARRAY_CLASS, INT_ARRAY_CLASS, FLOAT_ARRAY_CLASS, STRING_ARRAY_CLASS, LONG_ARRAY_CLASS); private Method method_; private Class<?>[] paramClasses_; private boolean isStatic_; private Class<?> selfClass_; private Type returnType_; public Invoker(String fullName, String paramSpecsStr) throws ClassNotFoundException, FrontendException, SecurityException, NoSuchMethodException { this(fullName, paramSpecsStr, "true"); } public Invoker(String fullName, String paramSpecsStr, String isStatic) throws ClassNotFoundException, FrontendException, SecurityException, NoSuchMethodException { String className = fullName.substring(0, fullName.lastIndexOf('.')); String methodName = fullName.substring(fullName.lastIndexOf('.')+1); Class<?> klazz; try { klazz = PigContext.resolveClassName(className); } catch (IOException e) { // the amusing part is that PigContext throws this to wrap one of // the exceptions we declare! throw new FrontendException(e); } String[] paramSpecs = "".equals(paramSpecsStr) ? new String[0] : paramSpecsStr.split(" "); isStatic_ = "static".equalsIgnoreCase(isStatic) || "true".equals(isStatic); paramClasses_ = new Class<?>[paramSpecs.length]; for (int i = 0; i < paramSpecs.length; i++) { paramClasses_[i] = stringToClass(paramSpecs[i]); } if (!isStatic_) { selfClass_ = paramClasses_[0]; } method_ = klazz.getMethod(methodName, (isStatic_ ? paramClasses_ : dropFirstClass(paramClasses_))); returnType_ = method_.getGenericReturnType(); } @SuppressWarnings("rawtypes") public Type getReturnType() { return unPrimitivize((Class) returnType_); } private static Class<?>[] dropFirstClass(Class<?>[] original) { if (original.length < 2) { return new Class[0]; } else { return Arrays.copyOfRange(original, 1, original.length-1); } } private static Object[] dropFirstObject(Object[] original) { if (original.length < 2) { return new Object[0]; } else { return Arrays.copyOfRange(original, 1, original.length-1); } } private static Class<?> stringToClass(String klass) throws FrontendException { if ("string".equalsIgnoreCase(klass)) { return String.class; } else if ("int".equalsIgnoreCase(klass)) { return Integer.TYPE; } else if ("double".equalsIgnoreCase(klass)) { return Double.TYPE; } else if ("float".equalsIgnoreCase(klass)){ return Float.TYPE; } else if ("long".equalsIgnoreCase(klass)) { return Long.TYPE; } else if ("double[]".equalsIgnoreCase(klass)) { return DOUBLE_ARRAY_CLASS; } else if ("int[]".equalsIgnoreCase(klass)) { return INT_ARRAY_CLASS; } else if ("long[]".equalsIgnoreCase(klass)) { return LONG_ARRAY_CLASS; } else if ("float[]".equalsIgnoreCase(klass)) { return FLOAT_ARRAY_CLASS; } else if ("string[]".equalsIgnoreCase(klass)) { return STRING_ARRAY_CLASS; } else { throw new FrontendException("unable to find matching class for " + klass); } } private static Class<?> unPrimitivize(Class<?> klass) { if (klass.equals(Integer.TYPE)) { return Integer.class; } if (klass.equals(Long.TYPE)) { return Long.class; } else if (klass.equals(Float.TYPE)) { return Float.class; } else if (klass.equals(Double.TYPE)) { return Double.class; } else { return klass; } } private static <T> T convertToExpectedArg(Class<T> klass, Object obj) throws ExecException { if (ARRAY_CLASSES.contains(klass)) { DataBag dbag = (DataBag) obj; if (STRING_ARRAY_CLASS.equals(klass)) { List<String> dataList = Lists.newArrayList(); for (Tuple t : dbag) { dataList.add( (String) t.get(0)); } String[] dataArray = new String[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { dataArray[i] = dataList.get(i); } obj = dataArray; } else { List<Number> dataList = bagToNumberList(dbag); if (DOUBLE_ARRAY_CLASS.equals(klass)) { double[] dataArray = new double[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { dataArray[i] = dataList.get(i).doubleValue(); } obj = dataArray; } else if (INT_ARRAY_CLASS.equals(klass)) { int[] dataArray = new int[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { dataArray[i] = dataList.get(i).intValue(); } obj = dataArray; } else if (FLOAT_ARRAY_CLASS.equals(klass)) { float[] dataArray = new float[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { dataArray[i] = dataList.get(i).floatValue(); } obj = dataArray; } else if (LONG_ARRAY_CLASS.equals(klass)) { long[] dataArray = new long[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { dataArray[i] = dataList.get(i).longValue(); } obj = dataArray; } } } try { return klass.cast(obj); } catch (ClassCastException e) { LOG.error("Error in dynamic argument processing. Casting to: " + klass + " from: " + obj.getClass(), e); throw new ExecException(e); } } private static List<Number> bagToNumberList(DataBag dbag) throws ExecException { List<Number> dataList = Lists.newArrayList(); for (Tuple t : dbag) { dataList.add( (Number) t.get(0)); } return dataList; } private Object[] tupleToArgs(Tuple t) throws ExecException { if ( (t == null && (paramClasses_ != null || paramClasses_.length != 0)) || (t != null && t.size() < paramClasses_.length)) { throw new ExecException("unable to match function arguments to declared signature."); } if (t == null) { return null; } Object[] args = new Object[paramClasses_.length]; for (int i = 0; i < paramClasses_.length; i++) { args[i] = convertToExpectedArg(unPrimitivize(paramClasses_[i]), t.get(i)); } return args; } @SuppressWarnings("unchecked") public T invoke(Tuple input) throws IOException { Object[] args = tupleToArgs(input); try { if (!isStatic_) { return (T) method_.invoke(selfClass_.cast(args[0]), dropFirstObject(args)); } else { return (T) method_.invoke(null, args); } } catch (IllegalArgumentException e) { throw new ExecException(e); } catch (IllegalAccessException e) { throw new ExecException(e); } catch (InvocationTargetException e) { throw new ExecException(e); } } }