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