001/* 002 * Copyright 2010-2017 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.FileInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.Set; 027import java.util.jar.JarEntry; 028import java.util.jar.JarInputStream; 029 030import org.apache.commons.io.FileUtils; 031import org.apache.commons.io.FilenameUtils; 032import org.objectweb.asm.ClassReader; 033import org.vafer.jdependency.asm.DependenciesClassAdapter; 034 035public final class Clazzpath { 036 037 private static abstract class Resource { 038 final String name; 039 040 Resource( String pName ) { 041 super(); 042 this.name = pName.substring(0, pName.length() - 6).replace('/', '.'); 043 } 044 045 abstract InputStream getInputStream() throws IOException; 046 047 static boolean isValidName( String pName ) { 048 return pName != null && pName.endsWith(".class") && !pName.contains( "-" ); 049 } 050 } 051 052 private final Set<ClazzpathUnit> units = new HashSet<ClazzpathUnit>(); 053 private final Map<String, Clazz> missing = new HashMap<String, Clazz>(); 054 private final Map<String, Clazz> clazzes = new HashMap<String, Clazz>(); 055 056 public Clazzpath() { 057 } 058 059 public boolean removeClazzpathUnit( final ClazzpathUnit pUnit ) { 060 061 final Set<Clazz> unitClazzes = pUnit.getClazzes(); 062 063 for (Clazz clazz : unitClazzes) { 064 clazz.removeClazzpathUnit(pUnit); 065 if (clazz.getClazzpathUnits().size() == 0) { 066 clazzes.remove(clazz.toString()); 067 // missing.put(clazz.toString(), clazz); 068 } 069 } 070 071 return units.remove(pUnit); 072 } 073 074 /** 075 * Add a {@link ClazzpathUnit} to this {@link Clazzpath}. 076 * @param pFile may be a directory or a jar file 077 * @return newly created {@link ClazzpathUnit} with id of pFile.absolutePath 078 * @throws IOException 079 */ 080 public final ClazzpathUnit addClazzpathUnit( final File pFile ) throws IOException { 081 return addClazzpathUnit(pFile, pFile.getAbsolutePath()); 082 } 083 084 public ClazzpathUnit addClazzpathUnit( final File pFile, final String pId ) throws IOException { 085 if ( pFile.isFile() ) { 086 return addClazzpathUnit( new FileInputStream(pFile), pId); 087 } 088 if (pFile.isDirectory()) { 089 final String prefix = 090 FilenameUtils.separatorsToUnix(FilenameUtils 091 .normalize(new StringBuilder(pFile.getAbsolutePath()) 092 .append(File.separatorChar).toString())); 093 final boolean recursive = true; 094 @SuppressWarnings("unchecked") 095 final Iterator<File> files = FileUtils.iterateFiles(pFile, new String[] { "class" }, recursive); 096 return addClazzpathUnit( new Iterable<Resource>() { 097 098 public Iterator<Resource> iterator() { 099 return new Iterator<Clazzpath.Resource>() { 100 101 public boolean hasNext() { 102 return files.hasNext(); 103 } 104 105 public Resource next() { 106 final File file = files.next(); 107 return new Resource(file.getAbsolutePath().substring(prefix.length())) { 108 109 @Override 110 InputStream getInputStream() throws IOException { 111 return new FileInputStream(file); 112 } 113 }; 114 } 115 116 public void remove() { 117 throw new UnsupportedOperationException(); 118 } 119 }; 120 } 121 122 }, pId, true); 123 } 124 throw new IllegalArgumentException(); 125 } 126 127 public ClazzpathUnit addClazzpathUnit(final InputStream pInputStream, final String pId) throws IOException { 128 final JarInputStream inputStream = new JarInputStream(pInputStream); 129 try { 130 final JarEntry[] entryHolder = new JarEntry[1]; 131 132 return addClazzpathUnit(new Iterable<Resource>() { 133 134 public Iterator<Resource> iterator() { 135 return new Iterator<Resource>() { 136 137 public boolean hasNext() { 138 try { 139 do { 140 entryHolder[0] = inputStream.getNextJarEntry(); 141 } while (entryHolder[0] != null && !Resource.isValidName(entryHolder[0].getName())); 142 } catch (IOException e) { 143 throw new RuntimeException(e); 144 } 145 return entryHolder[0] != null; 146 } 147 148 public Resource next() { 149 return new Resource(entryHolder[0].getName()) { 150 151 @Override 152 InputStream getInputStream() { 153 return inputStream; 154 } 155 }; 156 } 157 158 public void remove() { 159 throw new UnsupportedOperationException(); 160 } 161 162 }; 163 } 164 }, pId, false); 165 } finally { 166 inputStream.close(); 167 } 168 } 169 170 private ClazzpathUnit addClazzpathUnit(final Iterable<Resource> resources, final String pId, boolean shouldCloseResourceStream) throws IOException { 171 final Map<String, Clazz> unitClazzes = new HashMap<String, Clazz>(); 172 final Map<String, Clazz> unitDependencies = new HashMap<String, Clazz>(); 173 174 final ClazzpathUnit unit = new ClazzpathUnit(pId, unitClazzes, unitDependencies); 175 176 for (Resource resource : resources) { 177 178 final String clazzName = resource.name; 179 180 Clazz clazz = getClazz(clazzName); 181 182 if (clazz == null) { 183 clazz = missing.get(clazzName); 184 185 if (clazz != null) { 186 // already marked missing 187 clazz = missing.remove(clazzName); 188 } else { 189 clazz = new Clazz(clazzName); 190 } 191 } 192 193 clazz.addClazzpathUnit(unit); 194 195 clazzes.put(clazzName, clazz); 196 unitClazzes.put(clazzName, clazz); 197 198 final DependenciesClassAdapter v = new DependenciesClassAdapter(); 199 final InputStream inputStream = resource.getInputStream(); 200 try { 201 new ClassReader(inputStream).accept(v, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG); 202 } finally { 203 if (shouldCloseResourceStream) inputStream.close(); 204 } 205 206 final Set<String> depNames = v.getDependencies(); 207 208 for (String depName : depNames) { 209 210 Clazz dep = getClazz(depName); 211 212 if (dep == null) { 213 // there is no such clazz yet 214 dep = missing.get(depName); 215 } 216 217 if (dep == null) { 218 // it is also not recorded to be missing 219 dep = new Clazz(depName); 220 dep.addClazzpathUnit(unit); 221 missing.put(depName, dep); 222 } 223 224 if (dep != clazz) { 225 unitDependencies.put(depName, dep); 226 clazz.addDependency(dep); 227 } 228 } 229 } 230 231 units.add(unit); 232 233 return unit; 234 } 235 236 public Set<Clazz> getClazzes() { 237 final Set<Clazz> result = new HashSet<Clazz>(clazzes.values()); 238 return result; 239 } 240 241 public Set<Clazz> getClashedClazzes() { 242 final Set<Clazz> all = new HashSet<Clazz>(); 243 for (Clazz clazz : clazzes.values()) { 244 if (clazz.getClazzpathUnits().size() > 1) { 245 all.add(clazz); 246 } 247 } 248 return all; 249 } 250 251 public Set<Clazz> getMissingClazzes() { 252 final Set<Clazz> result = new HashSet<Clazz>(missing.values()); 253 return result; 254 } 255 256 public Clazz getClazz(final String pClazzName) { 257 final Clazz result = (Clazz) clazzes.get(pClazzName); 258 return result; 259 } 260 261 public ClazzpathUnit[] getUnits() { 262 final ClazzpathUnit[] result = units.toArray(new ClazzpathUnit[units.size()]); 263 return result; 264 } 265 266}