# 代码测试

# 原测试代码

地址: spring-boot-main\spring-boot-main\spring-boot-tests\spring-boot-smoke-tests\spring-boot-smoke-test-data-jpa\src\main\java\smoketest\data\jpa\service\CityServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.data.jpa.service;

import smoketest.data.jpa.domain.City;
import smoketest.data.jpa.domain.HotelSummary;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

@Component("cityService")
@Transactional
class CityServiceImpl implements CityService {

private final CityRepository cityRepository;

private final HotelRepository hotelRepository;

CityServiceImpl(CityRepository cityRepository, HotelRepository hotelRepository) {
this.cityRepository = cityRepository;
this.hotelRepository = hotelRepository;
}

@Override
public Page<City> findCities(CitySearchCriteria criteria, Pageable pageable) {

Assert.notNull(criteria, "Criteria must not be null");
String name = criteria.getName();

if (!StringUtils.hasLength(name)) {
return this.cityRepository.findAll(null);
}

String country = "";
int splitPos = name.lastIndexOf(',');

if (splitPos >= 0) {
country = name.substring(splitPos + 1);
name = name.substring(0, splitPos);
}

return this.cityRepository.findByNameContainingAndCountryContainingAllIgnoringCase(name.trim(), country.trim(),
pageable);
}

@Override
public City getCity(String name, String country) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(country, "Country must not be null");
return this.cityRepository.findByNameAndCountryAllIgnoringCase(name, country);
}

@Override
public Page<HotelSummary> getHotels(City city, Pageable pageable) {
Assert.notNull(city, "City must not be null");
return this.hotelRepository.findByCity(city, pageable);
}

}

spring-boot-main\spring-boot-main\spring-boot-tests\spring-boot-smoke-tests\spring-boot-smoke-test-data-jpa\src\main\java\smoketest\data\jpa\service\HotelServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.data.jpa.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import smoketest.data.jpa.domain.City;
import smoketest.data.jpa.domain.Hotel;
import smoketest.data.jpa.domain.Rating;
import smoketest.data.jpa.domain.RatingCount;
import smoketest.data.jpa.domain.Review;
import smoketest.data.jpa.domain.ReviewDetails;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

@Component("hotelService")
@Transactional
class HotelServiceImpl implements HotelService {

private final HotelRepository hotelRepository;

private final ReviewRepository reviewRepository;

HotelServiceImpl(HotelRepository hotelRepository, ReviewRepository reviewRepository) {
this.hotelRepository = hotelRepository;
this.reviewRepository = reviewRepository;
}

@Override
public Hotel getHotel(City city, String name) {
Assert.notNull(city, "City must not be null");
Assert.hasLength(name, "Name must not be empty");
return this.hotelRepository.findByCityAndName(city, name);
}

@Override
public Page<Review> getReviews(Hotel hotel, Pageable pageable) {
Assert.notNull(hotel, "Hotel must not be null");
return this.reviewRepository.findByHotel(hotel, pageable);
}

@Override
public Review getReview(Hotel hotel, int reviewNumber) {
Assert.notNull(hotel, "Hotel must not be null");
return this.reviewRepository.findByHotelAndIndex(hotel, reviewNumber);
}

@Override
public Review addReview(Hotel hotel, ReviewDetails details) {
Review review = new Review(hotel, 1, details);
return this.reviewRepository.save(review);
}

@Override
public ReviewsSummary getReviewSummary(Hotel hotel) {
List<RatingCount> ratingCounts = this.hotelRepository.findRatingCounts(hotel);
return new ReviewsSummaryImpl(ratingCounts);
}

private static class ReviewsSummaryImpl implements ReviewsSummary {

private final Map<Rating, Long> ratingCount;

ReviewsSummaryImpl(List<RatingCount> ratingCounts) {
this.ratingCount = new HashMap<>();
for (RatingCount ratingCount : ratingCounts) {
this.ratingCount.put(ratingCount.getRating(), ratingCount.getCount());
}
}

@Override
public long getNumberOfReviewsWithRating(Rating rating) {
Long count = this.ratingCount.get(rating);
return (count != null) ? count : 0;
}

}

}

spring-boot-main\spring-boot-main\spring-boot-system-tests\spring-boot-image-tests\src\systemTest\java\org\springframework\boot\image\assertions\ImageAssert.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.image.assertions;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.ListAssert;

import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.test.json.JsonContentAssert;

/**
* AssertJ {@link org.assertj.core.api.Assert} for Docker image contents.
*
* @author Scott Frederick
*/
public class ImageAssert extends AbstractAssert<ImageAssert, ImageReference> {

private final HashMap<String, Layer> layers = new HashMap<>();

ImageAssert(ImageReference imageReference) throws IOException {
super(imageReference, ImageAssert.class);
getLayers();
}

public void layer(String layerDigest, Consumer<LayerContentAssert> assertConsumer) {
if (!this.layers.containsKey(layerDigest)) {
failWithMessage("Layer with digest '%s' not found in image", layerDigest);
}
assertConsumer.accept(new LayerContentAssert(this.layers.get(layerDigest)));
}

private void getLayers() throws IOException {
new DockerApi().image().exportLayers(this.actual, (id, tarArchive) -> {
Layer layer = Layer.fromTarArchive(tarArchive);
this.layers.put(layer.getId().toString(), layer);
});
}

/**
* Asserts for image layers.
*/
public static class LayerContentAssert extends AbstractAssert<LayerContentAssert, Layer> {

public LayerContentAssert(Layer layer) {
super(layer, LayerContentAssert.class);
}

public ListAssert<String> entries() {
List<String> entryNames = new ArrayList<>();
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.actual.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
if (!entry.isDirectory()) {
entryNames.add(entry.getName().replaceFirst("^/workspace/", ""));
}
entry = in.getNextEntry();
}
}
}
catch (IOException ex) {
failWithMessage("IOException while reading image layer archive: '%s'", ex.getMessage());
}
return Assertions.assertThat(entryNames);
}

public void jsonEntry(String name, Consumer<JsonContentAssert> assertConsumer) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.actual.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().equals(name)) {
ByteArrayOutputStream entryOut = new ByteArrayOutputStream();
IOUtils.copy(in, entryOut);
assertConsumer.accept(new JsonContentAssert(LayerContentAssert.class, entryOut.toString()));
return;
}
entry = in.getNextEntry();
}
}
failWithMessage("Expected JSON entry '%s' in layer with digest '%s'", name, this.actual.getId());
}
catch (IOException ex) {
failWithMessage("IOException while reading image layer archive: '%s'", ex.getMessage());
}
}

}

}

地址: spring-boot-main\spring-boot-main\spring-boot-tests\spring-boot-smoke-tests\spring-boot-smoke-test-web-freemarker\src\test\java\smoketest\freemarker\SampleWebFreeMarkerApplicationTests.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.freemarker;

import java.util.Arrays;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Basic integration tests for FreeMarker application.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleWebFreeMarkerApplicationTests {

@Autowired
private TestRestTemplate testRestTemplate;

@Test
void testFreeMarkerTemplate() {
ResponseEntity<String> entity = this.testRestTemplate.getForEntity("/", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("Hello, Andy");
}

@Test
void testFreeMarkerErrorTemplate() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpEntity<String> requestEntity = new HttpEntity<>(headers);

ResponseEntity<String> responseEntity = this.testRestTemplate.exchange("/does-not-exist", HttpMethod.GET,
requestEntity, String.class);

assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(responseEntity.getBody()).contains("Something went wrong: 404 Not Found");
}

}

地址: spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.devtools.filewatch;

import java.io.File;
import java.io.FileFilter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.util.Assert;

/**
* Watches specific directories for file changes.
*
* @author Andy Clement
* @author Phillip Webb
* @since 1.3.0
* @see FileChangeListener
*/
public class FileSystemWatcher {

private static final Duration DEFAULT_POLL_INTERVAL = Duration.ofMillis(1000);

private static final Duration DEFAULT_QUIET_PERIOD = Duration.ofMillis(400);

private final List<FileChangeListener> listeners = new ArrayList<>();

private final boolean daemon;

private final long pollInterval;

private final long quietPeriod;

private final SnapshotStateRepository snapshotStateRepository;

private final AtomicInteger remainingScans = new AtomicInteger(-1);

private final Map<File, DirectorySnapshot> directories = new HashMap<>();

private Thread watchThread;

private FileFilter triggerFilter;

private final Object monitor = new Object();

/**
* Create a new {@link FileSystemWatcher} instance.
*/
public FileSystemWatcher() {
this(true, DEFAULT_POLL_INTERVAL, DEFAULT_QUIET_PERIOD);
}

/**
* Create a new {@link FileSystemWatcher} instance.
* @param daemon if a daemon thread used to monitor changes
* @param pollInterval the amount of time to wait between checking for changes
* @param quietPeriod the amount of time required after a change has been detected to
* ensure that updates have completed
*/
public FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod) {
this(daemon, pollInterval, quietPeriod, null);
}

