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