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