/**
* Create a new {@link FileSystemWatcher} instance.
* @param daemon if a daemon thread used to monitor changes
* @param pollInterval the amount of time to wait between checking for changes
* @param quietPeriod the amount of time required after a change has been detected to
* ensure that updates have completed
* @param snapshotStateRepository the snapshot state repository
* @since 2.4.0
*/
public FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod,
SnapshotStateRepository snapshotStateRepository) {
Assert.notNull(pollInterval, "PollInterval must not be null");
Assert.notNull(quietPeriod, "QuietPeriod must not be null");
Assert.isTrue(pollInterval.toMillis() > 0, "PollInterval must be positive");
Assert.isTrue(quietPeriod.toMillis() > 0, "QuietPeriod must be positive");
Assert.isTrue(pollInterval.toMillis() > quietPeriod.toMillis(),
"PollInterval must be greater than QuietPeriod");
this.daemon = daemon;
this.pollInterval = pollInterval.toMillis();
this.quietPeriod = quietPeriod.toMillis();
this.snapshotStateRepository = (snapshotStateRepository != null) ? snapshotStateRepository
: SnapshotStateRepository.NONE;
}

/**
* Add listener for file change events. Cannot be called after the watcher has been
* {@link #start() started}.
* @param fileChangeListener the listener to add
*/
public void addListener(FileChangeListener fileChangeListener) {
Assert.notNull(fileChangeListener, "FileChangeListener must not be null");
synchronized (this.monitor) {
checkNotStarted();
this.listeners.add(fileChangeListener);
}
}

/**
* Add source directories to monitor. Cannot be called after the watcher has been
* {@link #start() started}.
* @param directories the directories to monitor
*/
public void addSourceDirectories(Iterable<File> directories) {
Assert.notNull(directories, "Directories must not be null");
synchronized (this.monitor) {
directories.forEach(this::addSourceDirectory);
}
}

/**
* Add a source directory to monitor. Cannot be called after the watcher has been
* {@link #start() started}.
* @param directory the directory to monitor
*/
public void addSourceDirectory(File directory) {
Assert.notNull(directory, "Directory must not be null");
Assert.isTrue(!directory.isFile(), () -> "Directory '" + directory + "' must not be a file");
synchronized (this.monitor) {
checkNotStarted();
this.directories.put(directory, null);
}
}

/**
* Set an optional {@link FileFilter} used to limit the files that trigger a change.
* @param triggerFilter a trigger filter or null
*/
public void setTriggerFilter(FileFilter triggerFilter) {
synchronized (this.monitor) {
this.triggerFilter = triggerFilter;
}
}

private void checkNotStarted() {
Assert.state(this.watchThread == null, "FileSystemWatcher already started");
}

/**
* Start monitoring the source directory for changes.
*/
public void start() {
synchronized (this.monitor) {
createOrRestoreInitialSnapshots();
if (this.watchThread == null) {
Map<File, DirectorySnapshot> localDirectories = new HashMap<>(this.directories);
Watcher watcher = new Watcher(this.remainingScans, new ArrayList<>(this.listeners), this.triggerFilter,
this.pollInterval, this.quietPeriod, localDirectories, this.snapshotStateRepository);
this.watchThread = new Thread(watcher);
this.watchThread.setName("File Watcher");
this.watchThread.setDaemon(this.daemon);
this.watchThread.start();
}
}
}

@SuppressWarnings("unchecked")
private void createOrRestoreInitialSnapshots() {
Map<File, DirectorySnapshot> restored = (Map<File, DirectorySnapshot>) this.snapshotStateRepository.restore();
this.directories.replaceAll((f, v) -> {
DirectorySnapshot restoredSnapshot = (restored != null) ? restored.get(f) : null;
return (restoredSnapshot != null) ? restoredSnapshot : new DirectorySnapshot(f);
});
}

/**
* Stop monitoring the source directories.
*/
public void stop() {
stopAfter(0);
}

