• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019. Uber Technologies
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.uber.nullaway.jarinfer;
17 
18 import com.google.common.base.Preconditions;
19 import com.google.common.collect.ImmutableSet;
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStreamReader;
23 import java.util.HashSet;
24 import java.util.Set;
25 import java.util.jar.JarEntry;
26 import java.util.jar.JarFile;
27 import java.util.jar.JarInputStream;
28 import java.util.zip.ZipEntry;
29 import java.util.zip.ZipFile;
30 
31 public class EntriesComparator {
32   private static String classesJarInAar = "classes.jar";
33 
34   /**
35    * Compares the entries in the given 2 jar files. However, this function does not compare the
36    * contents of the entries themselves.
37    *
38    * @param jarFile1 Path to the first jar file.
39    * @param jarFile2 Path to the second jar file.
40    * @return True iff the entries present in the two jar files are the same.
41    * @throws IOException if an error happens when reading jar files.
42    */
compareEntriesInJars(String jarFile1, String jarFile2)43   public static boolean compareEntriesInJars(String jarFile1, String jarFile2) throws IOException {
44     Preconditions.checkArgument(jarFile1.endsWith(".jar"), "invalid jar file: " + jarFile1);
45     Preconditions.checkArgument(jarFile2.endsWith(".jar"), "invalid jar file: " + jarFile2);
46     JarFile jar1 = new JarFile(jarFile1);
47     JarFile jar2 = new JarFile(jarFile2);
48     Set<String> jar1Entries =
49         jar1.stream().map(ZipEntry::getName).collect(ImmutableSet.toImmutableSet());
50     Set<String> jar2Entries =
51         jar2.stream().map(ZipEntry::getName).collect(ImmutableSet.toImmutableSet());
52     return jar1Entries.equals(jar2Entries);
53   }
54 
55   /**
56    * Compares the entries in the given 2 aar files and the entries in "classes.jar" in them.
57    * However, this function does not compare the contents of the entries themselves.
58    *
59    * @param aarFile1 Path to the first aar file.
60    * @param aarFile2 Path to the second aar file.
61    * @return True iff the entries present in the two aar files are the same and entries in
62    *     "classes.jar" in the two aar files are the same.
63    * @throws IOException if an error happens when reading aar files.
64    */
compareEntriesInAars(String aarFile1, String aarFile2)65   public static boolean compareEntriesInAars(String aarFile1, String aarFile2) throws IOException {
66     Preconditions.checkArgument(aarFile1.endsWith(".aar"), "invalid aar file: " + aarFile1);
67     Preconditions.checkArgument(aarFile2.endsWith(".aar"), "invalid aar file: " + aarFile2);
68     ZipFile zip1 = new ZipFile(aarFile1);
69     ZipFile zip2 = new ZipFile(aarFile2);
70     Set<String> zip1Entries =
71         zip1.stream().map(ZipEntry::getName).collect(ImmutableSet.toImmutableSet());
72     Set<String> zip2Entries =
73         zip2.stream().map(ZipEntry::getName).collect(ImmutableSet.toImmutableSet());
74     if (!zip1Entries.equals(zip2Entries)) {
75       return false;
76     }
77 
78     // Check if all the entries in "classes.jar" in both aar files are the same.
79     // We expect to find a classes.jar entry in the aar files.
80     ZipEntry zip1Jar = zip1.getEntry(classesJarInAar);
81     ZipEntry zip2Jar = zip2.getEntry(classesJarInAar);
82     if (zip1Jar == null || zip2Jar == null) {
83       return false;
84     }
85     JarInputStream jarIS1 = new JarInputStream(zip1.getInputStream(zip1Jar));
86     JarInputStream jarIS2 = new JarInputStream(zip2.getInputStream(zip2Jar));
87     Set<String> jar1Entries = new HashSet<>();
88     JarEntry jar1Entry = jarIS1.getNextJarEntry();
89     while (jar1Entry != null) {
90       jar1Entries.add(jar1Entry.getName());
91       jar1Entry = jarIS1.getNextJarEntry();
92     }
93     Set<String> jar2Entries = new HashSet<>();
94     JarEntry jar2Entry = jarIS2.getNextJarEntry();
95     while (jar2Entry != null) {
96       jar2Entries.add(jar2Entry.getName());
97       jar2Entry = jarIS2.getNextJarEntry();
98     }
99     return jar1Entries.equals(jar2Entries);
100   }
101 
readManifestFromJar(String jarfile)102   private static String readManifestFromJar(String jarfile) throws IOException {
103     JarFile jar = new JarFile(jarfile);
104     ZipEntry manifestEntry = jar.getEntry("META-INF/MANIFEST.MF");
105     if (manifestEntry == null) {
106       throw new IllegalArgumentException("Jar does not contain a manifest at META-INF/MANIFEST.MF");
107     }
108     StringBuilder stringBuilder = new StringBuilder();
109     BufferedReader bufferedReader =
110         new BufferedReader(new InputStreamReader(jar.getInputStream(manifestEntry), "UTF-8"));
111     String currentLine;
112     while ((currentLine = bufferedReader.readLine()) != null) {
113       // Ignore empty new lines
114       if (currentLine.trim().length() > 0) {
115         stringBuilder.append(currentLine + "\n");
116       }
117     }
118     return stringBuilder.toString();
119   }
120 
121   /**
122    * Compares the META-INF/MANIFEST.MF file in the given 2 jar files. We ignore empty newlines.
123    *
124    * @param jarFile1 Path to the first jar file.
125    * @param jarFile2 Path to the second jar file.
126    * @return True iff the MANIFEST.MF files in the two jar files exist and are the same.
127    * @throws IOException if an error happens when reading jar files.
128    * @throws IllegalArgumentException if either jar does not contain a manifest.
129    */
compareManifestContents(String jarFile1, String jarFile2)130   public static boolean compareManifestContents(String jarFile1, String jarFile2)
131       throws IOException {
132     Preconditions.checkArgument(jarFile1.endsWith(".jar"), "invalid jar file: " + jarFile1);
133     Preconditions.checkArgument(jarFile2.endsWith(".jar"), "invalid jar file: " + jarFile2);
134     String manifest1 = readManifestFromJar(jarFile1);
135     String manifest2 = readManifestFromJar(jarFile2);
136     return manifest1.equals(manifest2);
137   }
138 }
139