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