blob: ad6a6f00910024fa1bff4e0c8ab0ccbcd5524ace [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 com.android.jill.frontend.java;
import com.android.jill.ContainerType;
import com.android.jill.JillException;
import com.android.jill.Options;
import com.android.jill.backend.jayce.JayceWriter;
import com.android.jill.utils.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Transforms java binary files into jayce.
*/
public class JavaTransformer {
@Nonnull
private static final String LIB_MAJOR_VERSION = "1";
@Nonnull
private static final String LIB_MINOR_VERSION = "1";
@Nonnull
private static final String JAYCE_MAJOR_VERSION = "2";
@Nonnull
private static final String JAYCE_MINOR_VERSION = "15";
@Nonnull
private static final String KEY_LIB_MAJOR_VERSION = "lib.version.major";
@Nonnull
private static final String KEY_LIB_MINOR_VERSION = "lib.version.minor";
@Nonnull
private static final String KEY_LIB_EMITTER = "lib.emitter";
@Nonnull
private static final String KEY_LIB_EMITTER_VERSION = "lib.emitter.version";
@Nonnull
private static final String KEY_JAYCE = "jayce";
@Nonnull
private static final String KEY_JAYCE_MAJOR_VERSION = "jayce.version.major";
@Nonnull
private static final String KEY_JAYCE_MINOR_VERSION = "jayce.version.minor";
@Nonnull
private static final String JACK_LIBRARY_PROPERTIES = "jack.properties";
@Nonnull
private final String version;
private final Options options;
@Nonnull
private static final String JAYCE_FILE_EXTENSION = ".jayce";
@Nonnull
private static final String JAYCE_PREFIX_INTO_LIB = "jayce";
@Nonnull
private static final char TYPE_NAME_SEPARATOR = '/';
@Nonnull
private final Properties jackLibraryProperties;
public JavaTransformer(@Nonnull String version, @Nonnull Options options) {
this.version = version;
this.options = options;
jackLibraryProperties = new Properties();
jackLibraryProperties.put(KEY_LIB_EMITTER, "jill");
jackLibraryProperties.put(KEY_LIB_EMITTER_VERSION, version);
jackLibraryProperties.put(KEY_LIB_MAJOR_VERSION, LIB_MAJOR_VERSION);
jackLibraryProperties.put(KEY_LIB_MINOR_VERSION, LIB_MINOR_VERSION);
}
public void transform(@Nonnull List<File> javaBinaryFiles) {
ZipOutputStream zos = null;
try {
if (options.getOutputContainer() == ContainerType.ZIP) {
zos = new ZipOutputStream(new FileOutputStream(options.getOutput()));
for (File fileToTransform : javaBinaryFiles) {
FileInputStream fis = new FileInputStream(fileToTransform);
try {
transformToZip(fis, zos, null);
} catch (DuplicateJackFileException e) {
System.err.println(e.getMessage());
} finally {
fis.close();
}
}
} else {
for (File fileToTransform : javaBinaryFiles) {
FileInputStream fis = new FileInputStream(fileToTransform);
try {
transformToDir(fis, options.getOutput());
} catch (DuplicateJackFileException e) {
System.err.println(e.getMessage());
} finally {
fis.close();
}
}
}
dumpJackLibraryProperties(zos);
} catch (IOException e) {
throw new JillException("Transformation failure.", e);
} finally {
if (zos != null) {
try {
zos.close();
} catch (IOException e) {
throw new JillException("Error closing zip.", e);
}
}
}
}
public void transform(@Nonnull JarFile jarFile) {
ZipOutputStream zos = null;
try {
if (options.getOutputContainer() == ContainerType.ZIP) {
zos = new ZipOutputStream(new FileOutputStream(options.getOutput()));
}
transformJavaFiles(jarFile, zos);
dumpJackLibraryProperties(zos);
} catch (Exception e) {
throw new JillException("Failed to transform " + jarFile.getName(), e);
} finally {
if (zos != null) {
try {
zos.close();
} catch (IOException e) {
throw new JillException("Error closing zip.", e);
}
}
}
}
private void dumpJackLibraryProperties(@CheckForNull ZipOutputStream zos) {
if (zos != null) {
dumpPropertiesToZip(zos, jackLibraryProperties);
} else {
dumpPropertiesToFile(new File(options.getOutput(), JACK_LIBRARY_PROPERTIES),
jackLibraryProperties);
}
}
private void dumpPropertiesToZip(@Nonnull ZipOutputStream zos,
@Nonnull Properties libraryProperties) {
try {
ZipEntry entry = new ZipEntry(JACK_LIBRARY_PROPERTIES);
zos.putNextEntry(entry);
libraryProperties.store(zos, "Library Properties");
} catch (IOException e) {
throw new JillException("Error writing '" + JACK_LIBRARY_PROPERTIES + "' to output zip", e);
}
}
private void dumpPropertiesToFile(@Nonnull File outputFile,
@Nonnull Properties libraryProperties) {
File outputDir = options.getOutput();
File libraryPropertiesFile = new File(outputDir, JACK_LIBRARY_PROPERTIES);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(libraryPropertiesFile);
libraryProperties.store(fos, "Library Properties");
} catch (IOException e) {
throw new JillException(
"Error writing '" + JACK_LIBRARY_PROPERTIES + "' to " + outputFile.getAbsolutePath(), e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new JillException("Error closing output " + outputFile.getAbsolutePath(), e);
}
}
}
}
private void transformJavaFiles(@Nonnull JarFile jarFile, @CheckForNull ZipOutputStream zos)
throws IOException {
final Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();
String name = entry.getName();
if (FileUtils.isJavaBinaryFile(name)) {
JarEntry fileEntry = jarFile.getJarEntry(name);
if (!fileEntry.isDirectory()) {
InputStream is = jarFile.getInputStream(fileEntry);
try {
if (zos != null) {
assert options.getOutputContainer() == ContainerType.ZIP;
transformToZip(is, zos, jarFile);
} else {
assert options.getOutputContainer() == ContainerType.DIR;
transformToDir(is, options.getOutput());
}
} catch (DuplicateJackFileException e) {
System.err.println(e.getMessage());
}
}
}
}
}
private void transformToZip(@Nonnull InputStream is, @Nonnull ZipOutputStream zipOutputStream,
@CheckForNull JarFile jarFile) throws IOException, DuplicateJackFileException {
ClassNode cn = getClassNode(is);
String filePath = getFilePath(cn.name);
if (jarFile != null && jarFile.getEntry(filePath) != null) {
throw new DuplicateJackFileException("Jack file '" + filePath
+ "' was already copied as a resource to archive '" + options.getOutput()
+ "' and thus won't be retransformed from class file.");
}
try {
ZipEntry entry = new ZipEntry(filePath);
zipOutputStream.putNextEntry(entry);
transform(cn, zipOutputStream);
} catch (IOException e) {
throw new JillException("Error writing to output zip", e);
}
}
private void transformToDir(@Nonnull InputStream is, @Nonnull File outputDir)
throws IOException, DuplicateJackFileException {
ClassNode cn = getClassNode(is);
String filePath = getFilePath(cn.name);
File outputFile = new File(outputDir, filePath);
if (outputFile.exists()) {
throw new DuplicateJackFileException("Jack file '" + outputFile.getAbsolutePath()
+ "' was already copied as a resource and thus won't be retransformed from class file.");
}
FileOutputStream fos = null;
try {
createParentDirectories(outputFile);
fos = new FileOutputStream(outputFile);
transform(cn, fos);
} catch (IOException e) {
throw new JillException("Unable to create output file " + outputFile.getName(), e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new JillException("Error closing output " + outputFile.getAbsolutePath(), e);
}
}
}
}
private void transform(@Nonnull ClassNode cn, @Nonnull OutputStream os) throws IOException {
JayceWriter writer = createWriter(os);
ClassNodeWriter asm2jayce =
new ClassNodeWriter(writer, new SourceInfoWriter(writer), options);
asm2jayce.write(cn);
writer.flush();
}
private void createParentDirectories(File outputFile) throws IOException {
File parentFile = outputFile.getParentFile();
if (!parentFile.exists() && !parentFile.mkdirs()) {
throw new IOException("Could not create directory \"" + parentFile.getName() + "\"");
}
}
private JayceWriter createWriter(@Nonnull OutputStream os) {
JayceWriter writer = new JayceWriter(os);
setJayceProperties();
return writer;
}
@Nonnull
private static String getFilePath(@Nonnull String typeBinaryName) {
return JAYCE_PREFIX_INTO_LIB + File.separatorChar
+ typeBinaryName.replace(TYPE_NAME_SEPARATOR, File.separatorChar) + JAYCE_FILE_EXTENSION;
}
@Nonnull
private ClassNode getClassNode(@Nonnull InputStream is) throws IOException {
ClassReader cr = new ClassReader(is);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.SKIP_FRAMES
| (options.isEmitDebugInfo() ? 0 : ClassReader.SKIP_DEBUG));
return cn;
}
private void setJayceProperties() {
jackLibraryProperties.put(KEY_JAYCE, String.valueOf(true));
jackLibraryProperties.put(KEY_JAYCE_MAJOR_VERSION, JAYCE_MAJOR_VERSION);
jackLibraryProperties.put(KEY_JAYCE_MINOR_VERSION, JAYCE_MINOR_VERSION);
}
}