com.github.jknack.handlebars.helper.MethodHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.github.jknack.handlebars.helper.MethodHelper.java

Source

/**
 * Copyright (c) 2012-2013 Edgar Espina
 *
 * This file is part of Handlebars.java.
 *
 * Licensed 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 com.github.jknack.handlebars.helper;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;

/**
 * Wrap a method as Handlebars helper.
 *
 * @author edgar.espina
 * @see Handlebars#registerHelpers(Object)
 * @see Handlebars#registerHelpers(Class)
 */
public class MethodHelper implements Helper<Object> {

    /**
     * The source or instance object. Might be null.
     */
    private Object source;

    /**
     * The method to invoke. Required.
     */
    private Method method;

    /**
     * Creates a new {@link MethodHelper}.
     *
     * @param method The method to invoke. Required.
     * @param source The source or instance object. Might be null.
     */
    public MethodHelper(final Method method, final Object source) {
        this.method = notNull(method, "A helper method is required.");
        this.source = source;
    }

    @Override
    public CharSequence apply(final Object context, final Options options) throws IOException {
        Class<?>[] paramTypes = method.getParameterTypes();
        Object[] args = new Object[paramTypes.length];
        // collect the parameters
        int pidx = 0;
        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> paramType = paramTypes[i];
            Object ctx = i == 0 ? context : null;
            Options opts = i == paramTypes.length - 1 ? options : null;
            Object candidate = options.param(pidx, null);
            Object arg = argument(paramType, candidate, ctx, opts);
            args[i] = arg;
            if (candidate == arg) {
                pidx++;
            }
        }
        try {
            return (CharSequence) method.invoke(source, args);
        } catch (InvocationTargetException ex) {
            throw launderThrowable(ex.getCause());
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("could not execute helper: " + method.getName(), ex);
        }
    }

    /**
     * Return a runtime exception or throw an {@link IOException}.
     *
     * @param cause The invocation cause.
     * @return A runtime exception or throw an {@link IOException}.
     * @throws IOException If the cause is an {@link IOException}.
     */
    private RuntimeException launderThrowable(final Throwable cause) throws IOException {
        if (cause instanceof RuntimeException) {
            return (RuntimeException) cause;
        }
        if (cause instanceof IOException) {
            throw (IOException) cause;
        }
        return new IllegalStateException("could not execute helper: " + method.getName(), cause);
    }

    /**
     * Choose between context, options or a possible argument that matches the parameter type.
     *
     * @param paramType The expected parameter type.
     * @param argument The possible argument.
     * @param context The context object.
     * @param options The options object.
     * @return An object argument.
     */
    private Object argument(final Class<?> paramType, final Object argument, final Object context,
            final Options options) {
        // priority order is as follows:
        // 1. context
        // 2. argument
        // 3. options
        for (Object candidate : new Object[] { context, argument, options }) {
            if (paramType.isInstance(candidate) || wrap(paramType).isInstance(candidate)) {
                return candidate;
            }
        }
        return null;
    }

    /**
     * Wrap (if possible) a primitive type to their wrapper.
     *
     * @param type The candidate type.
     * @return A wrapper for the primitive type or the original type.
     */
    private static Class<?> wrap(final Class<?> type) {
        if (type.isPrimitive()) {
            if (type == Integer.TYPE) {
                return Integer.class;
            } else if (type == Boolean.TYPE) {
                return Boolean.class;
            } else if (type == Character.TYPE) {
                return Character.class;
            } else if (type == Double.TYPE) {
                return Double.class;
            } else if (type == Long.TYPE) {
                return Long.class;
            } else if (type == Float.TYPE) {
                return Float.class;
            } else if (type == Short.TYPE) {
                return Short.class;
            } else if (type == Byte.TYPE) {
                return Byte.class;
            }
        }
        return type;
    }
}