aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/PropertyHelper.java
blob: 1dbb280c9214866f061dc6b68dc840691724ab82 (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
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
/*
 *  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.
 *
 */
package org.apache.tools.ant;

import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.tools.ant.property.GetProperty;
import org.apache.tools.ant.property.NullReturn;
import org.apache.tools.ant.property.ParseNextProperty;
import org.apache.tools.ant.property.ParseProperties;
import org.apache.tools.ant.property.PropertyExpander;

/* ISSUES:
 - ns param. It could be used to provide "namespaces" for properties, which
 may be more flexible.
 - Object value. In ant1.5 String is used for Properties - but it would be nice
 to support generic Objects (the property remains immutable - you can't change
 the associated object). This will also allow JSP-EL style setting using the
 Object if an attribute contains only the property (name="${property}" could
 avoid Object->String->Object conversion)
 - Currently we "chain" only for get and set property (probably most users
 will only need that - if they need more they can replace the top helper).
 Need to discuss this and find if we need more.
 */

/* update for impending Ant 1.8.0:

   - I can't see any reason for ns and would like to deprecate it.
   - Replacing chaining with delegates for certain behavioral aspects.
   - Object value seems valuable as outlined.

 */

/**
 * Deals with properties - substitution, dynamic properties, etc.
 *
 * <p>This code has been heavily restructured for Ant 1.8.0.  It is
 * expected that custom PropertyHelper implementation that used the
 * older chaining mechanism of Ant 1.6 won't work in all cases, and
 * its usage is deprecated.  The preferred way to customize Ant's
 * property handling is by {@link #add adding} {@link
 * PropertyHelper.Delegate delegates} of the appropriate subinterface
 * and have this implementation use them.</p>
 *
 * <p>When {@link #parseProperties expanding a string that may contain
 * properties} this class will delegate the actual parsing to {@link
 * org.apache.tools.ant.property.ParseProperties#parseProperties
 * parseProperties} inside the ParseProperties class which in turn
 * uses the {@link org.apache.tools.ant.property.PropertyExpander
 * PropertyExpander delegates} to find properties inside the string
 * and this class to expand the propertiy names found into the
 * corresponding values.</p>
 *
 * <p>When {@link #getProperty looking up a property value} this class
 * will first consult all {@link PropertyHelper.PropertyEvaluator
 * PropertyEvaluator} delegates and fall back to an internal map of
 * "project properties" if no evaluator matched the property name.</p>
 *
 * <p>When {@link #setProperty setting a property value} this class
 * will first consult all {@link PropertyHelper.PropertySetter
 * PropertySetter} delegates and fall back to an internal map of
 * "project properties" if no setter matched the property name.</p>
 *
 * @since Ant 1.6
 */
public class PropertyHelper implements GetProperty {

    //  --------------------------------------------------------
    //
    //    The property delegate interfaces
    //
    //  --------------------------------------------------------

    /**
     * Marker interface for a PropertyHelper delegate.
     * @since Ant 1.8.0
     */
    public interface Delegate {
    }

    /**
     * Looks up a property's value based on its name.
     *
     * <p>Can be used to look up properties in a different storage
     * than the project instance (like local properties for example)
     * or to implement custom "protocols" like Ant's
     * <code>${toString:refid}</code> syntax.</p>
     *
     * @since Ant 1.8.0
     */
    public interface PropertyEvaluator extends Delegate {
        /**
         * Evaluate a property.
         *
         * @param property the property's String "identifier".
         * @param propertyHelper the invoking PropertyHelper.
         * @return null if the property name could not be found, an
         * instance of {@link org.apache.tools.ant.property.NullReturn
         * NullReturn} to indicate a property with a name that can be
         * matched but a value of <code>null</code> and the property's
         * value otherwise.
         */
        Object evaluate(String property, PropertyHelper propertyHelper);
    }

    /**
     * Sets or overrides a property.
     *
     * <p>Can be used to store properties in a different storage than
     * the project instance (like local properties for example).</p>
     *
     * @since Ant 1.8.0
     */
    public interface PropertySetter extends Delegate {
        /**
         * Set a *new" property.
         *
         * <p>Should not replace the value of an existing property.</p>
         *
         * @param property the property's String "identifier".
         * @param value    the value to set.
         * @param propertyHelper the invoking PropertyHelper.
         * @return true if this entity 'owns' the property.
         */
        boolean setNew(
            String property, Object value, PropertyHelper propertyHelper);

        /**
         * Set a property.
         *
         * <p>May replace the value of an existing property.</p>
         *
         * @param property the property's String "identifier".
         * @param value    the value to set.
         * @param propertyHelper the invoking PropertyHelper.
         * @return true if this entity 'owns' the property.
         */
        boolean set(
            String property, Object value, PropertyHelper propertyHelper);
    }

    //TODO PropertyEnumerator Delegate type, would improve PropertySet

    //  --------------------------------------------------------
    //
    //    The predefined property delegates
    //
    //  --------------------------------------------------------

    private static final PropertyEvaluator TO_STRING = new PropertyEvaluator() {
        private final String PREFIX = "toString:";
        private final int PREFIX_LEN = PREFIX.length();

        public Object evaluate(String property, PropertyHelper propertyHelper) {
            Object o = null;
            if (property.startsWith(PREFIX) && propertyHelper.getProject() != null) {
                o = propertyHelper.getProject().getReference(property.substring(PREFIX_LEN));
            }
            return o == null ? null : o.toString();
        }
    };

    private static final PropertyExpander DEFAULT_EXPANDER = new PropertyExpander() {
        public String parsePropertyName(
            String s, ParsePosition pos, ParseNextProperty notUsed) {
            int index = pos.getIndex();
            //directly check near, triggering characters:
            if (s.length() - index >= 3
                    && '$' == s.charAt(index) && '{' == s.charAt(index + 1)) {
                int start = index + 2;
                //defer to String.indexOf() for protracted check:
                int end = s.indexOf('}', start);
                if (end < 0) {
                    throw new BuildException("Syntax error in property: "
                            + s.substring(index));
                }
                pos.setIndex(end + 1);
                return start == end ? "" :  s.substring(start, end);
            }
            return null;
        }
    };

    /** dummy */
    private static final PropertyExpander SKIP_DOUBLE_DOLLAR
        = new PropertyExpander() {
            // CheckStyle:LineLengthCheck OFF see too long
            /**
             * {@inheritDoc}
             * @see org.apache.tools.ant.property.PropertyExpander#parsePropertyName(java.lang.String, java.text.ParsePosition, org.apache.tools.ant.PropertyHelper)
             */
            // CheckStyle:LineLengthCheck ON
            public String parsePropertyName(
                String s, ParsePosition pos, ParseNextProperty notUsed) {
                int index = pos.getIndex();
                if (s.length() - index >= 2) {
                    /* check for $$; if found, advance by one--
                     * this expander is at the bottom of the stack
                     * and will thus be the last consulted,
                     * so the next thing that ParseProperties will do
                     * is advance the parse position beyond the second $
                     */
                    if ('$' == s.charAt(index) && '$' == s.charAt(++index)) {
                        pos.setIndex(index);
                    }
                }
                return null;
            }
        };

    /**
     * @since Ant 1.8.0
     */
    private static final PropertyEvaluator FROM_REF = new PropertyEvaluator() {
        private final String PREFIX = "ant.refid:";
        private final int PREFIX_LEN = PREFIX.length();

        public Object evaluate(String prop, PropertyHelper helper) {
            return prop.startsWith(PREFIX) && helper.getProject() != null
                ? helper.getProject().getReference(prop.substring(PREFIX_LEN))
                : null;
        }
    };

    private Project project;
    private PropertyHelper next;
    private final Hashtable<Class<? extends Delegate>, List<Delegate>> delegates = new Hashtable<Class<? extends Delegate>, List<Delegate>>();

    /** Project properties map (usually String to String). */
    private Hashtable<String, Object> properties = new Hashtable<String, Object>();

    /**
     * Map of "user" properties (as created in the Ant task, for example).
     * Note that these key/value pairs are also always put into the
     * project properties, so only the project properties need to be queried.
     */
    private Hashtable<String, Object> userProperties = new Hashtable<String, Object>();

    /**
     * Map of inherited "user" properties - that are those "user"
     * properties that have been created by tasks and not been set
     * from the command line or a GUI tool.
     */
    private Hashtable<String, Object> inheritedProperties = new Hashtable<String, Object>();

    /**
     * Default constructor.
     */
    protected PropertyHelper() {
        add(FROM_REF);
        add(TO_STRING);
        add(SKIP_DOUBLE_DOLLAR);
        add(DEFAULT_EXPANDER);
    }

    //  --------------------------------------------------------
    //
    //    Some helper static methods to get and set properties
    //
    //  --------------------------------------------------------

    /**
     * A helper static method to get a property
     * from a particular project.
     * @param project the project in question.
     * @param name the property name
     * @return the value of the property if present, null otherwise.
     * @since Ant 1.8.0
     */
    public static Object getProperty(Project project, String name) {
        return PropertyHelper.getPropertyHelper(project)
            .getProperty(name);
    }

    /**
     * A helper static method to set a property
     * from a particular project.
     * @param project the project in question.
     * @param name the property name
     * @param value the value to use.
     * @since Ant 1.8.0
     */
    public static void setProperty(Project project, String name, Object value) {
        PropertyHelper.getPropertyHelper(project)
            .setProperty(name, value, true);
    }

    /**
     * A helper static method to set a new property
     * from a particular project.
     * @param project the project in question.
     * @param name the property name
     * @param value the value to use.
     * @since Ant 1.8.0
     */
    public static void setNewProperty(
        Project project, String name, Object value) {
        PropertyHelper.getPropertyHelper(project)
            .setNewProperty(name, value);
    }

    //override facility for subclasses to put custom hashtables in

    // --------------------  Hook management  --------------------

    /**
     * Set the project for which this helper is performing property resolution.
     *
     * @param p the project instance.
     */
    public void setProject(Project p) {
        this.project = p;
    }

    /**
     * Get this PropertyHelper's Project.
     * @return Project
     */
    public Project getProject() {
        return project;
    }

    /**
     * Prior to Ant 1.8.0 there have been 2 ways to hook into property handling:
     *
     *  - you can replace the main PropertyHelper. The replacement is required
     * to support the same semantics (of course :-)
     *
     *  - you can chain a property helper capable of storing some properties.
     *  Again, you are required to respect the immutability semantics (at
     *  least for non-dynamic properties)
     *
     * <p>As of Ant 1.8.0 this method is never invoked by any code
     * inside of Ant itself.</p>
     *
     * @param next the next property helper in the chain.
     * @deprecated use the delegate mechanism instead
     */
    public void setNext(PropertyHelper next) {
        this.next = next;
    }

    /**
     * Get the next property helper in the chain.
     *
     * <p>As of Ant 1.8.0 this method is never invoked by any code
     * inside of Ant itself except the {@link #setPropertyHook
     * setPropertyHook} and {@link #getPropertyHook getPropertyHook}
     * methods in this class.</p>
     *
     * @return the next property helper.
     * @deprecated use the delegate mechanism instead
     */
    public PropertyHelper getNext() {
        return next;
    }

    /**
     * Factory method to create a property processor.
     * Users can provide their own or replace it using "ant.PropertyHelper"
     * reference. User tasks can also add themselves to the chain, and provide
     * dynamic properties.
     *
     * @param project the project for which the property helper is required.
     *
     * @return the project's property helper.
     */
    public static synchronized PropertyHelper getPropertyHelper(Project project) {
        PropertyHelper helper = null;
        if (project != null) {
            helper = (PropertyHelper) project.getReference(MagicNames
                                                           .REFID_PROPERTY_HELPER);
        }
        if (helper != null) {
            return helper;
        }

        helper = new PropertyHelper();
        helper.setProject(project);

        if (project != null) {
            project.addReference(MagicNames.REFID_PROPERTY_HELPER, helper);
        }

        return helper;
    }

    /**
     * Get the {@link PropertyExpander expanders}.
     * @since Ant 1.8.0
     * @return the expanders.
     */
    public Collection<PropertyExpander> getExpanders() {
        return getDelegates(PropertyExpander.class);
    }


    // --------------------  Methods to override  --------------------

    /**
     * Sets a property. Any existing property of the same name
     * is overwritten, unless it is a user property.
     *
     * If all helpers return false, the property will be saved in
     * the default properties table by setProperty.
     *
     * <p>As of Ant 1.8.0 this method is never invoked by any code
     * inside of Ant itself.</p>
     *
     * @param ns   The namespace that the property is in (currently
     *             not used.
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     * @param inherited True if this property is inherited (an [sub]ant[call] property).
     * @param user      True if this property is a user property.
     * @param isNew     True is this is a new property.
     * @return true if this helper has stored the property, false if it
     *    couldn't. Each helper should delegate to the next one (unless it
     *    has a good reason not to).
     * @deprecated PropertyHelper chaining is deprecated.
     */
    public boolean setPropertyHook(String ns, String name,
                                   Object value,
                                   boolean inherited, boolean user,
                                   boolean isNew) {
        if (getNext() != null) {
            boolean subst = getNext().setPropertyHook(ns, name, value, inherited, user, isNew);
            // If next has handled the property
            if (subst) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get a property. If all hooks return null, the default
     * tables will be used.
     *
     * <p>As of Ant 1.8.0 this method is never invoked by any code
     * inside of Ant itself.</p>
     *
     * @param ns namespace of the sought property.
     * @param name name of the sought property.
     * @param user True if this is a user property.
     * @return The property, if returned by a hook, or null if none.
     * @deprecated PropertyHelper chaining is deprecated.
     */
    public Object getPropertyHook(String ns, String name, boolean user) {
        if (getNext() != null) {
            Object o = getNext().getPropertyHook(ns, name, user);
            if (o != null) {
                return o;
            }
        }
        // Experimental/Testing, will be removed
        if (project != null && name.startsWith("toString:")) {
            name = name.substring("toString:".length());
            Object v = project.getReference(name);
            return (v == null) ? null : v.toString();
        }
        return null;
    }

    // -------------------- Optional methods   --------------------
    // You can override those methods if you want to optimize or
    // do advanced things (like support a special syntax).
    // The methods do not chain - you should use them when embedding ant
    // (by replacing the main helper)

    /**
     * Parses a string containing <code>${xxx}</code> style property
     * references into two lists. The first list is a collection
     * of text fragments, while the other is a set of string property names.
     * <code>null</code> entries in the first list indicate a property
     * reference from the second list.
     *
     * <p>Delegates to {@link #parsePropertyStringDefault
     * parsePropertyStringDefault}.</p>
     *
     * <p>As of Ant 1.8.0 this method is never invoked by any code
     * inside of Ant itself except {ProjectHelper#parsePropertyString
     * ProjectHelper.parsePropertyString}.</p>
     *
     * @param value     Text to parse. Must not be <code>null</code>.
     * @param fragments List to add text fragments to.
     *                  Must not be <code>null</code>.
     * @param propertyRefs List to add property names to.
     *                     Must not be <code>null</code>.
     *
     * @exception BuildException if the string contains an opening
     *                           <code>${</code> without a closing
     *                           <code>}</code>
     * @deprecated use the other mechanisms of this class instead
     */
    public void parsePropertyString(String value, Vector<String> fragments,
                                    Vector<String> propertyRefs) throws BuildException {
        parsePropertyStringDefault(value, fragments, propertyRefs);
    }

    /**
     * Replaces <code>${xxx}</code> style constructions in the given value
     * with the string value of the corresponding data types.
     *
     * <p>Delegates to the one-arg version, completely ignoring the ns
     * and keys parameters.</p>
     *
     * @param ns    The namespace for the property.
     * @param value The string to be scanned for property references.
     *              May be <code>null</code>, in which case this
     *              method returns immediately with no effect.
     * @param keys  Mapping (String to Object) of property names to their
     *              values. If <code>null</code>, only project properties will
     *              be used.
     *
     * @exception BuildException if the string contains an opening
     *                           <code>${</code> without a closing
     *                           <code>}</code>
     * @return the original string with the properties replaced, or
     *         <code>null</code> if the original string is <code>null</code>.
     */
    //TODO deprecate?  Recall why no longer using ns/keys params
    public String replaceProperties(String ns, String value, Hashtable<String, Object> keys) throws BuildException {
        return replaceProperties(value);
    }

    /**
     * Replaces <code>${xxx}</code> style constructions in the given value
     * with the string value of the corresponding data types.
     *
     * @param value The string to be scanned for property references.
     *              May be <code>null</code>, in which case this
     *              method returns immediately with no effect.
     *
     * @exception BuildException if the string contains an opening
     *                           <code>${</code> without a closing
     *                           <code>}</code>
     * @return the original string with the properties replaced, or
     *         <code>null</code> if the original string is <code>null</code>.
     */
    public String replaceProperties(String value) throws BuildException {
        Object o = parseProperties(value);
        return o == null || o instanceof String ? (String) o : o.toString();
    }

    /**
     * Decode properties from a String representation.  If the entire
     * contents of the String resolve to a single property, that value
     * is returned.  Otherwise a String is returned.
     *
     * @param value The string to be scanned for property references.
     *              May be <code>null</code>, in which case this
     *              method returns immediately with no effect.
     *
     * @exception BuildException if the string contains an opening
     *                           <code>${</code> without a closing
     *                           <code>}</code>
     * @return the original string with the properties replaced, or
     *         <code>null</code> if the original string is <code>null</code>.
     */
    public Object parseProperties(String value) throws BuildException {
        return new ParseProperties(getProject(), getExpanders(), this)
            .parseProperties(value);
    }

    /**
     * Learn whether a String contains replaceable properties.
     * @param value the String to check.
     * @return <code>true</code> if <code>value</code> contains property notation.
     */
    public boolean containsProperties(String value) {
        return new ParseProperties(getProject(), getExpanders(), this)
            .containsProperties(value);
    }

    // -------------------- Default implementation  --------------------
    // Methods used to support the default behavior and provide backward
    // compatibility. Some will be deprecated, you should avoid calling them.

    /**
     * Default implementation of setProperty. Will be called from Project.
     * This is the original 1.5 implementation, with calls to the hook
     * added.
     *
     * <p>Delegates to the three-arg version, completely ignoring the
     * ns parameter.</p>
     *
     * @param ns      The namespace for the property (currently not used).
     * @param name    The name of the property.
     * @param value   The value to set the property to.
     * @param verbose If this is true output extra log messages.
     * @return true if the property is set.
     * @deprecated namespaces are unnecessary.
     */
    public boolean setProperty(String ns, String name, Object value, boolean verbose) {
        return setProperty(name, value, verbose);
    }

    /**
     * Default implementation of setProperty. Will be called from Project.
     *  @param name    The name of the property.
     *  @param value   The value to set the property to.
     *  @param verbose If this is true output extra log messages.
     *  @return true if the property is set.
     */
    public boolean setProperty(String name, Object value, boolean verbose) {
        for (PropertySetter setter : getDelegates(PropertySetter.class)) {
            if (setter.set(name, value, this)) {
                return true;
            }
        }
        synchronized (this) {
            // user (CLI) properties take precedence
            if (userProperties.containsKey(name)) {
                if (project != null && verbose) {
                    project.log("Override ignored for user property \""
                                + name + "\"", Project.MSG_VERBOSE);
                }
                return false;
            }
            if (project != null && verbose) {
                if (properties.containsKey(name)) {
                    project.log("Overriding previous definition of property \""
                                + name + "\"", Project.MSG_VERBOSE);
                }
                project.log("Setting project property: " + name + " -> "
                            + value, Project.MSG_DEBUG);
            }
            if (name != null && value != null) {
                properties.put(name, value);
            }
            return true;
        }
    }

    /**
     * Sets a property if no value currently exists. If the property
     * exists already, a message is logged and the method returns with
     * no other effect.
     *
     * <p>Delegates to the two-arg version, completely ignoring the
     * ns parameter.</p>
     *
     * @param ns   The namespace for the property (currently not used).
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     * @since Ant 1.6
     * @deprecated namespaces are unnecessary.
     */
    public void setNewProperty(String ns, String name, Object value) {
        setNewProperty(name, value);
    }

    /**
     * Sets a property if no value currently exists. If the property
     * exists already, a message is logged and the method returns with
     * no other effect.
     *
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     * @since Ant 1.8.0
     */
    public void setNewProperty(String name, Object value) {
        for (PropertySetter setter : getDelegates(PropertySetter.class)) {
            if (setter.setNew(name, value, this)) {
                return;
            }
        }
        synchronized (this) {
            if (project != null && properties.containsKey(name)) {
                project.log("Override ignored for property \"" + name
                            + "\"", Project.MSG_VERBOSE);
                return;
            }
            if (project != null) {
                project.log("Setting project property: " + name
                            + " -> " + value, Project.MSG_DEBUG);
            }
            if (name != null && value != null) {
                properties.put(name, value);
            }
        }
    }

    /**
     * Sets a user property, which cannot be overwritten by
     * set/unset property calls. Any previous value is overwritten.
     *
     * <p>Delegates to the two-arg version, completely ignoring the
     * ns parameter.</p>
     *
     * @param ns   The namespace for the property (currently not used).
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     * @deprecated namespaces are unnecessary.
     */
    public void setUserProperty(String ns, String name, Object value) {
        setUserProperty(name, value);
    }

    /**
     * Sets a user property, which cannot be overwritten by
     * set/unset property calls. Any previous value is overwritten.
     *
     * <p>Does <code>not</code> consult any delegates.</p>
     *
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     */
    public void setUserProperty(String name, Object value) {
        if (project != null) {
            project.log("Setting ro project property: "
                        + name + " -> " + value, Project.MSG_DEBUG);
        }
        synchronized (this) {
            userProperties.put(name, value);
            properties.put(name, value);
        }
    }

    /**
     * Sets an inherited user property, which cannot be overwritten by set/unset
     * property calls. Any previous value is overwritten. Also marks
     * these properties as properties that have not come from the
     * command line.
     *
     * <p>Delegates to the two-arg version, completely ignoring the
     * ns parameter.</p>
     *
     * @param ns   The namespace for the property (currently not used).
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     * @deprecated namespaces are unnecessary.
     */
    public void setInheritedProperty(String ns, String name, Object value) {
        setInheritedProperty(name, value);
    }

    /**
     * Sets an inherited user property, which cannot be overwritten by set/unset
     * property calls. Any previous value is overwritten. Also marks
     * these properties as properties that have not come from the
     * command line.
     *
     * <p>Does <code>not</code> consult any delegates.</p>
     *
     * @param name The name of property to set.
     *             Must not be <code>null</code>.
     * @param value The new value of the property.
     *              Must not be <code>null</code>.
     */
    public void setInheritedProperty(String name, Object value) {
        if (project != null) {
            project.log("Setting ro project property: " + name + " -> "
                        + value, Project.MSG_DEBUG);
        }

        synchronized (this) {
            inheritedProperties.put(name, value);
            userProperties.put(name, value);
            properties.put(name, value);
        }
    }

    // -------------------- Getting properties  --------------------

    /**
     * Returns the value of a property, if it is set.  You can override
     * this method in order to plug your own storage.
     *
     * <p>Delegates to the one-arg version ignoring the ns parameter.</p>
     *
     * @param ns   The namespace for the property (currently not used).
     * @param name The name of the property.
     *             May be <code>null</code>, in which case
     *             the return value is also <code>null</code>.
     * @return the property value, or <code>null</code> for no match
     *         or if a <code>null</code> name is provided.
     * @deprecated namespaces are unnecessary.
     */
    public Object getProperty(String ns, String name) {
        return getProperty(name);
    }

    /**
     * Returns the value of a property, if it is set.
     *
     * <p>This is the method that is invoked by {Project#getProperty
     * Project.getProperty}.</p>
     *
     * <p>You can override this method in order to plug your own
     * storage but the recommended approach is to add your own
     * implementation of {@link PropertyEvaluator PropertyEvaluator}
     * instead.</p>
     *
     * @param name The name of the property.
     *             May be <code>null</code>, in which case
     *             the return value is also <code>null</code>.
     * @return the property value, or <code>null</code> for no match
     *         or if a <code>null</code> name is provided.
     */
    public Object getProperty(String name) {
        if (name == null) {
            return null;
        }
        for (PropertyEvaluator evaluator : getDelegates(PropertyEvaluator.class)) {
            final Object o = evaluator.evaluate(name, this);
            if (o == null) {
                continue;
            }
            return o instanceof NullReturn ? null : o;
        }
        return properties.get(name);
    }

    /**
     * Returns the value of a user property, if it is set.
     *
     * <p>Delegates to the one-arg version ignoring the ns parameter.</p>
     *
     * @param ns   The namespace for the property (currently not used).
     * @param name The name of the property.
     *             May be <code>null</code>, in which case
     *             the return value is also <code>null</code>.
     * @return the property value, or <code>null</code> for no match
     *         or if a <code>null</code> name is provided.
     * @deprecated namespaces are unnecessary.
     */
    public Object getUserProperty(String ns, String name) {
        return getUserProperty(name);
    }

    /**
     * Returns the value of a user property, if it is set.
     *
     * <p>Does <code>not</code> consult any delegates.</p>
     *
     * @param name The name of the property.
     *             May be <code>null</code>, in which case
     *             the return value is also <code>null</code>.
     * @return the property value, or <code>null</code> for no match
     *         or if a <code>null</code> name is provided.
     */
    public Object getUserProperty(String name) {
        if (name == null) {
            return null;
        }
        return userProperties.get(name);
    }

    // -------------------- Access to property tables  --------------------
    // This is used to support ant call and similar tasks. It should be
    // deprecated, it is possible to use a better (more efficient)
    // mechanism to preserve the context.

    /**
     * Returns a copy of the properties table.
     *
     * <p>Does not contain properties held by implementations of
     * delegates (like local properties).</p>
     *
     * @return a hashtable containing all properties (including user properties).
     */
    public Hashtable<String, Object> getProperties() {
        //avoid concurrent modification:
        synchronized (properties) {
            return new Hashtable<String, Object>(properties);
        }
        // There is a better way to save the context. This shouldn't
        // delegate to next, it's for backward compatibility only.
    }

    /**
     * Returns a copy of the user property hashtable
     *
     * <p>Does not contain properties held by implementations of
     * delegates (like local properties).</p>
     *
     * @return a hashtable containing just the user properties
     */
    public Hashtable<String, Object> getUserProperties() {
        //avoid concurrent modification:
        synchronized (userProperties) {
            return new Hashtable<String, Object>(userProperties);
        }
    }

    /**
     * Returns a copy of the inherited property hashtable
     *
     * <p>Does not contain properties held by implementations of
     * delegates (like local properties).</p>
     *
     * @return a hashtable containing just the inherited properties
     */
    public Hashtable<String, Object> getInheritedProperties() {
        //avoid concurrent modification:
        synchronized (inheritedProperties) {
            return new Hashtable<String, Object>(inheritedProperties);
        }
    }

    /**
     * special back door for subclasses, internal access to the hashtables
     * @return the live hashtable of all properties
     */
    protected Hashtable<String, Object> getInternalProperties() {
        return properties;
    }

    /**
     * special back door for subclasses, internal access to the hashtables
     *
     * @return the live hashtable of user properties
     */
    protected Hashtable<String, Object> getInternalUserProperties() {
        return userProperties;
    }

    /**
     * special back door for subclasses, internal access to the hashtables
     *
     * @return the live hashtable inherited properties
     */
    protected Hashtable<String, Object> getInternalInheritedProperties() {
        return inheritedProperties;
    }

    /**
     * Copies all user properties that have not been set on the
     * command line or a GUI tool from this instance to the Project
     * instance given as the argument.
     *
     * <p>To copy all "user" properties, you will also have to call
     * {@link #copyUserProperties copyUserProperties}.</p>
     *
     * <p>Does not copy properties held by implementations of
     * delegates (like local properties).</p>
     *
     * @param other the project to copy the properties to.  Must not be null.
     *
     * @since Ant 1.6
     */
    public void copyInheritedProperties(Project other) {
        //avoid concurrent modification:
        synchronized (inheritedProperties) {
            Enumeration<String> e = inheritedProperties.keys();
            while (e.hasMoreElements()) {
                String arg = e.nextElement().toString();
                if (other.getUserProperty(arg) != null) {
                    continue;
                }
                Object value = inheritedProperties.get(arg);
                other.setInheritedProperty(arg, value.toString());
            }
        }
    }

    /**
     * Copies all user properties that have been set on the command
     * line or a GUI tool from this instance to the Project instance
     * given as the argument.
     *
     * <p>To copy all "user" properties, you will also have to call
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
     *
     * <p>Does not copy properties held by implementations of
     * delegates (like local properties).</p>
     *
     * @param other the project to copy the properties to.  Must not be null.
     *
     * @since Ant 1.6
     */
    public void copyUserProperties(Project other) {
        //avoid concurrent modification:
        synchronized (userProperties) {
            Enumeration<String> e = userProperties.keys();
            while (e.hasMoreElements()) {
                Object arg = e.nextElement();
                if (inheritedProperties.containsKey(arg)) {
                    continue;
                }
                Object value = userProperties.get(arg);
                other.setUserProperty(arg.toString(), value.toString());
            }
        }
    }

    // -------------------- Property parsing  --------------------
    // Moved from ProjectHelper. You can override the static method -
    // this is used for backward compatibility (for code that calls
    // the parse method in ProjectHelper).

    /**
     * Default parsing method. It is here only to support backward compatibility
     * for the static ProjectHelper.parsePropertyString().
     */
    static void parsePropertyStringDefault(String value, Vector<String> fragments, Vector<String> propertyRefs)
            throws BuildException {
        int prev = 0;
        int pos;
        //search for the next instance of $ from the 'prev' position
        while ((pos = value.indexOf("$", prev)) >= 0) {

            //if there was any text before this, add it as a fragment
            //TODO, this check could be modified to go if pos>prev;
            //seems like this current version could stick empty strings
            //into the list
            if (pos > 0) {
                fragments.addElement(value.substring(prev, pos));
            }
            //if we are at the end of the string, we tack on a $
            //then move past it
            if (pos == (value.length() - 1)) {
                fragments.addElement("$");
                prev = pos + 1;
            } else if (value.charAt(pos + 1) != '{') {
                //peek ahead to see if the next char is a property or not
                //not a property: insert the char as a literal
                /*
                fragments.addElement(value.substring(pos + 1, pos + 2));
                prev = pos + 2;
                */
                if (value.charAt(pos + 1) == '$') {
                    //backwards compatibility two $ map to one mode
                    fragments.addElement("$");
                    prev = pos + 2;
                } else {
                    //new behaviour: $X maps to $X for all values of X!='$'
                    fragments.addElement(value.substring(pos, pos + 2));
                    prev = pos + 2;
                }
            } else {
                //property found, extract its name or bail on a typo
                int endName = value.indexOf('}', pos);
                if (endName < 0) {
                    throw new BuildException("Syntax error in property: " + value);
                }
                String propertyName = value.substring(pos + 2, endName);
                fragments.addElement(null);
                propertyRefs.addElement(propertyName);
                prev = endName + 1;
            }
        }
        //no more $ signs found
        //if there is any tail to the file, append it
        if (prev < value.length()) {
            fragments.addElement(value.substring(prev));
        }
    }

    /**
     * Add the specified delegate object to this PropertyHelper.
     * Delegates are processed in LIFO order.
     * @param delegate the delegate to add.
     * @since Ant 1.8.0
     */
    public void add(Delegate delegate) {
        synchronized (delegates) {
            for (Class<? extends Delegate> key : getDelegateInterfaces(delegate)) {
                List<Delegate> list = delegates.get(key);
                if (list == null) {
                    list = new ArrayList<Delegate>();
                } else {
                    //copy on write, top priority
                    list = new ArrayList<Delegate>(list);
                    list.remove(delegate);
                }
                list.add(0, delegate);
                delegates.put(key, Collections.unmodifiableList(list));
            }
        }
    }

    /**
     * Get the Collection of delegates of the specified type.
     *
     * @param type
     *            delegate type.
     * @return Collection.
     * @since Ant 1.8.0
     */
    protected <D extends Delegate> List<D> getDelegates(Class<D> type) {
        @SuppressWarnings("unchecked")
        final List<D> result = (List<D>) delegates.get(type);
        return result == null ? Collections.<D> emptyList() : result;
    }

    /**
     * Get all Delegate interfaces (excluding Delegate itself) from the specified Delegate.
     * @param d the Delegate to inspect.
     * @return Set&lt;Class&gt;
     * @since Ant 1.8.0
     */
    protected static Set<Class<? extends Delegate>> getDelegateInterfaces(Delegate d) {
        final HashSet<Class<? extends Delegate>> result = new HashSet<Class<? extends Delegate>>();
        Class<?> c = d.getClass();
        while (c != null) {
            Class<?>[] ifs = c.getInterfaces();
            for (int i = 0; i < ifs.length; i++) {
                if (Delegate.class.isAssignableFrom(ifs[i])) {
                    @SuppressWarnings("unchecked")
                    final Class<? extends Delegate> delegateInterface = (Class<? extends Delegate>) ifs[i];
                    result.add(delegateInterface);
                }
            }
            c = c.getSuperclass();
        }
        result.remove(Delegate.class);
        return result;
    }

    /**
     * If the given object can be interpreted as a true/false value,
     * turn it into a matching Boolean - otherwise return null.
     * @since Ant 1.8.0
     */
    public static Boolean toBoolean(Object value) {
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        if (value instanceof String) {
            String s = (String) value;
            if (Project.toBoolean(s)) {
                return Boolean.TRUE;
            }
            if ("off".equalsIgnoreCase(s)
                || "false".equalsIgnoreCase(s)
                || "no".equalsIgnoreCase(s)) {
                return Boolean.FALSE;
            }
        }
        return null;
    }

    /**
     * Returns true if the object is null or an empty string.
     *
     * @since Ant 1.8.0
     */
    private static boolean nullOrEmpty(Object value) {
        return value == null || "".equals(value);

    }

    /**
     * Returns true if the value can be interpreted as a true value or
     * cannot be interpreted as a false value and a property of the
     * value's name exists.
     * @since Ant 1.8.0
     */
    private boolean evalAsBooleanOrPropertyName(Object value) {
        Boolean b = toBoolean(value);
        if (b != null) {
            return b.booleanValue();
        }
        return getProperty(String.valueOf(value)) != null;
    }

    /**
     * Returns true if the value is null or an empty string, can be
     * interpreted as a true value or cannot be interpreted as a false
     * value and a property of the value's name exists.
     * @since Ant 1.8.0
     */
    public boolean testIfCondition(Object value) {
        return nullOrEmpty(value) || evalAsBooleanOrPropertyName(value);
    }

    /**
     * Returns true if the value is null or an empty string, can be
     * interpreted as a false value or cannot be interpreted as a true
     * value and a property of the value's name doesn't exist.
     * @since Ant 1.8.0
     */
    public boolean testUnlessCondition(Object value) {
        return nullOrEmpty(value) || !evalAsBooleanOrPropertyName(value);
    }
}