001/* 002 * Copyright 2010-2024 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.util.HashMap; 019import java.util.HashSet; 020import java.util.Map; 021import java.util.Set; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import static org.apache.commons.io.FilenameUtils.separatorsToUnix; 026 027/** 028 * A `Clazz` represents the single class identifier inside a classpath. 029 * There is only one `Clazz` per classname. It has incoming and outgoing 030 * edges defining references and dependencies. If there are different 031 * versions found, it collects their sources as ClazzpathUnits. 032 */ 033public final class Clazz implements Comparable<Clazz> { 034 035 private final Set<Clazz> dependencies = new HashSet<>(); 036 private final Set<Clazz> references = new HashSet<>(); 037 private final Map<ClazzpathUnit, String> units = new HashMap<>(); 038 039 public static final class ClazzFile { 040 private ClazzpathUnit unit; 041 private String filename; 042 043 public ClazzFile(ClazzpathUnit unit, String filename) { 044 this.unit = unit; 045 this.filename = filename; 046 } 047 048 public ClazzpathUnit getUnit() { 049 return unit; 050 } 051 052 public String getFilename() { 053 return filename; 054 } 055 056 @Override 057 public String toString() { 058 return "ClazzFile{" + 059 "unit=" + unit + 060 ", filename='" + filename + '\'' + 061 '}'; 062 } 063 } 064 065 // Usually a class is only in a single file. 066 // When using MultiRelease Jar files this can be multiple files, one for each java release specified. 067 // The default filename is under the key "8". 068 private final Map<String, ClazzFile> classFilenames = new HashMap<>(); 069 070 // The name of the class (like "org.vafer.jdependency.Clazz") 071 private final String name; 072 073 public Clazz( final String pName ) { 074 name = pName; 075 } 076 077 private static final Pattern EXTRACT_MULTI_RELEASE_JAVA_VERSION = Pattern.compile("^(?:META-INF[\\/\\\\]versions[\\/\\\\](\\d+)[\\/\\\\])?([^.]+).class$"); 078 079 public static final class ParsedFileName { 080 public String className; 081 public String forJava; 082 083 @Override 084 public String toString() { 085 return "ParsedFileName{" + 086 "className='" + className + '\'' + 087 ", forJava='" + forJava + '\'' + 088 '}'; 089 } 090 } 091 092 /** 093 * Determine the class name for the provided filename. 094 * 095 * @param pFileName The filename 096 * @return the class name for the provided filename OR null if it is not a .class file. 097 */ 098 public static ParsedFileName parseClassFileName(String pFileName) { 099 if (pFileName == null || !pFileName.endsWith(".class")) { 100 return null;// Not a class filename 101 } 102 // foo/bar/Foo.class -> // foo.bar.Foo 103 104 Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName); 105 if (!matcher.matches()) { 106 return null; 107 } 108 ParsedFileName result = new ParsedFileName(); 109 result.forJava = matcher.group(1); 110 result.className = separatorsToUnix(matcher.group(2)).replace('/', '.'); 111 112 if (result.forJava == null || result.forJava.isEmpty()) { 113 if (result.className.contains("-")) { 114 return null; 115 } 116 result.forJava = "8"; 117 } 118 119 return result; 120 } 121 122 /** 123 * Determine if the provided filename is the name of a class that is specific for a java version. 124 * @param pFileName The filename to be evaluated 125 * @return true if this is a filename for a specific java version, false if it is not 126 */ 127 public static boolean isMultiReleaseClassFile(String pFileName) { 128 if (pFileName == null) { 129 return false; 130 } 131 Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName); 132 if (!matcher.matches()) { 133 return false; 134 } 135 return matcher.group(1) != null && !matcher.group(1).isEmpty(); 136 } 137 138 /** 139 * Record that this class name can be found at: 140 * @param pUnit The unit in which the class can be found 141 * @param pForJava For which Java version 142 * @param pFileName Under which filename in the jar. 143 */ 144 public void addMultiReleaseFile(ClazzpathUnit pUnit, String pForJava, String pFileName) { 145 classFilenames.put(pForJava, new ClazzFile(pUnit, pFileName)); 146 } 147 148 public String getName() { 149 return name; 150 } 151 152 public Map<String, ClazzFile> getFileNames() { 153 return classFilenames; 154 } 155 156 public void addClazzpathUnit( final ClazzpathUnit pUnit, final String pDigest ) { 157 units.put(pUnit, pDigest); 158 } 159 160 public void removeClazzpathUnit( final ClazzpathUnit pUnit ) { 161 units.remove(pUnit); 162 } 163 164 public Set<ClazzpathUnit> getClazzpathUnits() { 165 return units.keySet(); 166 } 167 168 public Set<String> getVersions() { 169 // System.out.println("clazz:" + name + " units:" + units); 170 return new HashSet<>(units.values()); 171 } 172 173 174 public void addDependency( final Clazz pClazz ) { 175 pClazz.references.add(this); 176 dependencies.add(pClazz); 177 } 178 179 public void removeDependency( final Clazz pClazz ) { 180 pClazz.references.remove(this); 181 dependencies.remove(pClazz); 182 } 183 184 public Set<Clazz> getDependencies() { 185 return dependencies; 186 } 187 188 189 190 public Set<Clazz> getReferences() { 191 return references; 192 } 193 194 195 public Set<Clazz> getTransitiveDependencies() { 196 final Set<Clazz> all = new HashSet<>(); 197 findTransitiveDependencies(all); 198 return all; 199 } 200 201 202 void findTransitiveDependencies( final Set<? super Clazz> pAll ) { 203 204 for (Clazz clazz : dependencies) { 205 if (!pAll.contains(clazz)) { 206 pAll.add(clazz); 207 clazz.findTransitiveDependencies(pAll); 208 } 209 } 210 } 211 212 213 public boolean equals( final Object pO ) { 214 if (pO.getClass() != Clazz.class) { 215 return false; 216 } 217 final Clazz c = (Clazz) pO; 218 return name.equals(c.name); 219 } 220 221 public int hashCode() { 222 return name.hashCode(); 223 } 224 225 public int compareTo( final Clazz pO ) { 226 return name.compareTo(((Clazz) pO).name); 227 } 228 229 public String toString() { 230 return name + " in " + classFilenames; 231 } 232 233}