Richard S. Hall | 85bafab | 2009-07-13 13:25:46 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | * or more contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. The ASF licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
| 19 | |
| 20 | package org.cauldron.bld.config; |
| 21 | |
| 22 | import java.io.IOException; |
| 23 | import java.io.PrintWriter; |
| 24 | import java.util.ArrayList; |
| 25 | import java.util.Arrays; |
| 26 | import java.util.Collections; |
| 27 | import java.util.List; |
| 28 | import java.util.Map; |
| 29 | import java.util.Properties; |
| 30 | import java.util.TreeMap; |
| 31 | |
| 32 | import org.cauldron.bld.core.util.QuoteUtil; |
| 33 | |
| 34 | public class BldConfig { |
| 35 | |
| 36 | // control properties |
| 37 | public static final String C_BUNDLES = "-bundles"; |
| 38 | public static final String C_REPOSITORIES = "-repositories"; |
| 39 | |
| 40 | // string properties |
| 41 | public static final String S_ACTIVATOR = "-activator"; |
| 42 | public static final String S_DEFAULTS = "-defaults"; |
| 43 | public static final String S_ID = "id"; |
| 44 | public static final String S_SYM_NAME = "name"; |
| 45 | public static final String S_VERSION = "version"; |
| 46 | public static final String[] STRING_KEYS = { S_ACTIVATOR, S_DEFAULTS, S_ID, S_SYM_NAME, S_VERSION }; |
| 47 | |
| 48 | // list properties |
| 49 | public static final String L_COMPOSITES = "-composites"; |
| 50 | public static final String L_CONTENTS = "-contents"; |
| 51 | public static final String L_DL_CONTENTS = "-downloads"; |
| 52 | public static final String L_SRC_CONTENTS = "-sourcedirs"; |
| 53 | public static final String L_RESOURCES = "-resources"; |
| 54 | public static final String[] LIST_KEYS = { |
| 55 | L_COMPOSITES, L_CONTENTS, L_DL_CONTENTS, L_SRC_CONTENTS, L_RESOURCES }; |
| 56 | |
| 57 | // map properties |
| 58 | public static final String M_EXPORTS = "-exports"; |
| 59 | public static final String M_IMPORTS = "-imports"; |
| 60 | public static final String M_REQUIRES = "-requires"; |
| 61 | public static final String M_FRAGMENT = "-fragment"; |
| 62 | public static final String M_LIBS = "-libs"; |
| 63 | public static final String[] MAP_KEYS = { M_EXPORTS, M_IMPORTS, M_REQUIRES, M_FRAGMENT, M_LIBS }; |
| 64 | |
| 65 | // property properties |
| 66 | public static final String P_HEADER = "header"; |
| 67 | public static final String P_OPTION = "option"; |
| 68 | public static final String P_PACKAGE_VERSION = "package"; |
| 69 | public static final String P_BUNDLE_VERSION = "bundle"; |
| 70 | public static final String[] PROP_KEYS = { P_HEADER, P_OPTION, P_PACKAGE_VERSION, P_BUNDLE_VERSION }; |
| 71 | |
| 72 | // private constants |
| 73 | private static final String LIST_REGEX = ",\\s*"; |
| 74 | private static final String MAPATTR_REGEX = ";\\s*"; |
| 75 | private static final String MAPATTR_SEP = ";"; |
| 76 | private static final String SUBKEY_SEP = ";"; |
| 77 | |
| 78 | // configuration is stored in typed maps |
| 79 | private Map<String, String> string = new TreeMap<String, String>(); |
| 80 | private Map<String, List<String>> list = new TreeMap<String, List<String>>(); |
| 81 | private Map<String, Map<String, Map<String, String>>> map = new TreeMap<String, Map<String,Map<String,String>>>(); |
| 82 | private Map<String, BldConfig> config = new TreeMap<String, BldConfig>(); |
| 83 | private Map<String, Properties> property = new TreeMap<String, Properties>(); |
| 84 | |
| 85 | // default config - not modified or saved |
| 86 | private BldConfig dflt; |
| 87 | |
| 88 | private Properties unknown = new Properties(); |
| 89 | private String comment = ""; |
| 90 | |
| 91 | public BldConfig() { |
| 92 | } |
| 93 | |
| 94 | public BldConfig(Properties p) throws IOException { |
| 95 | merge(p); |
| 96 | } |
| 97 | |
| 98 | public void setDefault(BldConfig dflt) { |
| 99 | this.dflt = dflt; |
| 100 | } |
| 101 | |
| 102 | public void setComment(String comment) { |
| 103 | this.comment = comment; |
| 104 | } |
| 105 | |
| 106 | public Properties getUnknown() { |
| 107 | return unknown; |
| 108 | } |
| 109 | |
| 110 | public String getString(String id, String key) { |
| 111 | if (id != null && config.containsKey(id)) { |
| 112 | String value = config.get(id).getString(null, key); |
| 113 | if (value != null) |
| 114 | return value; |
| 115 | } |
| 116 | return string.containsKey(key) ? string.get(key) : (dflt != null ? dflt.getString(id, key) : null); |
| 117 | } |
| 118 | |
| 119 | public List<String> getList(String id, String key) { |
| 120 | if (id != null && config.containsKey(id)) { |
| 121 | List<String> value = config.get(id).getList(null, key); |
| 122 | if (value != null) |
| 123 | return value; |
| 124 | } |
| 125 | return list.containsKey(key) ? list.get(key) : (dflt != null ? dflt.getList(id, key) : Collections.<String>emptyList()); |
| 126 | } |
| 127 | |
| 128 | public Map<String, Map<String,String>> getMap(String id, String key) { |
| 129 | if (id != null && config.containsKey(id)) { |
| 130 | Map<String, Map<String,String>> value = config.get(id).getMap(null, key); |
| 131 | if (value != null) |
| 132 | return value; |
| 133 | } |
| 134 | return map.containsKey(key) ? map.get(key) |
| 135 | : (dflt != null ? dflt.getMap(id, key) : Collections.<String, Map<String,String>>emptyMap()); |
| 136 | } |
| 137 | |
| 138 | public void setString(String id, String key, String value) { |
| 139 | if (!value.equals(getString(id, key))) { |
| 140 | if (id != null) { |
| 141 | if (!config.containsKey(id)) |
| 142 | config.put(id, new BldConfig()); |
| 143 | config.get(id).setString(null, key, value); |
| 144 | } else { |
| 145 | String dval = (dflt == null ? dflt.getString(null, key) : null); |
| 146 | if (value.equals("") && (dval == null || dval.equals(""))) { |
| 147 | string.remove(key); |
| 148 | } else { |
| 149 | string.put(key, value); |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | public void setList(String id, String key, List<String> value) { |
| 156 | if (!value.equals(getList(id, key))) { |
| 157 | if (id != null) { |
| 158 | if (!config.containsKey(id)) |
| 159 | config.put(id, new BldConfig()); |
| 160 | config.get(id).setList(null, key, value); |
| 161 | } else if (value.isEmpty() && (dflt == null || dflt.getList(null, key).isEmpty())) { |
| 162 | list.remove(key); |
| 163 | } else { |
| 164 | list.put(key, value); |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | public void setMap(String id, String key, Map<String, Map<String,String>> value) { |
| 170 | if (!value.equals(getMap(id, key))) { |
| 171 | if (id != null) { |
| 172 | if (!config.containsKey(id)) |
| 173 | config.put(id, new BldConfig()); |
| 174 | config.get(id).setMap(null, key, value); |
| 175 | } else if (value.isEmpty() && (dflt == null || dflt.getMap(null, key).isEmpty())) { |
| 176 | map.remove(key); |
| 177 | } else { |
| 178 | map.put(key, value); |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | public Properties getProps(String id, String key) { |
| 184 | // merge main and sub properties |
| 185 | Properties props = new Properties(); |
| 186 | |
| 187 | if (dflt != null) |
| 188 | props.putAll(dflt.getProps(id, key)); |
| 189 | |
| 190 | if (property.containsKey(key)) |
| 191 | props.putAll(property.get(key)); |
| 192 | |
| 193 | if (id != null && config.containsKey(id)) { |
| 194 | Properties p2 = config.get(id).getProps(null, key); |
| 195 | if (p2 != null) |
| 196 | props.putAll(p2); |
| 197 | } |
| 198 | |
| 199 | return props; |
| 200 | } |
| 201 | |
| 202 | // only sets one property at a time |
| 203 | public void setProp(String id, String key, String k2, String v2) { |
| 204 | if (v2 == null) |
| 205 | return; |
| 206 | Properties props = getProps(id, key); |
| 207 | if (!v2.equals(props.getProperty(key))) { |
| 208 | if (id != null) { |
| 209 | if (!config.containsKey(id)) |
| 210 | config.put(id, new BldConfig()); |
| 211 | config.get(id).setProp(null, key, k2, v2); |
| 212 | } else { |
| 213 | if (property.containsKey(key)) { |
| 214 | property.get(key).put(k2, v2); |
| 215 | } else { |
| 216 | Properties value = new Properties(); |
| 217 | value.put(k2, v2); |
| 218 | property.put(key, value); |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * write config in Property file format. |
| 226 | * This allows us to make it prettier than Properties.store(). |
| 227 | */ |
| 228 | public void write(final PrintWriter out) { |
| 229 | out.println(comment); |
| 230 | |
| 231 | // Note: don't add date stamp, or file will differ each time it's saved. |
| 232 | out.println("# sigil project file, saved by plugin.\n"); |
| 233 | |
| 234 | dump("", new Properties() { |
| 235 | private static final long serialVersionUID = 1L; //appease eclipse |
| 236 | @Override |
| 237 | public Object put(Object key, Object value) { |
| 238 | if (value instanceof String) { |
| 239 | out.println(key + ": " + value); |
| 240 | out.println(""); |
| 241 | } else if (value instanceof List) { |
| 242 | out.println(key + ": \\"); |
| 243 | for (Object k : (List<?>) value) { |
| 244 | out.println("\t" + k + ", \\"); |
| 245 | } |
| 246 | out.println(""); |
| 247 | } |
| 248 | else if (value instanceof Map) { |
| 249 | out.println(key + ": \\"); |
| 250 | StringBuilder b = new StringBuilder(); |
| 251 | for (Map.Entry<?, ?> e : ((Map<?,?>) value).entrySet()) { |
| 252 | b.append("\t"); |
| 253 | b.append(e.getKey()); |
| 254 | Map<?, ?> v = (Map<?, ?>) e.getValue(); |
| 255 | if (!v.isEmpty()) { |
| 256 | for (Map.Entry<?, ?> e2 : v.entrySet()) { |
| 257 | b.append(MAPATTR_SEP); |
| 258 | b.append(e2.getKey()); |
| 259 | b.append("="); |
| 260 | String v2 = e2.getValue().toString(); |
| 261 | if (v2.contains(",")) { |
| 262 | b.append("\""); |
| 263 | b.append(v2); |
| 264 | b.append("\""); |
| 265 | } |
| 266 | else { |
| 267 | b.append(v2); |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | b.append (", \\\n"); |
| 272 | } |
| 273 | out.println(b.toString()); |
| 274 | } |
| 275 | return null; |
| 276 | } |
| 277 | }); |
| 278 | out.println("# end"); |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * dump config in pseudo Properties format. |
| 283 | * Note: some values are not Strings (they're List<String>). |
| 284 | */ |
| 285 | private void dump(String prefix, Properties p) { |
| 286 | for (String key : string.keySet()) { |
| 287 | p.put(prefix + key, string.get(key)); |
| 288 | } |
| 289 | |
| 290 | for (String key : list.keySet()) { |
| 291 | List<String> list2 = list.get(key); |
| 292 | p.put(prefix + key, list2); |
| 293 | } |
| 294 | |
| 295 | for (String key : map.keySet()) { |
| 296 | Map<String, Map<String,String>> map2 = map.get(key); |
| 297 | p.put(prefix + key, map2); |
| 298 | } |
| 299 | |
| 300 | for (String key : property.keySet()) { |
| 301 | Properties props = property.get(key); |
| 302 | for (Object k2 : props.keySet()) { |
| 303 | p.put(prefix + key + SUBKEY_SEP + k2, props.get(k2)); |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | for (String key : config.keySet()) { |
| 308 | BldConfig config2 = config.get(key); |
| 309 | config2.dump(key + SUBKEY_SEP + prefix, p); |
| 310 | } |
| 311 | |
| 312 | for (Object key : unknown.keySet()) { |
| 313 | String value = unknown.getProperty((String)key); |
| 314 | if (value.length() > 0) |
| 315 | p.put(prefix + key, value); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | /** |
| 320 | * merges properties into current configuration. |
| 321 | * @param p |
| 322 | * @throws IOException |
| 323 | */ |
| 324 | public void merge(Properties p) throws IOException { |
| 325 | if (p.isEmpty()) |
| 326 | return; |
| 327 | |
| 328 | final List<String> strings = Arrays.asList(STRING_KEYS); |
| 329 | final List<String> lists = Arrays.asList(LIST_KEYS); |
| 330 | final List<String> maps = Arrays.asList(MAP_KEYS); |
| 331 | |
| 332 | List<String> bundleKeys = new ArrayList<String>(); |
| 333 | List<String> repoKeys = new ArrayList<String>(); |
| 334 | |
| 335 | String bundles = p.getProperty(C_BUNDLES); |
| 336 | if (bundles != null) { |
| 337 | bundleKeys.addAll(Arrays.asList(bundles.split(LIST_REGEX))); |
| 338 | list.put(C_BUNDLES, bundleKeys); |
| 339 | } |
| 340 | |
| 341 | String repos = p.getProperty(C_REPOSITORIES); |
| 342 | if (repos != null) { |
| 343 | for ( String s : repos.split(LIST_REGEX) ) { |
| 344 | repoKeys.add(s.trim()); |
| 345 | } |
| 346 | list.put(C_REPOSITORIES, repoKeys); |
| 347 | } |
| 348 | |
| 349 | List<String> subKeys = new ArrayList<String>(); |
| 350 | subKeys.addAll(Arrays.asList(PROP_KEYS)); |
| 351 | subKeys.addAll(bundleKeys); |
| 352 | subKeys.addAll(repoKeys); |
| 353 | |
| 354 | Map<String, Properties> sub = new TreeMap<String, Properties>(); |
| 355 | |
| 356 | for (Object k : p.keySet()) { |
| 357 | String key = (String) k; |
| 358 | if (key.equals(C_BUNDLES) || key.equals(C_REPOSITORIES)) |
| 359 | continue; |
| 360 | |
| 361 | String value = p.getProperty(key); |
| 362 | String[] keys = key.split(SUBKEY_SEP, 2); |
| 363 | |
| 364 | if (keys.length > 1) { |
| 365 | Properties p2 = sub.get(keys[0]); |
| 366 | if (p2 == null) { |
| 367 | p2 = new Properties(); |
| 368 | sub.put(keys[0], p2); |
| 369 | if (!subKeys.contains(keys[0])) { |
| 370 | unknown.setProperty(keys[0] + SUBKEY_SEP, ""); |
| 371 | } |
| 372 | } |
| 373 | p2.setProperty(keys[1], value); |
| 374 | } else if (strings.contains(key)) { |
| 375 | if (!string.containsKey(key)) |
| 376 | string.put(key, value); |
| 377 | } else if (lists.contains(key)) { |
| 378 | if (!list.containsKey(key)) { |
| 379 | ArrayList<String> list2 = new ArrayList<String>(); |
| 380 | for (String s : value.split(LIST_REGEX)) { |
| 381 | if ( s.trim().length() > 0 ) { |
| 382 | list2.add(s.trim()); |
| 383 | } |
| 384 | } |
| 385 | if ( !list2.isEmpty() ) { |
| 386 | list.put(key, list2); |
| 387 | } |
| 388 | } |
| 389 | } else if (maps.contains(key)) { |
| 390 | if (!map.containsKey(key)) { |
| 391 | Map<String, Map<String,String>> map2 = new TreeMap<String, Map<String,String>>(); |
| 392 | |
| 393 | for (String subValue : QuoteUtil.split(value)) { |
| 394 | if (subValue.trim().length() > 0) { |
| 395 | String[] split = subValue.split(MAPATTR_REGEX); |
| 396 | Map<String,String> map3 = new TreeMap<String,String>(); |
| 397 | for (int i = 1; i < split.length; ++i){ |
| 398 | String[] keyVal = split[i].split(":?=", 2); |
| 399 | if (keyVal.length != 2) { |
| 400 | throw new IOException("attribute missing '=':" + subValue); |
| 401 | } |
| 402 | map3.put(keyVal[0], keyVal[1]); |
| 403 | } |
| 404 | map2.put(split[0], map3); |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | map.put(key, map2); |
| 409 | } |
| 410 | } else { |
| 411 | unknown.setProperty(key, value); |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | for (String subKey : sub.keySet()) { |
| 416 | Properties props = sub.get(subKey); |
| 417 | if (!props.isEmpty()) { |
| 418 | if (bundleKeys.contains(subKey)) { |
| 419 | BldConfig config2 = new BldConfig(props); |
| 420 | Properties unkProps = config2.getUnknown(); |
| 421 | |
| 422 | if (config2.map.containsKey(M_IMPORTS)) |
| 423 | unkProps.setProperty(M_IMPORTS, ""); |
| 424 | |
| 425 | if (config2.map.containsKey(M_REQUIRES)) |
| 426 | unkProps.setProperty(M_REQUIRES, ""); |
| 427 | |
| 428 | for (Object unk : unkProps.keySet()) { |
| 429 | unknown.setProperty(subKey + SUBKEY_SEP + unk, ""); |
| 430 | } |
| 431 | config.put(subKey, config2); |
| 432 | } else { |
| 433 | property.put(subKey, props); |
| 434 | } |
| 435 | } |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | @Override |
| 440 | public String toString() { |
| 441 | return "string: " + string + " list:" + list + " map: " + map + " prop: " + property + " config:" + config; |
| 442 | } |
| 443 | |
| 444 | } |
| 445 | |