20
20
import java .io .IOException ;
21
21
import java .net .URISyntaxException ;
22
22
import java .net .URL ;
23
+ import java .nio .charset .Charset ;
24
+ import java .nio .charset .UnsupportedCharsetException ;
25
+ import java .nio .file .Files ;
26
+ import java .nio .file .Path ;
27
+ import java .nio .file .Paths ;
28
+ import java .util .ArrayList ;
29
+ import java .util .Arrays ;
30
+ import java .util .Collections ;
31
+ import java .util .List ;
23
32
import java .util .Locale ;
33
+ import java .util .stream .Collectors ;
34
+ import java .util .stream .Stream ;
24
35
25
36
import org .springframework .util .ObjectUtils ;
26
37
import org .springframework .util .StringUtils ;
31
42
* @author Stephane Nicoll
32
43
* @author Dmytro Nosan
33
44
*/
34
- final class ClasspathBuilder {
45
+ class ClasspathBuilder {
35
46
36
- private ClasspathBuilder () {
47
+ private final List <URL > urls ;
48
+
49
+ protected ClasspathBuilder (List <URL > urls ) {
50
+ this .urls = urls ;
51
+ }
52
+
53
+ /**
54
+ * Builds a classpath string or an argument file representing the classpath, depending
55
+ * on the operating system.
56
+ * @param urls an array of {@link URL} representing the elements of the classpath
57
+ * @return the classpath; on Windows, the path to an argument file is returned,
58
+ * prefixed with '@'
59
+ */
60
+ static ClasspathBuilder forURLs (List <URL > urls ) {
61
+ return new ClasspathBuilder (new ArrayList <>(urls ));
37
62
}
38
63
39
64
/**
@@ -43,47 +68,110 @@ private ClasspathBuilder() {
43
68
* @return the classpath; on Windows, the path to an argument file is returned,
44
69
* prefixed with '@'
45
70
*/
46
- static String build (URL ... urls ) {
47
- if (ObjectUtils .isEmpty (urls )) {
48
- return "" ;
71
+ static ClasspathBuilder forURLs (URL ... urls ) {
72
+ return new ClasspathBuilder (Arrays .asList (urls ));
73
+ }
74
+
75
+ Classpath build () {
76
+ if (ObjectUtils .isEmpty (this .urls )) {
77
+ return new Classpath ("" , Collections .emptyList ());
49
78
}
50
- if (urls .length == 1 ) {
51
- return toFile (urls [0 ]).toString ();
79
+ if (this .urls .size () == 1 ) {
80
+ Path file = toFile (this .urls .get (0 ));
81
+ return new Classpath (file .toString (), List .of (file ));
52
82
}
53
- StringBuilder builder = new StringBuilder ();
54
- for (URL url : urls ) {
55
- if (!builder .isEmpty ()) {
56
- builder .append (File .pathSeparator );
57
- }
58
- builder .append (toFile (url ));
83
+ List <Path > files = this .urls .stream ().map (ClasspathBuilder ::toFile ).toList ();
84
+ String argument = files .stream ().map (Object ::toString ).collect (Collectors .joining (File .pathSeparator ));
85
+ if (needsClasspathArgFile ()) {
86
+ argument = createArgFile (argument );
59
87
}
60
- String classpath = builder .toString ();
61
- if (runsOnWindows ()) {
62
- try {
63
- return "@" + ArgFile .create (classpath );
64
- }
65
- catch (IOException ex ) {
66
- return classpath ;
67
- }
88
+ return new Classpath (argument , files );
89
+ }
90
+
91
+ protected boolean needsClasspathArgFile () {
92
+ String os = System .getProperty ("os.name" );
93
+ if (!StringUtils .hasText (os )) {
94
+ return false ;
68
95
}
69
- return classpath ;
96
+ // Windows limits the maximum command length, so we use an argfile
97
+ return os .toLowerCase (Locale .ROOT ).contains ("win" );
70
98
}
71
99
72
- private static File toFile (URL url ) {
100
+ /**
101
+ * Create a temporary file with the given {@code} classpath. Return a suitable
102
+ * argument to load the file, that is the full path prefixed by {@code @}.
103
+ * @param classpath the classpath to use
104
+ * @return a suitable argument for the classpath using a file
105
+ */
106
+ private String createArgFile (String classpath ) {
73
107
try {
74
- return new File (url .toURI ());
108
+ return "@" + writeClasspathToFile (classpath );
109
+ }
110
+ catch (IOException ex ) {
111
+ return classpath ;
112
+ }
113
+ }
114
+
115
+ private Path writeClasspathToFile (CharSequence classpath ) throws IOException {
116
+ Path tempFile = Files .createTempFile ("spring-boot-" , ".argfile" );
117
+ tempFile .toFile ().deleteOnExit ();
118
+ Files .writeString (tempFile , "\" " + escape (classpath ) + "\" " , getCharset ());
119
+ return tempFile ;
120
+ }
121
+
122
+ private static Charset getCharset () {
123
+ String nativeEncoding = System .getProperty ("native.encoding" );
124
+ if (nativeEncoding == null ) {
125
+ return Charset .defaultCharset ();
126
+ }
127
+ try {
128
+ return Charset .forName (nativeEncoding );
129
+ }
130
+ catch (UnsupportedCharsetException ex ) {
131
+ return Charset .defaultCharset ();
132
+ }
133
+ }
134
+
135
+ private static String escape (CharSequence content ) {
136
+ return content .toString ().replace ("\\ " , "\\ \\ " );
137
+ }
138
+
139
+ private static Path toFile (URL url ) {
140
+ try {
141
+ return Paths .get (url .toURI ());
75
142
}
76
143
catch (URISyntaxException ex ) {
77
144
throw new IllegalArgumentException (ex );
78
145
}
79
146
}
80
147
81
- private static boolean runsOnWindows () {
82
- String os = System .getProperty ("os.name" );
83
- if (!StringUtils .hasText (os )) {
84
- return false ;
148
+ static final class Classpath {
149
+
150
+ private final String argument ;
151
+
152
+ private final List <Path > elements ;
153
+
154
+ private Classpath (String argument , List <Path > elements ) {
155
+ this .argument = argument ;
156
+ this .elements = elements ;
85
157
}
86
- return os .toLowerCase (Locale .ROOT ).contains ("win" );
158
+
159
+ /**
160
+ * Return the {@code -cp} argument value.
161
+ * @return the argument to use
162
+ */
163
+ String argument () {
164
+ return this .argument ;
165
+ }
166
+
167
+ /**
168
+ * Return the classpath elements.
169
+ * @return the JAR files to use
170
+ */
171
+ Stream <Path > elements () {
172
+ return this .elements .stream ();
173
+ }
174
+
87
175
}
88
176
89
177
}
0 commit comments