diff --git a/android/src/io/anuke/mindustry/AndroidLauncher.java b/android/src/io/anuke/mindustry/AndroidLauncher.java index 0faeba5a05..56861eeead 100644 --- a/android/src/io/anuke/mindustry/AndroidLauncher.java +++ b/android/src/io/anuke/mindustry/AndroidLauncher.java @@ -19,7 +19,6 @@ import io.anuke.arc.util.serialization.*; import io.anuke.mindustry.game.Saves.*; import io.anuke.mindustry.io.*; import io.anuke.mindustry.mod.*; -import io.anuke.mindustry.rhino.*; import io.anuke.mindustry.ui.dialogs.*; import java.io.*; @@ -68,7 +67,7 @@ public class AndroidLauncher extends AndroidApplication{ @Override public org.mozilla.javascript.Context getScriptContext(){ - return new RhinoBuilder(getContext()).enterContext(); + return AndroidRhinoContext.enterContext(getContext().getCacheDir()); } @Override diff --git a/android/src/io/anuke/mindustry/AndroidRhinoContext.java b/android/src/io/anuke/mindustry/AndroidRhinoContext.java new file mode 100644 index 0000000000..eadc1c12fd --- /dev/null +++ b/android/src/io/anuke/mindustry/AndroidRhinoContext.java @@ -0,0 +1,229 @@ +package io.anuke.mindustry; + +import android.annotation.*; +import android.os.*; +import com.android.dex.*; +import com.android.dx.cf.direct.*; +import com.android.dx.command.dexer.*; +import com.android.dx.dex.*; +import com.android.dx.dex.cf.*; +import com.android.dx.dex.file.DexFile; +import com.android.dx.merge.*; +import dalvik.system.*; +import io.anuke.arc.*; +import io.anuke.arc.backends.android.surfaceview.*; +import io.anuke.arc.util.ArcAnnotate.NonNull; +import io.anuke.arc.util.ArcAnnotate.Nullable; +import io.anuke.mindustry.AndroidRhinoContext.BaseAndroidClassLoader.*; +import org.mozilla.javascript.*; + +import java.io.*; +import java.nio.*; + +/** + * Helps to prepare a Rhino Context for usage on android. + * @author F43nd1r + * @since 11.01.2016 + */ +public class AndroidRhinoContext{ + + /** + * call this instead of {@link Context#enter()} + * @return a context prepared for android + */ + public static Context enterContext(File cacheDirectory){ + if(!SecurityController.hasGlobal()) + SecurityController.initGlobal(new SecurityController(){ + @Override + public GeneratedClassLoader createClassLoader(ClassLoader classLoader, Object o){ + return Context.getCurrentContext().createClassLoader(classLoader); + } + + @Override + public Object getDynamicSecurityDomain(Object o){ + return null; + } + }); + + AndroidContextFactory factory; + if(!ContextFactory.hasExplicitGlobal()){ + factory = new AndroidContextFactory(cacheDirectory); + ContextFactory.getGlobalSetter().setContextFactoryGlobal(factory); + }else if(!(ContextFactory.getGlobal() instanceof AndroidContextFactory)){ + throw new IllegalStateException("Cannot initialize factory for Android Rhino: There is already another factory"); + }else{ + factory = (AndroidContextFactory)ContextFactory.getGlobal(); + } + + return factory.enterContext(); + } + + /** + * Ensures that the classLoader used is correct + * @author F43nd1r + * @since 11.01.2016 + */ + public static class AndroidContextFactory extends ContextFactory{ + private final File cacheDirectory; + + /** + * Create a new factory. It will cache generated code in the given directory + * @param cacheDirectory the cache directory + */ + public AndroidContextFactory(File cacheDirectory){ + this.cacheDirectory = cacheDirectory; + initApplicationClassLoader(createClassLoader(AndroidContextFactory.class.getClassLoader())); + } + + /** + * Create a ClassLoader which is able to deal with bytecode + * @param parent the parent of the create classloader + * @return a new ClassLoader + */ + @Override + public BaseAndroidClassLoader createClassLoader(ClassLoader parent){ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + return new InMemoryAndroidClassLoader(parent); + } + return new FileAndroidClassLoader(parent, cacheDirectory); + } + + @Override + protected void onContextReleased(final Context cx){ + super.onContextReleased(cx); + ((BaseAndroidClassLoader)cx.getApplicationClassLoader()).reset(); + } + } + + /** + * Compiles java bytecode to dex bytecode and loads it + * @author F43nd1r + * @since 11.01.2016 + */ + abstract static class BaseAndroidClassLoader extends ClassLoader implements GeneratedClassLoader{ + + public BaseAndroidClassLoader(ClassLoader parent){ + super(parent); + } + + @Override + public Class defineClass(String name, byte[] data){ + try{ + DexOptions dexOptions = new DexOptions(); + DexFile dexFile = new DexFile(dexOptions); + DirectClassFile classFile = new DirectClassFile(data, name.replace('.', '/') + ".class", true); + classFile.setAttributeFactory(StdAttributeFactory.THE_ONE); + classFile.getMagic(); + DxContext context = new DxContext(); + dexFile.add(CfTranslator.translate(context, classFile, null, new CfOptions(), dexOptions, dexFile)); + Dex dex = new Dex(dexFile.toDex(null, false)); + Dex oldDex = getLastDex(); + if(oldDex != null){ + dex = new DexMerger(new Dex[]{dex, oldDex}, CollisionPolicy.KEEP_FIRST, context).merge(); + } + return loadClass(dex, name); + }catch(IOException | ClassNotFoundException e){ + throw new FatalLoadingException(e); + } + } + + protected abstract Class loadClass(Dex dex, String name) throws ClassNotFoundException; + + protected abstract Dex getLastDex(); + + protected abstract void reset(); + + @Override + public void linkClass(Class aClass){} + + @Override + public Class loadClass(String name, boolean resolve) + throws ClassNotFoundException{ + Class loadedClass = findLoadedClass(name); + if(loadedClass == null){ + Dex dex = getLastDex(); + if(dex != null){ + loadedClass = loadClass(dex, name); + } + if(loadedClass == null){ + loadedClass = getParent().loadClass(name); + } + } + return loadedClass; + } + + /** Might be thrown in any Rhino method that loads bytecode if the loading failed. */ + public static class FatalLoadingException extends RuntimeException{ + FatalLoadingException(Throwable t){ + super("Failed to define class", t); + } + } + + static class FileAndroidClassLoader extends BaseAndroidClassLoader{ + private static int instanceCounter = 0; + private final File dexFile; + + public FileAndroidClassLoader(ClassLoader parent, File cacheDir){ + super(parent); + int id = instanceCounter++; + dexFile = new File(cacheDir, id + ".dex"); + cacheDir.mkdirs(); + reset(); + } + + @Override + protected Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException{ + try{ + dex.writeTo(dexFile); + }catch(IOException e){ + e.printStackTrace(); + } + return new DexClassLoader(dexFile.getPath(), ((AndroidApplication)Core.app).getContext().getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name); + } + + @Nullable + @Override + protected Dex getLastDex(){ + if(dexFile.exists()){ + try{ + return new Dex(dexFile); + }catch(IOException e){ + e.printStackTrace(); + } + } + return null; + } + + @Override + protected void reset(){ + dexFile.delete(); + } + } + + @TargetApi(Build.VERSION_CODES.O) + static class InMemoryAndroidClassLoader extends BaseAndroidClassLoader{ + @Nullable private Dex last; + + public InMemoryAndroidClassLoader(ClassLoader parent){ + super(parent); + } + + @Override + protected Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException{ + last = dex; + return new InMemoryDexClassLoader(ByteBuffer.wrap(dex.getBytes()), getParent()).loadClass(name); + } + + @Nullable + @Override + protected Dex getLastDex(){ + return last; + } + + @Override + protected void reset(){ + last = null; + } + } + } +} diff --git a/android/src/io/anuke/mindustry/rhino/AndroidContextFactory.java b/android/src/io/anuke/mindustry/rhino/AndroidContextFactory.java deleted file mode 100644 index 318af0c2d4..0000000000 --- a/android/src/io/anuke/mindustry/rhino/AndroidContextFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.anuke.mindustry.rhino; - -import android.os.*; -import org.mozilla.javascript.*; - -import java.io.*; - -/** - * Ensures that the classLoader used is correct - * @author F43nd1r - * @since 11.01.2016 - */ -public class AndroidContextFactory extends ContextFactory{ - - private final File cacheDirectory; - - /** - * Create a new factory. It will cache generated code in the given directory - * @param cacheDirectory the cache directory - */ - public AndroidContextFactory(File cacheDirectory){ - this.cacheDirectory = cacheDirectory; - initApplicationClassLoader(createClassLoader(AndroidContextFactory.class.getClassLoader())); - } - - /** - * Create a ClassLoader which is able to deal with bytecode - * @param parent the parent of the create classloader - * @return a new ClassLoader - */ - @Override - public BaseAndroidClassLoader createClassLoader(ClassLoader parent){ - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - return new InMemoryAndroidClassLoader(parent); - } - return new FileAndroidClassLoader(parent, cacheDirectory); - } - - @Override - protected void onContextReleased(final Context cx){ - super.onContextReleased(cx); - ((BaseAndroidClassLoader)cx.getApplicationClassLoader()).reset(); - } -} diff --git a/android/src/io/anuke/mindustry/rhino/BaseAndroidClassLoader.java b/android/src/io/anuke/mindustry/rhino/BaseAndroidClassLoader.java deleted file mode 100644 index 83491956e8..0000000000 --- a/android/src/io/anuke/mindustry/rhino/BaseAndroidClassLoader.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.anuke.mindustry.rhino; - -import com.android.dex.*; -import com.android.dx.cf.direct.*; -import com.android.dx.command.dexer.*; -import com.android.dx.dex.*; -import com.android.dx.dex.cf.*; -import com.android.dx.dex.file.*; -import com.android.dx.merge.*; -import org.mozilla.javascript.*; - -import java.io.*; - -/** - * Compiles java bytecode to dex bytecode and loads it - * @author F43nd1r - * @since 11.01.2016 - */ -abstract class BaseAndroidClassLoader extends ClassLoader implements GeneratedClassLoader{ - - /** - * Create a new instance with the given parent classloader - * @param parent the parent - */ - public BaseAndroidClassLoader(ClassLoader parent){ - super(parent); - } - - /** - * {@inheritDoc} - */ - @Override - public Class defineClass(String name, byte[] data){ - try{ - DexOptions dexOptions = new DexOptions(); - DexFile dexFile = new DexFile(dexOptions); - DirectClassFile classFile = new DirectClassFile(data, name.replace('.', '/') + ".class", true); - classFile.setAttributeFactory(StdAttributeFactory.THE_ONE); - classFile.getMagic(); - DxContext context = new DxContext(); - dexFile.add(CfTranslator.translate(context, classFile, null, new CfOptions(), dexOptions, dexFile)); - Dex dex = new Dex(dexFile.toDex(null, false)); - Dex oldDex = getLastDex(); - if(oldDex != null){ - dex = new DexMerger(new Dex[]{dex, oldDex}, CollisionPolicy.KEEP_FIRST, context).merge(); - } - return loadClass(dex, name); - }catch(IOException | ClassNotFoundException e){ - throw new FatalLoadingException(e); - } - } - - protected abstract Class loadClass(Dex dex, String name) throws ClassNotFoundException; - - protected abstract Dex getLastDex(); - - protected abstract void reset(); - - /** - * Does nothing - * @param aClass ignored - */ - @Override - public void linkClass(Class aClass){ - //doesn't make sense on android - } - - /** - * Try to load a class. This will search all defined classes, all loaded jars and the parent class loader. - * @param name the name of the class to load - * @param resolve ignored - * @return the class - * @throws ClassNotFoundException if the class could not be found in any of the locations - */ - @Override - public Class loadClass(String name, boolean resolve) - throws ClassNotFoundException{ - Class loadedClass = findLoadedClass(name); - if(loadedClass == null){ - Dex dex = getLastDex(); - if(dex != null){ - loadedClass = loadClass(dex, name); - } - if(loadedClass == null){ - loadedClass = getParent().loadClass(name); - } - } - return loadedClass; - } - - /** - * Might be thrown in any Rhino method that loads bytecode if the loading failed - */ - public static class FatalLoadingException extends RuntimeException{ - FatalLoadingException(Throwable t){ - super("Failed to define class", t); - } - } -} diff --git a/android/src/io/anuke/mindustry/rhino/FileAndroidClassLoader.java b/android/src/io/anuke/mindustry/rhino/FileAndroidClassLoader.java deleted file mode 100644 index 4f16618e84..0000000000 --- a/android/src/io/anuke/mindustry/rhino/FileAndroidClassLoader.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.anuke.mindustry.rhino; - -import com.android.dex.*; -import dalvik.system.*; -import io.anuke.arc.*; -import io.anuke.arc.backends.android.surfaceview.*; -import io.anuke.arc.util.ArcAnnotate.*; - -import java.io.*; - -/** - * @author F43nd1r - * @since 24.10.2017 - */ -@SuppressWarnings("ResultOfMethodCallIgnored") -class FileAndroidClassLoader extends BaseAndroidClassLoader{ - private static int instanceCounter = 0; - private final File dexFile; - - /** - * Create a new instance with the given parent classloader - * @param parent the parent - */ - public FileAndroidClassLoader(ClassLoader parent, File cacheDir){ - super(parent); - int id = instanceCounter++; - dexFile = new File(cacheDir, id + ".dex"); - cacheDir.mkdirs(); - reset(); - } - - @Override - protected Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException{ - try{ - dex.writeTo(dexFile); - }catch(IOException e){ - e.printStackTrace(); - } - return new DexClassLoader(dexFile.getPath(), ((AndroidApplication)Core.app).getContext().getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name); - } - - @Nullable - @Override - protected Dex getLastDex(){ - if(dexFile.exists()){ - try{ - return new Dex(dexFile); - }catch(IOException e){ - e.printStackTrace(); - } - } - return null; - } - - @Override - protected void reset(){ - dexFile.delete(); - } -} diff --git a/android/src/io/anuke/mindustry/rhino/InMemoryAndroidClassLoader.java b/android/src/io/anuke/mindustry/rhino/InMemoryAndroidClassLoader.java deleted file mode 100644 index dd358d247f..0000000000 --- a/android/src/io/anuke/mindustry/rhino/InMemoryAndroidClassLoader.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.anuke.mindustry.rhino; - -import android.annotation.*; -import android.os.*; -import com.android.dex.*; -import dalvik.system.*; -import io.anuke.arc.util.ArcAnnotate.NonNull; -import io.anuke.arc.util.ArcAnnotate.Nullable; - -import java.nio.*; - -/** - * @author F43nd1r - * @since 24.10.2017 - */ - -@TargetApi(Build.VERSION_CODES.O) -class InMemoryAndroidClassLoader extends BaseAndroidClassLoader{ - @Nullable - private Dex last; - - public InMemoryAndroidClassLoader(ClassLoader parent){ - super(parent); - } - - @Override - protected Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException{ - last = dex; - return new InMemoryDexClassLoader(ByteBuffer.wrap(dex.getBytes()), getParent()).loadClass(name); - } - - @Nullable - @Override - protected Dex getLastDex(){ - return last; - } - - @Override - protected void reset(){ - last = null; - } -} diff --git a/android/src/io/anuke/mindustry/rhino/RhinoBuilder.java b/android/src/io/anuke/mindustry/rhino/RhinoBuilder.java deleted file mode 100644 index b6df496945..0000000000 --- a/android/src/io/anuke/mindustry/rhino/RhinoBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.anuke.mindustry.rhino; - -import org.mozilla.javascript.*; - -import java.io.*; - -/** - * Helps to prepare a Rhino Context for usage on android. - * @author F43nd1r - * @since 11.01.2016 - */ -public class RhinoBuilder{ - private final File cacheDirectory; - - /** - * Constructs a new helper using the default temporary directory. - * Note: It is recommended to use a custom directory, so no permission problems occur. - */ - public RhinoBuilder(){ - this(new File(System.getProperty("java.io.tmpdir", "."), "classes")); - } - - /** - * Constructs a new helper using a directory in the applications cache. - * @param context any context - */ - public RhinoBuilder(android.content.Context context){ - this(new File(context.getCacheDir(), "classes")); - } - - /** - * Constructs a helper using the specified directory as cache. - * @param cacheDirectory the cache directory to use - */ - public RhinoBuilder(File cacheDirectory){ - this.cacheDirectory = cacheDirectory; - } - - /** - * call this instead of {@link Context#enter()} - * @return a context prepared for android - */ - public Context enterContext(){ - if(!SecurityController.hasGlobal()) - SecurityController.initGlobal(new SecurityController(){ - @Override - public GeneratedClassLoader createClassLoader(ClassLoader classLoader, Object o){ - return Context.getCurrentContext().createClassLoader(classLoader); - } - - @Override - public Object getDynamicSecurityDomain(Object o){ - return null; - } - }); - return getContextFactory().enterContext(); - } - - /** - * @return The Context factory which has to be used on android. - */ - public AndroidContextFactory getContextFactory(){ - AndroidContextFactory factory; - if(!ContextFactory.hasExplicitGlobal()){ - factory = new AndroidContextFactory(cacheDirectory); - ContextFactory.getGlobalSetter().setContextFactoryGlobal(factory); - }else if(!(ContextFactory.getGlobal() instanceof AndroidContextFactory)){ - throw new IllegalStateException("Cannot initialize factory for Android Rhino: There is already another factory"); - }else{ - factory = (AndroidContextFactory)ContextFactory.getGlobal(); - } - return factory; - } - - /** - * @return a context prepared for android - * @deprecated use {@link #enterContext()} instead - */ - @Deprecated - public static Context prepareContext(){ - return new RhinoBuilder().enterContext(); - } -} diff --git a/gradle.properties b/gradle.properties index 7f9aee7e15..212385a749 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=b38d5e9c24d921f87f33bc48fcb65e245c1b1d0c +archash=190918590e8401b1686ecb9167e3c2a9e77eafaa