/**
* Stop monitoring the source directories.
* @param remainingScans the number of remaining scans
*/
void stopAfter(int remainingScans) {
Thread thread;
synchronized (this.monitor) {
thread = this.watchThread;
if (thread != null) {
this.remainingScans.set(remainingScans);
if (remainingScans <= 0) {
thread.interrupt();
}
}
this.watchThread = null;
}
if (thread != null && Thread.currentThread() != thread) {
try {
thread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}

private static final class Watcher implements Runnable {

private final AtomicInteger remainingScans;

private final List<FileChangeListener> listeners;

private final FileFilter triggerFilter;

private final long pollInterval;

private final long quietPeriod;

private Map<File, DirectorySnapshot> directories;

private final SnapshotStateRepository snapshotStateRepository;

private Watcher(AtomicInteger remainingScans, List<FileChangeListener> listeners, FileFilter triggerFilter,
long pollInterval, long quietPeriod, Map<File, DirectorySnapshot> directories,
SnapshotStateRepository snapshotStateRepository) {
this.remainingScans = remainingScans;
this.listeners = listeners;
this.triggerFilter = triggerFilter;
this.pollInterval = pollInterval;
this.quietPeriod = quietPeriod;
this.directories = directories;
this.snapshotStateRepository = snapshotStateRepository;

}

@Override
public void run() {
int remainingScans = this.remainingScans.get();
while (remainingScans > 0 || remainingScans == -1) {
try {
if (remainingScans > 0) {
this.remainingScans.decrementAndGet();
}
scan();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
remainingScans = this.remainingScans.get();
}
}

private void scan() throws InterruptedException {
Thread.sleep(this.pollInterval - this.quietPeriod);
Map<File, DirectorySnapshot> previous;
Map<File, DirectorySnapshot> current = this.directories;
do {
previous = current;
current = getCurrentSnapshots();
Thread.sleep(this.quietPeriod);
}
while (isDifferent(previous, current));
if (isDifferent(this.directories, current)) {
updateSnapshots(current.values());
}
}

private boolean isDifferent(Map<File, DirectorySnapshot> previous, Map<File, DirectorySnapshot> current) {
if (!previous.keySet().equals(current.keySet())) {
return true;
}
for (Map.Entry<File, DirectorySnapshot> entry : previous.entrySet()) {
DirectorySnapshot previousDirectory = entry.getValue();
DirectorySnapshot currentDirectory = current.get(entry.getKey());
if (!previousDirectory.equals(currentDirectory, this.triggerFilter)) {
return true;
}
}
return false;
}

private Map<File, DirectorySnapshot> getCurrentSnapshots() {
Map<File, DirectorySnapshot> snapshots = new LinkedHashMap<>();
for (File directory : this.directories.keySet()) {
snapshots.put(directory, new DirectorySnapshot(directory));
}
return snapshots;
}

private void updateSnapshots(Collection<DirectorySnapshot> snapshots) {
Map<File, DirectorySnapshot> updated = new LinkedHashMap<>();
Set<ChangedFiles> changeSet = new LinkedHashSet<>();
for (DirectorySnapshot snapshot : snapshots) {
DirectorySnapshot previous = this.directories.get(snapshot.getDirectory());
updated.put(snapshot.getDirectory(), snapshot);
ChangedFiles changedFiles = previous.getChangedFiles(snapshot, this.triggerFilter);
if (!changedFiles.getFiles().isEmpty()) {
changeSet.add(changedFiles);
}
}
this.directories = updated;
this.snapshotStateRepository.save(updated);
if (!changeSet.isEmpty()) {
fireListeners(Collections.unmodifiableSet(changeSet));
}
}

private void fireListeners(Set<ChangedFiles> changeSet) {
for (FileChangeListener listener : this.listeners) {
listener.onChange(changeSet);
}
}

}

}

地址: spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/LifecycleVersion.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.buildpack.platform.build;

import java.util.Comparator;

import org.springframework.util.Assert;

/**
* A lifecycle version number comprised of a major, minor and patch value.
*
* @author Phillip Webb
*/
class LifecycleVersion implements Comparable<LifecycleVersion> {

private static final Comparator<LifecycleVersion> COMPARATOR = Comparator.comparingInt(LifecycleVersion::getMajor)
.thenComparingInt(LifecycleVersion::getMinor)
.thenComparing(LifecycleVersion::getPatch);

private final int major;

private final int minor;

private final int patch;

LifecycleVersion(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LifecycleVersion other = (LifecycleVersion) obj;
boolean result = true;
result = result && this.major == other.major;
result = result && this.minor == other.minor;
result = result && this.patch == other.patch;
return result;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.major;
result = prime * result + this.minor;
result = prime * result + this.patch;
return result;
}

@Override
public String toString() {
return "v" + this.major + "." + this.minor + "." + this.patch;
}

/**
* Return if this version is greater than or equal to the specified version.
* @param other the version to compare
* @return {@code true} if this version is greater than or equal to the specified
* version
*/
boolean isEqualOrGreaterThan(LifecycleVersion other) {
return compareTo(other) >= 0;
}

@Override
public int compareTo(LifecycleVersion other) {
return COMPARATOR.compare(this, other);
}

/**
* Return the major version number.
* @return the major version
*/
int getMajor() {
return this.major;
}

/**
* Return the minor version number.
* @return the minor version
*/
int getMinor() {
return this.minor;
}

/**
* Return the patch version number.
* @return the patch version
*/
int getPatch() {
return this.patch;
}

/**
* Factory method to parse a string into a {@link LifecycleVersion} instance.
* @param value the value to parse.
* @return the corresponding {@link LifecycleVersion}
* @throws IllegalArgumentException if the value could not be parsed
*/
static LifecycleVersion parse(String value) {
Assert.hasText(value, "Value must not be empty");
if (value.startsWith("v") || value.startsWith("V")) {
value = value.substring(1);
}
String[] components = value.split("\\.");
Assert.isTrue(components.length <= 3, "Malformed version number '" + value + "'");
int[] versions = new int[3];
for (int i = 0; i < components.length; i++) {
try {
versions[i] = Integer.parseInt(components[i]);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Malformed version number '" + value + "'", ex);
}
}
return new LifecycleVersion(versions[0], versions[1], versions[2]);
}

}

# 投喂给 LLM 的代码,以 chatgpt3.5 为例

# CityServicelmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.data.jpa.service;

import smoketest.data.jpa.domain.City;
import smoketest.data.jpa.domain.HotelSummary;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

@Component("cityService")
@Transactional
class CityServiceImpl implements CityService {

private final CityRepository cityRepository;

private final HotelRepository hotelRepository;

CityServiceImpl(CityRepository cityRepository, HotelRepository hotelRepository) {
this.cityRepository = cityRepository;
this.hotelRepository = hotelRepository;
}

@Override
public Page<City> findCities(CitySearchCriteria criteria, Pageable pageable) {
String name = criteria.getName();

if (!StringUtils.hasLength(name)) {
return this.cityRepository.findAll(null);
}

String country = "";
int splitPos = name.lastIndexOf(',');

if (splitPos >= 0) {
country = name.substring(splitPos + 1);
name = name.substring(0, splitPos);
}

return this.cityRepository.findByNameContainingAndCountryContainingAllIgnoringCase(name.trim(), country.trim(),
pageable);
}

@Override
public City getCity(String name, String country) {

return this.cityRepository.findByNameAndCountryAllIgnoringCase(name, country);
}

@Override
public Page<HotelSummary> getHotels(City city, Pageable pageable) {
return this.hotelRepository.findByCity(city, pageable);
}

}

# HotelServicelmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.data.jpa.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import smoketest.data.jpa.domain.City;
import smoketest.data.jpa.domain.Hotel;
import smoketest.data.jpa.domain.Rating;
import smoketest.data.jpa.domain.RatingCount;
import smoketest.data.jpa.domain.Review;
import smoketest.data.jpa.domain.ReviewDetails;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

@Component("hotelService")
@Transactional
class HotelServiceImpl implements HotelService {

private final HotelRepository hotelRepository;

private final ReviewRepository reviewRepository;

HotelServiceImpl(HotelRepository hotelRepository, ReviewRepository reviewRepository) {
this.hotelRepository = hotelRepository;
this.reviewRepository = reviewRepository;
}

@Override
public Hotel getHotel(City city, String name) {
return this.hotelRepository.findByCityAndName(city, name);
}

@Override
public Page<Review> getReviews(Hotel hotel, Pageable pageable) {
return this.reviewRepository.findByHotel(hotel, pageable);
}

@Override
public Review getReview(Hotel hotel, int reviewNumber) {
return this.reviewRepository.findByHotelAndIndex(hotel, reviewNumber);
}

@Override
public Review addReview(Hotel hotel, ReviewDetails details) {
Review review = new Review(hotel, 1, details);
return this.reviewRepository.save(review);
}

@Override
public ReviewsSummary getReviewSummary(Hotel hotel) {
List<RatingCount> ratingCounts = this.hotelRepository.findRatingCounts(hotel);
return new ReviewsSummaryImpl(ratingCounts);
}

private static class ReviewsSummaryImpl implements ReviewsSummary {

private final Map<Rating, Long> ratingCount;

ReviewsSummaryImpl(List<RatingCount> ratingCounts) {
this.ratingCount = new HashMap<>();
for (RatingCount ratingCount : ratingCounts) {
this.ratingCount.put(ratingCount.getRating(), ratingCount.getCount());
}
}

@Override
public long getNumberOfReviewsWithRating(Rating rating) {
Long count = this.ratingCount.get(rating);
return (count != null) ? count : 0;
}

}

}

# SampleWebFreeMarkerApplicationTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.freemarker;

import java.util.Arrays;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Basic integration tests for FreeMarker application.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleWebFreeMarkerApplicationTests {

@Autowired
private TestRestTemplate testRestTemplate;

@Test
void testFreeMarkerTemplate() {
ResponseEntity<String> entity = this.testRestTemplate.getForEntity("/", String.class);
}

@Test
void testFreeMarkerErrorTemplate() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpEntity<String> requestEntity = new HttpEntity<>(headers);

ResponseEntity<String> responseEntity = this.testRestTemplate.exchange("/does-not-exist", HttpMethod.GET,
requestEntity, String.class);

}

}

# HotelServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.devtools.filewatch;

import java.io.File;
import java.io.FileFilter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.util.Assert;

/**
* Watches specific directories for file changes.
*
* @author Andy Clement
* @author Phillip Webb
* @since 1.3.0
* @see FileChangeListener
*/
public class FileSystemWatcher {

private static final Duration DEFAULT_POLL_INTERVAL = Duration.ofMillis(1000);

private static final Duration DEFAULT_QUIET_PERIOD = Duration.ofMillis(400);

private final List<FileChangeListener> listeners = new ArrayList<>();

private final boolean daemon;

private final long pollInterval;

private final long quietPeriod;

private final SnapshotStateRepository snapshotStateRepository;

private final AtomicInteger remainingScans = new AtomicInteger(-1);

private final Map<File, DirectorySnapshot> directories = new HashMap<>();

private Thread watchThread;

private FileFilter triggerFilter;

private final Object monitor = new Object();

/**
* Create a new {@link FileSystemWatcher} instance.
*/
public FileSystemWatcher() {
this(true, DEFAULT_POLL_INTERVAL, DEFAULT_QUIET_PERIOD);
}

/**
* Create a new {@link FileSystemWatcher} instance.
* @param daemon if a daemon thread used to monitor changes
* @param pollInterval the amount of time to wait between checking for changes
* @param quietPeriod the amount of time required after a change has been detected to
* ensure that updates have completed
*/
public FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod) {
this(daemon, pollInterval, quietPeriod, null);
}

/**
* Create a new {@link FileSystemWatcher} instance.
* @param daemon if a daemon thread used to monitor changes
* @param pollInterval the amount of time to wait between checking for changes
* @param quietPeriod the amount of time required after a change has been detected to
* ensure that updates have completed
* @param snapshotStateRepository the snapshot state repository
* @since 2.4.0
*/
public FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod,
SnapshotStateRepository snapshotStateRepository) {
this.daemon = daemon;
this.pollInterval = pollInterval.toMillis();
this.quietPeriod = quietPeriod.toMillis();
this.snapshotStateRepository = (snapshotStateRepository != null) ? snapshotStateRepository
: SnapshotStateRepository.NONE;
}

/**
* Add listener for file change events. Cannot be called after the watcher has been
* {@link #start() started}.
* @param fileChangeListener the listener to add
*/
public void addListener(FileChangeListener fileChangeListener) {
synchronized (this.monitor) {
checkNotStarted();
this.listeners.add(fileChangeListener);
}
}

/**
* Add source directories to monitor. Cannot be called after the watcher has been
* {@link #start() started}.
* @param directories the directories to monitor
*/
public void addSourceDirectories(Iterable<File> directories) {
synchronized (this.monitor) {
directories.forEach(this::addSourceDirectory);
}
}

