1 /* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 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.HashSet; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.Set; 37 import java.util.jar.JarEntry; 38 import java.util.jar.JarFile; 39 import java.util.stream.Collectors; 40 import java.util.stream.Stream; 41 import java.util.zip.ZipFile; 42 43 import jdk.tools.jlink.internal.Archive.Entry.EntryType; 44 45 /** 46 * An Archive backed by a jar file. 47 */ 48 public abstract class JarArchive implements Archive { 49 50 /** 51 * An entry located in a jar file. 52 */ 53 private class JarFileEntry extends Entry { 54 55 private final long size; 56 private final JarEntry entry; 57 private final JarFile file; 58 59 JarFileEntry(String path, String name, EntryType type, JarFile file, JarEntry entry) { 60 super(JarArchive.this, path, name, type); 61 this.entry = Objects.requireNonNull(entry); 62 this.file = Objects.requireNonNull(file); 63 size = entry.getSize(); 64 } 65 66 /** 67 * Returns the number of uncompressed bytes for this entry. 68 */ 69 @Override 70 public long size() { 71 return size; 72 } 73 74 @Override 75 public InputStream stream() throws IOException { 76 return file.getInputStream(entry); 77 } 78 } 79 80 private static final String MODULE_INFO = "module-info.class"; 81 82 private final Path file; 83 private final String moduleName; 84 // currently processed JarFile 85 private JarFile jarFile; 86 87 protected JarArchive(String mn, Path file) { 88 Objects.requireNonNull(mn); 89 Objects.requireNonNull(file); 90 this.moduleName = mn; 91 this.file = file; 92 } 93 94 @Override 95 public String moduleName() { 96 return moduleName; 97 } 98 99 @Override 100 public Path getPath() { 101 return file; 102 } 103 104 @Override 105 public Stream<Entry> entries() { 106 try { 107 if (jarFile == null) { 108 open(); 109 } 110 } catch (IOException ioe) { 111 throw new UncheckedIOException(ioe); 112 } 113 return versionedStream(jarFile).map(this::toEntry).filter(n -> n != null); 114 } 115 116 abstract EntryType toEntryType(String entryName); 117 118 abstract String getFileName(String entryName); 119 120 private Entry toEntry(JarEntry je) { 121 String name = je.getName(); 122 String fn = getFileName(name); 123 124 if (je.isDirectory() || fn.startsWith("_")) { 125 return null; 126 } 127 128 EntryType rt = toEntryType(name); 129 130 if (fn.equals(MODULE_INFO)) { 131 fn = moduleName + "/" + MODULE_INFO; 132 } 133 return new JarFileEntry(je.getName(), fn, rt, jarFile, je); 134 } 135 136 private Stream<JarEntry> versionedStream(JarFile jf) { 137 if (!jf.isMultiRelease()) { 138 return jf.stream(); 139 } 140 141 class Name { 142 private static final String VERSIONS_DIR = "META-INF/versions/"; 143 private static final int VERSIONS_DIR_LEN = 18; // VERSIONS_DIR.length(); 144 private final int version; 145 private final String name; 146 147 Name(JarEntry je) { 148 String name = je.getName(); 149 if (name.startsWith(VERSIONS_DIR)) { 150 name = name.substring(VERSIONS_DIR_LEN); 151 int offset = name.indexOf('/'); 152 if (offset == -1) 153 throw new RuntimeException("Can not obtain version in multi-release jar"); 154 this.version = Integer.parseInt(name.substring(0, offset)); 155 this.name = name.substring(offset + 1); 156 } else { 157 this.name = name; 158 this.version = JarFile.baseVersion().major(); 159 } 160 } 161 162 public String toString() { 163 return name; 164 } 165 } 166 167 int version = jf.getVersion().major(); 168 int base = JarFile.baseVersion().major(); 169 Set<String> finalNames = new HashSet<>(); 170 171 Map<Integer, List<Name>> versionsMap = jf 172 .stream() 173 .filter(je -> !je.isDirectory()) 174 .filter(je -> !je.getName().equals("META-INF/MANIFEST.MF")) 175 .map(je -> new Name(je)) 176 .filter(nm -> nm.version <= version) 177 .collect(Collectors.groupingBy(name -> name.version)); 178 179 // a legal multi-release jar always has a base entry 180 Set<String> baseNames = versionsMap.get(base).stream() 181 .map(nm -> nm.name) 182 .collect(Collectors.toSet()); 183 184 versionsMap.remove(base); 185 186 finalNames.addAll(baseNames); 187 188 versionsMap.keySet().forEach(v -> { 189 Stream<String> names = versionsMap.get(v).stream().map(nm -> nm.name); 190 if (v == version) { 191 finalNames.addAll(names.collect(Collectors.toSet())); 192 } else { 193 finalNames.addAll( 194 names.filter(nm -> { 195 if (nm.endsWith(".class")) { 196 return baseNames.contains(nm); 197 } else { 198 // resource entries are "promoted" 199 return true; 200 } 201 }).collect(Collectors.toSet()) 202 ); 203 } 204 }); 205 206 return finalNames.stream().map(nm -> jf.getJarEntry(nm)); 207 } 208 209 @Override 210 public void close() throws IOException { 211 if (jarFile != null) { 212 jarFile.close(); 213 } 214 } 215 216 @Override 217 public void open() throws IOException { 218 if (jarFile != null) { 219 jarFile.close(); 220 } 221 jarFile = new JarFile(file.toFile(), true, ZipFile.OPEN_READ, JarFile.runtimeVersion()); 222 } 223 }