blob: 9147a1be615a7d706806045645c533c7e2d236d7 [file] [log] [blame]
Simon Hunte556e942017-06-19 15:35:44 -07001/*
2 * Copyright 2017-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
Simon Hunt00b369a2017-06-20 19:46:40 -070018package org.onosproject.ui.impl.lion;
Simon Hunte556e942017-06-19 15:35:44 -070019
20import com.google.common.collect.ImmutableList;
21import com.google.common.collect.ImmutableSortedSet;
Yuta HIGUCHIa5323ce2017-06-21 09:54:20 -070022import com.google.common.io.Resources;
Simon Hunte556e942017-06-19 15:35:44 -070023
24import java.io.IOException;
Simon Hunte556e942017-06-19 15:35:44 -070025import java.util.ArrayList;
26import java.util.Collections;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32import java.util.TreeSet;
33import java.util.regex.Matcher;
34import java.util.regex.Pattern;
35
Yuta HIGUCHIa5323ce2017-06-21 09:54:20 -070036import static com.google.common.io.Resources.getResource;
Simon Hunte556e942017-06-19 15:35:44 -070037import static java.nio.charset.StandardCharsets.UTF_8;
38
39/**
40 * A Java representation of a lion configuration file. You can create one with
41 * something like the following:
42 * <pre>
43 * String filepath = "/path/to/some/file.lioncfg";
44 * LionConfig cfg = new LionConfig().load(filepath);
45 * </pre>
46 */
47public class LionConfig {
48 private static final Pattern RE_COMMENT = Pattern.compile("^\\s*#.*");
49 private static final Pattern RE_BLANK = Pattern.compile("^\\s*$");
50
51 static final Pattern RE_IMPORT =
52 Pattern.compile("^(\\S+)\\s+import\\s+(.*)$");
53
54 private static final String BUNDLE = "bundle";
55 private static final String ALIAS = "alias";
56 private static final String FROM = "from";
57 private static final String STAR = "*";
58 private static final char SPC = ' ';
59 private static final char DOT = '.';
60
61 private List<String> lines;
62 private List<String> badLines;
63
64 private CmdBundle bundle;
65 private final Set<CmdAlias> aliases = new TreeSet<>();
66 private final Set<CmdFrom> froms = new TreeSet<>();
67
68 private Map<String, String> aliasMap;
69 private Map<String, Set<String>> fromMap;
70
71 /**
72 * Loads in the specified file and attempts to parse it as a
73 * {@code .lioncfg} format file.
74 *
75 * @param source path to .lioncfg file
76 * @return the instance
77 * @throws IllegalArgumentException if there is a problem reading the file
78 */
79 public LionConfig load(String source) {
Simon Hunte556e942017-06-19 15:35:44 -070080 try {
Yuta HIGUCHIa5323ce2017-06-21 09:54:20 -070081 lines = Resources.readLines(getResource(getClass(), source), UTF_8);
82 } catch (IOException e) {
Simon Hunte556e942017-06-19 15:35:44 -070083 throw new IllegalArgumentException("Failed to read: " + source, e);
84 }
85
86 stripCommentsAndWhitespace();
87 parse();
88 processAliases();
89 processFroms();
90
91 return this;
92 }
93
94
95 private boolean isCommentOrBlank(String s) {
96 return RE_COMMENT.matcher(s).matches() || RE_BLANK.matcher(s).matches();
97 }
98
99
100 private void stripCommentsAndWhitespace() {
101 if (lines != null) {
102 lines.removeIf(this::isCommentOrBlank);
103 }
104 }
105
106 private void parse() {
107 badLines = new ArrayList<>();
108
109 lines.forEach(l -> {
110 int i = l.indexOf(SPC);
111 if (i < 1) {
112 badLines.add(l);
113 return;
114 }
115 String keyword = l.substring(0, i);
116 String params = l.substring(i + 1);
117
118 switch (keyword) {
119 case BUNDLE:
120 CmdBundle cb = new CmdBundle(l, params);
121
122 if (bundle != null) {
123 // we can only declare the bundle once
124 badLines.add(l);
125 } else {
126 bundle = cb;
127 }
128 break;
129
130 case ALIAS:
131 CmdAlias ca = new CmdAlias(l, params);
132 if (ca.malformed) {
133 badLines.add(l);
134 } else {
135 aliases.add(ca);
136 }
137 break;
138
139 case FROM:
140 CmdFrom cf = new CmdFrom(l, params);
141 if (cf.malformed) {
142 badLines.add(l);
143 } else {
144 froms.add(cf);
145 }
146 break;
147
148 default:
149 badLines.add(l);
150 break;
151 }
152 });
153 }
154
155 private void processAliases() {
156 aliasMap = new HashMap<>(aliasCount());
157 aliases.forEach(a -> aliasMap.put(a.alias, a.subst));
158 }
159
160 private void processFroms() {
161 fromMap = new HashMap<>(fromCount());
162 froms.forEach(f -> {
163 f.expandAliasIfAny(aliasMap);
164 if (singleStarCheck(f)) {
165 fromMap.put(f.expandedRes, f.keys);
166 } else {
167 badLines.add(f.orig);
168 }
169 });
170 }
171
172 private boolean singleStarCheck(CmdFrom from) {
173 from.starred = false;
174 Set<String> keys = from.keys();
Simon Hunt7379a3d2017-06-20 16:50:39 -0700175 for (String k : keys) {
Simon Hunte556e942017-06-19 15:35:44 -0700176 if (STAR.equals(k)) {
177 from.starred = true;
178 }
179 }
180 return !from.starred || keys.size() == 1;
181 }
182
183 @Override
184 public String toString() {
185 int nlines = lines == null ? 0 : lines.size();
186 return String.format("LionConfig{#lines=%d}", nlines);
187 }
188
189 /**
190 * Returns the configured bundle ID for this config.
191 *
192 * @return the bundle ID
193 */
194 String id() {
195 return bundle == null ? null : bundle.id;
196 }
197
198 /**
199 * Returns the number of aliases configured in this config.
200 *
201 * @return the alias count
202 */
203 int aliasCount() {
204 return aliases.size();
205 }
206
207 /**
208 * Returns the number of from...import lines configured in this config.
209 *
210 * @return the number of from...import lines
211 */
212 int fromCount() {
213 return froms.size();
214 }
215
216 /**
217 * Returns the substitution string for the given alias.
218 *
219 * @param a the alias
220 * @return the substitution
221 */
222 String alias(String a) {
223 return aliasMap.get(a);
224 }
225
226 /**
227 * Returns the number of keys imported from the specified resource.
228 *
229 * @param res the resource
230 * @return number of keys imported from that resource
231 */
232 int fromKeyCount(String res) {
233 Set<String> keys = fromMap.get(res);
234 return keys == null ? 0 : keys.size();
235 }
236
237 /**
238 * Returns true if the specified resource exists and contains the
239 * given key.
240 *
241 * @param res the resource
242 * @param key the key
243 * @return true, if resource exists and contains the key; false otherwise
244 */
245 boolean fromContains(String res, String key) {
246 Set<String> keys = fromMap.get(res);
247 return keys != null && keys.contains(key);
248 }
249
250 /**
251 * Returns the set of (expanded) "from" entries in this configuration.
252 *
253 * @return the entries
254 */
255 public Set<CmdFrom> entries() {
256 return froms;
257 }
258
259 /**
260 * Returns the number of parse errors detected.
261 *
262 * @return number of bad lines
263 */
264 public int errorCount() {
265 return badLines.size();
266 }
267
268 /**
269 * Returns the lines that failed the parser.
270 *
271 * @return the erroneous lines in the config
272 */
273 public List<String> errorLines() {
274 return ImmutableList.copyOf(badLines);
275 }
276
277 // ==== Mini class hierarchy of command types
278
279 private abstract static class Cmd {
280 final String orig;
281 boolean malformed = false;
282
283 Cmd(String orig) {
284 this.orig = orig;
285 }
286 }
287
288 private static final class CmdBundle extends Cmd {
289 private final String id;
290
291 private CmdBundle(String orig, String params) {
292 super(orig);
293 id = params;
294 }
295
296 @Override
297 public String toString() {
298 return "CmdBundle{id=\"" + id + "\"}";
299 }
300 }
301
302 private static final class CmdAlias extends Cmd
303 implements Comparable<CmdAlias> {
304 private final String alias;
305 private final String subst;
306
307 private CmdAlias(String orig, String params) {
308 super(orig);
309 int i = params.indexOf(SPC);
310 if (i < 1) {
311 malformed = true;
312 alias = null;
313 subst = null;
314 } else {
315 alias = params.substring(0, i);
316 subst = params.substring(i + 1);
317 }
318 }
319
320 @Override
321 public String toString() {
322 return "CmdAlias{alias=\"" + alias + "\", subst=\"" + subst + "\"}";
323 }
324
325 @Override
326 public int compareTo(CmdAlias o) {
327 return alias.compareTo(o.alias);
328 }
329 }
330
331 /**
332 * Represents a "from {res} import {stuff}" command in the configuration.
333 */
334 public static final class CmdFrom extends Cmd
335 implements Comparable<CmdFrom> {
336 private final String rawRes;
337 private final Set<String> keys;
338 private String expandedRes;
339 private boolean starred = false;
340
341 private CmdFrom(String orig, String params) {
342 super(orig);
343 Matcher m = RE_IMPORT.matcher(params);
344 if (!m.matches()) {
345 malformed = true;
346 rawRes = null;
347 keys = null;
348 } else {
349 rawRes = m.group(1);
350 keys = genKeys(m.group(2));
351 }
352 }
353
354 private Set<String> genKeys(String keys) {
355 String[] k = keys.split("\\s*,\\s*");
356 Set<String> allKeys = new HashSet<>();
357 Collections.addAll(allKeys, k);
358 return ImmutableSortedSet.copyOf(allKeys);
359 }
360
361 private void expandAliasIfAny(Map<String, String> aliases) {
362 String expanded = rawRes;
363 int i = rawRes.indexOf(DOT);
364 if (i > 0) {
365 String alias = rawRes.substring(0, i);
366 String sub = aliases.get(alias);
367 if (sub != null) {
368 expanded = sub + rawRes.substring(i);
369 }
370 }
371 expandedRes = expanded;
372 }
373
374 @Override
375 public String toString() {
376 return "CmdFrom{res=\"" + rawRes + "\", keys=" + keys + "}";
377 }
378
379 @Override
380 public int compareTo(CmdFrom o) {
381 return rawRes.compareTo(o.rawRes);
382 }
383
384 /**
385 * Returns the resource bundle name from which to import things.
386 *
387 * @return the resource bundle name
388 */
389 public String res() {
390 return expandedRes;
391 }
392
393 /**
394 * Returns the set of keys which should be imported.
395 *
396 * @return the keys to import
397 */
398 public Set<String> keys() {
399 return keys;
400 }
401
402 /**
403 * Returns true if this "from" command is importing ALL keys from
404 * the specified resource; false otherwise.
405 *
406 * @return true, if importing ALL keys; false otherwise
407 */
408 public boolean starred() {
409 return starred;
410 }
411 }
412}