/**
* Add a source directory to monitor. Cannot be called after the watcher has been
* {@link #start() started}.
* @param directory the directory to monitor
*/
public void addSourceDirectory(File directory) {
synchronized (this.monitor) {
checkNotStarted();
this.directories.put(directory, null);
}
}

/**
* Set an optional {@link FileFilter} used to limit the files that trigger a change.
* @param triggerFilter a trigger filter or null
*/
public void setTriggerFilter(FileFilter triggerFilter) {
synchronized (this.monitor) {
this.triggerFilter = triggerFilter;
}
}

private void checkNotStarted() {
Assert.state(this.watchThread == null, "FileSystemWatcher already started");
}

/**
* Start monitoring the source directory for changes.
*/
public void start() {
synchronized (this.monitor) {
createOrRestoreInitialSnapshots();
if (this.watchThread == null) {
Map<File, DirectorySnapshot> localDirectories = new HashMap<>(this.directories);
Watcher watcher = new Watcher(this.remainingScans, new ArrayList<>(this.listeners), this.triggerFilter,
this.pollInterval, this.quietPeriod, localDirectories, this.snapshotStateRepository);
this.watchThread = new Thread(watcher);
this.watchThread.setName("File Watcher");
this.watchThread.setDaemon(this.daemon);
this.watchThread.start();
}
}
}

@SuppressWarnings("unchecked")
private void createOrRestoreInitialSnapshots() {
Map<File, DirectorySnapshot> restored = (Map<File, DirectorySnapshot>) this.snapshotStateRepository.restore();
this.directories.replaceAll((f, v) -> {
DirectorySnapshot restoredSnapshot = (restored != null) ? restored.get(f) : null;
return (restoredSnapshot != null) ? restoredSnapshot : new DirectorySnapshot(f);
});
}

/**
* Stop monitoring the source directories.
*/
public void stop() {
stopAfter(0);
}

