All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.remotecontrol.groovy.client.ClosureCommandGenerator Maven / Gradle / Ivy

/*
 * Copyright 2012 the original author or authors.
 *
 * 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 io.remotecontrol.groovy.client;

import groovy.lang.Closure;
import io.remotecontrol.SerializationUtil;
import io.remotecontrol.UnserializableCommandException;
import io.remotecontrol.client.CommandGenerator;
import io.remotecontrol.groovy.ClosureCommand;
import io.remotecontrol.groovy.ClosureUtil;
import io.remotecontrol.util.UnexpectedIOException;
import org.codehaus.groovy.runtime.CurriedClosure;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;

import java.io.IOException;
import java.io.NotSerializableException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;

/**
 * Generates command objects from closures.
 */
public class ClosureCommandGenerator implements CommandGenerator {

    private final ClassLoader classLoader;

    public ClosureCommandGenerator() {
        this(Thread.currentThread().getContextClassLoader());
    }

    public ClosureCommandGenerator(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public Class getCommandType() {
        return ClosureCommand.class;
    }

    /**
     * For the given closure, generate a command object.
     */
    public ClosureCommand generate(RawClosureCommand rawClosureCommand) {
        byte[] bytes;
        byte[] classBytes;
        List supports;
        Closure cloned = (Closure) rawClosureCommand.getRoot().clone();
        Closure root = getRootClosure(cloned);
        bytes = serializeInstance((Closure) cloned, root);
        classBytes = getClassBytes(root.getClass());

        supports = new LinkedList(getSupportingClassesBytes(root.getClass()));

        List> used = rawClosureCommand.getUsed();
        if (!used.isEmpty()) {
            for (Closure usedClosure : used) {
                supports.add(getClassBytes(usedClosure.getClass()));
                supports.addAll(getSupportingClassesBytes(usedClosure.getClass()));
            }
        }

        return new ClosureCommand(bytes, classBytes, supports);
    }

    /**
     * Gets the generated closure instance that is underneath the potential layers of currying.
     *
     * If the given closure is the root closure it is returned.
     */
    protected Closure getRootClosure(Closure closure) {
        Closure root = closure;
        while (root instanceof CurriedClosure) {
            root = ((Closure) (root.getOwner()));
        }

        return root;
    }

    /**
     * Gets the class definition bytes of any closures classes that are used by the given closure class.
     *
     * @see InnerClosureClassDefinitionsFinder
     */
    protected List getSupportingClassesBytes(Class closureClass) {
        try {
            return new InnerClosureClassDefinitionsFinder(classLoader).find(closureClass);
        } catch (IOException e) {
            throw new UnexpectedIOException("cannnot find inner closures of: " + closureClass.getName(), e);
        }
    }

    /**
     * Gets the class definition bytes for the given closure class.
     */
    protected byte[] getClassBytes(final Class closureClass) {
        String classFileName = getClassFileName(closureClass);
        URL classFileResource = classLoader.getResource(classFileName);
        if (classFileResource == null) {
            throw new IllegalStateException("Could not find class file for class " + String.valueOf(closureClass));
        }

        try {
            return DefaultGroovyMethods.getBytes(classFileResource);
        } catch (IOException e) {
            throw new UnexpectedIOException("reading class files", e);
        }
    }

    protected String getClassFileName(Class closureClass) {
        return closureClass.getName().replace(".", "/") + ".class";
    }

    /**
     * Serialises the closure taking care to remove the owner, thisObject and delegate.
     *
     * The given closure may be curried which is why we need the "root" closure because it has the owner etc.
     *
     * closure and root will be the same object if closure is not curried.
     *
     * @param closure the target closure to serialise
     * @param root the actual generated closure that contains the implementation.
     */
    protected byte[] serializeInstance(Closure closure, Closure root) {
        ClosureUtil.nullFields(root);
        try {
            return SerializationUtil.serialize(closure);
        } catch (NotSerializableException e) {
            throw new UnserializableCommandException("Unable to serialize closure: " + closure, e);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy