1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.scm;
20
21 import java.io.Serializable;
22 import java.text.ParseException;
23 import java.text.SimpleDateFormat;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Date;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Set;
30
31 import org.apache.maven.scm.provider.ScmProviderRepository;
32 import org.apache.maven.scm.util.FilenameUtils;
33 import org.apache.maven.scm.util.ThreadSafeDateFormat;
34
35
36
37
38 public class ChangeSet implements Serializable {
39 private static final long serialVersionUID = 7097705862222539801L;
40
41
42
43
44 public static final String LESS_THAN_ENTITY = "<";
45
46
47
48
49 public static final String GREATER_THAN_ENTITY = ">";
50
51
52
53
54 public static final String AMPERSAND_ENTITY = "&";
55
56
57
58
59 public static final String APOSTROPHE_ENTITY = "'";
60
61
62
63
64 public static final String QUOTE_ENTITY = """;
65
66 private static final String DATE_PATTERN = "yyyy-MM-dd";
67
68
69
70
71 private static final ThreadSafeDateFormat DATE_FORMAT = new ThreadSafeDateFormat(DATE_PATTERN);
72
73 private static final String TIME_PATTERN = "HH:mm:ss";
74
75
76
77
78 private static final ThreadSafeDateFormat TIME_FORMAT = new ThreadSafeDateFormat(TIME_PATTERN);
79
80
81
82
83 private static final ThreadSafeDateFormat TIMESTAMP_FORMAT_1 = new ThreadSafeDateFormat("yyyy/MM/dd HH:mm:ss");
84
85 private static final ThreadSafeDateFormat TIMESTAMP_FORMAT_2 = new ThreadSafeDateFormat("yyyy-MM-dd HH:mm:ss");
86
87 private static final ThreadSafeDateFormat TIMESTAMP_FORMAT_3 = new ThreadSafeDateFormat("yyyy/MM/dd HH:mm:ss z");
88
89 private static final ThreadSafeDateFormat TIMESTAMP_FORMAT_4 = new ThreadSafeDateFormat("yyyy-MM-dd HH:mm:ss z");
90
91
92
93
94 private Date date;
95
96
97
98
99 private String author;
100
101
102
103
104 private String comment = "";
105
106
107
108
109 private List<ChangeFile> files;
110
111
112
113
114 private List<String> tags;
115
116
117
118
119
120
121 private String revision;
122
123
124
125
126
127
128 private String parentRevision;
129
130
131
132
133
134
135 private Set<String> mergedRevisions;
136
137
138
139
140
141
142
143
144 public ChangeSet(String strDate, String userDatePattern, String comment, String author, List<ChangeFile> files) {
145 this(null, comment, author, files);
146
147 setDate(strDate, userDatePattern);
148 }
149
150
151
152
153
154
155
156 public ChangeSet(Date date, String comment, String author, List<ChangeFile> files) {
157 setDate(date);
158
159 setAuthor(author);
160
161 setComment(comment);
162
163 this.files = files;
164 }
165
166
167
168
169 public ChangeSet() {
170
171 }
172
173
174
175
176
177
178 public List<ChangeFile> getFiles() {
179 if (files == null) {
180 return new ArrayList<>();
181 }
182 return files;
183 }
184
185
186
187
188
189
190 public void setFiles(List<ChangeFile> files) {
191 this.files = files;
192 }
193
194 public void addFile(ChangeFile file) {
195 if (files == null) {
196 files = new ArrayList<>();
197 }
198
199 files.add(file);
200 }
201
202
203
204
205
206
207
208 public boolean containsFilename(String filename, ScmProviderRepository repository) {
209 return containsFilename(filename);
210 }
211
212 public boolean containsFilename(String filename) {
213 if (files != null) {
214 for (ChangeFile file : files) {
215 String f1 = FilenameUtils.normalizeFilename(file.getName());
216 String f2 = FilenameUtils.normalizeFilename(filename);
217 if (f1.indexOf(f2) >= 0) {
218 return true;
219 }
220 }
221 }
222
223 return false;
224 }
225
226
227
228
229
230
231 public String getAuthor() {
232 return author;
233 }
234
235
236
237
238
239
240 public void setAuthor(String author) {
241 this.author = author;
242 }
243
244
245
246
247
248
249 public String getComment() {
250 return comment;
251 }
252
253
254
255
256
257
258 public void setComment(String comment) {
259 this.comment = comment;
260 }
261
262
263
264
265
266
267 public Date getDate() {
268 if (date != null) {
269 return (Date) date.clone();
270 }
271
272 return null;
273 }
274
275
276
277
278
279
280 public void setDate(Date date) {
281 if (date != null) {
282 this.date = new Date(date.getTime());
283 }
284 }
285
286
287
288
289
290
291 public void setDate(String date) {
292 setDate(date, null);
293 }
294
295
296
297
298
299
300
301 public void setDate(String date, String userDatePattern) {
302 try {
303 if (!(userDatePattern == null || userDatePattern.isEmpty())) {
304 SimpleDateFormat format = new SimpleDateFormat(userDatePattern);
305
306 this.date = format.parse(date);
307 } else {
308 this.date = TIMESTAMP_FORMAT_3.parse(date);
309 }
310 } catch (ParseException e) {
311 if (!(userDatePattern == null || userDatePattern.isEmpty())) {
312 try {
313 this.date = TIMESTAMP_FORMAT_3.parse(date);
314 } catch (ParseException pe) {
315 try {
316 this.date = TIMESTAMP_FORMAT_4.parse(date);
317 } catch (ParseException pe1) {
318 try {
319 this.date = TIMESTAMP_FORMAT_1.parse(date);
320 } catch (ParseException pe2) {
321 try {
322 this.date = TIMESTAMP_FORMAT_2.parse(date);
323 } catch (ParseException pe3) {
324 throw new IllegalArgumentException("Unable to parse date: " + date);
325 }
326 }
327 }
328 }
329 } else {
330 try {
331 this.date = TIMESTAMP_FORMAT_4.parse(date);
332 } catch (ParseException pe1) {
333 try {
334 this.date = TIMESTAMP_FORMAT_1.parse(date);
335 } catch (ParseException pe2) {
336 try {
337 this.date = TIMESTAMP_FORMAT_2.parse(date);
338 } catch (ParseException pe3) {
339 throw new IllegalArgumentException("Unable to parse date: " + date);
340 }
341 }
342 }
343 }
344 }
345 }
346
347
348
349
350 public String getDateFormatted() {
351 return DATE_FORMAT.format(getDate());
352 }
353
354
355
356
357 public String getTimeFormatted() {
358 return TIME_FORMAT.format(getDate());
359 }
360
361
362
363
364
365
366 public List<String> getTags() {
367 if (tags == null) {
368 return new ArrayList<>();
369 }
370 return tags;
371 }
372
373
374
375
376
377
378 public void setTags(List<String> tags) {
379 this.tags = tags;
380 }
381
382
383
384
385
386
387 public void addTag(String tag) {
388 if (tag == null) {
389 return;
390 }
391 tag = tag.trim();
392 if (tag.isEmpty()) {
393 return;
394 }
395 if (tags == null) {
396 tags = new ArrayList<>();
397 }
398 tags.add(tag);
399 }
400
401
402
403
404
405 public String getRevision() {
406 return revision;
407 }
408
409
410
411
412
413 public void setRevision(String revision) {
414 this.revision = revision;
415 }
416
417 public String getParentRevision() {
418 return parentRevision;
419 }
420
421 public void setParentRevision(String parentRevision) {
422 this.parentRevision = parentRevision;
423 }
424
425 public void addMergedRevision(String mergedRevision) {
426 if (mergedRevisions == null) {
427 mergedRevisions = new LinkedHashSet<>();
428 }
429 mergedRevisions.add(mergedRevision);
430 }
431
432 public Set<String> getMergedRevisions() {
433 return mergedRevisions == null ? Collections.emptySet() : mergedRevisions;
434 }
435
436 public void setMergedRevisions(Set<String> mergedRevisions) {
437 this.mergedRevisions = mergedRevisions;
438 }
439
440
441
442
443 public String toString() {
444 StringBuilder result = new StringBuilder(author == null ? " null " : author);
445 result.append("\n").append(date == null ? "null " : date.toString()).append("\n");
446 List<String> tags = getTags();
447 if (!tags.isEmpty()) {
448 result.append("tags:").append(tags).append("\n");
449 }
450
451 if (parentRevision != null) {
452 result.append("parent: ").append(parentRevision);
453 if (!getMergedRevisions().isEmpty()) {
454 result.append(" + ");
455 result.append(getMergedRevisions());
456 }
457 result.append("\n");
458 }
459 if (files != null) {
460 for (ChangeFile file : files) {
461 result.append(file == null ? " null " : file.toString()).append("\n");
462 }
463 }
464
465 result.append(comment == null ? " null " : comment);
466
467 return result.toString();
468 }
469
470
471
472
473
474
475
476 public String toXML() {
477 StringBuilder buffer = new StringBuilder("\t<changelog-entry>\n");
478
479 if (getDate() != null) {
480 buffer.append("\t\t<date pattern=\"" + DATE_PATTERN + "\">")
481 .append(getDateFormatted())
482 .append("</date>\n")
483 .append("\t\t<time pattern=\"" + TIME_PATTERN + "\">")
484 .append(getTimeFormatted())
485 .append("</time>\n");
486 }
487
488 buffer.append("\t\t<author><![CDATA[").append(author).append("]]></author>\n");
489
490 if (parentRevision != null) {
491 buffer.append("\t\t<parent>").append(getParentRevision()).append("</parent>\n");
492 }
493 for (String mergedRevision : getMergedRevisions()) {
494 buffer.append("\t\t<merge>").append(mergedRevision).append("</merge>\n");
495 }
496
497 if (files != null) {
498 for (ChangeFile file : files) {
499 buffer.append("\t\t<file>\n");
500 if (file.getAction() != null) {
501 buffer.append("\t\t\t<action>").append(file.getAction()).append("</action>\n");
502 }
503 buffer.append("\t\t\t<name>")
504 .append(escapeValue(file.getName()))
505 .append("</name>\n");
506 buffer.append("\t\t\t<revision>").append(file.getRevision()).append("</revision>\n");
507 if (file.getOriginalName() != null) {
508 buffer.append("\t\t\t<orig-name>");
509 buffer.append(escapeValue(file.getOriginalName()));
510 buffer.append("</orig-name>\n");
511 }
512 if (file.getOriginalRevision() != null) {
513 buffer.append("\t\t\t<orig-revision>");
514 buffer.append(file.getOriginalRevision());
515 buffer.append("</orig-revision>\n");
516 }
517 buffer.append("\t\t</file>\n");
518 }
519 }
520 buffer.append("\t\t<msg><![CDATA[").append(removeCDataEnd(comment)).append("]]></msg>\n");
521 List<String> tags = getTags();
522 if (!tags.isEmpty()) {
523 buffer.append("\t\t<tags>\n");
524 for (String tag : tags) {
525 buffer.append("\t\t\t<tag>").append(escapeValue(tag)).append("</tag>\n");
526 }
527 buffer.append("\t\t</tags>\n");
528 }
529 buffer.append("\t</changelog-entry>\n");
530
531 return buffer.toString();
532 }
533
534
535
536
537 public boolean equals(Object obj) {
538 if (obj instanceof ChangeSet) {
539 ChangeSet changeSet = (ChangeSet) obj;
540
541 if (toString().equals(changeSet.toString())) {
542 return true;
543 }
544 }
545
546 return false;
547 }
548
549
550
551
552 public int hashCode() {
553 final int prime = 31;
554 int result = 1;
555 result = prime * result + ((author == null) ? 0 : author.hashCode());
556 result = prime * result + ((comment == null) ? 0 : comment.hashCode());
557 result = prime * result + ((date == null) ? 0 : date.hashCode());
558 result = prime * result + ((parentRevision == null) ? 0 : parentRevision.hashCode());
559 result = prime * result + ((mergedRevisions == null) ? 0 : mergedRevisions.hashCode());
560 result = prime * result + ((files == null) ? 0 : files.hashCode());
561 return result;
562 }
563
564
565
566
567
568
569
570 private String removeCDataEnd(String message) {
571
572 int endCdata;
573 while (message != null && (endCdata = message.indexOf("]]>")) > -1) {
574 message = message.substring(0, endCdata) + "] ] >" + message.substring(endCdata + 3, message.length());
575 }
576 return message;
577 }
578
579
580
581
582
583
584
585
586
587
588 public static String escapeValue(Object value) {
589 StringBuilder buffer = new StringBuilder(value.toString());
590 for (int i = 0, size = buffer.length(); i < size; i++) {
591 switch (buffer.charAt(i)) {
592 case '<':
593 buffer.replace(i, i + 1, LESS_THAN_ENTITY);
594 size += 3;
595 i += 3;
596 break;
597 case '>':
598 buffer.replace(i, i + 1, GREATER_THAN_ENTITY);
599 size += 3;
600 i += 3;
601 break;
602 case '&':
603 buffer.replace(i, i + 1, AMPERSAND_ENTITY);
604 size += 4;
605 i += 4;
606 break;
607 case '\'':
608 buffer.replace(i, i + 1, APOSTROPHE_ENTITY);
609 size += 5;
610 i += 5;
611 break;
612 case '\"':
613 buffer.replace(i, i + 1, QUOTE_ENTITY);
614 size += 5;
615 i += 5;
616 break;
617 default:
618 }
619 }
620 return buffer.toString();
621 }
622 }