/**
* Stop monitoring the source directories.
* @param remainingScans the number of remaining scans
*/
void stopAfter(int remainingScans) {
Thread thread;
synchronized (this.monitor) {
thread = this.watchThread;
if (thread != null) {
this.remainingScans.set(remainingScans);
if (remainingScans <= 0) {
thread.interrupt();
}
}
this.watchThread = null;
}
if (thread != null && Thread.currentThread() != thread) {
try {
thread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}

private static final class Watcher implements Runnable {

private final AtomicInteger remainingScans;

private final List<FileChangeListener> listeners;

private final FileFilter triggerFilter;

private final long pollInterval;

private final long quietPeriod;

private Map<File, DirectorySnapshot> directories;

private final SnapshotStateRepository snapshotStateRepository;

private Watcher(AtomicInteger remainingScans, List<FileChangeListener> listeners, FileFilter triggerFilter,
long pollInterval, long quietPeriod, Map<File, DirectorySnapshot> directories,
SnapshotStateRepository snapshotStateRepository) {
this.remainingScans = remainingScans;
this.listeners = listeners;
this.triggerFilter = triggerFilter;
this.pollInterval = pollInterval;
this.quietPeriod = quietPeriod;
this.directories = directories;
this.snapshotStateRepository = snapshotStateRepository;

}

@Override
public void run() {
int remainingScans = this.remainingScans.get();
while (remainingScans > 0 || remainingScans == -1) {
try {
if (remainingScans > 0) {
this.remainingScans.decrementAndGet();
}
scan();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
remainingScans = this.remainingScans.get();
}
}

private void scan() throws InterruptedException {
Thread.sleep(this.pollInterval - this.quietPeriod);
Map<File, DirectorySnapshot> previous;
Map<File, DirectorySnapshot> current = this.directories;
do {
previous = current;
current = getCurrentSnapshots();
Thread.sleep(this.quietPeriod);
}
while (isDifferent(previous, current));
if (isDifferent(this.directories, current)) {
updateSnapshots(current.values());
}
}

private boolean isDifferent(Map<File, DirectorySnapshot> previous, Map<File, DirectorySnapshot> current) {
if (!previous.keySet().equals(current.keySet())) {
return true;
}
for (Map.Entry<File, DirectorySnapshot> entry : previous.entrySet()) {
DirectorySnapshot previousDirectory = entry.getValue();
DirectorySnapshot currentDirectory = current.get(entry.getKey());
if (!previousDirectory.equals(currentDirectory, this.triggerFilter)) {
return true;
}
}
return false;
}

private Map<File, DirectorySnapshot> getCurrentSnapshots() {
Map<File, DirectorySnapshot> snapshots = new LinkedHashMap<>();
for (File directory : this.directories.keySet()) {
snapshots.put(directory, new DirectorySnapshot(directory));
}
return snapshots;
}

private void updateSnapshots(Collection<DirectorySnapshot> snapshots) {
Map<File, DirectorySnapshot> updated = new LinkedHashMap<>();
Set<ChangedFiles> changeSet = new LinkedHashSet<>();
for (DirectorySnapshot snapshot : snapshots) {
DirectorySnapshot previous = this.directories.get(snapshot.getDirectory());
updated.put(snapshot.getDirectory(), snapshot);
ChangedFiles changedFiles = previous.getChangedFiles(snapshot, this.triggerFilter);
if (!changedFiles.getFiles().isEmpty()) {
changeSet.add(changedFiles);
}
}
this.directories = updated;
this.snapshotStateRepository.save(updated);
if (!changeSet.isEmpty()) {
fireListeners(Collections.unmodifiableSet(changeSet));
}
}

private void fireListeners(Set<ChangedFiles> changeSet) {
for (FileChangeListener listener : this.listeners) {
listener.onChange(changeSet);
}
}

}

}

# LifecycleVersion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.buildpack.platform.build;

import java.util.Comparator;

import org.springframework.util.Assert;

/**
* A lifecycle version number comprised of a major, minor and patch value.
*
* @author Phillip Webb
*/
class LifecycleVersion implements Comparable<LifecycleVersion> {

private static final Comparator<LifecycleVersion> COMPARATOR = Comparator.comparingInt(LifecycleVersion::getMajor)
.thenComparingInt(LifecycleVersion::getMinor)
.thenComparing(LifecycleVersion::getPatch);

private final int major;

private final int minor;

private final int patch;

LifecycleVersion(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LifecycleVersion other = (LifecycleVersion) obj;
boolean result = true;
result = result && this.major == other.major;
result = result && this.minor == other.minor;
result = result && this.patch == other.patch;
return result;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.major;
result = prime * result + this.minor;
result = prime * result + this.patch;
return result;
}

@Override
public String toString() {
return "v" + this.major + "." + this.minor + "." + this.patch;
}

/**
* Return if this version is greater than or equal to the specified version.
* @param other the version to compare
* @return {@code true} if this version is greater than or equal to the specified
* version
*/
boolean isEqualOrGreaterThan(LifecycleVersion other) {
return compareTo(other) >= 0;
}

@Override
public int compareTo(LifecycleVersion other) {
return COMPARATOR.compare(this, other);
}

/**
* Return the major version number.
* @return the major version
*/
int getMajor() {
return this.major;
}

/**
* Return the minor version number.
* @return the minor version
*/
int getMinor() {
return this.minor;
}

/**
* Return the patch version number.
* @return the patch version
*/
int getPatch() {
return this.patch;
}

/**
* Factory method to parse a string into a {@link LifecycleVersion} instance.
* @param value the value to parse.
* @return the corresponding {@link LifecycleVersion}
* @throws IllegalArgumentException if the value could not be parsed
*/
static LifecycleVersion parse(String value) {
if (value.startsWith("v") || value.startsWith("V")) {
value = value.substring(1);
}
String[] components = value.split("\\.");
int[] versions = new int[3];
for (int i = 0; i < components.length; i++) {
try {
versions[i] = Integer.parseInt(components[i]);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Malformed version number '" + value + "'", ex);
}
}
return new LifecycleVersion(versions[0], versions[1], versions[2]);
}

}

# LLM 产出的代码

# CityServicelmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.data.jpa.service;

import smoketest.data.jpa.domain.City;
import smoketest.data.jpa.domain.HotelSummary;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

@Component("cityService")
@Transactional
class CityServiceImpl implements CityService {

private final CityRepository cityRepository;

private final HotelRepository hotelRepository;

CityServiceImpl(CityRepository cityRepository, HotelRepository hotelRepository) {
Assert.notNull(cityRepository, "CityRepository must not be null");
Assert.notNull(hotelRepository, "HotelRepository must not be null");
this.cityRepository = cityRepository;
this.hotelRepository = hotelRepository;
}

@Override
public Page<City> findCities(CitySearchCriteria criteria, Pageable pageable) {
Assert.notNull(criteria, "CitySearchCriteria must not be null");
Assert.notNull(pageable, "Pageable must not be null");

String name = criteria.getName();
if (!StringUtils.hasLength(name)) {
return this.cityRepository.findAll(null);
}

String country = "";
int splitPos = name.lastIndexOf(',');

if (splitPos >= 0) {
country = name.substring(splitPos + 1);
name = name.substring(0, splitPos);
}

return this.cityRepository.findByNameContainingAndCountryContainingAllIgnoringCase(name.trim(), country.trim(), pageable);
}

@Override
public City getCity(String name, String country) {
Assert.hasLength(name, "Name must not be empty");
Assert.hasLength(country, "Country must not be empty");

return this.cityRepository.findByNameAndCountryAllIgnoringCase(name, country);
}

@Override
public Page<HotelSummary> getHotels(City city, Pageable pageable) {
Assert.notNull(city, "City must not be null");
Assert.notNull(pageable, "Pageable must not be null");

return this.hotelRepository.findByCity(city, pageable);
}

}

# HotelServicelmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.data.jpa.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import smoketest.data.jpa.domain.City;
import smoketest.data.jpa.domain.Hotel;
import smoketest.data.jpa.domain.Rating;
import smoketest.data.jpa.domain.RatingCount;
import smoketest.data.jpa.domain.Review;
import smoketest.data.jpa.domain.ReviewDetails;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

@Component("hotelService")
@Transactional
class HotelServiceImpl implements HotelService {

private final HotelRepository hotelRepository;

private final ReviewRepository reviewRepository;

HotelServiceImpl(HotelRepository hotelRepository, ReviewRepository reviewRepository) {
Assert.notNull(hotelRepository, "HotelRepository must not be null");
Assert.notNull(reviewRepository, "ReviewRepository must not be null");
this.hotelRepository = hotelRepository;
this.reviewRepository = reviewRepository;
}

@Override
public Hotel getHotel(City city, String name) {
Assert.notNull(city, "City must not be null");
Assert.hasLength(name, "Name must not be empty");

return this.hotelRepository.findByCityAndName(city, name);
}

@Override
public Page<Review> getReviews(Hotel hotel, Pageable pageable) {
Assert.notNull(hotel, "Hotel must not be null");
Assert.notNull(pageable, "Pageable must not be null");

return this.reviewRepository.findByHotel(hotel, pageable);
}

@Override
public Review getReview(Hotel hotel, int reviewNumber) {
Assert.notNull(hotel, "Hotel must not be null");
Assert.isTrue(reviewNumber > 0, "Review number must be greater than 0");

return this.reviewRepository.findByHotelAndIndex(hotel, reviewNumber);
}

@Override
public Review addReview(Hotel hotel, ReviewDetails details) {
Assert.notNull(hotel, "Hotel must not be null");
Assert.notNull(details, "ReviewDetails must not be null");

Review review = new Review(hotel, 1, details);
return this.reviewRepository.save(review);
}

@Override
public ReviewsSummary getReviewSummary(Hotel hotel) {
Assert.notNull(hotel, "Hotel must not be null");

List<RatingCount> ratingCounts = this.hotelRepository.findRatingCounts(hotel);
return new ReviewsSummaryImpl(ratingCounts);
}

private static class ReviewsSummaryImpl implements ReviewsSummary {

private final Map<Rating, Long> ratingCount;

ReviewsSummaryImpl(List<RatingCount> ratingCounts) {
Assert.notNull(ratingCounts, "RatingCounts must not be null");

this.ratingCount = new HashMap<>();
for (RatingCount ratingCount : ratingCounts) {
this.ratingCount.put(ratingCount.getRating(), ratingCount.getCount());
}
}

@Override
public long getNumberOfReviewsWithRating(Rating rating) {
Assert.notNull(rating, "Rating must not be null");

Long count = this.ratingCount.get(rating);
return (count != null) ? count : 0;
}

}

}

# SampleWebFreeMarkerApplicationTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.freemarker;

import java.util.Arrays;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Basic integration tests for FreeMarker application.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleWebFreeMarkerApplicationTests {

@Autowired
private TestRestTemplate testRestTemplate;

@Test
void testFreeMarkerTemplate() {
ResponseEntity<String> entity = this.testRestTemplate.getForEntity("/", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("Expected Content"); // Replace with expected content
}

@Test
void testFreeMarkerErrorTemplate() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpEntity<String> requestEntity = new HttpEntity<>(headers);

ResponseEntity<String> responseEntity = this.testRestTemplate.exchange("/does-not-exist", HttpMethod.GET,
requestEntity, String.class);

assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(responseEntity.getBody()).contains("Error Page Content"); // Replace with expected error content
}
}

# FileSystemWatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.devtools.filewatch;

import java.io.File;
import java.io.FileFilter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.util.Assert;

/**
* Watches specific directories for file changes.
*
* @author Andy Clement
* @author Phillip Webb
* @since 1.3.0
* @see FileChangeListener
*/
public class FileSystemWatcher {

private static final Duration DEFAULT_POLL_INTERVAL = Duration.ofMillis(1000);

private static final Duration DEFAULT_QUIET_PERIOD = Duration.ofMillis(400);

private final List<FileChangeListener> listeners = new ArrayList<>();

private final boolean daemon;

private final long pollInterval;

private final long quietPeriod;

private final SnapshotStateRepository snapshotStateRepository;

private final AtomicInteger remainingScans = new AtomicInteger(-1);

private final Map<File, DirectorySnapshot> directories = new HashMap<>();

private Thread watchThread;

private FileFilter triggerFilter;

private final Object monitor = new Object();

/**
* Create a new {@link FileSystemWatcher} instance.
*/
public FileSystemWatcher() {
this(true, DEFAULT_POLL_INTERVAL, DEFAULT_QUIET_PERIOD);
}

/**
* Create a new {@link FileSystemWatcher} instance.
* @param daemon if a daemon thread used to monitor changes
* @param pollInterval the amount of time to wait between checking for changes
* @param quietPeriod the amount of time required after a change has been detected to
* ensure that updates have completed
*/
public FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod) {
this(daemon, pollInterval, quietPeriod, null);
}

/**
* Create a new {@link FileSystemWatcher} instance.
* @param daemon if a daemon thread used to monitor changes
* @param pollInterval the amount of time to wait between checking for changes
* @param quietPeriod the amount of time required after a change has been detected to
* ensure that updates have completed
* @param snapshotStateRepository the snapshot state repository
* @since 2.4.0
*/
public FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod,
SnapshotStateRepository snapshotStateRepository) {
this.daemon = daemon;
this.pollInterval = pollInterval.toMillis();
this.quietPeriod = quietPeriod.toMillis();
this.snapshotStateRepository = (snapshotStateRepository != null) ? snapshotStateRepository
: SnapshotStateRepository.NONE;
}

/**
* Add listener for file change events. Cannot be called after the watcher has been
* {@link #start() started}.
* @param fileChangeListener the listener to add
*/
public void addListener(FileChangeListener fileChangeListener) {
synchronized (this.monitor) {
checkNotStarted();
this.listeners.add(fileChangeListener);
}
}

/**
* Add source directories to monitor. Cannot be called after the watcher has been
* {@link #start() started}.
* @param directories the directories to monitor
*/
public void addSourceDirectories(Iterable<File> directories) {
synchronized (this.monitor) {
Assert.notNull(directories, "Directories must not be null");
directories.forEach(this::addSourceDirectory);
}
}

/**
* Add a source directory to monitor. Cannot be called after the watcher has been
* {@link #start() started}.
* @param directory the directory to monitor
*/
public void addSourceDirectory(File directory) {
synchronized (this.monitor) {
Assert.notNull(directory, "Directory must not be null");
Assert.isTrue(directory.isDirectory(), "Directory '" + directory + "' must exist and be a directory");
checkNotStarted();
this.directories.put(directory, null);
}
}

/**
* Set an optional {@link FileFilter} used to limit the files that trigger a change.
* @param triggerFilter a trigger filter or null
*/
public void setTriggerFilter(FileFilter triggerFilter) {
synchronized (this.monitor) {
this.triggerFilter = triggerFilter;
}
}

private void checkNotStarted() {
Assert.state(this.watchThread == null, "FileSystemWatcher already started");
}

/**
* Start monitoring the source directory for changes.
*/
public void start() {
synchronized (this.monitor) {
createOrRestoreInitialSnapshots();
if (this.watchThread == null) {
Map<File, DirectorySnapshot> localDirectories = new HashMap<>(this.directories);
Watcher watcher = new Watcher(this.remainingScans, new ArrayList<>(this.listeners), this.triggerFilter,
this.pollInterval, this.quietPeriod, localDirectories, this.snapshotStateRepository);
this.watchThread = new Thread(watcher);
this.watchThread.setName("File Watcher");
this.watchThread.setDaemon(this.daemon);
this.watchThread.start();
}
}
}

@SuppressWarnings("unchecked")
private void createOrRestoreInitialSnapshots() {
Map<File, DirectorySnapshot> restored = (Map<File, DirectorySnapshot>) this.snapshotStateRepository.restore();
this.directories.replaceAll((f, v) -> {
DirectorySnapshot restoredSnapshot = (restored != null) ? restored.get(f) : null;
return (restoredSnapshot != null) ? restoredSnapshot : new DirectorySnapshot(f);
});
}

/**
* Stop monitoring the source directories.
*/
public void stop() {
stopAfter(0);
}

/**
* Stop monitoring the source directories.
* @param remainingScans the number of remaining scans
*/
void stopAfter(int remainingScans) {
Thread thread;
synchronized (this.monitor) {
thread = this.watchThread;
if (thread != null) {
this.remainingScans.set(remainingScans);
if (remainingScans <= 0) {
thread.interrupt();
}
}
this.watchThread = null;
}
if (thread != null && Thread.currentThread() != thread) {
try {
thread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}

private static final class Watcher implements Runnable {

private final AtomicInteger remainingScans;

private final List<FileChangeListener> listeners;

private final FileFilter triggerFilter;

private final long pollInterval;

private final long quietPeriod;

private Map<File, DirectorySnapshot> directories;

private final SnapshotStateRepository snapshotStateRepository;

private Watcher(AtomicInteger remainingScans, List<FileChangeListener> listeners, FileFilter triggerFilter,
long pollInterval, long quietPeriod, Map<File, DirectorySnapshot> directories,
SnapshotStateRepository snapshotStateRepository) {
this.remainingScans = remainingScans;
this.listeners = listeners;
this.triggerFilter = triggerFilter;
this.pollInterval = pollInterval;
this.quietPeriod = quietPeriod;
this.directories = directories;
this.snapshotStateRepository = snapshotStateRepository;

}

@Override
public void run() {
int remainingScans = this.remainingScans.get();
while (remainingScans > 0 || remainingScans == -1) {
try {
if (remainingScans > 0) {
this.remainingScans.decrementAndGet();
}
scan();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
remainingScans = this.remainingScans.get();
}
}

private void scan() throws InterruptedException {
Thread.sleep(this.pollInterval - this.quietPeriod);
Map<File, DirectorySnapshot> previous;
Map<File, DirectorySnapshot> current = this.directories;
do {
previous = current;
current = getCurrentSnapshots();
Thread.sleep(this.quietPeriod);
}
while (isDifferent(previous, current));
if (isDifferent(this.directories, current)) {
updateSnapshots(current.values());
}
}

private boolean isDifferent(Map<File, DirectorySnapshot> previous, Map<File, DirectorySnapshot> current) {
Assert.notNull(previous, "Previous snapshots must not be null");
Assert.notNull(current, "Current snapshots must not be null");
if (!previous.keySet().equals(current.keySet())) {
return true;
}
for (Map.Entry<File, DirectorySnapshot> entry : previous.entrySet()) {
DirectorySnapshot previousDirectory = entry.getValue();
DirectorySnapshot currentDirectory = current.get(entry.getKey());
if (!previousDirectory.equals(currentDirectory, this.triggerFilter)) {
return true;
}
}
return false;
}

private Map<File, DirectorySnapshot> getCurrentSnapshots() {
Map<File, DirectorySnapshot> snapshots = new LinkedHashMap<>();
for (File directory : this.directories.keySet()) {
snapshots.put(directory, new DirectorySnapshot(directory));
}
return snapshots;
}

private void updateSnapshots(Collection<DirectorySnapshot> snapshots) {
Assert.notNull(snapshots, "Snapshots must not be null");
Map<File, DirectorySnapshot> updated = new LinkedHashMap<>();
Set<ChangedFiles> changeSet = new LinkedHashSet<>();
for (DirectorySnapshot snapshot : snapshots) {
DirectorySnapshot previous = this.directories.get(snapshot.getDirectory());
updated.put(snapshot.getDirectory(), snapshot);
ChangedFiles changedFiles = previous.getChangedFiles(snapshot, this.triggerFilter);
if (!changedFiles.getFiles().isEmpty()) {
changeSet.add(changedFiles);
}
}
this.directories = updated;
this.snapshotStateRepository.save(updated);
if (!changeSet.isEmpty()) {
fireListeners(Collections.unmodifiableSet(changeSet));
}
}

private void fireListeners(Set<ChangedFiles> changeSet) {
Assert.notNull(changeSet, "Change set must not be null");
for (FileChangeListener listener : this.listeners) {
listener.onChange(changeSet);
}
}

}

}

# LifecycleVersion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.buildpack.platform.build;

import java.util.Comparator;

import org.springframework.util.Assert;

/**
* A lifecycle version number comprised of a major, minor and patch value.
*
* @author Phillip Webb
*/
class LifecycleVersion implements Comparable<LifecycleVersion> {

private static final Comparator<LifecycleVersion> COMPARATOR = Comparator.comparingInt(LifecycleVersion::getMajor)
.thenComparingInt(LifecycleVersion::getMinor)
.thenComparing(LifecycleVersion::getPatch);

private final int major;

private final int minor;

private final int patch;

LifecycleVersion(int major, int minor, int patch) {
Assert.isTrue(major >= 0, "Major version must be non-negative");
Assert.isTrue(minor >= 0, "Minor version must be non-negative");
Assert.isTrue(patch >= 0, "Patch version must be non-negative");
this.major = major;
this.minor = minor;
this.patch = patch;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LifecycleVersion other = (LifecycleVersion) obj;
boolean result = true;
result = result && this.major == other.major;
result = result && this.minor == other.minor;
result = result && this.patch == other.patch;
return result;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.major;
result = prime * result + this.minor;
result = prime * result + this.patch;
return result;
}

@Override
public String toString() {
return "v" + this.major + "." + this.minor + "." + this.patch;
}

/**
* Return if this version is greater than or equal to the specified version.
* @param other the version to compare
* @return {@code true} if this version is greater than or equal to the specified
* version
*/
boolean isEqualOrGreaterThan(LifecycleVersion other) {
Assert.notNull(other, "Other version must not be null");
return compareTo(other) >= 0;
}

@Override
public int compareTo(LifecycleVersion other) {
Assert.notNull(other, "Other version must not be null");
return COMPARATOR.compare(this, other);
}

/**
* Return the major version number.
* @return the major version
*/
int getMajor() {
return this.major;
}

/**
* Return the minor version number.
* @return the minor version
*/
int getMinor() {
return this.minor;
}

/**
* Return the patch version number.
* @return the patch version
*/
int getPatch() {
return this.patch;
}

/**
* Factory method to parse a string into a {@link LifecycleVersion} instance.
* @param value the value to parse.
* @return the corresponding {@link LifecycleVersion}
* @throws IllegalArgumentException if the value could not be parsed
*/
static LifecycleVersion parse(String value) {
Assert.notNull(value, "Value must not be null");
if (value.startsWith("v") || value.startsWith("V")) {
value = value.substring(1);
}
String[] components = value.split("\\.");
Assert.isTrue(components.length == 3, "Value must have major, minor, and patch components");
int[] versions = new int[3];
for (int i = 0; i < components.length; i++) {
try {
versions[i] = Integer.parseInt(components[i]);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Malformed version number '" + value + "'", ex);
}
}
return new LifecycleVersion(versions[0], versions[1], versions[2]);
}

}

# 分析

# 原代码

# CityServicelmpl

1
2
3
4
Assert.notNull(criteria, "Criteria must not be null");
Assert.notNull(name, "Name must not be null");
Assert.notNull(country, "Country must not be null");
Assert.notNull(city, "City must not be null");

  • 分别断言 criterianamecountrycity 不为 null, 如果为 null 则抛出警告
  • 依赖性分析:不依赖具体的 SUT 输出,因为这是对输入参数的检查。

# HotelServicelmpl

1
2
3
Assert.notNull(city, "City must not be null");
Assert.notNull(hotel, "Hotel must not be null");
Assert.notNull(hotel, "Hotel must not be null");

  • 断言 cityhotel 不能为 null
  • 依赖性分析:不依赖具体的 SUT 输出,因为这是对输入参数的检查。

1
Assert.hasLength(name, "Name must not be empty");

  • Assert.hasLength(name, "Name must not be empty") : 断言 name 字符串不为空且长度大于 0。如果 name 为空或长度为 0,则抛出 IllegalArgumentException ,并显示消息 "Name must not be empty"。
  • 依赖性分析:依赖输入值但不依赖具体的 SUT 输出,因为这是对输入参数的检查。

# LLM 生成

# CityServicelmpl

1
2
3
4
5
6
7
8
Assert.notNull(cityRepository, "CityRepository must not be null");
Assert.notNull(hotelRepository, "HotelRepository must not be null");
Assert.notNull(criteria, "CitySearchCriteria must not be null");
Assert.notNull(pageable, "Pageable must not be null");
Assert.hasLength(name, "Name must not be empty");
Assert.hasLength(country, "Country must not be empty");
Assert.notNull(city, "City must not be null");
Assert.notNull(pageable, "Pageable must not be null");

# HotelServicelmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
Assert.notNull(hotelRepository, "HotelRepository must not be null");
Assert.notNull(reviewRepository, "ReviewRepository must not be null");
Assert.notNull(city, "City must not be null");
Assert.hasLength(name, "Name must not be empty");
Assert.notNull(hotel, "Hotel must not be null");
Assert.notNull(pageable, "Pageable must not be null");
Assert.notNull(hotel, "Hotel must not be null");
Assert.isTrue(reviewNumber > 0, "Review number must be greater than 0");
Assert.notNull(hotel, "Hotel must not be null");
Assert.notNull(details, "ReviewDetails must not be null");
Assert.notNull(hotel, "Hotel must not be null");
Assert.notNull(ratingCounts, "RatingCounts must not be null");
Assert.notNull(rating, "Rating must not be null");

# LLM 生成代码分析

CityServiceImpl

  • Assert.notNull(cityRepository, "CityRepository must not be null");
    • 生成原因:确保构造函数中的依赖项 cityRepository 不为 null
    • 生成评价:这是一个好的实践,确保依赖项的非空检查。
  • Assert.notNull(hotelRepository, "HotelRepository must not be null");
    • 生成原因:确保构造函数中的依赖项 hotelRepository 不为 null
    • 生成评价:这是一个好的实践,确保依赖项的非空检查。
  • Assert.notNull(criteria, "CitySearchCriteria must not be null");
    • 生成原因:确保方法参数 criteria 不为 null
    • 生成评价:符合预期,因为原代码中也有类似的检查。
  • Assert.notNull(pageable, "Pageable must not be null");
    • 生成原因:确保方法参数 pageable 不为 null
    • 生成评价:符合预期,因为 pageable 是分页功能的关键参数。
  • Assert.hasLength(name, "Name must not be empty");
    • 生成原因:确保 name 字符串不为空且长度大于 0。
    • 生成评价:符合预期,这是对字符串参数的常见检查。
  • Assert.hasLength(country, "Country must not be empty");
    • 生成原因:确保 country 字符串不为空且长度大于 0。
    • 生成评价:符合预期,这是对字符串参数的常见检查。
  • Assert.notNull(city, "City must not be null");
    • 生成原因:确保方法参数 city 不为 null
    • 生成评价:符合预期,原代码中也有类似的检查。

HotelServiceImpl

  • Assert.notNull(hotelRepository, "HotelRepository must not be null");
    • 生成原因:确保构造函数中的依赖项 hotelRepository 不为 null
    • 生成评价:这是一个好的实践,确保依赖项的非空检查。
  • Assert.notNull(reviewRepository, "ReviewRepository must not be null");
    • 生成原因:确保构造函数中的依赖项 reviewRepository 不为 null
    • 生成评价:这是一个好的实践,确保依赖项的非空检查。
  • Assert.notNull(city, "City must not be null");
    • 生成原因:确保方法参数 city 不为 null
    • 生成评价:符合预期,原代码中也有类似的检查。
  • Assert.hasLength(name, "Name must not be empty");
    • 生成原因:确保 name 字符串不为空且长度大于 0。
    • 生成评价:符合预期,这是对字符串参数的常见检查。
  • Assert.notNull(hotel, "Hotel must not be null");
    • 生成原因:确保方法参数 hotel 不为 null
    • 生成评价:符合预期,原代码中也有类似的检查。
  • Assert.notNull(pageable, "Pageable must not be null");
    • 生成原因:确保方法参数 pageable 不为 null
    • 生成评价:符合预期,因为 pageable 是分页功能的关键参数。
  • Assert.isTrue(reviewNumber > 0, "Review number must be greater than 0");
    • 生成原因:确保 reviewNumber 参数大于 0。
    • 生成评价:符合预期,原代码中也有类似的检查。
  • Assert.notNull(details, "ReviewDetails must not be null");
    • 生成原因:确保方法参数 details 不为 null
    • 生成评价:符合预期,原代码中也有类似的检查。
  • Assert.notNull(ratingCounts, "RatingCounts must not be null");
    • 生成原因:确保方法参数 ratingCounts 不为 null
    • 生成评价:符合预期,原代码中也有类似的检查。
  • Assert.notNull(rating, "Rating must not be null");
    • 生成原因:确保方法参数 rating 不为 null
    • 生成评价:符合预期,原代码中也有类似的检查。

# SampleWebFreeMarkerApplicationTests

  1. 断言 1:

    1
    assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);

    • 验证内容: 此断言验证 / 端点返回的 HTTP 状态码是否为 200 OK
    • 是否依赖具体的 SUT 输出: 此断言依赖于被测软件(SUT)的具体输出,因为它检查的是返回的 HTTP 状态码,这是 web 应用程序的直接输出。
  2. 断言 2:

    1
    assertThat(entity.getBody()).contains("Expected Content"); // Replace with expected content

    • 验证内容: 此断言验证响应体中是否包含 “预期内容”。
    • 是否依赖具体的 SUT 输出: 此断言也依赖于具体的 SUT 输出,因为它检查的是响应体的内容,这是 web 应用程序返回的具体数据。

# 方法 2: testFreeMarkerErrorTemplate

  1. 断言 1:

    1
    assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);

    • 验证内容: 此断言验证 /does-not-exist 端点返回的 HTTP 状态码是否为 404 Not Found
    • 是否依赖具体的 SUT 输出: 此断言依赖于具体的 SUT 输出,因为它检查的是返回的 HTTP 状态码,这是 web 应用程序的直接输出。
  2. 断言 2:

    1
    assertThat(responseEntity.getBody()).contains("Error Page Content"); // Replace with expected error content

    • 验证内容: 此断言验证响应体中是否包含 “错误页面内容”。
    • 是否依赖具体的 SUT 输出: 此断言也依赖于具体的 SUT 输出,因为它检查的是响应体的内容,这是 web 应用程序返回的具体数据。

# LLM 生成代码断言分析

对比 原代码LLM生成代码 中的断言,分析每个断言为什么能生成或不能生成,以及它们验证的内容:

# 方法 1: testFreeMarkerTemplate

  1. 断言 1:

    1
    assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);

    • 生成原因: 此断言检查 HTTP 状态码,这是一个合理且常见的检查点。
    • 验证内容: 验证 HTTP 状态码是否为 200 OK
    • 是否依赖具体 SUT 输出: 依赖,因为检查的是具体的 HTTP 响应。
  2. 断言 2:

    1
    assertThat(entity.getBody()).contains("Expected Content");

    • 生成原因: 此断言检查响应体内容,验证返回的页面是否包含预期的内容。
    • 验证内容: 验证响应体中是否包含 “预期内容”。
    • 是否依赖具体 SUT 输出: 依赖,因为检查的是具体的响应体内容。

