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
123 private String revision;
124
125
126
127
128
129 private String parentRevision;
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,
145 List<ChangeFile> files )
146 {
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 {
160 setDate( date );
161
162 setAuthor( author );
163
164 setComment( comment );
165
166 this.files = files;
167 }
168
169
170
171
172 public ChangeSet()
173 {
174
175 }
176
177
178
179
180
181
182 public List<ChangeFile> getFiles()
183 {
184 if ( files == null )
185 {
186 return new ArrayList<ChangeFile>();
187 }
188 return files;
189 }
190
191
192
193
194
195
196 public void setFiles( List<ChangeFile> files )
197 {
198 this.files = files;
199 }
200
201 public void addFile( ChangeFile file )
202 {
203 if ( files == null )
204 {
205 files = new ArrayList<ChangeFile>();
206 }
207
208 files.add( file );
209 }
210
211
212
213
214
215
216
217 public boolean containsFilename( String filename, ScmProviderRepository repository )
218 {
219 return containsFilename( filename );
220 }
221
222 public boolean containsFilename( String filename )
223 {
224 if ( files != null )
225 {
226 for ( ChangeFile file : files )
227 {
228 String f1 = FilenameUtils.normalizeFilename( file.getName() );
229 String f2 = FilenameUtils.normalizeFilename( filename );
230 if ( f1.indexOf( f2 ) >= 0 )
231 {
232 return true;
233 }
234 }
235 }
236
237 return false;
238 }
239
240
241
242
243
244
245 public String getAuthor()
246 {
247 return author;
248 }
249
250
251
252
253
254
255 public void setAuthor( String author )
256 {
257 this.author = author;
258 }
259
260
261
262
263
264
265 public String getComment()
266 {
267 return comment;
268 }
269
270
271
272
273
274
275 public void setComment( String comment )
276 {
277 this.comment = comment;
278 }
279
280
281
282
283
284
285 public Date getDate()
286 {
287 if ( date != null )
288 {
289 return (Date) date.clone();
290 }
291
292 return null;
293 }
294
295
296
297
298
299
300 public void setDate( Date date )
301 {
302 if ( date != null )
303 {
304 this.date = new Date( date.getTime() );
305 }
306 }
307
308
309
310
311
312
313 public void setDate( String date )
314 {
315 setDate( date, null );
316 }
317
318
319
320
321
322
323
324 public void setDate( String date, String userDatePattern )
325 {
326 try
327 {
328 if ( !StringUtils.isEmpty( userDatePattern ) )
329 {
330 SimpleDateFormat format = new SimpleDateFormat( userDatePattern );
331
332 this.date = format.parse( date );
333 }
334 else
335 {
336 this.date = TIMESTAMP_FORMAT_3.parse( date );
337 }
338 }
339 catch ( ParseException e )
340 {
341 if ( !StringUtils.isEmpty( userDatePattern ) )
342 {
343 try
344 {
345 this.date = TIMESTAMP_FORMAT_3.parse( date );
346 }
347 catch ( ParseException pe )
348 {
349 try
350 {
351 this.date = TIMESTAMP_FORMAT_4.parse( date );
352 }
353 catch ( ParseException pe1 )
354 {
355 try
356 {
357 this.date = TIMESTAMP_FORMAT_1.parse( date );
358 }
359 catch ( ParseException pe2 )
360 {
361 try
362 {
363 this.date = TIMESTAMP_FORMAT_2.parse( date );
364 }
365 catch ( ParseException pe3 )
366 {
367 throw new IllegalArgumentException( "Unable to parse date: " + date );
368 }
369 }
370 }
371 }
372 }
373 else
374 {
375 try
376 {
377 this.date = TIMESTAMP_FORMAT_4.parse( date );
378 }
379 catch ( ParseException pe1 )
380 {
381 try
382 {
383 this.date = TIMESTAMP_FORMAT_1.parse( date );
384 }
385 catch ( ParseException pe2 )
386 {
387 try
388 {
389 this.date = TIMESTAMP_FORMAT_2.parse( date );
390 }
391 catch ( ParseException pe3 )
392 {
393 throw new IllegalArgumentException( "Unable to parse date: " + date );
394 }
395 }
396 }
397 }
398 }
399 }
400
401
402
403
404 public String getDateFormatted()
405 {
406 return DATE_FORMAT.format( getDate() );
407 }
408
409
410
411
412 public String getTimeFormatted()
413 {
414 return TIME_FORMAT.format( getDate() );
415 }
416
417
418
419
420
421 public String getRevision()
422 {
423 return revision;
424 }
425
426
427
428
429
430 public void setRevision( String revision )
431 {
432 this.revision = revision;
433 }
434
435 public String getParentRevision()
436 {
437 return parentRevision;
438 }
439
440 public void setParentRevision( String parentRevision )
441 {
442 this.parentRevision = parentRevision;
443 }
444
445 public void addMergedRevision( String mergedRevision )
446 {
447 if ( mergedRevisions == null )
448 {
449 mergedRevisions = new LinkedHashSet<String>();
450 }
451 mergedRevisions.add( mergedRevision );
452 }
453
454 public Set<String> getMergedRevisions()
455 {
456 return mergedRevisions == null ? Collections.<String>emptySet() : mergedRevisions;
457 }
458
459 public void setMergedRevisions( Set<String> mergedRevisions )
460 {
461 this.mergedRevisions = mergedRevisions;
462 }
463
464
465 public String toString()
466 {
467 StringBuilder result = new StringBuilder( author == null ? " null " : author );
468 result.append( "\n" ).append( date == null ? "null " : date.toString() ).append( "\n" );
469
470 if ( parentRevision != null )
471 {
472 result.append( "parent: " ).append( parentRevision );
473 if ( !mergedRevisions.isEmpty() )
474 {
475 result.append( " + " );
476 result.append( mergedRevisions );
477 }
478 result.append( "\n" );
479 }
480 if ( files != null )
481 {
482 for ( ChangeFile file : files )
483 {
484 result.append( file == null ? " null " : file.toString() ).append( "\n" );
485 }
486 }
487
488 result.append( comment == null ? " null " : comment );
489
490 return result.toString();
491 }
492
493
494
495
496
497
498
499 public String toXML()
500 {
501 StringBuilder buffer = new StringBuilder( "\t<changelog-entry>\n" );
502
503 if ( getDate() != null )
504 {
505 buffer.append( "\t\t<date pattern=\"" + DATE_PATTERN + "\">" )
506 .append( getDateFormatted() )
507 .append( "</date>\n" )
508 .append( "\t\t<time pattern=\"" + TIME_PATTERN + "\">" )
509 .append( getTimeFormatted() )
510 .append( "</time>\n" );
511 }
512
513 buffer.append( "\t\t<author><![CDATA[" )
514 .append( author )
515 .append( "]]></author>\n" );
516
517 if ( parentRevision != null )
518 {
519 buffer.append( "\t\t<parent>" ).append( getParentRevision() ).append( "</parent>\n" );
520 }
521 for ( String mergedRevision : getMergedRevisions() )
522 {
523 buffer.append( "\t\t<merge>" ).append( mergedRevision ).append( "</merge>\n" );
524 }
525
526 if ( files != null )
527 {
528 for ( ChangeFile file : files )
529 {
530 buffer.append( "\t\t<file>\n" );
531 if ( file.getAction() != null )
532 {
533 buffer.append( "\t\t\t<action>" ).append( file.getAction() ).append( "</action>\n" );
534 }
535 buffer.append( "\t\t\t<name>" ).append( escapeValue( file.getName() ) ).append( "</name>\n" );
536 buffer.append( "\t\t\t<revision>" ).append( file.getRevision() ).append( "</revision>\n" );
537 if ( file.getOriginalName() != null )
538 {
539 buffer.append( "\t\t\t<orig-name>" );
540 buffer.append( escapeValue( file.getOriginalName() ) );
541 buffer.append( "</orig-name>\n" );
542 }
543 if ( file.getOriginalRevision() != null )
544 {
545 buffer.append( "\t\t\t<orig-revision>" );
546 buffer.append( file.getOriginalRevision() );
547 buffer.append( "</orig-revision>\n" );
548 }
549 buffer.append( "\t\t</file>\n" );
550 }
551 }
552 buffer.append( "\t\t<msg><![CDATA[" )
553 .append( removeCDataEnd( comment ) )
554 .append( "]]></msg>\n" );
555 buffer.append( "\t</changelog-entry>\n" );
556
557 return buffer.toString();
558 }
559
560
561 public boolean equals( Object obj )
562 {
563 if ( obj instanceof ChangeSet )
564 {
565 ChangeSet changeSet = (ChangeSet) obj;
566
567 if ( toString().equals( changeSet.toString() ) )
568 {
569 return true;
570 }
571 }
572
573 return false;
574 }
575
576
577 public int hashCode()
578 {
579 final int prime = 31;
580 int result = 1;
581 result = prime * result + ( ( author == null ) ? 0 : author.hashCode() );
582 result = prime * result + ( ( comment == null ) ? 0 : comment.hashCode() );
583 result = prime * result + ( ( date == null ) ? 0 : date.hashCode() );
584 result = prime * result + ( ( parentRevision == null ) ? 0 : parentRevision.hashCode() );
585 result = prime * result + ( ( mergedRevisions == null ) ? 0 : mergedRevisions.hashCode() );
586 result = prime * result + ( ( files == null ) ? 0 : files.hashCode() );
587 return result;
588 }
589
590
591
592
593
594
595
596 private String removeCDataEnd( String message )
597 {
598
599 int endCdata;
600 while ( message != null && ( endCdata = message.indexOf( "]]>" ) ) > -1 )
601 {
602 message = message.substring( 0, endCdata ) + "] ] >" + message.substring( endCdata + 3, message.length() );
603 }
604 return message;
605 }
606
607
608
609
610
611
612
613
614
615
616 public static String escapeValue( Object value )
617 {
618 StringBuilder buffer = new StringBuilder( value.toString() );
619 for ( int i = 0, size = buffer.length(); i < size; i++ )
620 {
621 switch ( buffer.charAt( i ) )
622 {
623 case'<':
624 buffer.replace( i, i + 1, LESS_THAN_ENTITY );
625 size += 3;
626 i += 3;
627 break;
628 case'>':
629 buffer.replace( i, i + 1, GREATER_THAN_ENTITY );
630 size += 3;
631 i += 3;
632 break;
633 case'&':
634 buffer.replace( i, i + 1, AMPERSAND_ENTITY );
635 size += 4;
636 i += 4;
637 break;
638 case'\'':
639 buffer.replace( i, i + 1, APOSTROPHE_ENTITY );
640 size += 5;
641 i += 5;
642 break;
643 case'\"':
644 buffer.replace( i, i + 1, QUOTE_ENTITY );
645 size += 5;
646 i += 5;
647 break;
648 default:
649 }
650 }
651 return buffer.toString();
652 }
653 }