001/* 002 * Copyright 2010-2019 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.jar.JarInputStream; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import org.objectweb.asm.ClassReader; 029 030import org.vafer.jdependency.asm.DependenciesClassAdapter; 031 032import static org.apache.commons.io.FilenameUtils.normalize; 033import static org.apache.commons.io.FilenameUtils.separatorsToUnix; 034 035import static org.vafer.jdependency.utils.StreamUtils.asStream; 036 037 038 039public final class Clazzpath { 040 041 private final Set<ClazzpathUnit> units = new HashSet<ClazzpathUnit>(); 042 private final Map<String, Clazz> missing = new HashMap<String, Clazz>(); 043 private final Map<String, Clazz> clazzes = new HashMap<String, Clazz>(); 044 045 private static abstract class Resource { 046 047 final private static int ext = ".class".length(); 048 049 final public String name; 050 051 Resource( final String pName ) { 052 super(); 053 054 final int all = pName.length(); 055 056 // foo/bar/Foo.class -> // foo.bar.Foo 057 this.name = separatorsToUnix(pName) 058 .substring(0, all - ext) 059 .replace('/', '.'); 060 } 061 062 abstract InputStream getInputStream() throws IOException; 063 } 064 065 private static boolean isValidResourceName( final String pName ) { 066 return pName != null 067 && pName.endsWith(".class") 068 && !pName.contains( "-" ); 069 } 070 071 public boolean removeClazzpathUnit( final ClazzpathUnit pUnit ) { 072 073 final Set<Clazz> unitClazzes = pUnit.getClazzes(); 074 075 for (Clazz clazz : unitClazzes) { 076 clazz.removeClazzpathUnit(pUnit); 077 if (clazz.getClazzpathUnits().size() == 0) { 078 clazzes.remove(clazz.toString()); 079 } 080 } 081 082 return units.remove(pUnit); 083 } 084 085 public ClazzpathUnit addClazzpathUnit( final File pFile ) throws IOException { 086 return addClazzpathUnit(pFile.toPath()); 087 } 088 089 public ClazzpathUnit addClazzpathUnit( final File pFile, final String pId ) throws IOException { 090 return addClazzpathUnit(pFile.toPath(), pId); 091 } 092 093 094 public ClazzpathUnit addClazzpathUnit( final Path pPath ) throws IOException { 095 return addClazzpathUnit(pPath, pPath.toString()); 096 } 097 098 public ClazzpathUnit addClazzpathUnit( final Path pPath, final String pId ) throws IOException { 099 100 final Path path = pPath.toAbsolutePath(); 101 102 if (Files.isRegularFile(path)) { 103 104 return addClazzpathUnit(Files.newInputStream(path), pId); 105 106 } else if (Files.isDirectory(path)) { 107 108 final String prefix = separatorsToUnix(normalize(path.toString() + '/')); 109 110 Iterable<Resource> resources = Files.walk(path) 111 .filter(p -> Files.isRegularFile(p)) 112 .filter(p -> isValidResourceName(p.getFileName().toString())) 113 .map(p -> (Resource) new Resource(p.toString().substring(prefix.length())) { 114 InputStream getInputStream() throws IOException { 115 return Files.newInputStream(p); 116 } 117 })::iterator; 118 119 return addClazzpathUnit(resources, pId, true); 120 } 121 122 throw new IllegalArgumentException("neither file nor directory"); 123 } 124 125 public ClazzpathUnit addClazzpathUnit( final InputStream pInputStream, final String pId ) throws IOException { 126 127 final JarInputStream inputStream = new JarInputStream(pInputStream); 128 129 try { 130 131 Iterable<Resource> resources = asStream(inputStream) 132 .map(e -> e.getName()) 133 .filter(name -> isValidResourceName(name)) 134 .map(name -> (Resource) new Resource(name) { 135 InputStream getInputStream() throws IOException { 136 return inputStream; 137 } 138 })::iterator; 139 140 return addClazzpathUnit(resources, pId, false); 141 142 } finally { 143 inputStream.close(); 144 } 145 } 146 147 private ClazzpathUnit addClazzpathUnit( final Iterable<Resource> resources, final String pId, boolean shouldCloseResourceStream ) throws IOException { 148 149 final Map<String, Clazz> unitClazzes = new HashMap<String, Clazz>(); 150 final Map<String, Clazz> unitDependencies = new HashMap<String, Clazz>(); 151 152 final ClazzpathUnit unit = new ClazzpathUnit(pId, unitClazzes, unitDependencies); 153 154 for (Resource resource : resources) { 155 156 final String clazzName = resource.name; 157 158 Clazz clazz = getClazz(clazzName); 159 160 if (clazz == null) { 161 clazz = missing.get(clazzName); 162 163 if (clazz != null) { 164 // already marked missing 165 clazz = missing.remove(clazzName); 166 } else { 167 clazz = new Clazz(clazzName); 168 } 169 } 170 171 clazz.addClazzpathUnit(unit); 172 173 clazzes.put(clazzName, clazz); 174 unitClazzes.put(clazzName, clazz); 175 176 final DependenciesClassAdapter v = new DependenciesClassAdapter(); 177 final InputStream inputStream = resource.getInputStream(); 178 try { 179 new ClassReader(inputStream).accept(v, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG); 180 } finally { 181 if (shouldCloseResourceStream) inputStream.close(); 182 } 183 184 final Set<String> depNames = v.getDependencies(); 185 186 for (String depName : depNames) { 187 188 Clazz dep = getClazz(depName); 189 190 if (dep == null) { 191 // there is no such clazz yet 192 dep = missing.get(depName); 193 } 194 195 if (dep == null) { 196 // it is also not recorded to be missing 197 dep = new Clazz(depName); 198 dep.addClazzpathUnit(unit); 199 missing.put(depName, dep); 200 } 201 202 if (dep != clazz) { 203 unitDependencies.put(depName, dep); 204 clazz.addDependency(dep); 205 } 206 } 207 } 208 209 units.add(unit); 210 211 return unit; 212 } 213 214 public Set<Clazz> getClazzes() { 215 return new HashSet<Clazz>(clazzes.values()); 216 } 217 218 public Set<Clazz> getClashedClazzes() { 219 final Set<Clazz> all = new HashSet<Clazz>(); 220 for (Clazz clazz : clazzes.values()) { 221 if (clazz.getClazzpathUnits().size() > 1) { 222 all.add(clazz); 223 } 224 } 225 return all; 226 } 227 228 public Set<Clazz> getMissingClazzes() { 229 return new HashSet<Clazz>(missing.values()); 230 } 231 232 public Clazz getClazz( final String pClazzName ) { 233 return (Clazz) clazzes.get(pClazzName); 234 } 235 236 public ClazzpathUnit[] getUnits() { 237 return units.toArray(new ClazzpathUnit[units.size()]); 238 } 239 240}