# 方法 2: testFreeMarkerErrorTemplate

  1. 断言 1:

    1
    assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);

    • 生成原因: 此断言检查 HTTP 状态码,这是一个合理且常见的检查点。
    • 验证内容: 验证 HTTP 状态码是否为 404 Not Found
    • 是否依赖具体 SUT 输出: 依赖,因为检查的是具体的 HTTP 响应。
  2. 断言 2:

    1
    assertThat(responseEntity.getBody()).contains("Error Page Content");

    • 生成原因: 此断言检查响应体内容,验证返回的页面是否包含错误页面内容。
    • 验证内容: 验证响应体中是否包含 “错误页面内容”。
    • 是否依赖具体 SUT 输出: 依赖,因为检查的是具体的响应体内容。

# FileSystemWatcher

# 原代码断言分析

1
2
3
4
5
Assert.notNull(pollInterval, "PollInterval must not be null");
Assert.notNull(quietPeriod, "QuietPeriod must not be null");
Assert.isTrue(pollInterval.toMillis() > 0, "PollInterval must be positive");
Assert.isTrue(quietPeriod.toMillis() > 0, "QuietPeriod must be positive");
Assert.isTrue(pollInterval.toMillis() > quietPeriod.toMillis(), "PollInterval must be greater than QuietPeriod");

  • 作用:这些断言确保 pollIntervalquietPeriod 的参数不为 null 且为正值,并且 pollInterval 大于 quietPeriod
  • 依赖性:这些断言依赖于传入的参数,而非 SUT 的具体输出,因为它们仅验证构造函数参数的正确性。

