| /* |
| * Copyright 2012, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.jf.dexlib2; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.AbstractIterator; |
| import com.google.common.collect.Lists; |
| import org.jf.dexlib2.dexbacked.DexBackedDexFile; |
| import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile; |
| import org.jf.dexlib2.dexbacked.DexBackedOdexFile; |
| import org.jf.dexlib2.dexbacked.OatFile; |
| import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException; |
| import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; |
| import org.jf.dexlib2.iface.DexFile; |
| import org.jf.dexlib2.writer.pool.DexPool; |
| import org.jf.util.ExceptionWithContext; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import java.io.*; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| public final class DexFileFactory { |
| @Nonnull |
| public static DexBackedDexFile loadDexFile(@Nonnull String path) throws IOException { |
| return loadDexFile(new File(path), Opcodes.forApi(15)); |
| } |
| |
| @Nonnull |
| public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException { |
| return loadDexFile(new File(path), opcodes); |
| } |
| |
| @Nonnull |
| public static DexBackedDexFile loadDexFile(@Nonnull File file) throws IOException { |
| return loadDexFile(file, Opcodes.forApi(15)); |
| } |
| |
| /** |
| * Loads a dex/apk/odex/oat file. |
| * |
| * For oat files with multiple dex files, the first will be opened. For zip/apk files, the "classes.dex" entry |
| * will be opened. |
| * |
| * @param file The file to open |
| * @param opcodes The set of opcodes to use |
| * @return A DexBackedDexFile for the given file |
| * |
| * @throws UnsupportedOatVersionException If file refers to an unsupported oat file |
| * @throws DexFileNotFoundException If file does not exist, if file is a zip file but does not have a "classes.dex" |
| * entry, or if file is an oat file that has no dex entries. |
| * @throws UnsupportedFileTypeException If file is not a valid dex/zip/odex/oat file, or if the "classes.dex" entry |
| * in a zip file is not a valid dex file |
| */ |
| @Nonnull |
| public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException { |
| if (!file.exists()) { |
| throw new DexFileNotFoundException("%s does not exist", file.getName()); |
| } |
| |
| ZipFile zipFile = null; |
| try { |
| zipFile = new ZipFile(file); |
| } catch (IOException ex) { |
| // ignore and continue |
| } |
| |
| if (zipFile != null) { |
| try { |
| return new ZipDexEntryFinder(zipFile, opcodes).findEntry("classes.dex", true); |
| } finally { |
| zipFile.close(); |
| } |
| } |
| |
| InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); |
| try { |
| try { |
| return DexBackedDexFile.fromInputStream(opcodes, inputStream); |
| } catch (DexBackedDexFile.NotADexFile ex) { |
| // just eat it |
| } |
| |
| try { |
| return DexBackedOdexFile.fromInputStream(opcodes, inputStream); |
| } catch (DexBackedOdexFile.NotAnOdexFile ex) { |
| // just eat it |
| } |
| |
| // Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream |
| // back to the same position, if they fails |
| |
| OatFile oatFile = null; |
| try { |
| oatFile = OatFile.fromInputStream(inputStream); |
| } catch (NotAnOatFileException ex) { |
| // just eat it |
| } |
| |
| if (oatFile != null) { |
| if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) { |
| throw new UnsupportedOatVersionException(oatFile); |
| } |
| |
| List<OatDexFile> oatDexFiles = oatFile.getDexFiles(); |
| |
| if (oatDexFiles.size() == 0) { |
| throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName()); |
| } |
| |
| return oatDexFiles.get(0); |
| } |
| } finally { |
| inputStream.close(); |
| } |
| |
| throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath()); |
| } |
| |
| /** |
| * Loads a dex entry from a container format (zip/oat) |
| * |
| * This has two modes of operation, depending on the exactMatch parameter. When exactMatch is true, it will only |
| * load an entry whose name exactly matches that provided by the dexEntry parameter. |
| * |
| * When exactMatch is false, then it will search for any entry that dexEntry is a path suffix of. "path suffix" |
| * meaning all the path components in dexEntry must fully match the corresponding path components in the entry name, |
| * but some path components at the beginning of entry name can be missing. |
| * |
| * For example, if an oat file contains a "/system/framework/framework.jar:classes2.dex" entry, then the following |
| * will match (not an exhaustive list): |
| * |
| * "/system/framework/framework.jar:classes2.dex" |
| * "system/framework/framework.jar:classes2.dex" |
| * "framework/framework.jar:classes2.dex" |
| * "framework.jar:classes2.dex" |
| * "classes2.dex" |
| * |
| * Note that partial path components specifically don't match. So something like "work/framework.jar:classes2.dex" |
| * would not match. |
| * |
| * If dexEntry contains an initial slash, it will be ignored for purposes of this suffix match -- but not when |
| * performing an exact match. |
| * |
| * If multiple entries match the given dexEntry, a MultipleMatchingDexEntriesException will be thrown |
| * |
| * @param file The container file. This must be either a zip (apk) file or an oat file. |
| * @param dexEntry The name of the entry to load. This can either be the exact entry name, if exactMatch is true, |
| * or it can be a path suffix. |
| * @param exactMatch If true, dexE |
| * @param opcodes The set of opcodes to use |
| * @return A DexBackedDexFile for the given entry |
| * |
| * @throws UnsupportedOatVersionException If file refers to an unsupported oat file |
| * @throws DexFileNotFoundException If the file does not exist, or if no matching entry could be found |
| * @throws UnsupportedFileTypeException If file is not a valid zip/oat file, or if the matching entry is not a |
| * valid dex file |
| * @throws MultipleMatchingDexEntriesException If multiple entries match the given dexEntry |
| */ |
| public static DexBackedDexFile loadDexEntry(@Nonnull File file, @Nonnull String dexEntry, |
| boolean exactMatch, @Nonnull Opcodes opcodes) throws IOException { |
| if (!file.exists()) { |
| throw new DexFileNotFoundException("Container file %s does not exist", file.getName()); |
| } |
| |
| ZipFile zipFile = null; |
| try { |
| zipFile = new ZipFile(file); |
| } catch (IOException ex) { |
| // ignore and continue |
| } |
| |
| if (zipFile != null) { |
| try { |
| return new ZipDexEntryFinder(zipFile, opcodes).findEntry(dexEntry, exactMatch); |
| } finally { |
| zipFile.close(); |
| } |
| } |
| |
| InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); |
| try { |
| OatFile oatFile = null; |
| try { |
| oatFile = OatFile.fromInputStream(inputStream); |
| } catch (NotAnOatFileException ex) { |
| // just eat it |
| } |
| |
| if (oatFile != null) { |
| if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) { |
| throw new UnsupportedOatVersionException(oatFile); |
| } |
| |
| List<OatDexFile> oatDexFiles = oatFile.getDexFiles(); |
| |
| if (oatDexFiles.size() == 0) { |
| throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName()); |
| } |
| |
| return new OatDexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch); |
| } |
| } finally { |
| inputStream.close(); |
| } |
| |
| throw new UnsupportedFileTypeException("%s is not an apk or oat file.", file.getPath()); |
| } |
| |
| /** |
| * Loads all dex files from the given file. |
| * |
| * If the given file is a dex or odex file, it will return an iterable with one element. If the given file is |
| * an oat file, it will return all dex files within the oat file. If the given file is a zip file, it will return |
| * all dex files matching "classes[0-9]*.dex" |
| * |
| * @param file The file to open |
| * @param opcodes The set of opcodes to use |
| * @return An iterable of DexBackedDexFiles |
| * @throws IOException |
| * @throws DexFileNotFoundException If the given file does not exist |
| * @throws UnsupportedFileTypeException If the given file is not a zip or oat file |
| */ |
| public static Iterable<? extends DexBackedDexFile> loadAllDexFiles( |
| @Nonnull File file, @Nonnull final Opcodes opcodes) throws IOException { |
| if (!file.exists()) { |
| throw new DexFileNotFoundException("%s does not exist", file.getName()); |
| } |
| |
| ZipFile zipFile = null; |
| try { |
| zipFile = new ZipFile(file); |
| } catch (IOException ex) { |
| // ignore and continue |
| } |
| |
| if (zipFile != null) { |
| final Pattern dexPattern = Pattern.compile("classes[0-9]*.dex"); |
| final ZipFile finalZipFile = zipFile; |
| |
| return new Iterable<DexBackedDexFile>() { |
| @Override public Iterator<DexBackedDexFile> iterator() { |
| final Enumeration<? extends ZipEntry> entries = finalZipFile.entries(); |
| return new AbstractIterator<DexBackedDexFile>() { |
| @Override protected DexBackedDexFile computeNext() { |
| while (entries.hasMoreElements()) { |
| ZipEntry zipEntry = entries.nextElement(); |
| if (dexPattern.matcher(zipEntry.getName()).matches()) { |
| try { |
| return loadDexFromZip(finalZipFile, zipEntry, opcodes); |
| } catch (IOException ex) { |
| throw new ExceptionWithContext(ex, "Error while reading %s from %s", |
| zipEntry.getName(), finalZipFile.getName()); |
| } catch (NotADexFile ex) { |
| // ignore and continue |
| } |
| } |
| } |
| endOfData(); |
| return null; |
| } |
| }; |
| } |
| }; |
| } |
| |
| InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); |
| try { |
| try { |
| return Lists.newArrayList(DexBackedDexFile.fromInputStream(opcodes, inputStream)); |
| } catch (DexBackedDexFile.NotADexFile ex) { |
| // just eat it |
| } |
| |
| try { |
| return Lists.newArrayList(DexBackedOdexFile.fromInputStream(opcodes, inputStream)); |
| } catch (DexBackedOdexFile.NotAnOdexFile ex) { |
| // just eat it |
| } |
| |
| // Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream |
| // back to the same position, if they fails |
| |
| OatFile oatFile = null; |
| try { |
| oatFile = OatFile.fromInputStream(inputStream); |
| } catch (NotAnOatFileException ex) { |
| // just eat it |
| } |
| |
| if (oatFile != null) { |
| if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) { |
| throw new UnsupportedOatVersionException(oatFile); |
| } |
| |
| return oatFile.getDexFiles(); |
| } |
| } finally { |
| inputStream.close(); |
| } |
| |
| throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath()); |
| } |
| |
| /** |
| * Gets all dex entries from an oat/zip file. |
| * |
| * For zip files, only entries that match classes[0-9]*.dex will be returned. |
| * |
| * @param file The file to get dex entries from |
| |
| * @return A list of strings contains the dex entry names |
| * @throws IOException |
| * @throws DexFileNotFoundException If the given file does not exist |
| * @throws UnsupportedFileTypeException If the given file is not a zip or oat file |
| */ |
| public static List<String> getAllDexEntries(@Nonnull File file) throws IOException { |
| if (!file.exists()) { |
| throw new DexFileNotFoundException("%s does not exist", file.getName()); |
| } |
| |
| List<String> entries = Lists.newArrayList(); |
| Opcodes opcodes = Opcodes.forApi(15); |
| |
| ZipFile zipFile = null; |
| try { |
| zipFile = new ZipFile(file); |
| } catch (IOException ex) { |
| // ignore and continue |
| } |
| |
| if (zipFile != null) { |
| Pattern dexPattern = Pattern.compile("classes[0-9]*.dex"); |
| Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); |
| |
| while (zipEntries.hasMoreElements()) { |
| ZipEntry zipEntry = zipEntries.nextElement(); |
| if (dexPattern.matcher(zipEntry.getName()).matches()) { |
| try { |
| loadDexFromZip(zipFile, zipEntry, opcodes); |
| entries.add(zipEntry.getName()); |
| } catch (IOException ex) { |
| throw new IOException(String.format("Error while reading %s from %s", |
| zipEntry.getName(), zipFile.getName()), ex); |
| }catch (NotADexFile ex) { |
| // ignore and continue |
| } |
| } |
| } |
| return entries; |
| } |
| |
| InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); |
| try { |
| OatFile oatFile = null; |
| try { |
| oatFile = OatFile.fromInputStream(inputStream); |
| } catch (NotAnOatFileException ex) { |
| // just eat it |
| } |
| |
| if (oatFile != null) { |
| if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) { |
| throw new UnsupportedOatVersionException(oatFile); |
| } |
| |
| for (OatDexFile oatDexFile: oatFile.getDexFiles()) { |
| entries.add(oatDexFile.filename); |
| } |
| return entries; |
| } |
| } finally { |
| inputStream.close(); |
| } |
| |
| throw new UnsupportedFileTypeException("%s is not an apk, oat file.", file.getPath()); |
| } |
| |
| /** |
| * Writes a DexFile out to disk |
| * |
| * @param path The path to write the dex file to |
| * @param dexFile a Dexfile to write |
| * @throws IOException |
| */ |
| public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException { |
| DexPool.writeTo(path, dexFile); |
| } |
| |
| private DexFileFactory() {} |
| |
| public static class DexFileNotFoundException extends ExceptionWithContext { |
| public DexFileNotFoundException(@Nullable String message, Object... formatArgs) { |
| super(message, formatArgs); |
| } |
| } |
| |
| public static class UnsupportedOatVersionException extends ExceptionWithContext { |
| @Nonnull public final OatFile oatFile; |
| |
| public UnsupportedOatVersionException(@Nonnull OatFile oatFile) { |
| super("Unsupported oat version: %d", oatFile.getOatVersion()); |
| this.oatFile = oatFile; |
| } |
| } |
| |
| public static class MultipleMatchingDexEntriesException extends ExceptionWithContext { |
| public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) { |
| super(String.format(message, formatArgs)); |
| } |
| } |
| |
| public static class UnsupportedFileTypeException extends ExceptionWithContext { |
| public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) { |
| super(String.format(message, formatArgs)); |
| } |
| } |
| |
| /** |
| * Matches two entries fully, ignoring any initial slash, if any |
| */ |
| private static boolean fullEntryMatch(@Nonnull String entry, @Nonnull String targetEntry) { |
| if (entry.equals(targetEntry)) { |
| return true; |
| } |
| |
| if (entry.charAt(0) == '/') { |
| entry = entry.substring(1); |
| } |
| |
| if (targetEntry.charAt(0) == '/') { |
| targetEntry = targetEntry.substring(1); |
| } |
| |
| return entry.equals(targetEntry); |
| } |
| |
| /** |
| * Performs a partial match against entry and targetEntry. |
| * |
| * This is considered a partial match if targetEntry is a suffix of entry, and if the suffix starts |
| * on a path "part" (ignoring the initial separator, if any). Both '/' and ':' are considered separators for this. |
| * |
| * So entry="/blah/blah/something.dex" and targetEntry="lah/something.dex" shouldn't match, but |
| * both targetEntry="blah/something.dex" and "/blah/something.dex" should match. |
| */ |
| private static boolean partialEntryMatch(String entry, String targetEntry) { |
| if (entry.equals(targetEntry)) { |
| return true; |
| } |
| |
| if (!entry.endsWith(targetEntry)) { |
| return false; |
| } |
| |
| // Make sure the first matching part is a full entry. We don't want to match "/blah/blah/something.dex" with |
| // "lah/something.dex", but both "/blah/something.dex" and "blah/something.dex" should match |
| char precedingChar = entry.charAt(entry.length() - targetEntry.length() - 1); |
| char firstTargetChar = targetEntry.charAt(0); |
| // This is a device path, so we should always use the linux separator '/', rather than the current platform's |
| // separator |
| return firstTargetChar == ':' || firstTargetChar == '/' || precedingChar == ':' || precedingChar == '/'; |
| } |
| |
| protected abstract static class DexEntryFinder { |
| @Nullable |
| protected abstract DexBackedDexFile getEntry(@Nonnull String entry) throws IOException; |
| |
| @Nonnull |
| protected abstract List<String> getEntryNames(); |
| |
| @Nonnull |
| protected abstract String getFilename(); |
| |
| @Nonnull |
| public DexBackedDexFile findEntry(@Nonnull String targetEntry, boolean exactMatch) throws IOException { |
| if (exactMatch) { |
| DexBackedDexFile dexFile = getEntry(targetEntry); |
| if (dexFile == null) { |
| if (getEntryNames().contains(targetEntry)) { |
| throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry, |
| getFilename()); |
| } else { |
| throw new DexFileNotFoundException("Could not find %s in %s.", targetEntry, getFilename()); |
| } |
| } |
| return dexFile; |
| } |
| |
| // find all full and partial matches |
| List<String> fullMatches = Lists.newArrayList(); |
| List<DexBackedDexFile> fullEntries = Lists.newArrayList(); |
| List<String> partialMatches = Lists.newArrayList(); |
| List<DexBackedDexFile> partialEntries = Lists.newArrayList(); |
| for (String entry: getEntryNames()) { |
| if (fullEntryMatch(entry, targetEntry)) { |
| // We want to grab all full matches, regardless of whether they're actually a dex file. |
| fullMatches.add(entry); |
| fullEntries.add(getEntry(entry)); |
| } else if (partialEntryMatch(entry, targetEntry)) { |
| DexBackedDexFile dexFile = getEntry(entry); |
| // We only want to grab a partial match if it is actually a dex file. |
| if (dexFile != null) { |
| partialMatches.add(entry); |
| partialEntries.add(dexFile); |
| } |
| } |
| } |
| |
| // full matches always take priority |
| if (fullEntries.size() == 1) { |
| DexBackedDexFile dexFile = fullEntries.get(0); |
| if (dexFile == null) { |
| throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", |
| fullMatches.get(0), getFilename()); |
| } |
| return dexFile; |
| } |
| if (fullEntries.size() > 1) { |
| // This should be quite rare. This would only happen if an oat file has two entries that differ |
| // only by an initial path separator. e.g. "/blah/blah.dex" and "blah/blah.dex" |
| throw new MultipleMatchingDexEntriesException(String.format( |
| "Multiple entries in %s match %s: %s", getFilename(), targetEntry, |
| Joiner.on(", ").join(fullMatches))); |
| } |
| |
| if (partialEntries.size() == 0) { |
| throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s", |
| getFilename(), targetEntry); |
| } |
| if (partialEntries.size() > 1) { |
| throw new MultipleMatchingDexEntriesException(String.format( |
| "Multiple dex entries in %s match %s: %s", getFilename(), targetEntry, |
| Joiner.on(", ").join(partialMatches))); |
| } |
| return partialEntries.get(0); |
| } |
| } |
| |
| @Nonnull |
| private static DexBackedDexFile loadDexFromZip(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry, |
| @Nonnull Opcodes opcodes) throws IOException { |
| InputStream stream; |
| stream = zipFile.getInputStream(zipEntry); |
| try { |
| return DexBackedDexFile.fromInputStream(opcodes, new BufferedInputStream(stream)); |
| } finally { |
| if (stream != null) { |
| stream.close(); |
| } |
| } |
| } |
| |
| private static class ZipDexEntryFinder extends DexEntryFinder { |
| @Nonnull private final ZipFile zipFile; |
| @Nonnull private final Opcodes opcodes; |
| |
| public ZipDexEntryFinder(@Nonnull ZipFile zipFile, @Nonnull Opcodes opcodes) { |
| this.zipFile = zipFile; |
| this.opcodes = opcodes; |
| } |
| |
| @Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException { |
| ZipEntry zipEntry = zipFile.getEntry(entry); |
| if (zipEntry == null) { |
| return null; |
| } |
| |
| return loadDexFromZip(zipFile, zipEntry, opcodes); |
| } |
| |
| @Nonnull @Override protected List<String> getEntryNames() { |
| List<String> entries = Lists.newArrayList(); |
| Enumeration<? extends ZipEntry> entriesEnumeration = zipFile.entries(); |
| |
| while (entriesEnumeration.hasMoreElements()) { |
| ZipEntry entry = entriesEnumeration.nextElement(); |
| entries.add(entry.getName()); |
| } |
| |
| return entries; |
| } |
| |
| @Nonnull @Override protected String getFilename() { |
| return zipFile.getName(); |
| } |
| } |
| |
| private static class OatDexEntryFinder extends DexEntryFinder { |
| @Nonnull private final String fileName; |
| @Nonnull private final OatFile oatFile; |
| |
| public OatDexEntryFinder(@Nonnull String fileName, @Nonnull OatFile oatFile) { |
| this.fileName = fileName; |
| this.oatFile = oatFile; |
| } |
| |
| @Nullable @Override protected DexBackedDexFile getEntry(@Nonnull String entry) throws IOException { |
| for (OatDexFile dexFile: oatFile.getDexFiles()) { |
| if (dexFile.filename.equals(entry)) { |
| return dexFile; |
| } |
| } |
| return null; |
| } |
| |
| @Nonnull @Override protected List<String> getEntryNames() { |
| List<String> entries = Lists.newArrayList(); |
| |
| for (OatDexFile oatDexFile: oatFile.getDexFiles()) { |
| entries.add(oatDexFile.filename); |
| } |
| |
| return entries; |
| } |
| |
| @Nonnull @Override protected String getFilename() { |
| return fileName; |
| } |
| } |
| } |