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