CasperjsToolchainFactory.java

package com.github.casperjs.casperjsrunner.toolchain;

import static com.github.casperjs.casperjsrunner.toolchain.CasperjsToolchain.KEY_CASPERJS_EXECUTABLE;
import static com.github.casperjs.casperjsrunner.toolchain.CasperjsToolchain.KEY_CASPERJS_TYPE;

import org.apache.maven.toolchain.MisconfiguredToolchainException;
import org.apache.maven.toolchain.RequirementMatcherFactory;
import org.apache.maven.toolchain.ToolchainFactory;
import org.apache.maven.toolchain.ToolchainPrivate;
import org.apache.maven.toolchain.model.ToolchainModel;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import java.util.Properties;

/**
 * Factory for CasperJS runtime tool chain.
 *
 * Based on {@code org.apache.maven.toolchain.java.DefaultJavaToolchainFactory}.
 */
@Component(role = ToolchainFactory.class, hint = KEY_CASPERJS_TYPE, description = "A default factory for '" + KEY_CASPERJS_TYPE + "' toolchains")
public class CasperjsToolchainFactory implements ToolchainFactory {

    @Requirement
    private Logger logger;

    @Override
    public ToolchainPrivate createToolchain(final ToolchainModel model) throws MisconfiguredToolchainException {
        if (model == null) {
            return null;
        }

        final Properties configuration = toProperties((Xpp3Dom) model.getConfiguration());
        final String casperjsExecutable = configuration.getProperty(KEY_CASPERJS_EXECUTABLE);
        if (casperjsExecutable == null) {
            throw new MisconfiguredToolchainException("CasperJS toolchain without the " + KEY_CASPERJS_EXECUTABLE + " configuration element.");
        }
        final String normalizedCasperjsExecutablePath = FileUtils.normalize(casperjsExecutable);
        final File casperjsExecutableFile = new File(normalizedCasperjsExecutablePath);
        if (!casperjsExecutableFile.exists()) {
            throw new MisconfiguredToolchainException("Non-existing casperjs executable at " + casperjsExecutableFile.getAbsolutePath());
        }

        final CasperjsToolchain toolchain = new CasperjsToolchain(model, logger, normalizedCasperjsExecutablePath);

        // now populate the provides section.
        final Properties provides = getProvidesProperties(model);
        for (final Entry<Object, Object> provide : provides.entrySet()) {
            final String key = (String) provide.getKey();
            final String value = (String) provide.getValue();
            if (value == null) {
                throw new MisconfiguredToolchainException("Provides token '" + key + "' doesn't have any value configured.");
            }
            if ("version".equals(key)) {
                toolchain.addProvideToken(key, RequirementMatcherFactory.createVersionMatcher(value));
            } else {
                toolchain.addProvideToken(key, RequirementMatcherFactory.createExactMatcher(value));
            }
        }
        return toolchain;
    }

    @Override
    public ToolchainPrivate createDefaultToolchain() {
        return null;
    }

    public void setLogger(final Logger logger) {
        this.logger = logger;
    }

    /**
     * Get {@code provides} properties in in a way compatible with toolchains descriptor version 1.0 (Maven 2.0.9 to 3.2.3, where it is represented as
     * Object/DOM) and descriptor version 1.1 (Maven 3.2.4 and later, where it is represented as Properties).
     *
     * @param model
     *            the toolchain model as read from XML
     * @return the properties defined in the {@code provides} element
     * @see <a href="http://jira.codehaus.org/browse/MNG-5718">MNG-5718</a>
     */
    protected static Properties getProvidesProperties(final ToolchainModel model) {
        final Object value = getBeanProperty(model, "provides");
        return value instanceof Properties ? (Properties) value : toProperties((Xpp3Dom) value);
    }

    protected static Properties toProperties(final Xpp3Dom dom) {
        final Properties props = new Properties();
        final Xpp3Dom[] children = dom.getChildren();
        for (final Xpp3Dom child : children) {
            props.setProperty(child.getName(), child.getValue());
        }
        return props;
    }

    protected static Object getBeanProperty(final Object obj, final String property) {
        try {
            final Method method = new PropertyDescriptor(property, obj.getClass()).getReadMethod();
            return method.invoke(obj);
        } catch (final IntrospectionException e) {
            throw new IncompatibleToolchainAPIException(e);
        } catch (final IllegalAccessException e) {
            throw new IncompatibleToolchainAPIException(e);
        } catch (final InvocationTargetException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            throw new IncompatibleToolchainAPIException(e);
        }
    }
}