/* * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. * Copyright (C) 2013 Minecraft Forge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ package net.minecraftforge.gradle.tasks; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipInputStream; import net.minecraftforge.gradle.common.Constants; import net.minecraftforge.gradle.util.caching.Cached; import net.minecraftforge.gradle.util.caching.CachedTask; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.ParallelizableTask; import org.gradle.api.tasks.TaskAction; import com.google.common.collect.Maps; import com.google.common.io.ByteStreams; @ParallelizableTask public abstract class AbstractEditJarTask extends CachedTask { @InputFile private Object inJar; @Cached @OutputFile private Object outJar; protected File resolvedInJar; protected File resolvedOutJar; @TaskAction public void doTask() throws Throwable { resolvedInJar = getInJar(); resolvedOutJar = getOutJar(); doStuffBefore(); if (storeJarInRam()) { getLogger().debug("Reading jar: " + resolvedInJar); Map<String, String> sourceMap = Maps.newHashMap(); Map<String, byte[]> resourceMap = Maps.newHashMap(); readAndStoreJarInRam(resolvedInJar, sourceMap, resourceMap); doStuffMiddle(sourceMap, resourceMap); saveJar(resolvedOutJar, sourceMap, resourceMap); getLogger().debug("Saving jar: " + resolvedOutJar); } else { copyJar(resolvedInJar, resolvedOutJar); } doStuffAfter(); } /** * Do Stuff before the jar is read * @throws Exception for convenience */ public abstract void doStuffBefore() throws Exception; /** * Called as the .java files of the jar are read from the jar * @param name name of the current entry * @param file current contents of the entry * @return new new contents of the file * @throws Exception as a convenience for any potential exceptions thrown in this method */ public abstract String asRead(String name, String file) throws Exception; /** * Do Stuff after the jar is read, but before it is written. * @param sourceMap name->contents for all java files in the jar * @param resourceMap name->contents for everything else * @throws Exception for convenience */ public abstract void doStuffMiddle(Map<String, String> sourceMap, Map<String, byte[]> resourceMap) throws Exception; /** * Do Stuff after the jar is Written * @throws Exception for convenience */ public abstract void doStuffAfter() throws Exception; /** * Whether to store the contents of the jar in RAM. * If this returns false, then the doStuffMiddle method is not called. * @return store jar in ram */ protected abstract boolean storeJarInRam(); private final void readAndStoreJarInRam(File jar, Map<String, String> sourceMap, Map<String, byte[]> resourceMap) throws Exception { ZipInputStream zin = new ZipInputStream(new FileInputStream(jar)); ZipEntry entry = null; String fileStr; while ((entry = zin.getNextEntry()) != null) { // ignore META-INF, it shouldnt be here. If it is we remove it from the output jar. if (entry.getName().contains("META-INF")) { continue; } // resources or directories. if (entry.isDirectory() || (!entry.getName().endsWith(".java") && !entry.getName().endsWith(".scala") // scala files && !entry.getName().endsWith(".groovy") // groovy files && !entry.getName().endsWith(".kt") // kotlin files )) { resourceMap.put(entry.getName(), ByteStreams.toByteArray(zin)); } else { // source! fileStr = new String(ByteStreams.toByteArray(zin), Constants.CHARSET); fileStr = asRead(entry.getName(), fileStr); sourceMap.put(entry.getName(), fileStr); } } zin.close(); } protected static void saveJar(File output, Map<String, String> sourceMap, Map<String, byte[]> resourceMap) throws IOException { output.getParentFile().mkdirs(); JarOutputStream zout = new JarOutputStream(new FileOutputStream(output)); // write in resources for (Map.Entry<String, byte[]> entry : resourceMap.entrySet()) { zout.putNextEntry(new JarEntry(entry.getKey())); zout.write(entry.getValue()); zout.closeEntry(); } // write in sources for (Map.Entry<String, String> entry : sourceMap.entrySet()) { zout.putNextEntry(new JarEntry(entry.getKey())); zout.write(entry.getValue().getBytes()); zout.closeEntry(); } zout.close(); } private void copyJar(File input, File output) throws Exception { // begin reading jar ZipInputStream zin = new ZipInputStream(new FileInputStream(input)); JarOutputStream zout = new JarOutputStream(new FileOutputStream(output)); ZipEntry entry = null; while ((entry = zin.getNextEntry()) != null) { // no META or dirs. wel take care of dirs later. if (entry.getName().contains("META-INF")) { continue; } // resources or directories. try { if (entry.isDirectory() || !entry.getName().endsWith(".java")) { zout.putNextEntry(new JarEntry(entry)); ByteStreams.copy(zin, zout); zout.closeEntry(); } else { // source zout.putNextEntry(new JarEntry(entry.getName())); zout.write(asRead(entry.getName(), new String(ByteStreams.toByteArray(zin), Constants.CHARSET)).getBytes()); zout.closeEntry(); } } catch (ZipException ex) { getLogger().debug("Duplicate zip entry " + entry.getName() + " in " + input + " writing " + output); } } zout.close(); zin.close(); } public File getInJar() { return getProject().file(inJar); } public void setInJar(Object inJar) { this.inJar = inJar; } public File getOutJar() { return getProject().file(outJar); } public void setOutJar(Object outJar) { this.outJar = outJar; } }