summaryrefslogtreecommitdiff
path: root/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNUtil.java
blob: 9dd684d60ef6f5d5d4ce79f29685c07d0d49389c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
/**
 * @copyright
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 * @endcopyright
 */

package org.apache.subversion.javahl;

import org.apache.subversion.javahl.callback.*;
import org.apache.subversion.javahl.types.*;
import org.apache.subversion.javahl.util.*;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class SVNUtil
{
    //
    // Global configuration
    //
    private static final ConfigLib configLib = new ConfigLib();

    /**
     * Enable storing authentication credentials in Subversion's
     * standard credentials store in the configuration directory and
     * system-specific secure locations.
     * <p>
     * The standard credentials store is enabled by default.
     * <p>
     * This setting will be inherited by all ISVNClient and ISVNRemote
     * objects. Changing the setting will not affect existing such
     * objects.
     * @throws ClientException
     */
    public static void enableNativeCredentialsStore()
        throws ClientException
      {
          configLib.enableNativeCredentialsStore();
      }

    /**
     * Disable storing authentication credentials in Subversion's
     * standard credentials store in the configuration directory and
     * system-specific secure locations. In this mode, the
     * authentication (see {@link ISVNClient#setPrompt} and {@link
     * remote.RemoteFactory#setPrompt}) will be called every time the
     * underlying library needs access to the credentials.
     * <p>
     * This mode is intented to support client implementations that
     * use their own credentials store.
     * <p>
     * The standard credentials store is enabled by default.
     * <p>
     * This setting will be inherited by all ISVNClient and ISVNRemote
     * objects. Changing the setting will not affect existing such
     * objects.
     * @throws ClientException
     */
    public static void disableNativeCredentialsStore()
        throws ClientException
      {
          configLib.disableNativeCredentialsStore();
      }

    /**
     * Find out if the standard credentials store is enabled.
     */
    public static boolean isNativeCredentialsStoreEnabled()
        throws ClientException
      {
          return configLib.isNativeCredentialsStoreEnabled();
      }

    //
    // Credentials management
    //

    /**
     * Exception used by calling the wrong accessor on Credential for
     * the given credential type.
     */
    public static class CredentialTypeMismatch extends SubversionException
    {
        // Update the serialVersionUID when there is a incompatible change made to
        // this class.  See the java documentation for when a change is incompatible.
        // http://java.sun.com/javase/7/docs/platform/serialization/spec/version.html#6678
        private static final long serialVersionUID = 1L;

        public CredentialTypeMismatch(Credential.Kind kind, String attribute)
        {
            super("Credential type '" + kind.toString()
                  + "'  does not have the attribute '" + attribute + "'");
        }
    }

    /**
     * Generic credential description. Provides default accessors for
     * concrete implementations.
     */
    public static class Credential implements java.io.Serializable
    {
        // Update the serialVersionUID when there is a incompatible change made to
        // this class.  See the java documentation for when a change is incompatible.
        // http://java.sun.com/javase/7/docs/platform/serialization/spec/version.html#6678
        private static final long serialVersionUID = 1L;

        /**
         * Describes the kind of the credential.
         */
        public static enum Kind
        {
            /** The username for a realm. */
            username            ("svn.username"),

            /** The username and password for a realm. */
            simple              ("svn.simple"),

            /** The trusted SSL server certificate for a realm. */
            sslServer           ("svn.ssl.server"),

            /** The client certificate passphrase for a realm. */
            sslClientPassphrase ("svn.ssl.client-passphrase");

            private String token;

            Kind(String token)
            {
                this.token = token;
            }

            /** @return the string representation of the enumeration. */
            public String toString()
            {
                return this.token;
            }

            /* Factory used by the native implementation */
            private static Kind fromString(String stringrep)
            {
                for (Kind kind : Kind.values()) {
                    if (kind.toString().equals(stringrep))
                        return kind;
                }
                return null;
            }
        }

        /** @return the kind of the credential. */
        public Kind getKind()
        {
            return kind;
        }

        /** @return the realm that the credential is valid for. */
        public String getRealm()
        {
            return realm;
        }

        /**
         * @return the type of the secure store used for the secret
         * parts of this credential; may be <code>null</code> if the
         * credential does not contain any secrets bits.
         */
        public String getSecureStore()
            throws CredentialTypeMismatch
        {
            if (kind != Kind.simple && kind != Kind.sslClientPassphrase)
                throw new CredentialTypeMismatch(kind, "secure store");

            return store;
        }

        /**
         * @return the username associated with the credential, or
         * <code>null</code>, if there is no username in the concrete
         * credential type.
         */
        public String getUsername()
            throws CredentialTypeMismatch
        {
            if (kind != Kind.username && kind != Kind.simple)
                throw new CredentialTypeMismatch(kind, "username");

            return username;
        }

        /**
         * @return the password associated with the credential, or
         * <code>null</code>, if there is no password in the concrete
         * credential type.
         */
        public String getPassword()
            throws CredentialTypeMismatch
        {
            if (kind != Kind.simple && kind != Kind.sslClientPassphrase)
                throw new CredentialTypeMismatch(kind, "password");

            return password;
        }

        /**
         * @return the server certificate info associated with the
         * credential, or <code>null</code>, if there is no server
         * certificate in the concrete credential type.
         */
        public AuthnCallback.SSLServerCertInfo getServerCertInfo()
            throws CredentialTypeMismatch
        {
            if (kind != Kind.sslServer)
                throw new CredentialTypeMismatch(kind, "server cert info");

            return info;
        }

        /**
         * @return the accepted server certificate failures associated
         * with the credential, or <code>null</code>, if there is no
         * server certificate in the concrete credential type.
         */
        public AuthnCallback.SSLServerCertFailures getServerCertFailures()
            throws CredentialTypeMismatch
        {
            if (kind != Kind.sslServer)
                throw new CredentialTypeMismatch(kind, "server cert failures");

            return failures;
        }

        /**
         * @return the client certificate passphrase associated with
         * the credential, or <code>null</code>, if there is no client
         * certificate in the concrete credential type.
         */
        public String getClientCertPassphrase()
            throws CredentialTypeMismatch
        {
            if (kind != Kind.sslClientPassphrase)
                throw new CredentialTypeMismatch(kind, "passphrase");

            return passphrase;
        }

        // ### TODO: There are currently no proper APIs in Subversion
        //           for adding credentials. These factory methods are
        //           placeholders.
        //
        ///**
        // * Creates an "svn.username" credential.
        // * @param realm The realm string.
        // * @param username The username for <code>realm</code>.
        // */
        //public static Credential
        //    createUsername(String realm, String username)
        //{
        //    return new Credential(Kind.username, realm, null,
        //                          username, null, null, null, null);
        //}
        //
        ///**
        // * Creates an "svn.simple" credential.
        // * @param realm The realm string.
        // * @param username The username for <code>realm</code>.
        // * @param password The password for <code>username</code>.
        // */
        //public static Credential
        //    createSimple(String realm, String username, String password)
        //{
        //    return new Credential(Kind.simple, realm, null,
        //                          username, password, null, null, null);
        //}
        //
        ///** Creates an "svn.ssl.server" credential. */
        //public static Credential
        //    createSSLServerCertTrust(String realm,
        //                             AuthnCallback.SSLServerCertInfo info,
        //                             AuthnCallback.SSLServerCertFailures failures)
        //{
        //    return new Credential(Kind.sslServer, realm, null,
        //                          null, null, info, failures, null);
        //}
        //
        ///**
        // * Creates an "svn.ssl.client-passphrase" credential.
        // * @param realm The realm string.
        // * @param passphrase The passphrase for for the client certificate
        // *        used for <code>realm</code>.
        // */
        //public static Credential
        //    createSSLClientCertPassphrase(String realm, String passphrase)
        //{
        //    return new Credential(Kind.simple, realm, null,
        //                          null, null, null, null, passphrase);
        //}

        private Credential(Kind kind, String realm, String store,
                           String username, String password,
                           AuthnCallback.SSLServerCertInfo info,
                           AuthnCallback.SSLServerCertFailures failures,
                           String passphrase)
        {
            assert(kind != null && realm != null);
            switch (kind) {
            case username:
                assert(username != null && password == null
                       && info == null && failures == null
                       && passphrase == null);
                break;
            case simple:
                assert(username != null && password != null
                       && info == null && failures == null
                       && passphrase == null);
                break;
            case sslServer:
                assert(username == null && password == null
                       && info != null && failures != null
                       && passphrase == null);
                break;
            case sslClientPassphrase:
                assert(username == null && password == null
                       && info == null && failures == null
                       && passphrase != null);
                break;
            default:
                assert(kind == Kind.username
                       || kind == Kind.simple
                       || kind == Kind.sslServer
                       || kind == Kind.sslClientPassphrase);
            }

            this.kind = kind;
            this.realm = realm;
            this.store = store;
            this.username = username;
            this.password = password;
            this.info = info;
            this.failures = failures;
            this.passphrase = passphrase;
        }

        private Kind kind;
        private String realm;
        private String store;
        private String username;
        private String password;
        private AuthnCallback.SSLServerCertInfo info;
        private AuthnCallback.SSLServerCertFailures failures;
        private String passphrase;
    }

    /**
     * Find a stored credential.
     * Unlike {@link #searchCredentials}, the the realm name is not
     * a glob pattern.
     * <p>
     * <b>Note:</b> If the native credentials store is disabled, this
     *              method will always return <code>null</code>.
     *
     * @param configDir The path to the configuration directory; if
     *        <code>null</code>, the default (system-specific) user
     *        configuration path will be used.
     * @param kind The kind of the credential; may not be <code>null</code>.
     * @param realm The realm name; may not be <code>null</code>.
     * @return the matching credential, or <code>null</code> if not found.
     */
    public static Credential getCredential(String configDir,
                                           Credential.Kind kind,
                                           String realm)
        throws ClientException, SubversionException
    {
        return configLib.getCredential(configDir, kind, realm);
    }

    /**
     * Remove a stored credential.
     * Unlike {@link #deleteCredentials}, the the realm name is not
     * a glob pattern.
     * <p>
     * <b>Note:</b> If the native credentials store is disabled, this
     *              method will always return <code>null</code>.
     *
     * @param configDir The path to the configuration directory; if
     *        <code>null</code>, the default (system-specific) user
     *        configuration path will be used.
     * @param kind The kind of the credential; may not be <code>null</code>.
     * @param realm The realm name; may not be <code>null</code>.
     * @return the deleted credential, or <code>null</code> if not found.
     */
    public static Credential removeCredential(String configDir,
                                              Credential.Kind kind,
                                              String realm)
        throws ClientException, SubversionException
    {
        return configLib.removeCredential(configDir, kind, realm);
    }

    // ### TODO: There are currently no proper APIs in Subversion for
    //           adding credentials. This method is a placeholder.
    //
    ///**
    // * Store a new credential, or replace an existing credential.
    // * <p>
    // * <b>Note:</b> If the native credentials store is disabled, this
    // *              method will always return <code>null</code>.
    // *
    // * @param configDir The path to the configuration directory; if
    // *        <code>null</code>, the default (system-specific) user
    // *        configuration path will be used.
    // * @param credential The credential to store.
    // * @param replace If <code>true</code>, any existing matching
    // *        credential will be replaced.
    // *
    // * @return the stored credential. If <code>replace</code> was
    // * <code>false</code>, and a credential with the same kind and
    // * for the same realm exists, it will be returned. If the given
    // * credential was successfully added, the same object reference
    // * will be returned (the calling code can compare reference values
    // * to determine this). Will return <code>null</code> if the
    // * credential could not be stored for any reason.
    // */
    //public static Credential addCredential(String configDir,
    //                                       Credential credential,
    //                                       boolean replace)
    //    throws ClientException, SubversionException
    //{
    //    return configLib.addCredential(configDir, credential, replace);
    //}

    /**
     * Find stored credentials that match the given search criteria.
     * <p>
     * <b>Note:</b> If the native credentials store is disabled, this
     *              method will always return <code>null</code>.
     *
     * @param configDir The path to the configuration directory; if
     *        <code>null</code>, the default (system-specific) user
     *        configuration path will be used.
     * @param kind The kind of the credential; if <code>null</code>,
     *             all matching credential types will be returned.
     * @param realmPattern A glob pattern for the realm string;
     *             if <code>null</code>, all realms will be considered;
     *             otherwise, only those credentials whose realm matches
     *             the pattern will be returned.
     * @param usernamePattern A glob pattern for the username;
     *             if <code>null</code>, all credentials will be considered;
     *             otherwise, only those credentials that have a username,
     *             and where the username matches the pattern, will be
     *             returned.
     * @param hostnamePattern A glob pattern for the hostnames of a
     *             server certificate; if <code>null</code>, all
     *             credntials will be considered; otherwise, only
     *             those credentials that have a server certificate
     *             with a hostname that matches the pattern will be
     *             returned.
     * @param textPattern A glob pattern that must match any textual
     *             information in a credential, for example, a realm,
     *             username, certificate details, etc; passwords, passphrases
     *             and other info considered secret will not be matched;
     * @return the list of matching credentials.
     */
    public static List<Credential>
        searchCredentials(String configDir,
                          Credential.Kind kind,
                          String realmPattern,
                          String usernamePattern,
                          String hostnamePattern,
                          String textPattern)
        throws ClientException, SubversionException
    {
        return configLib.searchCredentials(configDir, kind, realmPattern,
                                           usernamePattern, hostnamePattern,
                                           textPattern);
    }

    //
    // Diff and Merge
    //
    private static final DiffLib diffLib = new DiffLib();

    /**
     * Options to control the behaviour of the file diff routines.
     */
    public static class DiffOptions
    {
        /**
         * To what extent whitespace should be ignored when comparing lines.
         */
        public enum IgnoreSpace
        {
            /** Do not ignore whitespace */
            none,

            /**
             * Ignore changes in sequences of whitespace characters,
             * treating each sequence of whitespace characters as a
             * single space.
             */
            change,

            /** Ignore all whitespace characters. */
            all
        }

        /**
         * @param ignoreSpace Whether and how to ignore space differences
         *        in the files. The default is {@link IgnoreSpace#none}.
         * @param ignoreEolStyle Whether to treat all end-of-line
         *        markers the same when comparing lines.  The default
         *        is <code>false</code>.
         * @param showCFunction Whether the "@@" lines of the unified
         *        diff output should include a prefix of the nearest
         *        preceding line that starts with a character that
         *        might be the initial character of a C language
         *        identifier. The default is <code>false</code>.
         */
        public DiffOptions(IgnoreSpace ignoreSpace,
                           boolean ignoreEolStyle,
                           boolean showCFunction)
        {
            this.ignoreSpace = ignoreSpace;
            this.ignoreEolStyle = ignoreEolStyle;
            this.showCFunction = showCFunction;
            this.contextSize = -1;
        }

        /**
         * Like the {@see #DiffOptions(IgnoreSpace,boolean,boolean)},
         * but with an additional parameter.
         * @param contextSize If this is greater than 0, then this
         * number of context lines will be used in the generated diff
         * output. Otherwise the legacy compile time default will be
         * used.
         */
        public DiffOptions(IgnoreSpace ignoreSpace,
                           boolean ignoreEolStyle,
                           boolean showCFunction,
                           int contextSize)
        {
            this.ignoreSpace = ignoreSpace;
            this.ignoreEolStyle = ignoreEolStyle;
            this.showCFunction = showCFunction;
            this.contextSize = contextSize;
        }

        public final IgnoreSpace ignoreSpace;
        public final boolean ignoreEolStyle;
        public final boolean showCFunction;
        public final int contextSize;
    }

    /** Style for displaying conflicts in merge output. */
    public enum ConflictDisplayStyle
    {
        /** Display modified and latest, with conflict markers. */
        modified_latest,

        /**
         * Like <code>modified_latest</code>, but with an extra effort
         * to identify common sequences between modified and latest.
         */
        resolved_modified_latest,

        /** Display modified, original, and latest, with conflict markers. */
        modified_original_latest,

        /** Just display modified, with no markers. */
        modified,

        /** Just display latest, with no markers. */
        latest,

        /**
         * Like <code>modified_original_latest</code>, but
         * <em>only<em> showing conflicts.
         */
        only_conflicts
    }

    /**
     * Given two versions of a file, base (<code>originalFile</code>)
     * and current (<code>modifiedFile</code>), show differences between
     * them in unified diff format.
     *
     * @param originalFile The base file version (unmodified)
     * @param modifiedFile The incoming file version (locally modified)
     * @param diffOptions Options controlling how files are compared.
     *        May be <code>null</code>.
     * @param originalHeader The header to display for the base file
     *        in the unidiff index block. If it is <code>null</code>,
     *        the <code>originalFile</code> path and its modification
     *        time will be used instead.
     * @param modifiedHeader The header to display for the current
     *        file in the unidiff index block. If it is <code>null</code>,
     *        the <code>currentFile</code> path and its modification
     *        time will be used instead.
     * @param headerEncoding The character encoding of the unidiff headers.
     * @param relativeToDir If this parameter is not <null>, it must
     *        be the path of a (possibly non-immediate) parent of both
     *        <code>originalFile</code> and <code>modifiedFile</code>.
     *        This path will be stripped from the beginning of those
     *        file names if they are used in the unidiff index header.
     * @param resultStream The stream that receives the merged output.
     * @return <code>true</code> if there were differences between the files.
     * @throws ClientException
     */
    public static boolean fileDiff(String originalFile,
                                   String modifiedFile,
                                   SVNUtil.DiffOptions diffOptions,

                                   String originalHeader,
                                   String modifiedHeader,
                                   String headerEncoding,
                                   String relativeToDir,

                                   OutputStream resultStream)
        throws ClientException
    {
        // ### TODO: Support cancellation as in svn_diff_file_output_unified3.
        return diffLib.fileDiff(originalFile, modifiedFile, diffOptions,
                                originalHeader, modifiedHeader,
                                headerEncoding,
                                relativeToDir, resultStream);
    }


    /**
     * Given three versions of a file, base (<code>originalFile</code>),
     * incoming (<code>modifiedFile</code>) and current
     * (<code>latestFile</code>, produce a merged result, possibly
     * displaying conflict markers.
     *
     * @param originalFile The base file version (common ancestor)
     * @param modifiedFile The incoming file version (modified elsewhere)
     * @param latestFile The current file version (locally modified)
     * @param diffOptions Options controlling how files are compared.
     *        May be <code>null</code>.
     * @param conflictOriginal Optional custom conflict marker for
     *        the <code>originalFile</code> contents.
     * @param conflictModified Optional custom conflict marker for
     *        the <code>modifiedFile</code> contents.
     * @param conflictLatest Optional custom conflict marker for
     *        the <code>latestFile</code> contents.
     * @param conflictSeparator Optional custom conflict separator.
     * @param conflictStyle Determines how conflicts are displayed.
     * @param resultStream The stream that receives the merged output.
     * @return <code>true</code> if there were any conflicts.
     * @throws ClientException
     */
    public static boolean fileMerge(String originalFile,
                                    String modifiedFile,
                                    String latestFile,
                                    DiffOptions diffOptions,

                                    String conflictOriginal,
                                    String conflictModified,
                                    String conflictLatest,
                                    String conflictSeparator,
                                    ConflictDisplayStyle conflictStyle,

                                    OutputStream resultStream)
        throws ClientException
    {
        return diffLib.fileMerge(originalFile, modifiedFile, latestFile,
                                 diffOptions,
                                 conflictOriginal, conflictModified,
                                 conflictLatest, conflictSeparator,
                                 conflictStyle, resultStream);
    }

    //
    // Property validation and parsing
    //
    private static final PropLib propLib = new PropLib();

    /**
     * Validate the value of an <code>svn:</code> property on file or
     * directory and return a canonical representation of its value.
     * @param name The name of the property (must be a valid svn: property)
     * @param value The property's value
     * @param path The path or URL of the file or directory that
     *        owns the property; only used for error messages
     * @param kind The node kind of the file or dir that owns the property
     * @param mimeType If <code>kind</code> is {@link NodeKind.file}, this is
     *        tye file's mime-type, used for extra validation for the
     *        <code>svn:eol-style</code> property. If it is <code>null</code>,
     *        the extra validation will be skipped.
     * @return a canonicalized representation of the property value
     * @see http://subversion.apache.org/docs/api/latest/group__svn__wc__properties.html#ga83296313ec59cc825176224ac8282ec2
     */
    public static byte[] canonicalizeNodeProperty(
        String name, byte[] value, String path, NodeKind kind,
        String mimeType)
        throws ClientException
    {
        return propLib.canonicalizeNodeProperty(
            name, value, path, kind, mimeType, null);
    }

    /**
     * Validate the value of an <code>svn:</code> property on file or
     * directory and return a canonical representation of its value.
     * @param name The name of the property (must be a valid svn: property)
     * @param value The property's value
     * @param path The path or URL of the file or directory that
     *        owns the property; only used for error messages
     * @param kind The node kind of the file or dir that owns the property
     * @param mimeType If <code>kind</code> is {@link NodeKind.file}, this is
     *        tye file's mime-type, used for extra validation for the
     *        <code>svn:eol-style</code> property. If it is <code>null</code>,
     *        the extra validation will be skipped.
     * @param fileContents A stream with the file's contents. Only used
     *        to check for line-ending consistency when validating the
     *        <code>svn:eol-style</code> property, and only when
     *        <code>kind</code> is {@link NodeKind.file} and
     *        <code>mimeType</code> is not <code>null</code>.
     * @return a canonicalized representation of the property value
     * @see http://subversion.apache.org/docs/api/latest/group__svn__wc__properties.html#ga83296313ec59cc825176224ac8282ec2
     */
    public static byte[] canonicalizeNodeProperty(
        String name, byte[] value, String path, NodeKind kind,
        String mimeType, InputStream fileContents)
        throws ClientException
    {
        return propLib.canonicalizeNodeProperty(
            name, value, path, kind, mimeType, fileContents);
    }

    /**
     * Parse <code>description</code>, assuming it is an externals
     * specification in the format required for the
     * <code>svn:externals</code> property, and return a list of
     * parsed external items.
     * @param description The externals description.
     * @param parentDirectory Used to construct error messages.
     * @param canonicalizeUrl Whe <code>true</code>, canonicalize the
     *     <code>url</code> member of the returned objects. If the
     *     <code>url</code> member refers to an absolute URL, it will
     *     be canonicalized as URL consistent with the way URLs are
     *     canonicalized throughout the Subversion API. If, however,
     *     the <code>url</code> member makes use of the recognized
     *     (SVN-specific) relative URL syntax for
     *     <code>svn:externals</code>, "canonicalization" is an
     *     ill-defined concept which may even result in munging the
     *     relative URL syntax beyond recognition. You've been warned.
     * @return a list of {@link ExternalItem}s
     */
    public static List<ExternalItem> parseExternals(byte[] description,
                                                    String parentDirectory,
                                                    boolean canonicalizeUrl)
        throws ClientException
    {
        return propLib.parseExternals(description, parentDirectory,
                                      canonicalizeUrl);
    }

    /**
     * Unparse and list of external items into a format suitable for
     * the value of the <code>svn:externals</code> property and
     * validate the result.
     * @param items The list of {@link ExternalItem}s
     * @param parentDirectory Used to construct error messages.
     * @param compatibleWithSvn1_5 When <code>true</code>, the format
     *     of the returned property value will be compatible with
     *     clients older than Subversion 1.5.
     */
    public static byte[] unparseExternals(List<ExternalItem> items,
                                          String parentDirectory)
        throws SubversionException
    {
        return propLib.unparseExternals(items, parentDirectory, false);
    }

    /**
     * Unparse and list of external items into a format suitable for
     * the value of the <code>svn:externals</code> property compatible
     * with Subversion clients older than release 1.5, and validate
     * the result.
     * @param items The list of {@link ExternalItem}s
     * @param parentDirectory Used to construct error messages.
     */
    public static byte[] unparseExternalsForAncientUnsupportedClients(
        List<ExternalItem> items, String parentDirectory)
        throws SubversionException
    {
        return propLib.unparseExternals(items, parentDirectory, true);
    }

    /**
     * If the URL in <code>external</code> is relative, resolve it to
     * an absolute URL, using <code>reposRootUrl</code> and
     * <code>parentDirUrl</code> to provide contest.
     *<p>
     * Regardless if the URL is absolute or not, if there are no
     * errors, the returned URL will be canonicalized.
     *<p>
     * The following relative URL formats are supported:
     * <dl>
     *  <dt><code>../</code></dt>
     *  <dd>relative to the parent directory of the external</dd>
     *  <dt><code>^/</code></dt>
     *  <dd>relative to the repository root</dd>
     *  <dt><code>//</code></dt>
     *  <dd>relative to the scheme</dd>
     *  <dt><code>/</code></dt>
     *  <dd>relative to the server's hostname</dd>
     * </dl>
     *<p>
     * The <code>../<code> and ^/ relative URLs may use <code>..</code>
     * to remove path elements up to the server root.
     *<p>
     * The external URL should not be canonicalized before calling
     * this function, as otherwise the scheme relative URL
     * '<code>//host/some/path</code>' would have been canonicalized
     * to '<code>/host/some/path</code>' and we would not be able to
     * match on the leading '<code>//</code>'.
    */
    public static String resolveExternalsUrl(ExternalItem external,
                                             String reposRootUrl,
                                             String parentDirUrl)
        throws ClientException
    {
        return propLib.resolveExternalsUrl(
                   external, reposRootUrl, parentDirUrl);
    }

    //
    // Newline translation and keyword expansion
    //
    private static final SubstLib substLib = new SubstLib();

    /**
     * Use the linefeed code point ('<code>\x0a</code>')
     * for the newline separator.
     * @see translateStream
     * @see untranslateStream
     */
    public static final byte[] EOL_LF = SubstLib.EOL_LF;

    /**
     * Use the carraige-return code point ('<code>\x0d</code>')
     * for the newline separator.
     * @see translateStream
     * @see untranslateStream
     */
    public static final byte[] EOL_CR = SubstLib.EOL_CR;

    /**
     * Use carriage-return/linefeed sequence ('<code>\x0d\x0a</code>')
     * for the newline separator.
     * @see translateStream
     * @see untranslateStream
     */
    public static final byte[] EOL_CRLF = SubstLib.EOL_CRLF;


    /**
     * Build a dictionary of expanded keyword values, given the
     * contents of a file's <code>svn:keywords</code> property, its
     * revision, URL, the date it was committed on, the author of the
     * commit and teh URL of the repository root.
     *<p>
     * Custom keywords defined in <code>svn:keywords</code> properties
     * are expanded using the provided parameters and in accordance
     * with the following format substitutions in the
     * <code>keywordsValue</code>:
     * <dl>
     *   <dt><code>%a</dt></code>
     * <dd>The author.</dd>
     *   <dt><code>%b</dt></code>
     * <dd>The basename of the URL.</dd>
     *   <dt><code>%d</dt></code>
     * <dd>Short format of the date.</dd>
     *   <dt><code>%D</dt></code>
     * <dd>Long format of the date.</dd>
     *   <dt><code>%P</dt></code>
     * <dd>The file's path, relative to the repository root URL.</dd>
     *   <dt><code>%r</dt></code>
     * <dd>The revision.</dd>
     *   <dt><code>%R</dt></code>
     * <dd>The URL to the root of the repository.</dd>
     *   <dt><code>%u</dt></code>
     * <dd>The URL of the file.</dd>
     *   <dt><code>%_</dt></code>
     * <dd>A space (keyword definitions cannot contain a literal space).</dd>
     *   <dt><code>%%</dt></code>
     * <dd>A literal '%'.</dd>
     *   <dt><code>%H</dt></code>
     * <dd>Equivalent to <code>%P%_%r%_%d%_%a</code>.</dd>
     *   <dt><code>%I</dt></code>
     * <dd>Equivalent to <code>%b%_%r%_%d%_%a</code>.</dd>
     * </dl>
     *<p>
     * Custom keywords are defined by appending '=' to the keyword
     * name, followed by a string containing any combination of the
     * format substitutions.
     *<p>
     * Any of the <code>revision</code>, <code>url</code>,
     * <code>reposRootUrl</code>, <code>date</code> and
     * <code>author</code> parameters may be <code>null</code>, or
     * {@link Revision#SVN_INVALID_REVNUM} for <code>revision</code>,
     * to indicate that the information is not present. Each piece of
     * information that is not present expands to the empty string
     * wherever it appears in an expanded keyword value.  (This can
     * result in multiple adjacent spaces in the expansion of a
     * multi-valued keyword such as "<code>Id</code>".)
     */
    public static Map<String, byte[]> buildKeywords(byte[] keywordsValue,
                                                    long revision,
                                                    String url,
                                                    String reposRootUrl,
                                                    Date date,
                                                    String author)
        throws SubversionException, ClientException
    {
        return substLib.buildKeywords(keywordsValue, revision,
                                      url, reposRootUrl, date, author);
    }

    /**
     * Return a stream which performs end-of-line translation and
     * keyword expansion when read from.
     *<p>
     * <b>Important:</b> Make sure you close the returned stream to
     * ensure all data are flushed and cleaned up (this will also
     * close the provided stream and dispose the related netive
     * object).
     *<p>
     * If <code>eolMarker</code> is not <code>null</code>, replace
     * whatever any end-of-line sequences in the input with
     * <code>eolMarker</code>.  If the input has an inconsistent line
     * ending style, then:
     * <ul>
     *   <li>if <code>repairEol</code> is <code>false</code>, then a
     *       subsequent read or other operation on the stream will
     *       generate an error when the inconsistency is detected;</li>
     *   <li>if <code>repaorEol</code> is <code>true</code>, convert any
     *       line ending to <code>eolMarker</code>.<br/>
     *       Recognized line endings are: "<code>\n</code>",
     *       "<code>\r</code>", and "<code>\r\n</code>".</li>
     * </ul>
     *<p>
     * Expand or contract keywords using the contents of
     * <code>keywords</code> as the new values.  If
     * <code>expandKeywords</code> is <code>true</code>, expand
     * contracted keywords and re-expand expanded keywords; otherwise,
     * contract expanded keywords and ignore contracted ones.
     * Keywords not found in the dictionary are ignored (not
     * contracted or expanded).  If the <code>keywords</code> itself
     * is <code>null</code>, keyword substitution will be altogether
     * ignored.
     *<p>
     * Detect only keywords that are no longer than
     * <code>SVN_KEYWORD_MAX_LEN</code> bytes (currently: 255),
     * including the delimiters and the keyword itself.
     *<p>
     * Recommendation: if <code>expandKeywords</code> is
     * <code>false</code>, then you don't care about the keyword
     * values, so just put <code>null</code> values into the
     * <code>keywords</code> dictionary.
     *<p>
     * If the inner stream implements marking and seeking via
     * {@link InputStream#mark} and {@link InputStream#reset}, the
     * translated stream will too.
     *
     * @param source the source (untranslated) stream.
     * @param eolMarker the byte sequence to use as the end-of-line marker;
     *     must be one of {@link #EOL_LF}, {@link #EOL_CR}
     *     or {@link #EOL_CRLF}.
     * @param repairEol flag to repair end-of-lines; see above
     * @param keywords the keyword dictionary; see {@link buildKeywords}
     * @param expandKeywords flag to expand keywords
     */
    public static InputStream translateStream(InputStream source,
                                              byte[] eolMarker,
                                              boolean repairEol,
                                              Map<String, byte[]> keywords,
                                              boolean expandKeywords)
        throws SubversionException, ClientException
    {
        return substLib.translateInputStream(
                    source, eolMarker, repairEol,
                    keywords, true, expandKeywords,
                    null, Revision.SVN_INVALID_REVNUM,
                    null, null, null, null);
    }

    /**
     * Expand keywords and return a stream which performs end-of-line
     * translation and keyword expansion when read from.
     * @see buildKeywords
     * @see translateStream(InputStream,byte[],boolean,Map,boolean)
     */
    public static InputStream translateStream(InputStream source,
                                              byte[] eolMarker,
                                              boolean repairEol,
                                              boolean expandKeywords,
                                              byte[] keywordsValue,
                                              long revision,
                                              String url,
                                              String reposRootUrl,
                                              Date date,
                                              String author)
        throws SubversionException, ClientException
    {
        return substLib.translateInputStream(
                    source, eolMarker, repairEol,
                    null, false, expandKeywords,
                    keywordsValue, revision,
                    url, reposRootUrl, date, author);
    }

    /**
     * Return a stream which performs end-of-line translation and
     * keyword expansion when written to. Behaves like
     * {@link #translateStream(InputStream,byte[],boolean,Map,boolean)},
     * except that it translates an <code>OutputStream</code> and never
     * supports marking and seeking.
     */
    public static OutputStream translateStream(OutputStream destination,
                                               byte[] eolMarker,
                                               boolean repairEol,
                                               Map<String, byte[]> keywords,
                                               boolean expandKeywords)
        throws SubversionException, ClientException
    {
        return substLib.translateOutputStream(
                    destination, eolMarker, repairEol,
                    keywords, true, expandKeywords,
                    null, Revision.SVN_INVALID_REVNUM,
                    null, null, null, null);
    }

    /**
     * Expand keywords and return a stream which performs end-of-line
     * translation and keyword expansion when written to.
     * @see buildKeywords
     * @see translateStream(OutputStream,byte[],boolean,Map,boolean)
     */
    public static OutputStream translateStream(OutputStream destination,
                                               byte[] eolMarker,
                                               boolean repairEol,
                                               boolean expandKeywords,
                                               byte[] keywordsValue,
                                               long revision,
                                               String url,
                                               String reposRootUrl,
                                               Date date,
                                               String author)
        throws SubversionException, ClientException
    {
        return substLib.translateOutputStream(
                    destination, eolMarker, repairEol,
                    null, false, expandKeywords,
                    keywordsValue, revision,
                    url, reposRootUrl, date, author);
    }
}