In osrfStringArrayRemove(): fixed a bug whereby we would decrement
[OpenSRF.git] / src / libopensrf / string_array.c
1 /**
2         @file string_array.c
3         @brief Implement osrfStringArray, a vector of character strings.
4
5         An osrfStringArray is implemented as a thin wrapper around an osrfList.  The latter is
6         incorporated bodily in the osrfStringArray structure, not as a pointer, so as to avoid
7         a layer of malloc() and free().
8
9         Operations on the osrfList are restricted so as not to leave NULL pointers in the middle.
10 */
11
12 #include <opensrf/string_array.h>
13
14 /**
15         @brief Create and initialize an osrfStringArray.
16         @param size How many strings to allow space for initially.
17         @return Pointer to a newly created osrfStringArray.
18
19         If the size parameter is zero, osrfNewStringArray uses a default value.  If the initial
20         allocation isn't big enough, more space will be added as needed.
21
22         The calling code is responsible for freeing the osrfStringArray by calling
23         osrfStringArrayFree().
24 */
25 osrfStringArray* osrfNewStringArray(int size) {
26         if(size > STRING_ARRAY_MAX_SIZE)
27                 osrfLogError( OSRF_LOG_MARK, "osrfNewStringArray size is too large");
28
29         osrfStringArray* arr;
30         OSRF_MALLOC(arr, sizeof(osrfStringArray));
31         arr->size = 0;
32
33         // Initialize list
34         arr->list.size = 0;
35         arr->list.freeItem = NULL;
36         if( size <= 0 )
37                 arr->list.arrsize = 16;
38         else
39                 arr->list.arrsize = size;
40         OSRF_MALLOC( arr->list.arrlist, arr->list.arrsize * sizeof(void*) );
41
42         // Nullify all pointers in the array
43
44         int i;
45         for( i = 0; i < arr->list.arrsize; ++i )
46                 arr->list.arrlist[ i ] = NULL;
47
48         osrfListSetDefaultFree(&arr->list);
49         return arr;
50 }
51
52 /**
53         @brief Add a string to the end of an osrfStringArray.
54         @param arr Pointer to the osrfStringArray to which the string will be added.
55         @param string Pointer to the character string to be added.
56
57         The string stored is a copy; the original is not affected.
58
59         If either parameter is NULL, nothing happens.
60 */
61 void osrfStringArrayAdd( osrfStringArray* arr, const char* string ) {
62         if(arr == NULL || string == NULL ) return;
63         if( arr->list.size > STRING_ARRAY_MAX_SIZE )
64                 osrfLogError( OSRF_LOG_MARK, "osrfStringArrayAdd size is too large" );
65     osrfListPush(&arr->list, strdup(string));
66     arr->size = arr->list.size;
67 }
68
69 /**
70         @brief Return a pointer to the string stored at a specified position in an osrfStringArray.
71         @param arr Pointer to the osrfStringArray.
72         @param index A zero-based index into the array
73         @return A pointer to the string stored at the specified position. if it exists;
74                 or else NULL.
75
76         The calling code should treat the returned pointer as if it were const.  Some day,
77         maybe it will be.
78 */
79 char* osrfStringArrayGetString( osrfStringArray* arr, int index ) {
80     if(!arr) return NULL;
81         return OSRF_LIST_GET_INDEX(&arr->list, index);
82 }
83
84 /**
85         @brief Free an osrfStringArray, and all the strings inside it.
86         @param arr Pointer to the osrfStringArray to be freed.
87 */
88 void osrfStringArrayFree(osrfStringArray* arr) {
89
90         // This function is a sleazy hack designed to avoid the
91         // need to duplicate the code in osrfListFree().  It
92         // works because:
93         //
94         // 1. The osrfList is the first member of an
95         //    osrfStringArray.  C guarantees that a pointer
96         //    to the one is also a pointer to the other.
97         //
98         // 2. The rest of the osrfStringArray owns no memory
99         //    and requires no special attention when freeing.
100         //
101         // If these facts ever cease to be true, we'll have to
102         // revisit this function.
103
104         osrfListFree( (osrfList*) arr );
105 }
106
107 /**
108         @brief Determine whether an osrfStringArray contains a specified string.
109         @param arr Pointer to the osrfStringArray.
110         @param string Pointer to the string to be sought.
111         @return A boolean: 1 if the string is present in the osrfStringArray, or 0 if it isn't.
112
113         The search is case-sensitive.
114 */
115 int osrfStringArrayContains(
116         const osrfStringArray* arr, const char* string ) {
117         if(!(arr && string)) return 0;
118         int i;
119         for( i = 0; i < arr->size; i++ ) {
120         char* str = OSRF_LIST_GET_INDEX(&arr->list, i);
121                 if(str && !strcmp(str, string))
122             return 1;
123         }
124
125         return 0;
126 }
127
128 /**
129         @brief Remove the first occurrence, if any, of a specified string from an osrfStringArray.
130         @param arr Pointer to the osrfStringArray.
131         @param tstr Pointer to a string to be removed.
132 */
133 void osrfStringArrayRemove( osrfStringArray* arr, const char* tstr ) {
134         if(!(arr && tstr)) return;
135         int i;
136     char* str;
137         int removed = 0;  // boolean
138
139         for( i = 0; i < arr->size; i++ ) {
140                 /* find and remove the string */
141                 str = OSRF_LIST_GET_INDEX(&arr->list, i);
142                 if(str && !strcmp(str, tstr)) {
143                         osrfListRemove(&arr->list, i);
144                         removed = 1;
145                         break;
146                 }
147         }
148
149         if( ! removed )
150                 return;         // Nothing was removed
151
152     /* disable automatic item freeing on delete, and shift
153      * items down in the array to fill in the gap
154      */
155     arr->list.freeItem = NULL;
156         for( ; i < arr->size - 1; i++ )
157         osrfListSet(&arr->list, OSRF_LIST_GET_INDEX(&arr->list, i+1), i);
158
159     /* remove the last item since it was shifted up */
160     osrfListRemove(&arr->list, i);
161
162     /* re-enable automatic item freeing in delete */
163     osrfListSetDefaultFree(&arr->list);
164         arr->size--;
165 }
166
167 /**
168         @brief Tokenize a string into an array of substrings separated by a specified delimiter.
169         @param src A pointer to the string to be parsed.
170         @param delim The delimiter character.
171         @return Pointer to a newly constructed osrfStringArray containing the tokens.
172
173         A series of consecutive delimiter characters is treated as a single separator.
174
175         The input string is left unchanged.
176
177         The calling code is responsible for freeing the osrfStringArray by calling
178         osrfStringArrayFree().
179 */
180 osrfStringArray* osrfStringArrayTokenize( const char* src, char delim )
181 {
182         // Take the length so that we know how big a buffer we need,
183         // in the worst case.  Estimate the number of tokens, assuming
184         // 5 characters per token, and add a few for a pad.
185
186         if( NULL == src || '\0' == *src )               // Got nothing?
187                 return osrfNewStringArray( 1 );         // Return empty array
188
189         size_t src_len = strlen( src );
190         size_t est_count = src_len / 6 + 5;
191         int in_token = 0;     // boolean
192         char buf[ src_len + 1 ];
193         char* out = buf;
194         osrfStringArray* arr = osrfNewStringArray( est_count );
195
196         for( ;; ++src ) {
197                 if( in_token ) {        // We're building a token
198                         if( *src == delim ) {
199                                 *out = '\0';
200                                 osrfStringArrayAdd( arr, buf );
201                                 in_token = 0;
202                         }
203                         else if( '\0' == *src ) {
204                                 *out = '\0';
205                                 osrfStringArrayAdd( arr, buf );
206                                 break;
207                         }
208                         else {
209                                 *out++ = *src;
210                         }
211                 }
212                 else {                          // We're between tokens
213                         if( *src == delim ) {
214                                 ;                       // skip it
215                         }
216                         else if( '\0' == *src ) {
217                                 break;
218                         }
219                         else {
220                                 out = buf;              // Start the next one
221                                 *out++ = *src;
222                                 in_token = 1;
223                         }
224                 }
225         }
226
227         return arr;
228 }