1 package mork;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Locale;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
8
9 /***
10 * A dictionary contains key/value pairs. The keys are used in data cells (e.g.
11 * Aliases Definitions) to compress data and reference the values stored in
12 * dictionaries.
13 *
14 * Each dictionary can optionally be categorized into a scope. The default scope
15 * is the Atom Scope, which is used for literal values of actual content (in
16 * contrast to the Column Scope, which us used for header data only)
17 *
18 * @author mhaller
19 */
20 public class Dict {
21
22 /*** A typed empty list of dictionaries */
23 public static final List<Dict> EMPTY_LIST = new ArrayList<Dict>(0);
24
25 /*** The name of the scope, e.g. 'a' or 'atomScope' */
26 private String scopeName;
27
28 /*** The value of the scope, usually 'c' */
29 private String scopeValue;
30
31 /***
32 * Internal reference to aliases which were included in the Dictionary
33 * definition
34 */
35 private Aliases aliases;
36
37 /***
38 * Parse a Dictionary using the given String content. The simplest
39 * dictionary possible is <code>><</code>.
40 *
41 * @param dictString
42 * a valid dictionary definition
43 */
44 public Dict(String dictString) {
45 this(dictString, Dict.EMPTY_LIST);
46 }
47
48 /***
49 * Parse a Dictionary using the given String content. The simplest
50 * dictionary possible is <code>><</code>.
51 *
52 * @param dictString
53 * a valid dictionary definition
54 * @param dicts
55 * a preexisting list of dictionaries
56 */
57 public Dict(String dictString, List<Dict> dicts) {
58
59
60
61 Pattern pattern =
62 Pattern.compile("//s*<//s*(<//(?.*//)?>)?[//s//n//r]*(.*)>[//s//r//n]*",
63 Pattern.MULTILINE);
64 Matcher matcher = pattern.matcher(dictString);
65 if (!matcher.find()) {
66 throw new RuntimeException("RegEx does not match: " + dictString);
67 }
68 String scopeDef = matcher.group(1);
69 String aliasesDef = matcher.group(2);
70
71
72 if (scopeDef != null) {
73 Pattern scopePattern = Pattern.compile("<//(?(.*)=([^//)])//)?>");
74 Matcher scopeMatcher = scopePattern.matcher(scopeDef);
75 if (scopeMatcher.matches()) {
76 scopeName = scopeMatcher.group(1);
77 scopeValue = scopeMatcher.group(2);
78 }
79 }
80
81
82 aliases = new Aliases(aliasesDef, dicts);
83 }
84
85 /***
86 * Returns the default scope of the parsed dictionary. This is not
87 * necessarily the same as the "global default scope", which is the Atom
88 * Scope.
89 *
90 * Since Aliases itself could be scoped, a dictionary has a its own default
91 * scope for contained non-scoped aliases.
92 *
93 * @return the default scope of the Dictionary, one of {@link ScopeTypes}
94 */
95 public ScopeTypes getDefaultScope() {
96 if (scopeValue != null &&
97 scopeValue.toLowerCase(Locale.getDefault()).startsWith("c")) {
98 return ScopeTypes.COLUMN_SCOPE;
99 }
100 return ScopeTypes.ATOM_SCOPE;
101 }
102
103 /***
104 * Returns the name of the scope,if the Dictionary included a scope
105 * definition.
106 *
107 * @return the name of the scope of the Dictionary, if there was any, or
108 * <code>null</code>
109 */
110 public String getScopeName() {
111 return scopeName;
112 }
113
114 /***
115 * Returns the value of the scope, if the Dictionary has any.
116 *
117 * @return the value of the scope, or <code>null</code> if there was no
118 * explicit scope definition.
119 */
120 public String getScopeValue() {
121 return scopeValue;
122 }
123
124 /***
125 * Returns the value of a parsed alias, if the Dictionary declared it.
126 *
127 * @param id
128 * the Alias Key to dereference
129 * @return the value of the alias with the key id
130 */
131 public String getValue(String id) {
132 return aliases.getValue(id);
133 }
134
135 /***
136 * Returns the number of aliases available in this Dictionary
137 *
138 * @return the count number of aliases available
139 */
140 public int getAliasCount() {
141 return aliases.count();
142 }
143
144 /***
145 * Dereferences a pointer to a value using this dictionary.
146 *
147 * @param id
148 * the id with the "^"-Prefix
149 */
150 public String dereference(String id) {
151 if (!id.startsWith("^")) {
152 throw new RuntimeException("dereference() must be called with a reference id including the prefix '^'");
153 }
154 String oid = id.substring(1);
155 String value = getValue(oid);
156 return value;
157 }
158
159 /***
160 * Dereferences a pointer to a value using the given list of dictionaries to
161 * resolve it in the given scope.
162 *
163 * @param id
164 * the pointer id
165 * @param dicts
166 * a list of dictionaries
167 * @param scope
168 * the scope to look in
169 * @return the value if could be dereferenced
170 * @throws RuntimeException
171 * if the dictionaries are empty or the value could not be found
172 */
173 public static String dereference(String id, List<Dict> dicts,
174 ScopeTypes scope) {
175 if (dicts.isEmpty()) {
176 throw new RuntimeException("Cannot dereference IDs without dictionaries");
177 }
178 String dereference = null;
179 for (Dict dict: dicts) {
180 if (dict.getDefaultScope() == scope) {
181 dereference = dict.dereference(id);
182 if (dereference != null) {
183 return dereference;
184 } else {
185 }
186 }
187 }
188 throw new RuntimeException("Dictionary could not dereference key: " +
189 id + " in scope " + scope);
190 }
191
192 }