1
Assert.notNull(fileChangeListener, "FileChangeListener must not be null");

  • 作用:确保传入的 fileChangeListener 不为 null。
  • 依赖性:依赖于传入的参数,而非 SUT 的具体输出。

1
Assert.notNull(directories, "Directories must not be null");

  • 作用:确保传入的 directories 不为 null。
  • 依赖性:依赖于传入的参数,而非 SUT 的具体输出。

1
2
Assert.notNull(directory, "Directory must not be null");
Assert.isTrue(!directory.isFile(), () -> "Directory '" + directory + "' must not be a file");

  • 作用:确保传入的 directory 不为 null 且不是文件。
  • 依赖性:依赖于传入的参数,而非 SUT 的具体输出。

1
Assert.state(this.watchThread == null, "FileSystemWatcher already started");

  • 作用:确保在 watchThread 未启动的状态下调用某些方法。
  • 依赖性:依赖于 SUT 的内部状态,即 watchThread 的状态。

1
2
Assert.notNull(previous, "Previous snapshots must not be null");
Assert.notNull(current, "Current snapshots must not be null");

  • 作用:确保传入的 previouscurrent 快照不为 null。
  • 依赖性:依赖于传入的参数,而非 SUT 的具体输出。

1
Assert.notNull(snapshots, "Snapshots must not be null");

  • 作用:确保传入的 snapshots 不为 null。
  • 依赖性:依赖于传入的参数,而非 SUT 的具体输出。

