16
16
package com .tngtech .archunit .core .importer ;
17
17
18
18
import java .io .File ;
19
+ import java .io .IOException ;
20
+ import java .net .JarURLConnection ;
19
21
import java .net .MalformedURLException ;
20
22
import java .net .URI ;
21
23
import java .net .URISyntaxException ;
22
24
import java .net .URL ;
23
25
import java .nio .file .InvalidPathException ;
26
+ import java .nio .file .Path ;
24
27
import java .nio .file .Paths ;
25
28
import java .util .ArrayList ;
29
+ import java .util .HashSet ;
26
30
import java .util .Iterator ;
27
31
import java .util .List ;
28
32
import java .util .Set ;
31
35
import com .google .common .base .Splitter ;
32
36
import com .google .common .collect .FluentIterable ;
33
37
import com .google .common .collect .ImmutableList ;
38
+ import com .google .common .collect .ImmutableSet ;
39
+ import com .google .common .collect .Sets ;
34
40
import com .tngtech .archunit .Internal ;
35
41
import com .tngtech .archunit .base .ArchUnitException .LocationException ;
36
42
import com .tngtech .archunit .base .Optional ;
37
43
import org .slf4j .Logger ;
38
44
import org .slf4j .LoggerFactory ;
39
45
46
+ import static com .google .common .base .Strings .nullToEmpty ;
47
+ import static com .google .common .collect .Iterables .concat ;
40
48
import static com .tngtech .archunit .core .importer .Location .toURI ;
49
+ import static java .util .Collections .emptySet ;
50
+ import static java .util .jar .Attributes .Name .CLASS_PATH ;
41
51
42
52
interface UrlSource extends Iterable <URL > {
43
53
@ Internal
@@ -68,10 +78,106 @@ private static Iterable<URL> unique(Iterable<URL> urls) {
68
78
}
69
79
70
80
static UrlSource classPathSystemProperties () {
71
- return iterable ( ImmutableList .<URL >builder ()
81
+ List < URL > directlySpecifiedAsProperties = ImmutableList .<URL >builder ()
72
82
.addAll (findUrlsForClassPathProperty (BOOT_CLASS_PATH_PROPERTY_NAME ))
73
83
.addAll (findUrlsForClassPathProperty (CLASS_PATH_PROPERTY_NAME ))
74
- .build ());
84
+ .build ();
85
+ Iterable <URL > transitivelySpecifiedThroughManifest = readClasspathEntriesFromManifests (directlySpecifiedAsProperties );
86
+ return iterable (concat (directlySpecifiedAsProperties , transitivelySpecifiedThroughManifest ));
87
+ }
88
+
89
+ private static Iterable <URL > readClasspathEntriesFromManifests (List <URL > urls ) {
90
+ Set <URI > result = new HashSet <>();
91
+ readClasspathUriEntriesFromManifests (result , FluentIterable .from (urls ).transform (URL_TO_URI ));
92
+ return FluentIterable .from (result ).transform (URI_TO_URL );
93
+ }
94
+
95
+ // Use URI because of better equals / hashcode
96
+ private static void readClasspathUriEntriesFromManifests (Set <URI > result , Iterable <URI > urls ) {
97
+ for (URI url : urls ) {
98
+ if (url .getScheme ().equals ("jar" )) {
99
+ Set <URI > manifestUris = readClasspathEntriesFromManifest (url );
100
+ Set <URI > unknownSoFar = ImmutableSet .copyOf (Sets .difference (manifestUris , result ));
101
+ result .addAll (unknownSoFar );
102
+ readClasspathUriEntriesFromManifests (result , unknownSoFar );
103
+ }
104
+ }
105
+ }
106
+
107
+ private static Set <URI > readClasspathEntriesFromManifest (URI url ) {
108
+ Optional <Path > jarPath = findParentPathOf (url );
109
+ if (!jarPath .isPresent ()) {
110
+ return emptySet ();
111
+ }
112
+
113
+ Set <URI > result = new HashSet <>();
114
+ for (String classpathEntry : Splitter .on (" " ).omitEmptyStrings ().split (readManifestClasspath (url ))) {
115
+ result .addAll (parseManifestClasspathEntry (jarPath .get (), classpathEntry ).asSet ());
116
+ }
117
+ return result ;
118
+ }
119
+
120
+ private static Optional <Path > findParentPathOf (URI uri ) {
121
+ try {
122
+ return Optional .fromNullable (Paths .get (ensureFileUrl (uri ).toURI ()).getParent ());
123
+ } catch (Exception e ) {
124
+ LOG .warn ("Could not find parent folder for " + uri , e );
125
+ return Optional .absent ();
126
+ }
127
+ }
128
+
129
+ private static URL ensureFileUrl (URI url ) throws IOException {
130
+ return ((JarURLConnection ) url .toURL ().openConnection ()).getJarFileURL ();
131
+ }
132
+
133
+ private static String readManifestClasspath (URI uri ) {
134
+ try {
135
+ String result = (String ) ((JarURLConnection ) uri .toURL ().openConnection ()).getMainAttributes ().get (CLASS_PATH );
136
+ return nullToEmpty (result );
137
+ } catch (Exception e ) {
138
+ return "" ;
139
+ }
140
+ }
141
+
142
+ private static Optional <URI > parseManifestClasspathEntry (Path parent , String classpathEntry ) {
143
+ if (isUrl (classpathEntry )) {
144
+ return parseUrl (parent , classpathEntry );
145
+ } else {
146
+ return parsePath (parent , classpathEntry );
147
+ }
148
+ }
149
+
150
+ private static boolean isUrl (String classpathEntry ) {
151
+ return classpathEntry .startsWith ("file:" ) || classpathEntry .startsWith ("jar:" );
152
+ }
153
+
154
+ private static Optional <URI > parseUrl (Path parent , String classpathUrlEntry ) {
155
+ try {
156
+ return Optional .of (convertToJarUrlIfNecessary (parent .toUri ().resolve (URI .create (classpathUrlEntry ).getSchemeSpecificPart ())));
157
+ } catch (Exception e ) {
158
+ LOG .warn ("Cannot parse URL classpath entry " + classpathUrlEntry , e );
159
+ return Optional .absent ();
160
+ }
161
+ }
162
+
163
+ private static Optional <URI > parsePath (Path parent , String classpathFilePathEntry ) {
164
+ try {
165
+ Path path = Paths .get (classpathFilePathEntry );
166
+ if (!path .isAbsolute ()) {
167
+ path = parent .resolve (path );
168
+ }
169
+ return Optional .of (convertToJarUrlIfNecessary (path .toUri ()));
170
+ } catch (Exception e ) {
171
+ LOG .warn ("Cannot parse file path classpath entry " + classpathFilePathEntry , e );
172
+ return Optional .absent ();
173
+ }
174
+ }
175
+
176
+ private static URI convertToJarUrlIfNecessary (URI uri ) {
177
+ if (uri .toString ().endsWith (".jar" )) {
178
+ return URI .create ("jar:" + uri + "!/" );
179
+ }
180
+ return uri ;
75
181
}
76
182
77
183
private static List <URL > findUrlsForClassPathProperty (String propertyName ) {
@@ -85,7 +191,7 @@ private static List<URL> findUrlsForClassPathProperty(String propertyName) {
85
191
}
86
192
87
193
private static Optional <URL > parseClassPathEntry (String path ) {
88
- return path .endsWith (".jar" ) ? newJarUri (path ) : newFileUri (path );
194
+ return path .endsWith (".jar" ) ? newJarUrl (path ) : newFileUri (path );
89
195
}
90
196
91
197
private static Optional <URL > newFileUri (String path ) {
@@ -118,7 +224,7 @@ private static Optional<URL> tryResolvePathFromUrl(String path) {
118
224
}
119
225
}
120
226
121
- private static Optional <URL > newJarUri (String path ) {
227
+ private static Optional <URL > newJarUrl (String path ) {
122
228
Optional <URL > fileUri = newFileUri (path );
123
229
124
230
try {
0 commit comments