diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adb897b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/build +**/**/build +.idea +.gradle +gradle.properties \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..14331af --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +subprojects { + apply plugin: 'java' + + group 'ru.kdev' + version '1.0-SNAPSHOT' + + repositories { + mavenCentral() + } + + dependencies { + compileOnly files('../extensions-core/build/libs/extensions-core-1.0-SNAPSHOT.jar') + implementation 'org.ow2.asm:asm:9.1' + implementation group: 'org.ow2.asm', name: 'asm-tree', version: '9.1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + } + + jar { + from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }} + } + + test { + useJUnitPlatform() + } +} \ No newline at end of file diff --git a/extensions-core/build.gradle b/extensions-core/build.gradle new file mode 100644 index 0000000..5b90455 --- /dev/null +++ b/extensions-core/build.gradle @@ -0,0 +1,7 @@ +jar { + manifest { + attributes 'Premain-Class': 'ru.kdev.extensions.ExtensionsBootstrap', + 'Can-Redefine-Classes': 'true', + 'Can-Retransform-Classes': 'true' + } +} \ No newline at end of file diff --git a/extensions-core/src/main/java/ru/kdev/extensions/ExtensionList.java b/extensions-core/src/main/java/ru/kdev/extensions/ExtensionList.java new file mode 100644 index 0000000..f57aa66 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/ExtensionList.java @@ -0,0 +1,49 @@ +package ru.kdev.extensions; + +import ru.kdev.extensions.annotation.Extension; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class ExtensionList { + + private final List> extensions = new ArrayList<>(); + + public static ExtensionList parse(InputStream stream) throws ClassNotFoundException { + Scanner scanner = new Scanner(stream); + ExtensionList list = new ExtensionList(); + + while (scanner.hasNext()) { + String line = scanner.nextLine(); + list.extensions.add(Class.forName(line)); + } + + return list; + } + + public Class getExtensionByTarget(Class target) { + for(Class extension : extensions) { + if(Arrays.asList(extension.getAnnotation(Extension.class).classes()).contains(target)) { + return extension; + } + } + return null; + } + + public Class[] getAllTargets() { + List> targets = new ArrayList<>(); + + for(Class ext : extensions) { + targets.addAll(Arrays.asList(ext.getAnnotation(Extension.class).classes())); + } + + return targets.toArray(new Class[0]); + } + + public List> getExtensions() { + return extensions; + } +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/ExtensionsBootstrap.java b/extensions-core/src/main/java/ru/kdev/extensions/ExtensionsBootstrap.java new file mode 100644 index 0000000..f491571 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/ExtensionsBootstrap.java @@ -0,0 +1,21 @@ +package ru.kdev.extensions; + +import ru.kdev.extensions.processor.ExtensionsClassFileTransformer; + +import java.lang.instrument.Instrumentation; + +public class ExtensionsBootstrap { + + public static void premain(String arg, Instrumentation instrumentation) { + try { + ExtensionList list = ExtensionList.parse(ExtensionsBootstrap.class.getResourceAsStream("/extensions")); + + instrumentation.addTransformer(new ExtensionsClassFileTransformer(list), true); + instrumentation.retransformClasses(list.getAllTargets()); + } catch (ClassNotFoundException ex) { + throw new IllegalArgumentException("Main class not found or main method not found."); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/annotation/Extension.java b/extensions-core/src/main/java/ru/kdev/extensions/annotation/Extension.java new file mode 100644 index 0000000..212465a --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/annotation/Extension.java @@ -0,0 +1,16 @@ +package ru.kdev.extensions.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Extension { + + /** + * Target classes + * */ + Class[] classes(); +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/annotation/Inject.java b/extensions-core/src/main/java/ru/kdev/extensions/annotation/Inject.java new file mode 100644 index 0000000..44b0780 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/annotation/Inject.java @@ -0,0 +1,32 @@ +package ru.kdev.extensions.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Inject { + + /** + * Target method(s) + * */ + String[] method(); + + /** + * Target place + * */ + At at(); + + /** + * Allows to change return node + * Only on bottom inject + * */ + boolean changeReturn() default false; + + enum At { + TOP, + BOTTOM + } +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/annotation/TargetField.java b/extensions-core/src/main/java/ru/kdev/extensions/annotation/TargetField.java new file mode 100644 index 0000000..a5d62c3 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/annotation/TargetField.java @@ -0,0 +1,11 @@ +package ru.kdev.extensions.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface TargetField { +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/annotation/TargetMethod.java b/extensions-core/src/main/java/ru/kdev/extensions/annotation/TargetMethod.java new file mode 100644 index 0000000..0888073 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/annotation/TargetMethod.java @@ -0,0 +1,11 @@ +package ru.kdev.extensions.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface TargetMethod { +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/internal/Util.java b/extensions-core/src/main/java/ru/kdev/extensions/internal/Util.java new file mode 100644 index 0000000..db4bfa5 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/internal/Util.java @@ -0,0 +1,30 @@ +package ru.kdev.extensions.internal; + +import org.objectweb.asm.Type; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class Util { + + public static MethodHandles.Lookup TRUSTED_LOOKUP; + public static Unsafe UNSAFE; + + public static String getMethodDescriptor(Method m) { + return Type.getMethodDescriptor(m); + } + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + UNSAFE = (Unsafe) theUnsafe.get(null); + Field implLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + TRUSTED_LOOKUP = (MethodHandles.Lookup) UNSAFE.getObjectVolatile(UNSAFE.staticFieldBase(implLookup), UNSAFE.staticFieldOffset(implLookup)); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/processor/ExtensionsClassFileTransformer.java b/extensions-core/src/main/java/ru/kdev/extensions/processor/ExtensionsClassFileTransformer.java new file mode 100644 index 0000000..7525a60 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/processor/ExtensionsClassFileTransformer.java @@ -0,0 +1,31 @@ +package ru.kdev.extensions.processor; + +import ru.kdev.extensions.ExtensionList; + +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; + +public class ExtensionsClassFileTransformer implements ClassFileTransformer { + + private final ExtensionList extensionList; + + public ExtensionsClassFileTransformer(ExtensionList list) { + this.extensionList = list; + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + Class extension = extensionList.getExtensionByTarget(classBeingRedefined); + + if(extension != null) { + try { + return ExtensionsProcessor.process(extension, classBeingRedefined); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return classfileBuffer; + } +} diff --git a/extensions-core/src/main/java/ru/kdev/extensions/processor/ExtensionsProcessor.java b/extensions-core/src/main/java/ru/kdev/extensions/processor/ExtensionsProcessor.java new file mode 100644 index 0000000..5c65767 --- /dev/null +++ b/extensions-core/src/main/java/ru/kdev/extensions/processor/ExtensionsProcessor.java @@ -0,0 +1,199 @@ +package ru.kdev.extensions.processor; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; +import ru.kdev.extensions.annotation.Extension; +import ru.kdev.extensions.annotation.Inject; +import ru.kdev.extensions.annotation.TargetField; +import ru.kdev.extensions.annotation.TargetMethod; +import ru.kdev.extensions.internal.Util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +public class ExtensionsProcessor { + + private static final List RETURN_OPCODES = Arrays.asList( + Opcodes.RETURN, + Opcodes.ARETURN, + Opcodes.DRETURN, + Opcodes.FRETURN, + Opcodes.IRETURN, + Opcodes.LRETURN + ); + + public static byte[] process(Class extension, Class target) throws IOException { + if(extension.isAnnotationPresent(Extension.class)) { + ClassNode extensionNode = new ClassNode(); + ClassReader extensionReader = new ClassReader(extension.getName()); + extensionReader.accept(extensionNode, 0); + + ClassNode classNode = new ClassNode(); + ClassReader reader = new ClassReader(target.getName()); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + reader.accept(classNode, 0); + + for(Method method : extension.getDeclaredMethods()) { + if(method.isAnnotationPresent(TargetMethod.class)) { + MethodNode node = extensionNode.methods.stream().filter(x -> x.name.equals(method.getName()) && x.desc.equals(Util.getMethodDescriptor(method))).findFirst().get(); + + for(MethodNode methodNode : extensionNode.methods) { + for(AbstractInsnNode insnNode : methodNode.instructions) { + if(insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + + if(methodInsnNode.name.equals(node.name) && + methodInsnNode.desc.equals(node.desc) && + methodInsnNode.owner.equals(extensionNode.name)) { + if(Modifier.isStatic(method.getModifiers())) { + methodNode.instructions.insertBefore( + methodInsnNode, + new MethodInsnNode(Opcodes.INVOKESTATIC, classNode.name, node.name, node.desc) + ); + } else { + methodNode.instructions.insertBefore( + methodInsnNode, + new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, node.name, node.desc) + ); + } + methodNode.instructions.remove(methodInsnNode); + } + } + } + } + } + } + + for(Field field : extension.getDeclaredFields()) { + if(field.isAnnotationPresent(TargetField.class)) { + FieldNode node = extensionNode.fields.stream().filter(x -> x.name.equals(field.getName())).findFirst().get(); + + for(MethodNode methodNode : extensionNode.methods) { + for(AbstractInsnNode insnNode : methodNode.instructions) { + if(insnNode instanceof FieldInsnNode) { + FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode; + + if(fieldInsnNode.name.equals(field.getName()) && + fieldInsnNode.desc.equals(node.desc) && + fieldInsnNode.owner.equals(extensionNode.name)) { + if(Modifier.isStatic(field.getModifiers())) { + methodNode.instructions.insertBefore( + fieldInsnNode, + new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, fieldInsnNode.name, fieldInsnNode.desc) + ); + } else { + methodNode.instructions.insertBefore( + fieldInsnNode, + new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldInsnNode.name, fieldInsnNode.desc) + ); + } + methodNode.instructions.remove(fieldInsnNode); + } + } + } + } + } + } + + for(Method method : extension.getDeclaredMethods()) { + if(method.isAnnotationPresent(Inject.class)) { + Inject inject = method.getAnnotation(Inject.class); + MethodNode injectMethodNode = extensionNode.methods.stream().filter(x -> x.name.equals(method.getName()) && x.desc.equals(Util.getMethodDescriptor(method))).findFirst().get(); + + Method[] targetMethods = Arrays.stream(target.getDeclaredMethods()) + .filter(x -> Arrays.stream(inject.method()).anyMatch(y -> x.getName().equals(y))) + .toArray(Method[]::new); + + for(Method targetMethod : targetMethods) { + classNode.methods.stream().filter(x -> x.name.equals(targetMethod.getName()) && x.desc.equals(injectMethodNode.desc)).findFirst().ifPresent(targetMethodNode -> { + if(inject.at() == Inject.At.TOP) { + injectMethodNode.instructions.resetLabels(); + + Map labels = new HashMap<>(); + + List reversedList = validateInsns(injectMethodNode.instructions.toArray()); + Collections.reverse(reversedList); + + reversedList.forEach(x -> { + if(x instanceof LabelNode) { + labels.put((LabelNode) x, new LabelNode(new Label())); + } + }); + + for(AbstractInsnNode insnNode : reversedList) { + AbstractInsnNode clone = insnNode.clone(labels); + targetMethodNode.instructions.insertBefore(targetMethodNode.instructions.getFirst(), clone); + } + } else if(inject.at() == Inject.At.BOTTOM) { + AbstractInsnNode[] returns = Arrays.stream(targetMethodNode.instructions.toArray()).filter(x -> RETURN_OPCODES.contains(x.getOpcode())) + .toArray(AbstractInsnNode[]::new); + AbstractInsnNode targetReturnInsnNode = returns[returns.length - 1]; + + injectMethodNode.instructions.resetLabels(); + + Map labels = new HashMap<>(); + + injectMethodNode.instructions.forEach(x -> { + if(x instanceof LabelNode) { + labels.put((LabelNode) x, new LabelNode(new Label())); + } + }); + + List validInsns = validateInsns(injectMethodNode.instructions.toArray()); + + for(AbstractInsnNode insnNode : validInsns) { + targetMethodNode.instructions.insertBefore(targetReturnInsnNode, insnNode.clone(labels)); + } + + if(inject.changeReturn()) { + AbstractInsnNode[] injectReturns = Arrays.stream(injectMethodNode.instructions.toArray()).filter(x -> RETURN_OPCODES.contains(x.getOpcode())) + .toArray(AbstractInsnNode[]::new); + AbstractInsnNode returnNode = injectReturns[injectReturns.length - 1]; + + targetMethodNode.instructions.insertBefore(targetReturnInsnNode, returnNode); + targetMethodNode.instructions.remove(targetReturnInsnNode); + } + } + }); + } + } + } + + classNode.accept(writer); + + File dirs = new File(".out/extensions"); + File outFile = new File(".out/extensions/" + target.getSimpleName() + ".class"); + if(!outFile.exists()) { + dirs.mkdirs(); + outFile.createNewFile(); + } + FileOutputStream out = new FileOutputStream(outFile); + out.write(writer.toByteArray()); + out.close(); + return writer.toByteArray(); + } + return new byte[0]; + } + + static List validateInsns(AbstractInsnNode[] insns) { + List validInsns; + + if(RETURN_OPCODES.contains(insns[insns.length - 2].getOpcode())) { + AbstractInsnNode[] buffer = Arrays.copyOf(insns, insns.length - 2); + validInsns = Arrays.asList(buffer); + } else { + validInsns = Arrays.asList(insns); + } + + return validInsns; + } +} diff --git a/extensions-test/build.gradle b/extensions-test/build.gradle new file mode 100644 index 0000000..32c2ade --- /dev/null +++ b/extensions-test/build.gradle @@ -0,0 +1,5 @@ +jar { + manifest { + attributes 'Main-Class': 'ru.kdev.extensions.test.TestClass' + } +} \ No newline at end of file diff --git a/extensions-test/src/main/java/ru/kdev/extensions/test/ExtensionTestClass.java b/extensions-test/src/main/java/ru/kdev/extensions/test/ExtensionTestClass.java new file mode 100644 index 0000000..cde5623 --- /dev/null +++ b/extensions-test/src/main/java/ru/kdev/extensions/test/ExtensionTestClass.java @@ -0,0 +1,32 @@ +package ru.kdev.extensions.test; + +import ru.kdev.extensions.annotation.*; + +@Extension(classes = {TestClass.class}) +public class ExtensionTestClass { + + @TargetField + private int test; + + @TargetField + private static TestClass testClassImpl; + + @Inject(method = { "newMethod" }, at = Inject.At.TOP) + public void testInject() { + System.out.println("hello from inject!"); + System.out.println(test); + } + + @Inject(method = { "hello" }, at = Inject.At.BOTTOM, changeReturn = true) + public String helloInject() { + return "injected hello"; + } + + @Inject(method = { "main" }, at = Inject.At.BOTTOM) + public static void mainInject(String[] args) { + ((TestInterface) testClassImpl).testInterface(); + } + + @TargetMethod + public void newMethod() {} +} diff --git a/extensions-test/src/main/java/ru/kdev/extensions/test/TestClass.java b/extensions-test/src/main/java/ru/kdev/extensions/test/TestClass.java new file mode 100644 index 0000000..7bf6733 --- /dev/null +++ b/extensions-test/src/main/java/ru/kdev/extensions/test/TestClass.java @@ -0,0 +1,42 @@ +package ru.kdev.extensions.test; + +public class TestClass { + + private final int test = 1; + private static TestClass testClassImpl; + + public static void main(String[] args) { + test(); + test2(); + + testClassImpl = new TestClass(); + testClassImpl.aaa("aaaa"); + + System.out.println(testClassImpl.hello()); + } + + public void aaa(String a) { + System.out.println(test); + System.out.println(a); + newMethod(); + } + + public String hello() { + return "hello"; + } + + public void newMethod() { + System.out.println("invoked new method!"); + } + + public void testInterface() { + System.out.println("hi"); + } + + public static void test() { + System.out.println("test"); + } + public static void test2() { + System.out.println("test 2"); + } +} diff --git a/extensions-test/src/main/java/ru/kdev/extensions/test/TestInterface.java b/extensions-test/src/main/java/ru/kdev/extensions/test/TestInterface.java new file mode 100644 index 0000000..70f0991 --- /dev/null +++ b/extensions-test/src/main/java/ru/kdev/extensions/test/TestInterface.java @@ -0,0 +1,5 @@ +package ru.kdev.extensions.test; + +public interface TestInterface { + void testInterface(); +} diff --git a/extensions-test/src/main/resources/extensions b/extensions-test/src/main/resources/extensions new file mode 100644 index 0000000..a02c06c --- /dev/null +++ b/extensions-test/src/main/resources/extensions @@ -0,0 +1 @@ +ru.kdev.extensions.test.ExtensionTestClass \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..da9702f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9b915be --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'extensions' + +include 'extensions-core' +include 'extensions-test' \ No newline at end of file