12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.tools.jlink.internal;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.UncheckedIOException;
31 import java.nio.file.Path;
32 import java.util.Objects;
33 import java.util.stream.Stream;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipFile;
36 import jdk.tools.jlink.internal.Archive.Entry.EntryType;
37
38 /**
39 * An Archive backed by a jar file.
40 */
41 public abstract class JarArchive implements Archive {
42
43 /**
44 * An entry located in a jar file.
45 */
46 private class JarEntry extends Entry {
47
48 private final long size;
49 private final ZipEntry entry;
50 private final ZipFile file;
51
52 JarEntry(String path, String name, EntryType type, ZipFile file, ZipEntry entry) {
53 super(JarArchive.this, path, name, type);
54 this.entry = Objects.requireNonNull(entry);
55 this.file = Objects.requireNonNull(file);
56 size = entry.getSize();
57 }
58
59 /**
60 * Returns the number of uncompressed bytes for this entry.
61 */
62 @Override
63 public long size() {
64 return size;
65 }
66
67 @Override
68 public InputStream stream() throws IOException {
69 return file.getInputStream(entry);
70 }
71 }
72
73 private static final String MODULE_INFO = "module-info.class";
74
75 private final Path file;
76 private final String moduleName;
77 // currently processed ZipFile
78 private ZipFile zipFile;
79
80 protected JarArchive(String mn, Path file) {
81 Objects.requireNonNull(mn);
82 Objects.requireNonNull(file);
83 this.moduleName = mn;
84 this.file = file;
85 }
86
87 @Override
88 public String moduleName() {
89 return moduleName;
90 }
91
92 @Override
93 public Path getPath() {
94 return file;
95 }
96
97 @Override
98 public Stream<Entry> entries() {
99 try {
100 if (zipFile == null) {
101 open();
102 }
103 } catch (IOException ioe) {
104 throw new UncheckedIOException(ioe);
105 }
106 return zipFile.stream().map(this::toEntry).filter(n -> n != null);
107 }
108
109 abstract EntryType toEntryType(String entryName);
110
111 abstract String getFileName(String entryName);
112
113 private Entry toEntry(ZipEntry ze) {
114 String name = ze.getName();
115 String fn = getFileName(name);
116
117 if (ze.isDirectory() || fn.startsWith("_")) {
118 return null;
119 }
120
121 EntryType rt = toEntryType(name);
122
123 if (fn.equals(MODULE_INFO)) {
124 fn = moduleName + "/" + MODULE_INFO;
125 }
126 return new JarEntry(ze.getName(), fn, rt, zipFile, ze);
127 }
128
129 @Override
130 public void close() throws IOException {
131 if (zipFile != null) {
132 zipFile.close();
133 }
134 }
135
136 @Override
137 public void open() throws IOException {
138 if (zipFile != null) {
139 zipFile.close();
140 }
141 zipFile = new ZipFile(file.toFile());
142 }
143 }
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.tools.jlink.internal;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.UncheckedIOException;
31 import java.nio.file.Path;
32 import java.util.Comparator;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.jar.JarEntry;
36 import java.util.jar.JarFile;
37 import java.util.stream.Collectors;
38 import java.util.stream.Stream;
39 import java.util.zip.ZipFile;
40 import jdk.tools.jlink.internal.Archive.Entry.EntryType;
41
42 /**
43 * An Archive backed by a jar file.
44 */
45 public abstract class JarArchive implements Archive {
46
47 /**
48 * An entry located in a jar file.
49 */
50 private class JarFileEntry extends Entry {
51
52 private final long size;
53 private final JarEntry entry;
54 private final JarFile file;
55
56 JarFileEntry(String path, String name, EntryType type, JarFile file, JarEntry entry) {
57 super(JarArchive.this, path, name, type);
58 this.entry = Objects.requireNonNull(entry);
59 this.file = Objects.requireNonNull(file);
60 size = entry.getSize();
61 }
62
63 /**
64 * Returns the number of uncompressed bytes for this entry.
65 */
66 @Override
67 public long size() {
68 return size;
69 }
70
71 @Override
72 public InputStream stream() throws IOException {
73 return file.getInputStream(entry);
74 }
75 }
76
77 private static final String MANIFEST = "META-INF/MANIFEST.MF";
78 private static final String MODULE_INFO = "module-info.class";
79 private static final String VERSIONS_DIR = "META-INF/versions/";
80 private static final int VERSIONS_DIR_LEN = VERSIONS_DIR.length();
81
82 private final Path file;
83 private final String moduleName;
84 // currently processed JarFile
85 private JarFile jarFile;
86 private int version;
87 private int offset;
88
89 protected JarArchive(String mn, Path file) {
90 Objects.requireNonNull(mn);
91 Objects.requireNonNull(file);
92 this.moduleName = mn;
93 this.file = file;
94 }
95
96 @Override
97 public String moduleName() {
98 return moduleName;
99 }
100
101 @Override
102 public Path getPath() {
103 return file;
104 }
105
106 @Override
107 public Stream<Entry> entries() {
108 try {
109 if (jarFile == null) {
110 open();
111 }
112 } catch (IOException ioe) {
113 throw new UncheckedIOException(ioe);
114 }
115 if (jarFile.isMultiRelease()) {
116 // sort entries in ascending version order, extract the base name
117 // and add them to a map, then return the Entries as with regular jar
118 return jarFile.stream()
119 .filter(je -> !je.isDirectory())
120 .filter(je -> !je.getName().equals(MANIFEST))
121 .filter(this::versionAcceptable)
122 .sorted(entryComparator)
123 .collect(Collectors.toMap(this::extractBaseName, je -> je, (v1, v2) -> v2))
124 .entrySet()
125 .stream().map(this::toEntry).filter(n -> n != null);
126 }
127 return jarFile.stream().map(this::toEntry).filter(n -> n != null);
128 }
129
130 abstract EntryType toEntryType(String entryName);
131
132 abstract String getFileName(String entryName);
133
134 // sort base entries before versioned entries
135 private Comparator<JarEntry> entryComparator = (je1, je2) -> {
136 String s1 = je1.getName();
137 String s2 = je2.getName();
138 if (s1.equals(s2)) return 0;
139 boolean b1 = s1.startsWith(VERSIONS_DIR);
140 boolean b2 = s2.startsWith(VERSIONS_DIR);
141 if (b1 && !b2) return 1;
142 if (!b1 && b2) return -1;
143 int n = 0; // starting char for String compare
144 if (b1 && b2) {
145 // normally strings would be sorted so "10" goes before "9", but
146 // version number strings need to be sorted numerically
147 n = VERSIONS_DIR.length(); // skip the common prefix
148 int i1 = s1.indexOf('/', n);
149 int i2 = s1.indexOf('/', n);
150 if (i1 == -1) throw new RuntimeException(s1); // fixme, better message
151 if (i2 == -1) throw new RuntimeException(s2); // fixme, better message
152 // shorter version numbers go first
153 if (i1 != i2) return i1 - i2;
154 // otherwise, handle equal length numbers below
155 }
156 int l1 = s1.length();
157 int l2 = s2.length();
158 int lim = Math.min(l1, l2);
159 for (int k = n; k < lim; k++) {
160 char c1 = s1.charAt(k);
161 char c2 = s2.charAt(k);
162 if (c1 != c2) {
163 return c1 - c2;
164 }
165 }
166 return l1 - l2;
167 };
168
169 // must be invoked after versionAcceptable
170 private String extractBaseName(JarEntry je) {
171 String name = je.getName();
172 if (name.startsWith(VERSIONS_DIR)) {
173 return name.substring(VERSIONS_DIR_LEN + offset + 1);
174 }
175 return name;
176 }
177
178 private Entry toEntry(JarEntry je) {
179 String name = je.getName();
180 return toEntry(name, je);
181 }
182
183 private Entry toEntry(Map.Entry<String,JarEntry> entry) {
184 String name = entry.getKey();
185 JarEntry je = entry.getValue();
186 return toEntry(name, je);
187 }
188
189 private Entry toEntry(String name, JarEntry je) {
190 String fn = getFileName(name);
191
192 if (je.isDirectory() || fn.startsWith("_")) {
193 return null;
194 }
195
196 EntryType rt = toEntryType(name);
197
198 if (fn.equals(MODULE_INFO)) {
199 fn = moduleName + "/" + MODULE_INFO;
200 }
201 return new JarFileEntry(name, fn, rt, jarFile, je);
202 }
203
204 // must be invoked before extractBaseName
205 private boolean versionAcceptable(JarEntry je) {
206 String name = je.getName();
207 if (name.startsWith(VERSIONS_DIR)) {
208 name = name.substring(VERSIONS_DIR_LEN);
209 offset = name.indexOf('/');
210 if (offset == -1)
211 throw new RuntimeException("");// fixme, better message
212 if (Integer.parseInt(name.substring(0, offset)) <= version) {
213 return true;
214 }
215 return false;
216 }
217 return true;
218 }
219
220 @Override
221 public void close() throws IOException {
222 if (jarFile != null) {
223 jarFile.close();
224 }
225 }
226
227 @Override
228 public void open() throws IOException {
229 if (jarFile != null) {
230 jarFile.close();
231 }
232 // open this way to determine if it's a multi-release jar
233 jarFile = new JarFile(file.toFile(), true, ZipFile.OPEN_READ, JarFile.runtimeVersion());
234 version = jarFile.getVersion().major();
235 }
236 }
|