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 if ( value.length() != 40 )
292 {
293 return false;
294 }
295
296
297 int sepTS = value.indexOf( '#' );
298
299 if ( sepTS < 0 )
300 {
301 return false;
302 }
303
304 String timestampStr = value.substring( 0, sepTS ).trim();
305
306 if ( timestampStr.length() != 22 )
307 {
308 return false;
309 }
310
311
312 String realTimestamp = timestampStr.substring( 0, 14 );
313
314 synchronized ( SDF )
315 {
316 try
317 {
318 SDF.parse( realTimestamp ).getTime();
319 }
320 catch ( ParseException pe )
321 {
322 return false;
323 }
324 }
325
326
327 String millisStr = timestampStr.substring( 15, 21 );
328
329 if ( Strings.isEmpty( millisStr ) )
330 {
331 return false;
332 }
333
334 for ( int i = 0; i < 6; i++ )
335 {
336 if ( !Chars.isDigit( millisStr, i ) )
337 {
338 return false;
339 }
340 }
341
342 try
343 {
344 Integer.valueOf( millisStr );
345 }
346 catch ( NumberFormatException nfe )
347 {
348 return false;
349 }
350
351
352 int sepCC = value.indexOf( '#', sepTS + 1 );
353
354 if ( sepCC < 0 )
355 {
356 return false;
357 }
358
359 String changeCountStr = value.substring( sepTS + 1, sepCC ).trim();
360
361 if ( Strings.isEmpty( changeCountStr ) )
362 {
363 return false;
364 }
365
366 if ( changeCountStr.length() != 6 )
367 {
368 return false;
369 }
370
371 try
372 {
373 for ( int i = 0; i < 6; i++ )
374 {
375 if ( !Chars.isHex( changeCountStr, i ) )
376 {
377 return false;
378 }
379 }
380
381 Integer.parseInt( changeCountStr, 16 );
382 }
383 catch ( NumberFormatException nfe )
384 {
385 return false;
386 }
387
388
389 int sepRI = value.indexOf( '#', sepCC + 1 );
390
391 if ( sepRI < 0 )
392 {
393 return false;
394 }
395
396 String replicaIdStr = value.substring( sepCC + 1, sepRI ).trim();
397
398 if ( Strings.isEmpty( replicaIdStr ) )
399 {
400 return false;
401 }
402
403 if ( replicaIdStr.length() != 3 )
404 {
405 return false;
406 }
407
408 for ( int i = 0; i < 3; i++ )
409 {
410 if ( !Chars.isHex( replicaIdStr, i ) )
411 {
412 return false;
413 }
414 }
415
416 try
417 {
418 Integer.parseInt( replicaIdStr, 16 );
419 }
420 catch ( NumberFormatException nfe )
421 {
422 return false;
423 }
424
425
426 if ( sepCC == value.length() )
427 {
428 return false;
429 }
430
431 String operationNumberStr = value.substring( sepRI + 1 ).trim();
432
433 if ( operationNumberStr.length() != 6 )
434 {
435 return false;
436 }
437
438 for ( int i = 0; i < 6; i++ )
439 {
440 if ( !Chars.isHex( operationNumberStr, i ) )
441 {
442 return false;
443 }
444 }
445
446 try
447 {
448 Integer.parseInt( operationNumberStr, 16 );
449 }
450 catch ( NumberFormatException nfe )
451 {
452 return false;
453 }
454
455 return true;
456 }
457
458
459
460
461
462
463
464 Csn( byte[] value )
465 {
466 csnStr = Strings.utf8ToString( value );
467 Csn csn = new Csn( csnStr );
468 timestamp = csn.timestamp;
469 changeCount = csn.changeCount;
470 replicaId = csn.replicaId;
471 operationNumber = csn.operationNumber;
472 bytes = Strings.getBytesUtf8( csnStr );
473 }
474
475
476
477
478
479
480
481
482
483
484 public byte[] getBytes()
485 {
486 if ( bytes == null )
487 {
488 bytes = Strings.getBytesUtf8( csnStr );
489 }
490
491 byte[] copy = new byte[bytes.length];
492 System.arraycopy( bytes, 0, copy, 0, bytes.length );
493 return copy;
494 }
495
496
497
498
499
500 public long getTimestamp()
501 {
502 return timestamp;
503 }
504
505
506
507
508
509 public int getChangeCount()
510 {
511 return changeCount;
512 }
513
514
515
516
517
518 public int getReplicaId()
519 {
520 return replicaId;
521 }
522
523
524
525
526
527 public int getOperationNumber()
528 {
529 return operationNumber;
530 }
531
532
533
534
535
536 public String toString()
537 {
538 if ( csnStr == null )
539 {
540 StringBuilder buf = new StringBuilder( 40 );
541
542 synchronized ( SDF )
543 {
544 buf.append( SDF.format( new Date( timestamp ) ) );
545 }
546
547
548 long millis = ( timestamp % 1000 ) * 1000;
549 String millisStr = Long.toString( millis );
550
551 buf.append( '.' ).append( PADDING_6[millisStr.length() - 1] ).append( millisStr ).append( "Z#" );
552
553 String countStr = Integer.toHexString( changeCount );
554
555 buf.append( PADDING_6[countStr.length() - 1] ).append( countStr );
556 buf.append( '#' );
557
558 String replicaIdStr = Integer.toHexString( replicaId );
559
560 buf.append( PADDING_3[replicaIdStr.length() - 1] ).append( replicaIdStr );
561 buf.append( '#' );
562
563 String operationNumberStr = Integer.toHexString( operationNumber );
564
565 buf.append( PADDING_6[operationNumberStr.length() - 1] ).append( operationNumberStr );
566
567 csnStr = buf.toString();
568 }
569
570 return csnStr;
571 }
572
573
574
575
576
577
578
579 public int hashCode()
580 {
581 int h = 37;
582
583 h = h * 17 + ( int ) ( timestamp ^ ( timestamp >>> 32 ) );
584 h = h * 17 + changeCount;
585 h = h * 17 + replicaId;
586 h = h * 17 + operationNumber;
587
588 return h;
589 }
590
591
592
593
594
595
596
597
598
599 public boolean equals( Object o )
600 {
601 if ( this == o )
602 {
603 return true;
604 }
605
606 if ( !( o instanceof Csn ) )
607 {
608 return false;
609 }
610
611 Csn that = ( Csn ) o;
612
613 return ( timestamp == that.timestamp ) && ( changeCount == that.changeCount )
614 && ( replicaId == that.replicaId ) && ( operationNumber == that.operationNumber );
615 }
616
617
618
619
620
621
622
623
624
625
626
627 public int compareTo( Csn csn )
628 {
629 if ( csn == null )
630 {
631 return 1;
632 }
633
634
635 if ( this.timestamp < csn.timestamp )
636 {
637 return -1;
638 }
639 else if ( this.timestamp > csn.timestamp )
640 {
641 return 1;
642 }
643
644
645 if ( this.changeCount < csn.changeCount )
646 {
647 return -1;
648 }
649 else if ( this.changeCount > csn.changeCount )
650 {
651 return 1;
652 }
653
654
655 int replicaIdCompareResult = getReplicaIdCompareResult( csn );
656
657 if ( replicaIdCompareResult != 0 )
658 {
659 return replicaIdCompareResult;
660 }
661
662
663 if ( this.operationNumber < csn.operationNumber )
664 {
665 return -1;
666 }
667 else if ( this.operationNumber > csn.operationNumber )
668 {
669 return 1;
670 }
671 else
672 {
673 return 0;
674 }
675 }
676
677
678 private int getReplicaIdCompareResult( Csn csn )
679 {
680 if ( this.replicaId < csn.replicaId )
681 {
682 return -1;
683 }
684 if ( this.replicaId > csn.replicaId )
685 {
686 return 1;
687 }
688 return 0;
689 }
690 }