• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gephi / graphstore / #550

23 Apr 2026 07:06PM UTC coverage: 91.037% (-0.005%) from 91.042%
#550

push

mbastian
Fix minor issues on parsing and formatting

8 of 9 new or added lines in 3 files covered. (88.89%)

43 existing lines in 1 file now uncovered.

11742 of 12898 relevant lines covered (91.04%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.76
/src/main/java/org/gephi/graph/impl/TimestampsParser.java
1
/*
2
 * Copyright 2012-2013 Gephi Consortium
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
 * use this file except in compliance with the License. You may obtain a copy of
6
 * the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
 * License for the specific language governing permissions and limitations under
14
 * the License.
15
 */
16
package org.gephi.graph.impl;
17

18
import static org.gephi.graph.impl.FormattingAndParsingUtils.COMMA;
19
import static org.gephi.graph.impl.FormattingAndParsingUtils.DYNAMIC_TYPE_LEFT_BOUND;
20
import static org.gephi.graph.impl.FormattingAndParsingUtils.DYNAMIC_TYPE_RIGHT_BOUND;
21
import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE;
22
import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_BRACKET;
23
import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_SQUARE_BRACKET;
24
import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_BRACKET;
25
import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_SQUARE_BRACKET;
26

27
import java.io.IOException;
28
import java.io.StringReader;
29
import java.time.ZoneId;
30
import java.time.format.DateTimeParseException;
31
import java.util.ArrayList;
32
import org.gephi.graph.api.AttributeUtils;
33
import org.gephi.graph.api.types.TimestampBooleanMap;
34
import org.gephi.graph.api.types.TimestampByteMap;
35
import org.gephi.graph.api.types.TimestampCharMap;
36
import org.gephi.graph.api.types.TimestampDoubleMap;
37
import org.gephi.graph.api.types.TimestampFloatMap;
38
import org.gephi.graph.api.types.TimestampIntegerMap;
39
import org.gephi.graph.api.types.TimestampLongMap;
40
import org.gephi.graph.api.types.TimestampMap;
41
import org.gephi.graph.api.types.TimestampSet;
42
import org.gephi.graph.api.types.TimestampShortMap;
43
import org.gephi.graph.api.types.TimestampStringMap;
44

45
/**
46
 * <p>
47
 * Class for parsing timestamp types.
48
 * </p>
49
 *
50
 * <p>
51
 * The standard format for {@link TimestampMap} is &lt;[timestamp, value1];
52
 * [timestamp, value2]&gt;.
53
 * </p>
54
 *
55
 * <p>
56
 * The standard format for {@link TimestampSet} is &lt;[timestamp1, timestamp2,
57
 * timestamp3, ...]&gt;.
58
 * </p>
59
 *
60
 * <p>
61
 * Timestamps values can be both numbers and ISO dates or datetimes. Dates and
62
 * datetimes will be converted to their millisecond-precision timestamp.
63
 * </p>
64
 *
65
 * Examples of valid timestamp maps are:
66
 * <ul>
67
 * <li>&lt;(1, 2, v1); [3, 5, v2]&gt;</li>
68
 * <li>[1.15,2.21, "literal value ' \" ,[]()"]</li>
69
 * <li>[1.15,2.21, 'literal value " \' ,[]()']</li>
70
 * </ul>
71
 *
72
 * Examples of valid timestamp sets are:
73
 * <ul>
74
 * <li>&lt;[1,2]; [3, 4]&gt;</li>
75
 * <li>[1,2]</li>
76
 * <li>[1,2] (5,6)</li>
77
 * </ul>
78
 *
79
 * <p>
80
 * The most correct examples are those that include &lt; &gt; and proper commas
81
 * and semicolons for separation, but the parser will be indulgent when
82
 * possible.
83
 * </p>
84
 *
85
 * @author Eduardo Ramos
86
 */
87
public final class TimestampsParser {
×
88

89
    /**
90
     * Parses a {@link TimestampSet} type with one or more timestamps.
91
     *
92
     * @param input Input string to parse
93
     * @param zoneId Time zone to use or null to use default time zone (UTC)
94
     * @return Resulting {@link TimestampSet}, or null if the input equals
95
     *         '&lt;empty&gt;' or is null
96
     * @throws IllegalArgumentException Thrown if there are no timestamps in the
97
     *         input string or bounds cannot be parsed into doubles or
98
     *         dates/datetimes.
99
     */
100
    public static TimestampSet parseTimestampSet(String input, ZoneId zoneId) throws IllegalArgumentException {
101
        if (input == null) {
1✔
102
            return null;
1✔
103
        }
104

105
        if (input.equalsIgnoreCase(EMPTY_VALUE)) {
1✔
106
            return new TimestampSet();
1✔
107
        }
108

109
        ArrayList<String> values = new ArrayList<>();
1✔
110
        try {
111
            StringReader reader = new StringReader(input + ' ');// Add 1 space
1✔
112
                                                                // so
113
                                                                // reader.skip
114
                                                                // function
115
                                                                // always works
116
                                                                // when
117
                                                                // necessary
118
                                                                // (end of
119
                                                                // string not
120
                                                                // reached).
121
            int r;
122
            char c;
123
            while ((r = reader.read()) != -1) {
1✔
124
                c = (char) r;
1✔
125
                switch (c) {
1✔
126
                    case DYNAMIC_TYPE_LEFT_BOUND:
127
                    case DYNAMIC_TYPE_RIGHT_BOUND:
128
                    case RIGHT_BOUND_SQUARE_BRACKET:
129
                    case RIGHT_BOUND_BRACKET:
130
                    case LEFT_BOUND_BRACKET:
131
                    case LEFT_BOUND_SQUARE_BRACKET:
132
                    case ' ':
133
                    case '\t':
134
                    case '\r':
135
                    case '\n':
136
                    case COMMA:
137
                        // Ignore special characters and leading whitespace or
138
                        // similar until a value or literal starts:
139
                        break;
1✔
140
                    case '"':
141
                    case '\'':
142
                        values.add(FormattingAndParsingUtils.parseLiteral(reader, c));
×
143
                        break;
×
144
                    default:
145
                        reader.skip(-1);// Go backwards 1 position, for reading
1✔
146
                                        // start of value
147
                        values.add(FormattingAndParsingUtils.parseValue(reader));
1✔
148
                }
149
            }
150
        } catch (IOException ex) {
×
151
            throw new RuntimeException("Unexpected expection while parsing timestamps", ex);
×
152
        }
1✔
153

154
        TimestampSet result = new TimestampSet(values.size());
1✔
155

156
        try {
157
            for (String value : values) {
1✔
158
                result.add(FormattingAndParsingUtils.parseDateTimeOrTimestamp(value, zoneId));
1✔
159
            }
1✔
160
        } catch (DateTimeParseException ex) {
1✔
161
            throw new IllegalArgumentException("Invalid timestamp value: " + ex.getMessage(), ex);
1✔
162
        }
1✔
163

164
        return result;
1✔
165
    }
166

167
    /**
168
     * Parses a {@link TimestampSet} type with one or more timestamps. Default time
169
     * zone is used (UTC).
170
     *
171
     * @param input Input string to parse
172
     * @return Resulting {@link TimestampSet}, or null if the input equals
173
     *         '&lt;empty&gt;' or is null
174
     * @throws IllegalArgumentException Thrown if there are no timestamps in the
175
     *         input string or bounds cannot be parsed into doubles or
176
     *         dates/datetimes.
177
     */
178
    public static TimestampSet parseTimestampSet(String input) throws IllegalArgumentException {
179
        return parseTimestampSet(input, null);
1✔
180
    }
181

182
    /**
183
     * Parses a {@link TimestampMap} type with one or more timestamps, and their
184
     * associated values.
185
     *
186
     * @param <T> Underlying type of the {@link TimestampMap} values
187
     * @param typeClass Simple type or {@link TimestampMap} subtype for the result
188
     *        values.
189
     * @param input Input string to parse
190
     * @param zoneId Time zone to use or null to use default time zone (UTC)
191
     * @return Resulting {@link TimestampMap}, or null if the input equals
192
     *         '&lt;empty&gt;' or is null
193
     * @throws IllegalArgumentException Thrown if type class is not supported, any
194
     *         of the timestamps don't have a value or have an invalid value, there
195
     *         are no timestamps in the input string or bounds cannot be parsed into
196
     *         doubles or dates/datetimes.
197
     */
198
    public static <T> TimestampMap<T> parseTimestampMap(Class<T> typeClass, String input, ZoneId zoneId) throws IllegalArgumentException {
199
        if (typeClass == null) {
1✔
200
            throw new IllegalArgumentException("typeClass required");
×
201
        }
202

203
        if (input == null) {
1✔
204
            return null;
×
205
        }
206

207
        TimestampMap result;
208

209
        typeClass = AttributeUtils.getStandardizedType(typeClass);
1✔
210
        if (typeClass.equals(String.class)) {
1✔
211
            result = new TimestampStringMap();
1✔
212
        } else if (typeClass.equals(Byte.class)) {
1✔
213
            result = new TimestampByteMap();
1✔
214
        } else if (typeClass.equals(Short.class)) {
1✔
215
            result = new TimestampShortMap();
1✔
216
        } else if (typeClass.equals(Integer.class)) {
1✔
217
            result = new TimestampIntegerMap();
1✔
218
        } else if (typeClass.equals(Long.class)) {
1✔
219
            result = new TimestampLongMap();
1✔
220
        } else if (typeClass.equals(Float.class)) {
1✔
221
            result = new TimestampFloatMap();
1✔
222
        } else if (typeClass.equals(Double.class)) {
1✔
223
            result = new TimestampDoubleMap();
1✔
224
        } else if (typeClass.equals(Boolean.class)) {
1✔
225
            result = new TimestampBooleanMap();
1✔
226
        } else if (typeClass.equals(Character.class)) {
1✔
227
            result = new TimestampCharMap();
1✔
228
        } else {
NEW
229
            throw new IllegalArgumentException("Unsupported type " + typeClass.getCanonicalName());
×
230
        }
231

232
        if (input.equalsIgnoreCase(EMPTY_VALUE)) {
1✔
233
            return result;
1✔
234
        }
235

236
        StringReader reader = new StringReader(input + ' ');// Add 1 space so
1✔
237
                                                            // reader.skip
238
                                                            // function always
239
                                                            // works when
240
                                                            // necessary (end of
241
                                                            // string not
242
                                                            // reached).
243

244
        try {
245
            int r;
246
            char c;
247
            while ((r = reader.read()) != -1) {
1✔
248
                c = (char) r;
1✔
249
                switch (c) {
1✔
250
                    case LEFT_BOUND_SQUARE_BRACKET:
251
                    case LEFT_BOUND_BRACKET:
252
                        parseTimestampAndValue(typeClass, reader, result, zoneId);
1✔
253
                        break;
1✔
254
                    default:
255
                        // Ignore other chars outside of bounds
256
                }
257
            }
258
        } catch (IOException ex) {
×
259
            throw new RuntimeException("Unexpected expection while parsing timestamps", ex);
×
260
        }
1✔
261

262
        return result;
1✔
263
    }
264

265
    /**
266
     * Parses a {@link TimestampMap} type with one or more timestamps, and their
267
     * associated values. Default time zone is used (UTC).
268
     *
269
     * @param <T> Underlying type of the {@link TimestampMap} values
270
     * @param typeClass Simple type or {@link TimestampMap} subtype for the result
271
     *        values.
272
     * @param input Input string to parse
273
     * @return Resulting {@link TimestampMap}, or null if the input equals
274
     *         '&lt;empty&gt;' or is null
275
     * @throws IllegalArgumentException Thrown if type class is not supported, any
276
     *         of the timestamps don't have a value or have an invalid value, there
277
     *         are no timestamps in the input string or bounds cannot be parsed into
278
     *         doubles or dates/datetimes.
279
     */
280
    public static <T> TimestampMap<T> parseTimestampMap(Class<T> typeClass, String input) throws IllegalArgumentException {
281
        return parseTimestampMap(typeClass, input, null);
1✔
282
    }
283

284
    private static <T> void parseTimestampAndValue(Class<T> typeClass, StringReader reader, TimestampMap<T> result, ZoneId zoneId) throws IOException {
285
        ArrayList<String> values = new ArrayList<>();
1✔
286

287
        int r;
288
        char c;
289
        while ((r = reader.read()) != -1) {
1✔
290
            c = (char) r;
1✔
291
            switch (c) {
1✔
292
                case RIGHT_BOUND_SQUARE_BRACKET:
293
                case RIGHT_BOUND_BRACKET:
294
                    addTimestampAndValue(typeClass, values, result, zoneId);
1✔
295
                    return;
1✔
296
                case ' ':
297
                case '\t':
298
                case '\r':
299
                case '\n':
300
                case COMMA:
301
                    // Ignore leading whitespace or similar until a value or
302
                    // literal starts:
303
                    break;
1✔
304
                case '"':
305
                case '\'':
306
                    values.add(FormattingAndParsingUtils.parseLiteral(reader, c));
1✔
307
                    break;
1✔
308
                default:
309
                    reader.skip(-1);// Go backwards 1 position, for reading
1✔
310
                                    // start of value
311
                    values.add(FormattingAndParsingUtils.parseValue(reader));
1✔
312
            }
313
        }
314

315
        addTimestampAndValue(typeClass, values, result, zoneId);
×
316
    }
×
317

318
    private static <T> void addTimestampAndValue(Class<T> typeClass, ArrayList<String> values, TimestampMap<T> result, ZoneId zoneId) {
319
        if (values.size() != 2) {
1✔
320
            throw new IllegalArgumentException("Each timestamp and value array must have 2 values");
×
321
        }
322

323
        try {
324
            double timestamp = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(0), zoneId);
1✔
325

326
            String valString = values.get(1);
1✔
327
            T value = FormattingAndParsingUtils.convertValue(typeClass, valString);
1✔
328

329
            result.put(timestamp, value);
1✔
330
        } catch (DateTimeParseException ex) {
×
331
            throw new IllegalArgumentException("Invalid timestamp value: " + values.get(0), ex);
×
332
        }
1✔
333
    }
1✔
334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc