4
4
import com .google .gson .stream .JsonReader ;
5
5
import java .io .File ;
6
6
import java .io .FileReader ;
7
+ import java .io .IOException ;
7
8
import java .io .Reader ;
8
9
import java .nio .charset .StandardCharsets ;
9
10
import java .nio .file .Path ;
16
17
import org .unicode .cldr .util .CLDRConfig ;
17
18
import org .unicode .cldr .util .CLDRConfigImpl ;
18
19
20
+ /**
21
+ * Utility class for reading a signatures.json file This file can be downloaded from
22
+ * cla-assistant.io and placed in the cldr/ server directory. location can be overridden with the
23
+ * CLA_FILE configuration preference. Currently, the file is re-read every time a query occurs, to
24
+ * allow the file to be updated.
25
+ *
26
+ * <p>The simplest way to use this is as follows (given the id “github”): <code>
27
+ * ClaGithubList.getInstance().getSignStatus("github")</code>
28
+ */
19
29
public class ClaGithubList {
30
+ /** Only 'signed' indicates a valid CLA. */
20
31
public enum SignStatus {
32
+ /** The signature was not found. Used by higher level APIs. */
21
33
missing ,
34
+ /** A good CLA was found. */
22
35
signed ,
36
+ /** The CLA was found, but had been revoked. */
23
37
revoked ,
24
38
};
25
39
40
+ /** One entry in the CLA file. */
26
41
public static final class SignEntry {
42
+ /** The GitHub user ID */
27
43
public String user_name ;
44
+
45
+ /** Signing date, if any. */
28
46
public String signed_at ;
47
+
48
+ /** Revocation date, if any. */
29
49
public String revoked_at ;
50
+
51
+ /** User's real name. */
30
52
public String name ;
53
+
54
+ /** Type of signature */
31
55
public String category ;
56
+
57
+ /** User's employer */
32
58
public String employer ;
59
+
60
+ /** User's email */
33
61
public String email ;
34
62
35
- // etc - more fields we don't need.
63
+ // Note: There are additional fields in the JSON file which
64
+ // are not currently read, and these are ignored.
36
65
37
66
public SignStatus getSignStatus () {
38
67
if (revoked_at != null && !revoked_at .isBlank ()) {
@@ -68,6 +97,7 @@ public Date getSignedAt() {
68
97
69
98
static final Logger logger = SurveyLog .forClass (ClaGithubList .class );
70
99
100
+ /** get the singleton instance */
71
101
public static final ClaGithubList getInstance () {
72
102
return Helper .INSTANCE ;
73
103
}
@@ -77,76 +107,46 @@ private static final class Helper {
77
107
static final ClaGithubList INSTANCE = new ClaGithubList ();
78
108
}
79
109
80
- static final Gson gson = new Gson ();
110
+ /** default file path, from configuration */
111
+ private final String CLA_FILE ;
81
112
82
- private final CLDRConfig instance = CLDRConfig .getInstance ();
83
-
84
- private final String CLA_FILE = instance .getProperty ("CLA_FILE" , "./signatures.json" );
113
+ /** full path to .json file */
85
114
private final Path claFilePath ;
86
115
87
116
ClaGithubList () {
88
- final File homeFile = ((CLDRConfigImpl ) instance ).getHomeFile ();
89
- claFilePath = new File (homeFile , CLA_FILE ).toPath ();
90
- logger .info ("CLA_FILE=" + claFilePath .toString ());
117
+ final CLDRConfig instance = CLDRConfig .getInstance ();
118
+ CLA_FILE = instance .getProperty ("CLA_FILE" , "./signatures.json" );
119
+ if (instance instanceof CLDRConfigImpl ) {
120
+ final File homeFile = ((CLDRConfigImpl ) instance ).getHomeFile ();
121
+ claFilePath = new File (homeFile , CLA_FILE ).toPath ();
122
+ logger .fine ("CLA_FILE=" + claFilePath .toString ());
123
+ } else {
124
+ claFilePath = null ;
125
+ logger .fine ("claFile path not set, could not get CLDRConfigImpl" );
126
+ }
91
127
}
92
128
129
+ /**
130
+ * @returns the SignEntry for an id, or null if not found
131
+ */
93
132
public SignEntry getSignEntry (final String id ) {
94
- // Impl: reread each time
95
- Map <String , SignEntry > allSigners = new HashMap <>();
96
- try (final Reader r = new FileReader (claFilePath .toFile (), StandardCharsets .UTF_8 );
97
- final JsonReader jr = gson .newJsonReader (r ); ) {
98
- jr .beginArray ();
99
- while (jr .hasNext ()) {
100
- SignEntry e = new SignEntry ();
101
- jr .beginObject ();
102
- while (jr .hasNext ()) {
103
- switch (jr .nextName ()) {
104
- case "user_name" :
105
- e .user_name = jr .nextString ();
106
- break ;
107
- case "signed_at" :
108
- e .signed_at = jr .nextString ();
109
- break ;
110
- case "revoked_at" :
111
- e .revoked_at = jr .nextString ();
112
- break ;
113
- case "name" :
114
- e .name = jr .nextString ();
115
- break ;
116
- case "category" :
117
- e .category = jr .nextString ();
118
- break ;
119
- case "employer" :
120
- e .employer = jr .nextString ();
121
- break ;
122
- case "email" :
123
- e .email = jr .nextString ();
124
- break ;
125
- default :
126
- jr .skipValue ();
127
- break ; // ignore
128
- }
129
- }
130
- jr .endObject ();
131
-
132
- // the list is in REVERSE order (latest first). So we only track the first
133
- // appearance.
134
- if (!allSigners .containsKey (e .user_name )) {
135
- allSigners .put (e .user_name , e );
136
- // TODO: we could break here.
137
-
138
- } // else: ignore, only count first entry
139
- }
140
- jr .endArray ();
141
- } catch (Throwable t ) {
142
- logger .log (Level .SEVERE , "Trying to read signatures for " + id , t );
133
+ Map <String , SignEntry > allSigners ;
134
+ try {
135
+ allSigners = readSigners ();
136
+ } catch (IOException t ) {
137
+ logger .log (Level .SEVERE , "Trying to read signatures" , t );
138
+ return null ;
143
139
}
144
- logger .info ("Read " + allSigners .size () + " signatures" );
145
140
// Get response
146
141
final SignEntry requestedEntry = allSigners .get (id );
147
142
return requestedEntry ;
148
143
}
149
144
145
+ /**
146
+ * This is the simplest and most recommended API for most use cases.
147
+ *
148
+ * @return the signing status of a GitHub ID, or missing.
149
+ */
150
150
public SignStatus getSignStatus (final String id ) {
151
151
final SignEntry e = getSignEntry (id );
152
152
if (e == null ) {
@@ -155,4 +155,82 @@ public SignStatus getSignStatus(final String id) {
155
155
return e .getSignStatus ();
156
156
}
157
157
}
158
+
159
+ /** read the default .json file */
160
+ Map <String , SignEntry > readSigners () throws IOException {
161
+ if (claFilePath == null ) {
162
+ throw new NullPointerException (
163
+ "CLA_FILE=" + CLA_FILE + " but could not find file path." );
164
+ }
165
+ return readSigners (claFilePath );
166
+ }
167
+
168
+ /** read a specific path */
169
+ Map <String , SignEntry > readSigners (final Path path ) throws IOException {
170
+ try (final Reader r = new FileReader (path .toFile (), StandardCharsets .UTF_8 ); ) {
171
+ return readSigners (r );
172
+ }
173
+ }
174
+
175
+ /** read from a Reader */
176
+ Map <String , SignEntry > readSigners (final Reader r ) throws IOException {
177
+ final Gson gson = new Gson ();
178
+ Map <String , SignEntry > allSigners = new HashMap <>();
179
+ try (final JsonReader jr = gson .newJsonReader (r ); ) {
180
+ jr .beginArray ();
181
+ while (jr .hasNext ()) {
182
+ SignEntry e ;
183
+ try {
184
+ e = parseSignEntry (jr );
185
+ // the list is in REVERSE order (latest first). So we only track the first
186
+ // appearance.
187
+ if (!allSigners .containsKey (e .user_name )) {
188
+ allSigners .put (e .user_name , e );
189
+ }
190
+ // else: ignore, we only count the first (most recent entry).
191
+
192
+ } catch (Throwable t ) {
193
+ logger .log (Level .SEVERE , "reading SignEntry - will skip" , t );
194
+ }
195
+ }
196
+ jr .endArray ();
197
+ }
198
+ logger .info ("Read " + allSigners .size () + " signatures" );
199
+ return allSigners ;
200
+ }
201
+
202
+ private SignEntry parseSignEntry (final JsonReader jr ) throws IOException {
203
+ final SignEntry e = new SignEntry ();
204
+ jr .beginObject ();
205
+ while (jr .hasNext ()) {
206
+ switch (jr .nextName ()) {
207
+ case "user_name" :
208
+ e .user_name = jr .nextString ();
209
+ break ;
210
+ case "signed_at" :
211
+ e .signed_at = jr .nextString ();
212
+ break ;
213
+ case "revoked_at" :
214
+ e .revoked_at = jr .nextString ();
215
+ break ;
216
+ case "name" :
217
+ e .name = jr .nextString ();
218
+ break ;
219
+ case "category" :
220
+ e .category = jr .nextString ();
221
+ break ;
222
+ case "employer" :
223
+ e .employer = jr .nextString ();
224
+ break ;
225
+ case "email" :
226
+ e .email = jr .nextString ();
227
+ break ;
228
+ default :
229
+ jr .skipValue ();
230
+ break ; // ignore
231
+ }
232
+ }
233
+ jr .endObject ();
234
+ return e ;
235
+ }
158
236
}
0 commit comments