1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.directory.api.ldap.model.csn;
21
22
23 import java.text.ParseException;
24 import java.text.SimpleDateFormat;
25 import java.util.Date;
26 import java.util.Locale;
27 import java.util.TimeZone;
28
29 import org.apache.directory.api.i18n.I18n;
30 import org.apache.directory.api.util.Chars;
31 import org.apache.directory.api.util.Strings;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public class Csn implements Comparable<Csn>
60 {
61
62 private static final Logger LOG = LoggerFactory.getLogger( Csn.class );
63
64
65 private final long timestamp;
66
67
68 private final int replicaId;
69
70
71 private final int operationNumber;
72
73
74 private final int changeCount;
75
76
77 private String csnStr;
78
79
80 private byte[] bytes;
81
82
83 private final SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMddHHmmss", Locale.ROOT );
84
85 private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
86
87
88 private static final String[] PADDING_6 = new String[]
89 { "00000", "0000", "000", "00", "0", "" };
90
91
92 private static final String[] PADDING_3 = new String[]
93 { "00", "0", "" };
94
95
96
97
98
99
100
101
102
103
104
105 public Csn( long timestamp, int changeCount, int replicaId, int operationNumber )
106 {
107 this.timestamp = timestamp;
108 this.replicaId = replicaId;
109 this.operationNumber = operationNumber;
110 this.changeCount = changeCount;
111 sdf.setTimeZone( UTC_TIME_ZONE );
112 }
113
114
115
116
117
118
119
120
121
122
123 public Csn( String value )
124 {
125 sdf.setTimeZone( UTC_TIME_ZONE );
126
127 if ( Strings.isEmpty( value ) )
128 {
129 String message = I18n.err( I18n.ERR_13015_NULL_OR_EMPTY_CSN );
130 LOG.error( message );
131 throw new InvalidCSNException( message );
132 }
133
134 if ( value.length() != 40 )
135 {
136 String message = I18n.err( I18n.ERR_13016_INCORRECT_CSN_LENGTH );
137 LOG.error( message );
138 throw new InvalidCSNException( message );
139 }
140
141
142 int sepTS = value.indexOf( '#' );
143
144 if ( sepTS < 0 )
145 {
146 String message = I18n.err( I18n.ERR_13017_CANT_FIND_SHARP_IN_CSN );
147 LOG.error( message );
148 throw new InvalidCSNException( message );
149 }
150
151 String timestampStr = value.substring( 0, sepTS ).trim();
152
153 if ( timestampStr.length() != 22 )
154 {
155 String message = I18n.err( I18n.ERR_13018_TIMESTAMP_NOT_LONG_ENOUGH );
156 LOG.error( message );
157 throw new InvalidCSNException( message );
158 }
159
160
161 String realTimestamp = timestampStr.substring( 0, 14 );
162
163 long tempTimestamp = 0L;
164
165 synchronized ( sdf )
166 {
167 try
168 {
169 tempTimestamp = sdf.parse( realTimestamp ).getTime();
170 }
171 catch ( ParseException pe )
172 {
173 String message = I18n.err( I18n.ERR_13019_CANNOT_PARSE_TIMESTAMP, timestampStr );
174 LOG.error( message );
175 throw new InvalidCSNException( message, pe );
176 }
177 }
178
179 int millis = 0;
180
181
182 try
183 {
184 millis = Integer.parseInt( timestampStr.substring( 15, 21 ) );
185 }
186 catch ( NumberFormatException nfe )
187 {
188 String message = I18n.err( I18n.ERR_13020_INVALID_MICROSECOND );
189 LOG.error( message );
190 throw new InvalidCSNException( message, nfe );
191 }
192
193 tempTimestamp += ( millis / 1000 );
194 timestamp = tempTimestamp;
195
196
197 int sepCC = value.indexOf( '#', sepTS + 1 );
198
199 if ( sepCC < 0 )
200 {
201 String message = I18n.err( I18n.ERR_13014_DN_ATTR_FLAG_INVALID, value );
202 LOG.error( message );
203 throw new InvalidCSNException( message );
204 }
205
206 String changeCountStr = value.substring( sepTS + 1, sepCC ).trim();
207
208 try
209 {
210 changeCount = Integer.parseInt( changeCountStr, 16 );
211 }
212 catch ( NumberFormatException nfe )
213 {
214 String message = I18n.err( I18n.ERR_13021_INVALID_CHANGE_COUNT, changeCountStr );
215 LOG.error( message );
216 throw new InvalidCSNException( message, nfe );
217 }
218
219
220 int sepRI = value.indexOf( '#', sepCC + 1 );
221
222 if ( sepRI < 0 )
223 {
224 String message = I18n.err( I18n.ERR_13022_MISSING_SHARP_IN_CSN, value );
225 LOG.error( message );
226 throw new InvalidCSNException( message );
227 }
228
229 String replicaIdStr = value.substring( sepCC + 1, sepRI ).trim();
230
231 if ( Strings.isEmpty( replicaIdStr ) )
232 {
233 String message = I18n.err( I18n.ERR_13023_REPLICA_ID_NULL );
234 LOG.error( message );
235 throw new InvalidCSNException( message );
236 }
237
238 try
239 {
240 replicaId = Integer.parseInt( replicaIdStr, 16 );
241 }
242 catch ( NumberFormatException nfe )
243 {
244 String message = I18n.err( I18n.ERR_13024_INVALID_REPLICA_ID, replicaIdStr );
245 LOG.error( message );
246 throw new InvalidCSNException( message, nfe );
247 }
248
249
250 if ( sepCC == value.length() )
251 {
252 String message = I18n.err( I18n.ERR_13025_NO_OPERATION_NUMBER );
253 LOG.error( message );
254 throw new InvalidCSNException( message );
255 }
256
257 String operationNumberStr = value.substring( sepRI + 1 ).trim();
258
259 try
260 {
261 operationNumber = Integer.parseInt( operationNumberStr, 16 );
262 }
263 catch ( NumberFormatException nfe )
264 {
265 String message = I18n.err( I18n.ERR_13026_INVALID_OPERATION_NUMBER, operationNumberStr );
266 LOG.error( message );
267 throw new InvalidCSNException( message, nfe );
268 }
269
270 csnStr = value;
271 bytes = Strings.getBytesUtf8( csnStr );
272 }
273
274
275
276
277
278
279
280 Csn( byte[] value )
281 {
282 csnStr = Strings.utf8ToString( value );
283 Csn csn = new Csn( csnStr );
284 timestamp = csn.timestamp;
285 changeCount = csn.changeCount;
286 replicaId = csn.replicaId;
287 operationNumber = csn.operationNumber;
288 bytes = Strings.getBytesUtf8( csnStr );
289 }
290
291
292
293
294
295
296
297
298 public static boolean isValid( String value )
299 {
300 if ( Strings.isEmpty( value ) )
301 {
302 return false;
303 }
304
305 char[] chars = value.toCharArray();
306
307 if ( chars.length != 40 )
308 {
309 return false;
310 }
311
312
313
314 for ( int pos = 0; pos < 4; pos++ )
315 {
316 if ( !Chars.isDigit( chars[pos] ) )
317 {
318 return false;
319 }
320 }
321
322
323 switch ( chars[4] )
324 {
325 case '0' :
326 if ( !Chars.isDigit( chars[5] ) )
327 {
328 return false;
329 }
330
331 if ( chars[5] == '0' )
332 {
333 return false;
334 }
335
336 break;
337
338 case '1' :
339 if ( ( chars[5] != '0' ) && ( chars[5] != '1' ) && ( chars[5] != '2' ) )
340 {
341 return false;
342 }
343
344 break;
345
346 default :
347 return false;
348 }
349
350
351 switch ( chars[6] )
352 {
353 case '0' :
354 if ( !Chars.isDigit( chars[7] ) )
355 {
356 return false;
357 }
358
359 if ( chars[7] == '0' )
360 {
361 return false;
362 }
363
364 break;
365
366 case '1' :
367 case '2' :
368 if ( !Chars.isDigit( chars[7] ) )
369 {
370 return false;
371 }
372
373 break;
374
375 case '3' :
376
377 if ( ( chars[7] != '0' ) && ( chars[7] != '1' ) )
378 {
379 return false;
380 }
381
382 break;
383
384 default :
385 return false;
386 }
387
388
389 switch ( chars[8] )
390 {
391 case '0' :
392 case '1' :
393 if ( !Chars.isDigit( chars[9] ) )
394 {
395 return false;
396 }
397
398 break;
399
400 case '2' :
401 if ( ( chars[9] != '0' ) && ( chars[9] != '1' ) && ( chars[9] != '2' ) && ( chars[9] != '3' ) )
402 {
403 return false;
404 }
405
406 break;
407
408 default :
409 return false;
410 }
411
412
413 switch ( chars[10] )
414 {
415 case '0' :
416 case '1' :
417 case '2' :
418 case '3' :
419 case '4' :
420 case '5' :
421 break;
422
423 default :
424 return false;
425 }
426
427 if ( !Chars.isDigit( chars[11] ) )
428 {
429 return false;
430 }
431
432
433 switch ( chars[12] )
434 {
435 case '0' :
436 case '1' :
437 case '2' :
438 case '3' :
439 case '4' :
440 case '5' :
441 break;
442
443 default :
444 return false;
445 }
446
447 if ( !Chars.isDigit( chars[13] ) )
448 {
449 return false;
450 }
451
452
453 if ( chars[14] != '.' )
454 {
455 return false;
456 }
457
458 for ( int i = 0; i < 6; i++ )
459 {
460 if ( !Chars.isDigit( chars[15 + i] ) )
461 {
462 return false;
463 }
464 }
465
466 if ( chars[21] != 'Z' )
467 {
468 return false;
469 }
470
471 if ( chars[22] != '#' )
472 {
473 return false;
474 }
475
476
477 if ( !Chars.isHex( ( byte ) chars[23] )
478 || !Chars.isHex( ( byte ) chars[24] )
479 || !Chars.isHex( ( byte ) chars[25] )
480 || !Chars.isHex( ( byte ) chars[26] )
481 || !Chars.isHex( ( byte ) chars[27] )
482 || !Chars.isHex( ( byte ) chars[28] ) )
483 {
484 return false;
485 }
486
487 if ( chars[29] != '#' )
488 {
489 return false;
490 }
491
492
493 if ( !Chars.isHex( ( byte ) chars[30] )
494 || !Chars.isHex( ( byte ) chars[31] )
495 || !Chars.isHex( ( byte ) chars[32] ) )
496 {
497 return false;
498 }
499
500 if ( chars[33] != '#' )
501 {
502 return false;
503 }
504
505
506 if ( !Chars.isHex( ( byte ) chars[34] )
507 || !Chars.isHex( ( byte ) chars[35] )
508 || !Chars.isHex( ( byte ) chars[36] )
509 || !Chars.isHex( ( byte ) chars[37] )
510 || !Chars.isHex( ( byte ) chars[38] )
511 || !Chars.isHex( ( byte ) chars[39] ) )
512 {
513 return false;
514 }
515
516 return true;
517 }
518
519
520
521
522
523
524
525
526
527
528 public byte[] getBytes()
529 {
530 if ( bytes == null )
531 {
532 bytes = Strings.getBytesUtf8( csnStr );
533 }
534
535 byte[] copy = new byte[bytes.length];
536 System.arraycopy( bytes, 0, copy, 0, bytes.length );
537 return copy;
538 }
539
540
541
542
543
544 public long getTimestamp()
545 {
546 return timestamp;
547 }
548
549
550
551
552
553 public int getChangeCount()
554 {
555 return changeCount;
556 }
557
558
559
560
561
562 public int getReplicaId()
563 {
564 return replicaId;
565 }
566
567
568
569
570
571 public int getOperationNumber()
572 {
573 return operationNumber;
574 }
575
576
577
578
579
580 @Override
581 public String toString()
582 {
583 if ( csnStr == null )
584 {
585 StringBuilder buf = new StringBuilder( 40 );
586
587 synchronized ( sdf )
588 {
589 buf.append( sdf.format( new Date( timestamp ) ) );
590 }
591
592
593 long millis = ( timestamp % 1000 ) * 1000;
594 String millisStr = Long.toString( millis );
595
596 buf.append( '.' ).append( PADDING_6[millisStr.length() - 1] ).append( millisStr ).append( "Z#" );
597
598 String countStr = Integer.toHexString( changeCount );
599
600 buf.append( PADDING_6[countStr.length() - 1] ).append( countStr );
601 buf.append( '#' );
602
603 String replicaIdStr = Integer.toHexString( replicaId );
604
605 buf.append( PADDING_3[replicaIdStr.length() - 1] ).append( replicaIdStr );
606 buf.append( '#' );
607
608 String operationNumberStr = Integer.toHexString( operationNumber );
609
610 buf.append( PADDING_6[operationNumberStr.length() - 1] ).append( operationNumberStr );
611
612 csnStr = buf.toString();
613 }
614
615 return csnStr;
616 }
617
618
619
620
621
622
623
624 @Override
625 public int hashCode()
626 {
627 int h = 37;
628
629 h = h * 17 + ( int ) ( timestamp ^ ( timestamp >>> 32 ) );
630 h = h * 17 + changeCount;
631 h = h * 17 + replicaId;
632 h = h * 17 + operationNumber;
633
634 return h;
635 }
636
637
638
639
640
641
642
643
644
645 @Override
646 public boolean equals( Object o )
647 {
648 if ( this == o )
649 {
650 return true;
651 }
652
653 if ( !( o instanceof Csn ) )
654 {
655 return false;
656 }
657
658 Csn that = ( Csn ) o;
659
660 return ( timestamp == that.timestamp ) && ( changeCount == that.changeCount )
661 && ( replicaId == that.replicaId ) && ( operationNumber == that.operationNumber );
662 }
663
664
665
666
667
668
669
670
671
672
673
674 @Override
675 public int compareTo( Csn csn )
676 {
677 if ( csn == null )
678 {
679 return 1;
680 }
681
682
683 if ( this.timestamp < csn.timestamp )
684 {
685 return -1;
686 }
687 else if ( this.timestamp > csn.timestamp )
688 {
689 return 1;
690 }
691
692
693 if ( this.changeCount < csn.changeCount )
694 {
695 return -1;
696 }
697 else if ( this.changeCount > csn.changeCount )
698 {
699 return 1;
700 }
701
702
703 int replicaIdCompareResult = getReplicaIdCompareResult( csn );
704
705 if ( replicaIdCompareResult != 0 )
706 {
707 return replicaIdCompareResult;
708 }
709
710
711 if ( this.operationNumber < csn.operationNumber )
712 {
713 return -1;
714 }
715 else if ( this.operationNumber > csn.operationNumber )
716 {
717 return 1;
718 }
719 else
720 {
721 return 0;
722 }
723 }
724
725
726 private int getReplicaIdCompareResult( Csn csn )
727 {
728 if ( this.replicaId < csn.replicaId )
729 {
730 return -1;
731 }
732 if ( this.replicaId > csn.replicaId )
733 {
734 return 1;
735 }
736 return 0;
737 }
738 }