org.cp.elements.process.java.EmbeddedJavaProcessExecutor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cp-elements Show documentation
Show all versions of cp-elements Show documentation
Java Simplified. Extensions and Useful Constructs for the Java Platform.
Codeprimate Elements (a.k.a. cp-elements) is a Java library and micro-framework used to simplify
the development of software applications written in Java. Elements packages several APIs into one
library in order to address various application concerns and aspects of software design and development
collectively and conveniently. Elements is a highly simple, yet robust and proven library built on
solid OO principles, software design patterns and best practices to effectively solve common
and reoccurring problems in software development.
/*
* Copyright 2016 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 org.cp.elements.process.java;
import static java.util.Arrays.asList;
import static org.cp.elements.lang.ClassUtils.getName;
import static org.cp.elements.lang.ClassUtils.getSimpleName;
import static org.cp.elements.lang.ClassUtils.isConstructorWithArrayParameter;
import static org.cp.elements.lang.ClassUtils.isDefaultConstructor;
import static org.cp.elements.lang.ObjectUtils.isNullOrEqualTo;
import static org.cp.elements.util.ArrayUtils.indexOf;
import static org.cp.elements.util.ArrayUtils.nullSafeArray;
import static org.cp.elements.util.ArrayUtils.toStringArray;
import static org.cp.elements.util.stream.StreamUtils.stream;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.Callable;
import org.cp.elements.io.FileSystemUtils;
import org.cp.elements.lang.Assert;
import org.cp.elements.lang.ClassUtils;
import org.cp.elements.lang.Executable;
import org.cp.elements.lang.ObjectUtils;
import org.cp.elements.lang.StringUtils;
import org.cp.elements.lang.annotation.NullSafe;
import org.cp.elements.process.EmbeddedProcessExecutionException;
import org.cp.elements.process.ProcessExecutor;
/**
* The EmbeddedJavaProcessExecutor class...
*
* @author John Blum
* @since 1.0.0
*/
@SuppressWarnings("unused")
public class EmbeddedJavaProcessExecutor implements ProcessExecutor {
protected static final Collection JAVA_CLASS_EXECUTORS = Collections.unmodifiableList(asList(
new ExecutableExecutor<>(), new RunnableExecutor<>(), new CallableExecutor<>(), new MainMethodExecutor<>()));
/**
* Factory method used to construct an instance of the {@link EmbeddedJavaProcessExecutor}, which is used to execute
* Java {@link Class Classes} embedded in the currently running Java program.
*
* @return a new instance of the {@link EmbeddedJavaProcessExecutor} class.
* @see org.cp.elements.process.java.EmbeddedJavaProcessExecutor
*/
public static EmbeddedJavaProcessExecutor newEmbeddedJavaProcessExecutor() {
return new EmbeddedJavaProcessExecutor();
}
/**
* Executes the given {@link String command-line} executable in the given {@link File directory}.
*
* @return {@link Void}. The Java {@link Process} is run in embedded mode and therefore will not
* have a forked {@link Process}.
* @throws IllegalArgumentException if the {@link File directory} is the current working directory or {@literal null},
* or the Java {@link Class} could not be resolved from the given command-line.
* @see org.cp.elements.process.ProcessExecutor#execute(File, String...)
* @see #execute(Class, String...)
* @see java.io.File
*/
@Override
public Void execute(File directory, String... commandLine) {
Assert.isTrue(isNullOrEqualTo(directory, FileSystemUtils.WORKING_DIRECTORY),
"The Java class can only be ran in the same working directory [%1$s] as the containing process"
+ "; directory was [%2$s]", FileSystemUtils.WORKING_DIRECTORY, directory);
Class type = resolveJavaClassFrom(commandLine);
Assert.notNull(type, "The Java class to execute could not be resolved from the given command-line [%s]",
Arrays.toString(commandLine));
String[] args = resolveArgumentsFrom(type, commandLine);
execute(type, args);
return null;
}
/**
* Resolves the arguments to the Java program identified by the given {@link Class} type
* from the given array of {@link String arguments}.
*
* @param type Java {@link Class} containing the {@literal main} method of the Java program;
* must not be {@literal null}.
* @param args array of {@link String arguments} from which to extract the Java program arguments.
* @return the array of {@link String arguments} to be passed to the Java program.
*/
protected String[] resolveArgumentsFrom(Class type, String... args) {
int index = indexOf(args, type.getName());
int position = (index + 1);
String[] arguments = StringUtils.EMPTY_STRING_ARRAY;
if (index != -1 && position < args.length) {
int length = (args.length - position);
arguments = new String[length];
System.arraycopy(args, position, arguments, 0, length);
}
return arguments;
}
/**
* Resolves the Java {@link Class} containing the {@literal main} method to the Java program to execute
* from the given array of {@link String arguments}.
*
* @param {@link Class} type of the main Java program.
* @param args array of {@link String arguments} from which to extract the main Java {@link Class}
* of the Java program.
* @return the Java {@link Class} containing the {@literal main} method to the Java program.
* @see java.lang.Class
*/
protected Class resolveJavaClassFrom(String... args) {
return Arrays.stream(nullSafeArray(args, String.class)).filter(ObjectUtils::isPresent).findFirst()
.>map(ObjectUtils::loadClass).orElse(null);
}
/**
* Executes the given Java {@link Class} passing the given array of {@link String arguments}.
*
* This execute method employs a {@link JavaClassExecutor} strategy to execute the given Java {@link Class}.
* The Java {@link Class} is expected to either implement {@link Runnable}, {@link Callable}, {@link Executable}
* or implement a {@literal main} {@link Method}.
*
* @param {@link Class} type of the Java program return value.
* @param type Java {@link Class} to execute in embedded mode.
* @param args array of {@link String arguments} to pass to the Java {@link Class}.
* @return an {@link Optional} return value from the execution of the Java {@link Class}.
* @throws IllegalArgumentException if the Java {@link Class} is {@literal null}.
* @throws EmbeddedProcessExecutionException if the Java {@link Class} is not executable.
* @see org.cp.elements.process.java.EmbeddedJavaProcessExecutor.JavaClassExecutor
* @see java.lang.Class
* @see java.util.Optional
*/
@SuppressWarnings("unchecked")
public Optional execute(Class type, String... args) {
Assert.notNull(type, "Class type must not be null");
return JAVA_CLASS_EXECUTORS.stream().filter(javaClassExecutor -> javaClassExecutor.isExecutable(type))
.findFirst().map(javaClassExecutor -> javaClassExecutor.execute(type, (Object[]) args)).orElseThrow(() ->
new EmbeddedProcessExecutionException(String.format("Unable to execute Java class [%s];"
+ " Please verify that your class either implements Runnable, Callable, Executable or has a main method",
getName(type))));
}
/**
* Strategy interface for executing Java {@link Class Classes}.
*
* @param {@link Class} type of the executions return value (result).
*/
interface JavaClassExecutor {
/**
* Determines whether the given Java {@link Class} is executable by this executor.
*
* @param type Java {@link Class} to evaluate.
* @return a boolean value indicating whether this executor can execute the given Java {@link Class}.
* @see java.lang.Class
*/
boolean isExecutable(Class type);
/**
* Executes the given Java {@link Class} passing the array of {@link String arguments} used during execution.
*
* @param type Java {@link Class} to execute.
* @param args array of {@link String arguments} passed during execution.
* @return the {@link Optional} return value (result) from the execution.
* @see java.lang.Class
* @see java.util.Optional
*/
Optional execute(Class type, Object... args);
@NullSafe
default boolean isTargetConstructor(Constructor> constructor) {
return Optional.ofNullable(constructor).map(localConstructor ->
isDefaultConstructor(constructor) || isConstructorWithArrayParameter(constructor))
.orElse(false);
}
@SuppressWarnings("unchecked")
default Constructor findConstructor(Class type) {
return (Constructor) stream(nullSafeArray(type.getDeclaredConstructors(), Constructor.class))
.filter(this::isTargetConstructor).sorted((constructorOne, constructorTwo) ->
constructorTwo.getParameterCount() - constructorOne.getParameterCount())
.findFirst().orElseThrow(() -> new EmbeddedProcessExecutionException(String.format(
"No default constructor or constructor with arguments (%1$s(:Object[]) for type [%2$s] was found",
getSimpleName(type), getName(type))));
}
default T constructInstance(Class type, Object[] args) {
try {
Constructor constructor = findConstructor(type);
constructor.setAccessible(true);
return (isConstructorWithArrayParameter(constructor) ? constructor.newInstance((Object) args)
: constructor.newInstance());
}
catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new EmbeddedProcessExecutionException(
String.format("Failed to construct an instance of Java class [%s]", getName(type)), e);
}
}
}
/**
* Execution stragey for executing {@link Callable} Java {@link Class Classes}.
* @param {@link Class} type of the executions return value (result).
* @see java.util.concurrent.Callable
*/
static class CallableExecutor implements JavaClassExecutor {
/**
* @inheritDoc
*/
@Override
public boolean isExecutable(Class type) {
return Callable.class.isAssignableFrom(type);
}
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("unchecked")
public Optional execute(Class type, Object... args) {
try {
Callable callable = this.>constructInstance(type, args);
return Optional.ofNullable(callable.call());
}
catch (Exception e) {
throw new EmbeddedProcessExecutionException(String.format("Failed to call Java class [%s]", getName(type)), e);
}
}
}
/**
* Execution stragey for executing {@link Executable} Java {@link Class Classes}.
* @param {@link Class} type of the executions return value (result).
* @see org.cp.elements.lang.Executable
*/
static class ExecutableExecutor implements JavaClassExecutor {
/**
* @inheritDoc
*/
@Override
public boolean isExecutable(Class type) {
return Executable.class.isAssignableFrom(type);
}
/**
* @inheritDoc
*/
@SuppressWarnings("unchecked")
public Optional execute(Class type, Object... args) {
Executable executable = this.>constructInstance(type, args);
return Optional.ofNullable(executable.execute((Object[]) args));
}
}
/**
* Execution stragey for executing Java {@link Class Classes} having a {@literal main} {@link Method}.
* @param {@link Class} type of the executions return value (result).
*/
static class MainMethodExecutor implements JavaClassExecutor {
/**
* @inheritDoc
*/
@Override
public boolean isExecutable(Class type) {
return Arrays.stream(type.getDeclaredMethods()).anyMatch(ClassUtils::isMainMethod);
}
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("unchecked")
public Optional execute(Class type, Object... args) {
try {
Method mainMethod = type.getDeclaredMethod(ClassUtils.MAIN_METHOD_NAME, String[].class);
mainMethod.invoke(null, (Object) toStringArray(args));
return Optional.empty();
}
catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new EmbeddedProcessExecutionException(
String.format("Failed to execute Java class [%s] using main method", getName(type)), e);
}
}
}
/**
* Execution stragey for executing {@link Runnable} Java {@link Class Classes}.
* @param {@link Class} type of the executions return value (result).
* @see java.lang.Runnable
*/
static class RunnableExecutor implements JavaClassExecutor {
/**
* @inheritDoc
*/
@Override
public boolean isExecutable(Class type) {
return Runnable.class.isAssignableFrom(type);
}
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("unchecked")
public Optional execute(Class type, Object... args) {
Runnable runnable = this.constructInstance(type, args);
runnable.run();
return Optional.empty();
}
}
}