blob: e6d73d3514e6c578a5ba00d1f35d505a5f4c9075 [file] [log] [blame]
Simon Hunte556e942017-06-19 15:35:44 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Simon Hunte556e942017-06-19 15:35:44 -07003 *
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
Ray Milkey1567b532018-02-05 10:25:32 -080020import com.google.common.base.Objects;
Simon Hunte556e942017-06-19 15:35:44 -070021import com.google.common.collect.ImmutableList;
22import com.google.common.collect.ImmutableSortedSet;
Simon Hunt7d1c0812017-06-21 13:48:30 -070023import com.google.common.io.CharStreams;
Simon Hunte556e942017-06-19 15:35:44 -070024
25import java.io.IOException;
Simon Hunt7d1c0812017-06-21 13:48:30 -070026import java.io.InputStreamReader;
27import java.io.Reader;
Simon Hunte556e942017-06-19 15:35:44 -070028import java.util.ArrayList;
29import java.util.Collections;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35import java.util.TreeSet;
36import java.util.regex.Matcher;
37import java.util.regex.Pattern;
38
39import static java.nio.charset.StandardCharsets.UTF_8;
40
41/**
42 * A Java representation of a lion configuration file. You can create one with
43 * something like the following:
44 * <pre>
45 * String filepath = "/path/to/some/file.lioncfg";
46 * LionConfig cfg = new LionConfig().load(filepath);
47 * </pre>
48 */
49public class LionConfig {
50 private static final Pattern RE_COMMENT = Pattern.compile("^\\s*#.*");
51 private static final Pattern RE_BLANK = Pattern.compile("^\\s*$");
52
53 static final Pattern RE_IMPORT =
54 Pattern.compile("^(\\S+)\\s+import\\s+(.*)$");
55
56 private static final String BUNDLE = "bundle";
57 private static final String ALIAS = "alias";
58 private static final String FROM = "from";
59 private static final String STAR = "*";
60 private static final char SPC = ' ';
61 private static final char DOT = '.';
62
63 private List<String> lines;
64 private List<String> badLines;
65
66 private CmdBundle bundle;
67 private final Set<CmdAlias> aliases = new TreeSet<>();
68 private final Set<CmdFrom> froms = new TreeSet<>();
69
70 private Map<String, String> aliasMap;
71 private Map<String, Set<String>> fromMap;
72
73 /**
74 * Loads in the specified file and attempts to parse it as a
75 * {@code .lioncfg} format file.
76 *
77 * @param source path to .lioncfg file
78 * @return the instance
79 * @throws IllegalArgumentException if there is a problem reading the file
80 */
81 public LionConfig load(String source) {
Simon Hunt7d1c0812017-06-21 13:48:30 -070082 try (Reader r = new InputStreamReader(getClass().getResourceAsStream(source),
83 UTF_8)) {
84 lines = CharStreams.readLines(r);
Yuta HIGUCHIa5323ce2017-06-21 09:54:20 -070085 } catch (IOException e) {
Simon Hunte556e942017-06-19 15:35:44 -070086 throw new IllegalArgumentException("Failed to read: " + source, e);
87 }
88
89 stripCommentsAndWhitespace();
90 parse();
91 processAliases();
92 processFroms();
93
94 return this;
95 }
96
97
98 private boolean isCommentOrBlank(String s) {
99 return RE_COMMENT.matcher(s).matches() || RE_BLANK.matcher(s).matches();
100 }
101
102
103 private void stripCommentsAndWhitespace() {
104 if (lines != null) {
105 lines.removeIf(this::isCommentOrBlank);
106 }
107 }
108
109 private void parse() {
110 badLines = new ArrayList<>();
111
112 lines.forEach(l -> {
113 int i = l.indexOf(SPC);
114 if (i < 1) {
115 badLines.add(l);
116 return;
117 }
118 String keyword = l.substring(0, i);
119 String params = l.substring(i + 1);
120
121 switch (keyword) {
122 case BUNDLE:
123 CmdBundle cb = new CmdBundle(l, params);
124
125 if (bundle != null) {
126 // we can only declare the bundle once
127 badLines.add(l);
128 } else {
129 bundle = cb;
130 }
131 break;
132
133 case ALIAS:
134 CmdAlias ca = new CmdAlias(l, params);
135 if (ca.malformed) {
136 badLines.add(l);
137 } else {
138 aliases.add(ca);
139 }
140 break;
141
142 case FROM:
143 CmdFrom cf = new CmdFrom(l, params);
144 if (cf.malformed) {
145 badLines.add(l);
146 } else {
147 froms.add(cf);
148 }
149 break;
150
151 default:
152 badLines.add(l);
153 break;
154 }
155 });
156 }
157
158 private void processAliases() {
159 aliasMap = new HashMap<>(aliasCount());
160 aliases.forEach(a -> aliasMap.put(a.alias, a.subst));
161 }
162
163 private void processFroms() {
164 fromMap = new HashMap<>(fromCount());
165 froms.forEach(f -> {
166 f.expandAliasIfAny(aliasMap);
167 if (singleStarCheck(f)) {
168 fromMap.put(f.expandedRes, f.keys);
169 } else {
170 badLines.add(f.orig);
171 }
172 });
173 }
174
175 private boolean singleStarCheck(CmdFrom from) {
176 from.starred = false;
177 Set<String> keys = from.keys();
Simon Hunt7379a3d2017-06-20 16:50:39 -0700178 for (String k : keys) {
Simon Hunte556e942017-06-19 15:35:44 -0700179 if (STAR.equals(k)) {
180 from.starred = true;
181 }
182 }
183 return !from.starred || keys.size() == 1;
184 }
185
186 @Override
187 public String toString() {
188 int nlines = lines == null ? 0 : lines.size();
189 return String.format("LionConfig{#lines=%d}", nlines);
190 }
191
192 /**
193 * Returns the configured bundle ID for this config.
194 *
195 * @return the bundle ID
196 */
197 String id() {
198 return bundle == null ? null : bundle.id;
199 }
200
201 /**
202 * Returns the number of aliases configured in this config.
203 *
204 * @return the alias count
205 */
206 int aliasCount() {
207 return aliases.size();
208 }
209
210 /**
211 * Returns the number of from...import lines configured in this config.
212 *
213 * @return the number of from...import lines
214 */
215 int fromCount() {
216 return froms.size();
217 }
218
219 /**
220 * Returns the substitution string for the given alias.
221 *
222 * @param a the alias
223 * @return the substitution
224 */
225 String alias(String a) {
226 return aliasMap.get(a);
227 }
228
229 /**
230 * Returns the number of keys imported from the specified resource.
231 *
232 * @param res the resource
233 * @return number of keys imported from that resource
234 */
235 int fromKeyCount(String res) {
236 Set<String> keys = fromMap.get(res);
237 return keys == null ? 0 : keys.size();
238 }
239
240 /**
241 * Returns true if the specified resource exists and contains the
242 * given key.
243 *
244 * @param res the resource
245 * @param key the key
246 * @return true, if resource exists and contains the key; false otherwise
247 */
248 boolean fromContains(String res, String key) {
249 Set<String> keys = fromMap.get(res);
250 return keys != null && keys.contains(key);
251 }
252
253 /**
254 * Returns the set of (expanded) "from" entries in this configuration.
255 *
256 * @return the entries
257 */
258 public Set<CmdFrom> entries() {
259 return froms;
260 }
261
262 /**
263 * Returns the number of parse errors detected.
264 *
265 * @return number of bad lines
266 */
267 public int errorCount() {
268 return badLines.size();
269 }
270
271 /**
272 * Returns the lines that failed the parser.
273 *
274 * @return the erroneous lines in the config
275 */
276 public List<String> errorLines() {
277 return ImmutableList.copyOf(badLines);
278 }
279
280 // ==== Mini class hierarchy of command types
281
282 private abstract static class Cmd {
283 final String orig;
284 boolean malformed = false;
285
286 Cmd(String orig) {
287 this.orig = orig;
288 }
Simon Huntd8754652017-06-21 11:45:22 -0700289
290 /**
291 * Returns the original string from the configuration file.
292 *
293 * @return original from string
294 */
295 public String orig() {
296 return orig;
297 }
Simon Hunte556e942017-06-19 15:35:44 -0700298 }
299
300 private static final class CmdBundle extends Cmd {
301 private final String id;
302
303 private CmdBundle(String orig, String params) {
304 super(orig);
305 id = params;
306 }
307
308 @Override
309 public String toString() {
310 return "CmdBundle{id=\"" + id + "\"}";
311 }
312 }
313
314 private static final class CmdAlias extends Cmd
315 implements Comparable<CmdAlias> {
316 private final String alias;
317 private final String subst;
318
319 private CmdAlias(String orig, String params) {
320 super(orig);
321 int i = params.indexOf(SPC);
322 if (i < 1) {
323 malformed = true;
324 alias = null;
325 subst = null;
326 } else {
327 alias = params.substring(0, i);
328 subst = params.substring(i + 1);
329 }
330 }
331
332 @Override
333 public String toString() {
334 return "CmdAlias{alias=\"" + alias + "\", subst=\"" + subst + "\"}";
335 }
336
337 @Override
338 public int compareTo(CmdAlias o) {
339 return alias.compareTo(o.alias);
340 }
Ray Milkey1567b532018-02-05 10:25:32 -0800341
342 @Override
343 public boolean equals(Object obj) {
344
345 if (obj == null) {
346 return false;
347 }
348 if (getClass() != obj.getClass()) {
349 return false;
350 }
351 final CmdAlias that = (CmdAlias) obj;
352 return Objects.equal(this.alias, that.alias);
353 }
354
355 @Override
356 public int hashCode() {
357 return Objects.hashCode(this.alias);
358 }
Simon Hunte556e942017-06-19 15:35:44 -0700359 }
360
361 /**
362 * Represents a "from {res} import {stuff}" command in the configuration.
363 */
364 public static final class CmdFrom extends Cmd
365 implements Comparable<CmdFrom> {
366 private final String rawRes;
367 private final Set<String> keys;
368 private String expandedRes;
369 private boolean starred = false;
370
371 private CmdFrom(String orig, String params) {
372 super(orig);
373 Matcher m = RE_IMPORT.matcher(params);
374 if (!m.matches()) {
375 malformed = true;
376 rawRes = null;
377 keys = null;
378 } else {
379 rawRes = m.group(1);
380 keys = genKeys(m.group(2));
381 }
382 }
383
384 private Set<String> genKeys(String keys) {
385 String[] k = keys.split("\\s*,\\s*");
386 Set<String> allKeys = new HashSet<>();
387 Collections.addAll(allKeys, k);
388 return ImmutableSortedSet.copyOf(allKeys);
389 }
390
391 private void expandAliasIfAny(Map<String, String> aliases) {
392 String expanded = rawRes;
393 int i = rawRes.indexOf(DOT);
394 if (i > 0) {
395 String alias = rawRes.substring(0, i);
396 String sub = aliases.get(alias);
397 if (sub != null) {
398 expanded = sub + rawRes.substring(i);
399 }
400 }
401 expandedRes = expanded;
402 }
403
404 @Override
405 public String toString() {
406 return "CmdFrom{res=\"" + rawRes + "\", keys=" + keys + "}";
407 }
408
409 @Override
410 public int compareTo(CmdFrom o) {
411 return rawRes.compareTo(o.rawRes);
412 }
413
Ray Milkey1567b532018-02-05 10:25:32 -0800414 @Override
415 public boolean equals(Object obj) {
416
417 if (obj == null) {
418 return false;
419 }
420 if (getClass() != obj.getClass()) {
421 return false;
422 }
423 final CmdFrom that = (CmdFrom) obj;
424 return Objects.equal(this.rawRes, that.rawRes);
425 }
426
427 @Override
428 public int hashCode() {
429 return Objects.hashCode(this.rawRes);
430 }
431
Simon Hunte556e942017-06-19 15:35:44 -0700432 /**
433 * Returns the resource bundle name from which to import things.
434 *
435 * @return the resource bundle name
436 */
437 public String res() {
438 return expandedRes;
439 }
440
441 /**
442 * Returns the set of keys which should be imported.
443 *
444 * @return the keys to import
445 */
446 public Set<String> keys() {
447 return keys;
448 }
449
450 /**
451 * Returns true if this "from" command is importing ALL keys from
452 * the specified resource; false otherwise.
453 *
454 * @return true, if importing ALL keys; false otherwise
455 */
456 public boolean starred() {
457 return starred;
458 }
459 }
460}