| /* |
| * 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.JillException; |
| import com.android.jill.Options; |
| import com.android.jill.backend.jayce.JayceWriter; |
| import com.android.jill.utils.FileUtils; |
| import com.android.sched.util.codec.CodecContext; |
| import com.android.sched.util.codec.MessageDigestCodec; |
| import com.android.sched.util.config.MessageDigestFactory; |
| import com.android.sched.util.file.CannotChangePermissionException; |
| import com.android.sched.util.file.CannotCloseException; |
| import com.android.sched.util.file.CannotCreateFileException; |
| import com.android.sched.util.file.CannotWriteException; |
| import com.android.sched.util.file.Directory; |
| import com.android.sched.util.file.FileAlreadyExistsException; |
| import com.android.sched.util.file.FileOrDirectory.ChangePermission; |
| import com.android.sched.util.file.FileOrDirectory.Existence; |
| import com.android.sched.util.file.FileOrDirectory.Permission; |
| import com.android.sched.util.file.NoSuchFileException; |
| import com.android.sched.util.file.NotDirectoryException; |
| import com.android.sched.util.file.NotFileException; |
| import com.android.sched.util.file.OutputZipFile; |
| import com.android.sched.util.file.OutputZipFile.Compression; |
| import com.android.sched.util.file.WrongPermissionException; |
| import com.android.sched.util.location.Location; |
| import com.android.sched.vfs.DeflateFS; |
| import com.android.sched.vfs.DirectFS; |
| import com.android.sched.vfs.GenericOutputVFS; |
| import com.android.sched.vfs.MessageDigestFS; |
| import com.android.sched.vfs.OutputVFS; |
| import com.android.sched.vfs.OutputVFile; |
| import com.android.sched.vfs.PrefixedFS; |
| import com.android.sched.vfs.VFS; |
| import com.android.sched.vfs.VPath; |
| import com.android.sched.vfs.WriteZipFS; |
| import com.android.sched.vfs.WrongVFSFormatException; |
| |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.tree.ClassNode; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.security.Provider.Service; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| |
| import javax.annotation.Nonnull; |
| |
| /** |
| * Transforms java binary files into jayce. |
| */ |
| public class JavaTransformer { |
| |
| @Nonnull |
| private static final String LIB_MAJOR_VERSION = "3"; |
| |
| @Nonnull |
| private static final String LIB_MINOR_VERSION = "2"; |
| |
| @Nonnull |
| private static final String JAYCE_MAJOR_VERSION = "4"; |
| |
| @Nonnull |
| private static final String JAYCE_MINOR_VERSION = "1"; |
| |
| @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 KEY_LIB_JAYCE_DIGEST = "lib.jayce.digest"; |
| |
| @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 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); |
| jackLibraryProperties.put(KEY_LIB_JAYCE_DIGEST, "true"); |
| } |
| |
| public void transform(@Nonnull List<File> javaBinaryFiles) { |
| |
| try (VFS baseVFS = getBaseOutputVFS()) { |
| try (OutputVFS outputVFS = wrapOutputVFS(baseVFS)) { |
| |
| for (File fileToTransform : javaBinaryFiles) { |
| try (FileInputStream fis = new FileInputStream(fileToTransform)) { |
| transformToVFS(fis, outputVFS); |
| } |
| } |
| dumpJackLibraryProperties(baseVFS); |
| } |
| } catch (IOException | CannotCloseException e) { |
| throw new JillException(e); |
| } |
| } |
| |
| public void transform(@Nonnull JarFile jarFile) { |
| |
| try (VFS baseVFS = getBaseOutputVFS()) { |
| try (OutputVFS outputVFS = wrapOutputVFS(baseVFS)) { |
| transformJavaFiles(jarFile, outputVFS); |
| } |
| dumpJackLibraryProperties(baseVFS); |
| } catch (IOException | CannotCloseException e) { |
| throw new JillException(e); |
| } |
| } |
| |
| @Nonnull |
| private VFS getBaseOutputVFS() { |
| VFS baseVFS; |
| try { |
| switch (options.getOutputContainer()) { |
| case DIR: |
| baseVFS = |
| new DirectFS( |
| new Directory(options.getOutput().getPath(), /* hooks = */ null, |
| Existence.MUST_EXIST, Permission.WRITE, ChangePermission.NOCHANGE), |
| Permission.WRITE); |
| break; |
| case ZIP: |
| baseVFS = |
| new WriteZipFS(new OutputZipFile(options.getOutput().getPath(), /* hooks = */ null, |
| Existence.MAY_EXIST, ChangePermission.NOCHANGE, Compression.UNCOMPRESSED)); |
| break; |
| default: |
| throw new AssertionError(); |
| } |
| } catch (NotFileException | FileAlreadyExistsException | CannotCreateFileException |
| | CannotChangePermissionException | WrongPermissionException | NoSuchFileException |
| | NotDirectoryException e) { |
| throw new JillException(e); |
| } |
| return baseVFS; |
| } |
| |
| @Nonnull |
| private OutputVFS wrapOutputVFS(@Nonnull VFS baseVFS) { |
| MessageDigestCodec mdCodec = new MessageDigestCodec(); |
| Service service = mdCodec.parseString(new CodecContext(), "SHA"); |
| |
| try { |
| return new GenericOutputVFS(new DeflateFS(new MessageDigestFS( |
| new PrefixedFS(baseVFS, new VPath("jayce", '/')), new MessageDigestFactory(service)))); |
| } catch (NotDirectoryException | CannotCreateFileException | WrongVFSFormatException e) { |
| throw new JillException(e); |
| } |
| } |
| |
| private void dumpJackLibraryProperties(@Nonnull VFS baseVFS) { |
| try { |
| @SuppressWarnings("resource") |
| OutputVFS goVFS = new GenericOutputVFS(baseVFS); |
| OutputVFile libraryPropertiesOut = |
| goVFS.getRootOutputVDir().createOutputVFile(new VPath(JACK_LIBRARY_PROPERTIES, '/')); |
| try (OutputStream os = libraryPropertiesOut.getOutputStream()) { |
| jackLibraryProperties.store(os, "Library Properties"); |
| } catch (IOException e) { |
| throw new CannotCloseException(libraryPropertiesOut, e); |
| } |
| } catch (CannotCreateFileException | WrongPermissionException | CannotCloseException e) { |
| throw new JillException(e); |
| } |
| } |
| |
| private void transformJavaFiles(@Nonnull JarFile jarFile, @Nonnull OutputVFS outputVFS) |
| 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); |
| transformToVFS(is, outputVFS); |
| } |
| } |
| } |
| } |
| |
| private void transformToVFS(@Nonnull InputStream is, @Nonnull OutputVFS outputVFS) |
| throws IOException { |
| ClassNode cn = getClassNode(is); |
| VPath outputPath = getVPath(cn.name); |
| try { |
| OutputVFile vFile = outputVFS.getRootOutputVDir().createOutputVFile(outputPath); |
| try (OutputStream os = vFile.getOutputStream()) { |
| transform(cn, os, vFile.getLocation()); |
| } catch (IOException e) { |
| throw new CannotCloseException(vFile.getLocation(), e); |
| } |
| } catch (CannotCreateFileException | WrongPermissionException | CannotCloseException |
| | CannotWriteException e) { |
| throw new JillException(e); |
| } |
| } |
| |
| private void transform(@Nonnull ClassNode cn, @Nonnull OutputStream os, |
| @Nonnull Location location) throws CannotWriteException { |
| |
| JayceWriter writer = createWriter(os); |
| |
| ClassNodeWriter asm2jayce = |
| new ClassNodeWriter(writer, new SourceInfoWriter(writer), options); |
| |
| try { |
| asm2jayce.write(cn); |
| |
| writer.flush(); |
| } catch (IOException e) { |
| throw new CannotWriteException(location, e); |
| } |
| } |
| |
| private JayceWriter createWriter(@Nonnull OutputStream os) { |
| JayceWriter writer = new JayceWriter(os); |
| setJayceProperties(); |
| return writer; |
| } |
| |
| @Nonnull |
| private static VPath getVPath(@Nonnull String typeBinaryName) { |
| return new VPath(typeBinaryName + JAYCE_FILE_EXTENSION, TYPE_NAME_SEPARATOR); |
| } |
| |
| @Nonnull |
| private ClassNode getClassNode(@Nonnull InputStream is) throws IOException { |
| try { |
| ClassReader cr = new ClassReader(is); |
| ClassNode cn = new ClassNode(); |
| cr.accept(cn, |
| ClassReader.SKIP_FRAMES | (options.isEmitDebugInfo() ? 0 : ClassReader.SKIP_DEBUG)); |
| return cn; |
| } catch (IllegalArgumentException e) { |
| // It means that class files come from an unsupported Java version |
| throw new JillException("class files coming from an unsupported Java version", e); |
| } |
| } |
| |
| 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); |
| } |
| } |