1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 2007, International Business Machines Corporation and         *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9package com.ibm.icu.dev.test.util;
10
11import com.ibm.icu.impl.Utility;
12
13/**
14 * @author srl
15 *
16 * analog of FieldsSet in C++
17 */
18public class FieldsSet {
19    public static final int NO_ENUM = -1;
20
21    protected FieldsSet(int whichEnum, int fieldsCount) {
22        if (fieldsCount <= 0 && whichEnum != NO_ENUM) {
23            fieldsCount = DebugUtilities.enumCount(whichEnum);
24        }
25        fEnum = whichEnum;
26        fFieldsCount = fieldsCount;
27        if(fieldsCount<0) {
28            throw new InternalError("Preposterous field count " + fieldsCount);
29        }
30        fValues = new int[fFieldsCount];
31        fIsSet = new boolean[fFieldsCount];
32        clear();
33    }
34
35    protected int fEnum = NO_ENUM;
36
37    protected int fFieldsCount = 0;
38
39    protected int fValues[] = null;
40
41    protected boolean fIsSet[] = null;
42
43    public void clear() {
44        for (int i = 0; i < fFieldsCount; i++) {
45            clear(i);
46        }
47    }
48
49    public void clear(int field) {
50        fValues[field] = -1;
51        fIsSet[field] = false;
52    }
53
54    public void set(int field, int amount) {
55        fValues[field] = amount;
56        fIsSet[field] = true;
57    }
58
59    public boolean isSet(int field) {
60        return fIsSet[field];
61    }
62
63    public int get(int field) {
64        if (fIsSet[field]) {
65            return fValues[field];
66        } else {
67            return -1;
68        }
69    }
70
71    public boolean isSameType(FieldsSet other) {
72        return ((other.fEnum == fEnum) && (other.fFieldsCount == fFieldsCount));
73    }
74
75    public int fieldCount() {
76        return fFieldsCount;
77    }
78
79    /**
80     * @param other  "expected" set to match against
81     * @return a formatted string listing which fields are set in this, with the
82     *         comparison made agaainst those fields in other, or, 'null' if there is no difference.
83     */
84    public String diffFrom(FieldsSet other) {
85        StringBuffer str = new StringBuffer();
86        if(!isSameType(other)) {
87            throw new IllegalArgumentException("U_ILLEGAL_ARGUMENT_ERROR: FieldsSet of a different type!");
88        }
89        for (int i=0; i<fieldCount(); i++) {
90            if (isSet(i)) {
91                int myVal = get(i);
92                int theirVal = other.get(i);
93
94                if(fEnum != NO_ENUM) {
95                    String fieldName = DebugUtilities.enumString(fEnum, i);
96
97                    String aval = Integer.toString(myVal);
98                    String bval = Integer.toString(theirVal);
99
100                    str.append(fieldName +"="+aval+" not "+bval+", ");
101                } else {
102                    str.append(Integer.toString(i) + "=" + myVal+" not " + theirVal+", ");
103                }
104            }
105        }
106        if(str.length()==0) {
107            return null;
108        }
109        return str.toString();
110    }
111
112    /**
113     * @param str string to parse
114     * @param status formatted string for status
115     */
116    public int parseFrom(String str) {
117        return parseFrom(str, null);
118    }
119
120    public int parseFrom(String str, FieldsSet inheritFrom) {
121        int goodFields = 0;
122
123        String[] fields = Utility.split(str, ',');
124        for(int i=0;i<fields.length;i++) {
125            String fieldStr = fields[i];
126            String kv[] = Utility.split(fieldStr, '=');
127            if(kv.length < 1 || kv.length > 2) {
128                throw new InternalError("split around '=' failed: " + fieldStr);
129            }
130            String key = kv[0];
131            String value = "";
132            if(kv.length>1) {
133                value = kv[1];
134            }
135
136            int field = handleParseName(inheritFrom, key, value);
137            if(field != -1) {
138                handleParseValue(inheritFrom, field, value);
139                goodFields++;
140            }
141        }
142
143        return goodFields;
144    }
145
146    /**
147     * Callback interface for subclass. This function is called when parsing a
148     * field name, such as "MONTH" in "MONTH=4". Base implementation is to
149     * lookup the enum value using udbg_* utilities, or else as an integer if
150     * enum is not available.
151     *
152     * If there is a special directive, the implementer can catch it here and
153     * return -1 after special processing completes.
154     *
155     * @param inheritFrom  the set inheriting from - may be null.
156     * @param name  the field name (key side)
157     * @param substr  the string in question (value side)
158     * @param status  error status - set to error for failure.
159     * @return field number, or negative if field should be skipped.
160     */
161    protected int handleParseName(FieldsSet inheritFrom, String name,
162            String substr) {
163        int field = -1;
164        if(fEnum != NO_ENUM) {
165            field = DebugUtilities.enumByString(fEnum, name);
166        }
167        if(field < 0) {
168            field = Integer.parseInt(name);
169        }
170        return field;
171    }
172
173    /**
174     * Callback interface for subclass. Base implementation is to call
175     * parseValueDefault(...)
176     *
177     * @param inheritFrom  the set inheriting from - may be null.
178     * @param field   which field is being parsed
179     * @param substr  the string in question (value side)
180     * @param status  error status - set to error for failure.
181     * @see parseValueDefault
182     */
183    protected void handleParseValue(FieldsSet inheritFrom, int field,
184            String substr) {
185        parseValueDefault(inheritFrom, field, substr);
186    }
187
188    /**
189     * the default implementation for handleParseValue. Base implementation is
190     * to parse a decimal integer value, or inherit from inheritFrom if the
191     * string is 0-length. Implementations of this function should call
192     * set(field,...) on successful parse.
193     *
194     * @see handleParseValue
195     */
196    protected void parseValueDefault(FieldsSet inheritFrom, int field,
197            String substr) {
198        if(substr.length()==0) {
199            if(inheritFrom == null) {
200                throw new InternalError("Trying to inherit from field " + field + " but inheritFrom is null");
201            }
202            if(!inheritFrom.isSet(field)) {
203                throw new InternalError("Trying to inherit from field " + field + " but inheritFrom["+field+"] is  not set");
204            }
205            set(field,inheritFrom.get(field));
206        } else {
207            int value = Integer.parseInt(substr);
208            set(field, value);
209        }
210    }
211
212    /**
213     * convenience implementation for handleParseValue attempt to load a value
214     * from an enum value using udbg_enumByString() if fails, will call
215     * parseValueDefault()
216     *
217     * @see handleParseValue
218     */
219    protected void parseValueEnum(int type, FieldsSet inheritFrom, int field,
220            String substr) {
221        int value = DebugUtilities.enumByString(type, substr);
222        if(value>=0) {
223            set(field,value);
224            return;
225        }
226        parseValueDefault(inheritFrom, field, substr);
227    }
228
229    public String fieldName(int field) {
230        return (fEnum!=NO_ENUM)?DebugUtilities.enumString(fEnum, field):Integer.toString(field);
231    }
232
233    public String toString() {
234        String str = getClass().getName()+" ["+fFieldsCount+","
235        +(fEnum!=NO_ENUM?DebugUtilities.typeString(fEnum):Integer.toString(fEnum))+"]: ";
236        for(int i=0;i<fFieldsCount;i++) {
237            if(isSet(i)) {
238                str = str + fieldName(i)+"="+get(i)+",";
239            }
240        }
241        return str;
242    }
243}
244