com.fortify.processrunner.context.Context.java Source code

Java tutorial

Introduction

Here is the source code for com.fortify.processrunner.context.Context.java

Source

/*******************************************************************************
 * (c) Copyright 2017 EntIT Software LLC, a Micro Focus company
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including without 
 * limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to 
 * whom the Software is furnished to do so, subject to the following 
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
 * IN THE SOFTWARE.
 ******************************************************************************/
package com.fortify.processrunner.context;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

import com.fortify.util.spring.SpringExpressionUtil;

/**
 * <p>This class defines the process runner context. It is basically a 
 * {@link HashMap} that can contain arbitrary String keys together with 
 * arbitrary Object values.</p>
 * 
 * <p>To allow type-safe access to this {@link Map}, callers can call the
 * {@link #as(Class)} method to 'cast' the {@link Map} to an arbitrary
 * interface. This will generate a Java proxy that provides access to 
 * the {@link Map} via regular bean methods like getX() and setX(value).</p>
 * 
 * <p>Note that interfaces used with the {@link #as(Class)} method should
 * use unique method names (for example based on the interface name) to avoid
 * naming conflicts in the map between different types of functionality.</p>
 * 
 * <p>Currently the following method declarations are supported in interfaces
 * passed to the {@link #as(Class)} method:</p>
 * <ul>
 *  <li><code>get[SomeProperty]()</code>Get the value for the [SomeProperty] key from the context</li>
 *  <li><code>set[SomeProperty](value)</code>Set the value for the [SomeProperty] key on the context</li>
 * </ul>
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class Context extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;
    private final Map<Class, Object> proxies = new HashMap<Class, Object>();

    /**
     * Constructor for new, empty context
     */
    public Context() {
    }

    /**
     * Constructor for copying an existing {@link Context} instance
     * @param context
     */
    public Context(Context context) {
        super(context);
    }

    /**
     * Convenience method for chaining put operations 
     */
    public Context chainedPut(String key, Object value) {
        put(key, value);
        return this;
    }

    public <T> T get(String key, Class<T> returnType) {
        return SpringExpressionUtil.evaluateExpression(this, key, returnType);
    }

    /**
     * Indicate whether this {@link Context} contains a value for the given key.
     * If the {@link Context} contains the given key, but it is a blank string,
     * this method will also return 'false'
     * @param key
     * @return
     */
    public final boolean hasValueForKey(String key) {
        Object value = get(key);
        return value != null && !(value instanceof String && StringUtils.isBlank((String) value));
    }

    public final boolean hasValueForAnyKey(String... keys) {
        for (String key : keys) {
            if (hasValueForKey(key)) {
                return true;
            }
        }
        return false;
    }

    public final boolean hasValueForAllKeys(String... keys) {
        for (String key : keys) {
            if (!hasValueForKey(key)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Make the contents of this {@link Context} instance available through the
     * given interface, allowing for type-safe access.
     * @param iface
     * @return
     */
    public final <T> T as(Class<T> iface) {
        T result = (T) proxies.get(iface);
        if (result == null) {
            result = (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { iface },
                    new MapBasedInvocationHandler(this));
        }
        return result;
    }

    /**
     * This {@link InvocationHandler} implementation can be used to provide
     * method implementations based on a backing map, for example to get and 
     * set specific properties in the backing map. 
     */
    private static final class MapBasedInvocationHandler implements InvocationHandler {
        private final Map<String, Object> map;
        private final List<MethodHandler> methodHandlers = Arrays
                .asList(new MethodHandler[] { new GetMethodHandler(), new SetMethodHandler() });

        public MapBasedInvocationHandler(Map<String, Object> map) {
            this.map = map;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            for (MethodHandler mh : methodHandlers) {
                if (mh.canHandleMethod(map, method, args)) {
                    return mh.handleMethod(map, method, args);
                }
            }
            throw new RuntimeException("No handler found for method " + method);
        }

        private static interface MethodHandler {
            public Object handleMethod(Map<String, Object> map, Method method, Object[] args);

            public boolean canHandleMethod(Map<String, Object> map, Method method, Object[] args);
        }

        private static abstract class AbstractMethodHandler implements MethodHandler {
            protected abstract String getSupportedPrefix();

            protected abstract boolean canHandleArgs(Object[] args);

            protected abstract Object handleKey(Map<String, Object> map, String key, Object[] args);

            public boolean canHandleMethod(Map<String, Object> map, Method method, Object[] args) {
                return method.getName().startsWith(getSupportedPrefix());
            }

            public final Object handleMethod(Map<String, Object> map, Method method, Object[] args) {
                return handleKey(map, method.getName().substring(getSupportedPrefix().length()), args);
            }
        }

        /**
         * Given an interface method named get[SomeProperty](), this 
         * {@link AbstractMethodHandler} implementation will get the
         * value for the [SomeProperty] key from the backing map. 
         */
        private static final class GetMethodHandler extends AbstractMethodHandler {
            @Override
            protected String getSupportedPrefix() {
                return "get";
            }

            @Override
            protected boolean canHandleArgs(Object[] args) {
                return args.length == 0;
            }

            @Override
            protected Object handleKey(Map<String, Object> map, String key, Object[] args) {
                return map.get(key);
            }
        }

        /**
         * Given an interface method named set[SomeProperty](), this 
         * {@link AbstractMethodHandler} implementation will set the
         * value for the [SomeProperty] key on the backing map. 
         */
        private static final class SetMethodHandler extends AbstractMethodHandler {
            @Override
            protected String getSupportedPrefix() {
                return "set";
            }

            @Override
            protected boolean canHandleArgs(Object[] args) {
                return args.length == 1;
            }

            @Override
            protected Object handleKey(Map<String, Object> map, String key, Object[] args) {
                return map.put(key, args[0]);
            }
        }
    }
}