001/* 002 * Copyright 2010-2023 The jdependency developers. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.vafer.jdependency; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.Map; 024import java.util.Set; 025import java.util.Base64; 026import java.util.TreeMap; 027import java.util.jar.JarInputStream; 028import java.nio.file.Files; 029import java.nio.file.Path; 030import java.security.MessageDigest; 031 032import org.apache.commons.io.input.MessageDigestInputStream; 033import org.objectweb.asm.ClassReader; 034import static org.apache.commons.io.FilenameUtils.normalize; 035import static org.apache.commons.io.FilenameUtils.separatorsToUnix; 036 037import org.vafer.jdependency.Clazz.ParsedFileName; 038import org.vafer.jdependency.asm.DependenciesClassAdapter; 039 040import static org.vafer.jdependency.Clazz.parseClassFileName; 041import static org.vafer.jdependency.utils.StreamUtils.asStream; 042 043 044 045public final class Clazzpath { 046 047 private final Set<ClazzpathUnit> units = new HashSet<>(); 048 private final Map<String, Clazz> missing = new HashMap<>(); 049 private final Map<String, Clazz> clazzes = new HashMap<>(); 050 private final boolean versions; 051 052 private abstract static class Resource { 053 public final String fileName; 054 public final String forJava; 055 public final String name; // Class name ! 056 057 Resource( final String pFileName ) { 058 super(); 059 this.fileName = pFileName; 060 ParsedFileName parsedFileName = parseClassFileName(pFileName); 061 forJava = parsedFileName.forJava; 062 name = parsedFileName.className; 063 } 064 065 abstract InputStream getInputStream() throws IOException; 066 } 067 068 private static boolean isValidResourceName( final String pName ) { 069 return pName != null 070 && pName.endsWith(".class") 071 && ( !pName.contains( "-" ) || pName.contains("META-INF/versions/") ); 072 } 073 074 public Clazzpath() { 075 this(false); 076 } 077 078 public Clazzpath( final boolean pVersions ) { 079 versions = pVersions; 080 } 081 082 public boolean removeClazzpathUnit( final ClazzpathUnit pUnit ) { 083 084 final Set<Clazz> unitClazzes = pUnit.getClazzes(); 085 086 for (Clazz clazz : unitClazzes) { 087 clazz.removeClazzpathUnit(pUnit); 088 if (clazz.getClazzpathUnits().size() == 0) { 089 clazzes.remove(clazz.getName()); 090 } 091 } 092 093 return units.remove(pUnit); 094 } 095 096 public ClazzpathUnit addClazzpathUnit( final File pFile ) throws IOException { 097 return addClazzpathUnit(pFile.toPath()); 098 } 099 100 public ClazzpathUnit addClazzpathUnit( final File pFile, final String pId ) throws IOException { 101 return addClazzpathUnit(pFile.toPath(), pId); 102 } 103 104 105 public ClazzpathUnit addClazzpathUnit( final Path pPath ) throws IOException { 106 return addClazzpathUnit(pPath, pPath.toString()); 107 } 108 109 public ClazzpathUnit addClazzpathUnit( final Path pPath, final String pId ) throws IOException { 110 111 final Path path = pPath.toAbsolutePath(); 112 113 if (Files.isRegularFile(path)) { 114 115 return addClazzpathUnit(Files.newInputStream(path), pId); 116 117 } else if (Files.isDirectory(path)) { 118 119 final String prefix = separatorsToUnix(normalize(path.toString() + '/')); 120 121 Iterable<Resource> resources = Files.walk(path) 122 .filter(p -> Files.isRegularFile(p)) 123 .filter(p -> isValidResourceName(p.getFileName().toString())) 124 .map(p -> (Resource) new Resource(p.toString().substring(prefix.length())) { 125 InputStream getInputStream() throws IOException { 126 return Files.newInputStream(p); 127 } 128 })::iterator; 129 130 return addClazzpathUnit(resources, pId, true); 131 } 132 133 throw new IllegalArgumentException("neither file nor directory"); 134 } 135 136 public ClazzpathUnit addClazzpathUnit( final InputStream pInputStream, final String pId ) throws IOException { 137 138 final JarInputStream inputStream = new JarInputStream(pInputStream); 139 140 try { 141 142 Iterable<Resource> resources = asStream(inputStream) 143 .map(e -> e.getName()) 144 .filter(name -> isValidResourceName(name)) 145 .map(name -> (Resource) new Resource(name) { 146 InputStream getInputStream() throws IOException { 147 return inputStream; 148 } 149 })::iterator; 150 151 return addClazzpathUnit(resources, pId, false); 152 153 } finally { 154 inputStream.close(); 155 } 156 } 157 158 private ClazzpathUnit addClazzpathUnit( final Iterable<Resource> resources, final String pId, boolean shouldCloseResourceStream ) throws IOException { 159 160 final Map<String, Clazz> unitClazzes = new HashMap<>(); 161 final Map<String, Clazz> unitDependencies = new HashMap<>(); 162 163 final ClazzpathUnit unit = new ClazzpathUnit(pId, unitClazzes, unitDependencies); 164 165 for (Resource resource : resources) { 166 167 // extract dependencies of clazz 168 InputStream inputStream = resource.getInputStream(); 169 try { 170 final MessageDigest digest = MessageDigest.getInstance("SHA-256"); 171 final MessageDigestInputStream calculatingInputStream = 172 MessageDigestInputStream.builder().setInputStream(inputStream).setMessageDigest(digest).get(); 173 174 if (versions) { 175 inputStream = calculatingInputStream; 176 } 177 178 final DependenciesClassAdapter v = new DependenciesClassAdapter(); 179 new ClassReader(inputStream).accept(v, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG); 180 181 // get or create clazz 182 final String clazzName = resource.name; 183 Clazz clazz = getClazz(clazzName); 184 if (clazz == null) { 185 clazz = missing.get(clazzName); 186 187 if (clazz != null) { 188 // already marked missing 189 clazz = missing.remove(clazzName); 190 } else { 191 clazz = new Clazz(clazzName); 192 } 193 } 194 clazz.addMultiReleaseFile(unit, resource.forJava, resource.fileName); 195 final String d = Base64.getEncoder().encodeToString(digest.digest()); 196 clazz.addClazzpathUnit(unit, d); 197 198 /// add to classpath 199 clazzes.put(clazzName, clazz); 200 201 // add to classpath unit 202 unitClazzes.put(clazzName, clazz); 203 204 205 // iterate through all dependencies 206 final Set<String> depNames = v.getDependencies(); 207 for (String depName : depNames) { 208 209 Clazz dep = getClazz(depName); 210 211 if (dep == null) { 212 // there is no such clazz yet 213 dep = missing.get(depName); 214 } 215 216 if (dep == null) { 217 // it is also not recorded to be missing 218 dep = new Clazz(depName); 219 // add as missing 220 missing.put(depName, dep); 221 } 222 223 if (dep != clazz) { 224 // unit depends on dep 225 unitDependencies.put(depName, dep); 226 // clazz depends on dep 227 clazz.addDependency(dep); 228 } 229 } 230 } catch(java.security.NoSuchAlgorithmException e) { 231 // well, let's pack and go home 232 } finally { 233 if (shouldCloseResourceStream && inputStream != null) { 234 inputStream.close(); 235 } 236 } 237 } 238 239 units.add(unit); 240 241 return unit; 242 } 243 244 public Set<Clazz> getClazzes() { 245 return new HashSet<>(clazzes.values()); 246 } 247 248 public Map<String, Clazz> getClazzesMap() { 249 return new TreeMap<>(clazzes); 250 } 251 252 public Set<Clazz> getClashedClazzes() { 253 final Set<Clazz> all = new HashSet<>(); 254 for (Clazz clazz : clazzes.values()) { 255 if (clazz.getClazzpathUnits().size() > 1) { 256 all.add(clazz); 257 } 258 } 259 return all; 260 } 261 262 public Set<Clazz> getMissingClazzes() { 263 return new HashSet<>(missing.values()); 264 } 265 266 public Clazz getClazz( final String pClazzName ) { 267 return clazzes.get(pClazzName); 268 } 269 270 public ClazzpathUnit[] getUnits() { 271 return units.toArray(new ClazzpathUnit[units.size()]); 272 } 273 274}