1
Assert.notNull(changeSet, "Change set must not be null");

  • 作用:确保传入的 changeSet 不为 null。
  • 依赖性:依赖于传入的参数,而非 SUT 的具体输出。

# LLM 生成的断言

FileSystemWatcher(boolean daemon, Duration pollInterval, Duration quietPeriod, SnapshotStateRepository snapshotStateRepository)

1
2
3
4
5
Assert.notNull(pollInterval, "PollInterval must not be null");
Assert.notNull(quietPeriod, "QuietPeriod must not be null");
Assert.isTrue(pollInterval.toMillis() > 0, "PollInterval must be positive");
Assert.isTrue(quietPeriod.toMillis() > 0, "QuietPeriod must be positive");
Assert.isTrue(pollInterval.toMillis() > quietPeriod.toMillis(), "PollInterval must be greater than QuietPeriod");

  • 生成原因:LLM 能正确识别出需要对构造函数参数进行检查以确保其有效性,并生成了与原代码一致的断言。
  • 分析:这些断言确保了输入参数的有效性和合理性,不依赖于具体的 SUT 输出,因此生成是正确和必要的。

1
Assert.notNull(fileChangeListener, "FileChangeListener must not be null");

  • 生成原因:LLM 识别到需要确保传入的监听器不为 null。
  • 分析:这是一个合理的生成,确保了参数有效性,不依赖于具体的 SUT 输出。

1
Assert.notNull(directories, "Directories must not be null");

  • 生成原因:LLM 识别到需要确保传入的目录集合不为 null。
  • 分析:这是一个合理的生成,确保了参数有效性,不依赖于具体的 SUT 输出。

1
2
Assert.notNull(directory, "Directory must not be null");
Assert.isTrue(directory.isDirectory(), "Directory '" + directory + "' must exist and be a directory");

  • 生成原因:LLM 识别到需要确保传入的目录不为 null 且为目录。
  • 分析:这个生成合理,确保了参数的有效性,并且相比原代码更严格地验证了目录的存在性和类型,这样生成是更好的。

1
Assert.state(this.watchThread == null, "FileSystemWatcher already started");

  • 生成原因:LLM 识别到需要确保在调用某些方法前,监视线程未启动。
  • 分析:合理生成,依赖于 SUT 的内部状态。

1
2
Assert.notNull(previous, "Previous snapshots must not be null");
Assert.notNull(current, "Current snapshots must not be null");

  • 生成原因:LLM 识别到需要确保传入的快照参数不为 null。
  • 分析:合理生成,确保了参数有效性,不依赖于具体的 SUT 输出。

1
Assert.notNull(snapshots, "Snapshots must not be null");

  • 生成原因:LLM 识别到需要确保传入的快照集合不为 null。
  • 分析:合理生成,确保了参数有效性,不依赖于具体的 SUT 输出。

1
Assert.notNull(changeSet, "Change set must not be null");

  • 生成原因:LLM 识别到需要确保传入的更改集不为 null。
  • 分析:合理生成,确保了参数有效性,不依赖于具体的 SUT 输出。

# 新增的断言

1
Assert.isTrue(directory.isDirectory(), "Directory '" + directory + "' must exist and be a directory");

  • 生成原因:LLM 可能认为需要进一步验证目录的存在性和类型。
  • 分析:这个断言比原代码更严格,确保了传入的参数不仅不为 null 且为目录。这是一个合理且改进的生成,有助于提高代码的健壮性。

# LifecycleVersion

# 原代码

Assert.hasText(value, "Value must not be empty")

  • 断言解释: 确保传入的字符串 value 非空且包含非空白字符。
  • 依赖性分析: 该断言只依赖于传入的字符串 value ,与具体的 SUT 无关。

Assert.isTrue(components.length <= 3, "Malformed version number '" + value + "'")

  • 断言解释: 确保版本号格式正确,即由最多三个部分组成(主版本号、次版本号、补丁版本号)。
  • 依赖性分析: 该断言只依赖于传入的字符串 value 的格式,与具体的 SUT 无关。

# LLM 生成代码

断言解释:

  • Assert.isTrue(major >= 0, "Major version must be non-negative") : 确保主版本号非负。
  • Assert.isTrue(minor >= 0, "Minor version must be non-negative") : 确保次版本号非负。
  • Assert.isTrue(patch >= 0, "Patch version must be non-negative") : 确保补丁版本号非负。

为什么能生成或不能生成:

  • 这些断言生成的原因是因为它们是合理的输入验证,以确保版本号的各个部分都是有效的非负整数。

断言解释:

  • Assert.notNull(other, "Other version must not be null") : 确保比较的 other 对象非空。

为什么能生成或不能生成:

  • 生成这些断言是为了确保传入的 other 对象非空,避免 NullPointerException

断言解释:

  • Assert.notNull(other, "Other version must not be null") : 确保比较的 other 对象非空。

为什么能生成或不能生成:

  • 生成这些断言是为了确保传入的 other 对象非空,避免 NullPointerException

断言解释:

  • Assert.notNull(value, "Value must not be null") : 确保传入的字符串 value 非空。
  • Assert.isTrue(components.length == 3, "Value must have major, minor, and patch components") : 确保版本号由三个部分组成。

为什么能生成或不能生成:

  • Assert.notNull(value, "Value must not be null") : 生成该断言是为了确保传入的字符串非空。
  • Assert.isTrue(components.length == 3, "Value must have major, minor, and patch components") : 生成该断言是为了确保版本号格式正确,包含主版本号、次版本号和补丁版本号。

# 比较分析

  1. 原代码和 LLM 生成代码在 LifecycleVersion.parse 方法中的断言:
    • 相同点:
      • 都有确保传入字符串非空和格式正确的断言。
    • 不同点:
      • LLM 生成的代码增加了 Assert.notNull(value, "Value must not be null") ,而不是 Assert.hasText(value, "Value must not be empty") 。这两者的差异在于 hasText 不仅检查非空,还检查是否包含非空白字符。
      • LLM 生成的代码断言版本号必须包含主版本号、次版本号和补丁版本号,而不是最多三个部分。
  2. 原代码没有但 LLM 生成的断言:
    • 构造函数中的断言:
      • 原代码没有对传入参数进行验证,而 LLM 生成的代码对 majorminorpatch 参数进行了非负验证。
      • 好处: 增强了输入参数的验证,防止无效数据进入对象。
    • isEqualOrGreaterThancompareTo 方法中的断言:
      • 原代码没有对传入的 other 对象进行空检查,而 LLM 生成的代码添加了 Assert.notNull 检查。
      • 好处: 防止在比较操作中传入空值,避免潜在的 NullPointerException