2020import java .io .IOException ;
2121import java .net .URISyntaxException ;
2222import 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 ;
2332import java .util .Locale ;
33+ import java .util .stream .Collectors ;
34+ import java .util .stream .Stream ;
2435
2536import org .springframework .util .ObjectUtils ;
2637import org .springframework .util .StringUtils ;
3142 * @author Stephane Nicoll
3243 * @author Dmytro Nosan
3344 */
34- final class ClasspathBuilder {
45+ class ClasspathBuilder {
3546
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 ));
3762 }
3863
3964 /**
@@ -43,47 +68,110 @@ private ClasspathBuilder() {
4368 * @return the classpath; on Windows, the path to an argument file is returned,
4469 * prefixed with '@'
4570 */
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 ());
4978 }
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 ));
5282 }
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 );
5987 }
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 ;
6895 }
69- return classpath ;
96+ // Windows limits the maximum command length, so we use an argfile
97+ return os .toLowerCase (Locale .ROOT ).contains ("win" );
7098 }
7199
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 ) {
73107 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 ());
75142 }
76143 catch (URISyntaxException ex ) {
77144 throw new IllegalArgumentException (ex );
78145 }
79146 }
80147
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 ;
85157 }
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+
87175 }
88176
89177}
0 commit comments