001/* 002 * $Id: EntryTransforms.java,v 1.35 2011/04/06 20:05:33 jbeavers Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031package edu.wisc.ssec.mcidasv.servermanager; 032 033import static ucar.unidata.xml.XmlUtil.findChildren; 034import static ucar.unidata.xml.XmlUtil.getAttribute; 035 036import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 037import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.cast; 038import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.map; 039import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashSet; 040import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newMap; 041 042import java.io.BufferedReader; 043import java.io.BufferedWriter; 044import java.io.FileReader; 045import java.io.FileWriter; 046import java.io.IOException; 047import java.io.InputStream; 048import java.io.InputStreamReader; 049import java.util.Collection; 050import java.util.Collections; 051import java.util.EnumSet; 052import java.util.HashMap; 053import java.util.HashSet; 054import java.util.List; 055import java.util.Map; 056import java.util.Set; 057import java.util.Map.Entry; 058import java.util.regex.Matcher; 059import java.util.regex.Pattern; 060 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063import org.w3c.dom.Element; 064 065import ucar.unidata.idv.IdvResourceManager; 066import ucar.unidata.idv.chooser.adde.AddeServer; 067import ucar.unidata.idv.chooser.adde.AddeServer.Group; 068import ucar.unidata.util.IOUtil; 069import ucar.unidata.util.LogUtil; 070import ucar.unidata.util.StringUtil; 071 072import edu.wisc.ssec.mcidasv.ResourceManager; 073import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 074import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus; 075import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 076import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 077import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry.AddeFormat; 078import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry.ServerName; 079import edu.wisc.ssec.mcidasv.util.Contract; 080import edu.wisc.ssec.mcidasv.util.functional.Function; 081 082/** 083 * Useful methods for doing things like converting a 084 * {@link ucar.unidata.idv.chooser.adde.AddeServer AddeServer} to a 085 * {@link edu.wisc.ssec.mcidasv.servermanager.RemoteAddeEntry RemoteAddeEntry}. 086 */ 087public class EntryTransforms { 088 089 /** Logger object. */ 090 private static final Logger logger = LoggerFactory.getLogger(EntryTransforms.class); 091 092 /** Matches dataset routing information in a MCTABLE file. */ 093 private static final Pattern routePattern = 094 Pattern.compile("^ADDE_ROUTE_(.*)=(.*)$"); 095 096 /** Matches {@literal "host"} declarations in a MCTABLE file. */ 097 private static final Pattern hostPattern = 098 Pattern.compile("^HOST_(.*)=(.*)$"); 099 100 /** No sense in rebuilding things that don't need to be rebuilt. */ 101 private static final Matcher routeMatcher = routePattern.matcher(""); 102 103 /** No sense in rebuilding things that don't need to be rebuilt. */ 104 private static final Matcher hostMatcher = hostPattern.matcher(""); 105 106 // TODO(jon): plz to be removing these 107 private static final String cygwinPrefix = "/cygdrive/"; 108 private static final int cygwinPrefixLength = cygwinPrefix.length(); 109 110 /** This is a utility class. Don't create it! */ 111 private EntryTransforms() { } 112 113 /** 114 * {@link Function} that transforms an {@link AddeServer} into a {@link RemoteAddeEntry}. 115 */ 116 // TODO(jon): shouldn't this use AddeEntry rather than RemoteAddeEntry? 117 public static final Function<AddeServer, RemoteAddeEntry> convertIdvServer = new Function<AddeServer, RemoteAddeEntry>() { 118 public RemoteAddeEntry apply(final AddeServer arg) { 119 String hostname = arg.toString().toLowerCase(); 120 for (AddeServer.Group group : (List<AddeServer.Group>)arg.getGroups()) { 121 122 } 123 return new RemoteAddeEntry.Builder(hostname, "temp").build(); 124 } 125 }; 126 127 @SuppressWarnings({"SetReplaceableByEnumSet"}) 128 public static Set<EntryType> findEntryTypes(final Collection<? extends AddeEntry> entries) { 129 Set<EntryType> types = new HashSet<EntryType>(entries.size()); 130 for (AddeEntry entry : entries) { 131 types.add(entry.getEntryType()); 132 } 133 return EnumSet.copyOf(types); 134 } 135 136 // converts a list of AddeServers to a set of RemoteAddeEntry 137 public static Set<RemoteAddeEntry> convertIdvServers(final List<AddeServer> idvServers) { 138 Set<RemoteAddeEntry> addeEntries = newLinkedHashSet(); 139 addeEntries.addAll(map(convertIdvServer, idvServers)); 140 return addeEntries; 141 } 142 143 public static Set<AddeServer> convertMcvServers(final Collection<AddeEntry> entries) { 144 Set<AddeServer> addeServs = newLinkedHashSet(entries.size()); 145 Set<String> addrs = newLinkedHashSet(entries.size()); 146 for (AddeEntry e : entries) { 147 EntryStatus status = e.getEntryStatus(); 148 if (status == EntryStatus.DISABLED || status == EntryStatus.INVALID) { 149 continue; 150 } 151 String addr = e.getAddress(); 152 if (addrs.contains(addr)) { 153 continue; 154 } 155 156 String newGroup = e.getGroup(); 157 String type = entryTypeToStr(e.getEntryType()); 158 159 AddeServer addeServ; 160 if (e instanceof LocalAddeEntry) { 161 addeServ = new AddeServer("localhost:"+EntryStore.getLocalPort(), "<LOCAL-DATA>"); 162 addeServ.setIsLocal(true); 163 } else { 164 addeServ = new AddeServer(addr); 165 } 166 Group addeGroup = new Group(type, newGroup, newGroup); 167 addeServ.addGroup(addeGroup); 168 addeServs.add(addeServ); 169 addrs.add(addr); 170 } 171 return addeServs; 172 } 173 174 /** 175 * Converts the XML contents of {@link ResourceManager#RSC_NEW_USERSERVERS} 176 * to a {@link Set} of {@link RemoteAddeEntry}s. 177 * 178 * @param root {@literal "Root"} of the XML to convert. 179 * 180 * @return {@code Set} of {@code RemoteAddeEntry}s described by 181 * {@code root}. 182 */ 183 protected static Set<RemoteAddeEntry> convertUserXml(final Element root) { 184 // <entry name="SERVER/DATASET" user="ASDF" proj="0000" source="user" enabled="true" type="image"/> 185 Pattern slashSplit = Pattern.compile("/"); 186 List<Element> elements = cast(findChildren(root, "entry")); 187 Set<RemoteAddeEntry> entries = newLinkedHashSet(elements.size()); 188 for (Element entryXml : elements) { 189 String name = getAttribute(entryXml, "name"); 190 String user = getAttribute(entryXml, "user"); 191 String proj = getAttribute(entryXml, "proj"); 192 String source = getAttribute(entryXml, "source"); 193 String type = getAttribute(entryXml, "type"); 194 195 boolean enabled = Boolean.parseBoolean(getAttribute(entryXml, "enabled")); 196 197 EntryType entryType = strToEntryType(type); 198 EntryStatus entryStatus = (enabled) ? EntryStatus.ENABLED : EntryStatus.DISABLED; 199 EntrySource entrySource = strToEntrySource(source); 200 201 if (name != null) { 202 String[] arr = slashSplit.split(name); 203 String description = arr[0]; 204 if (arr[0].toLowerCase().contains("localhost")) { 205 description = "<LOCAL-DATA>"; 206 } 207 208 RemoteAddeEntry.Builder incomplete = 209 new RemoteAddeEntry.Builder(arr[0], arr[1]) 210 .type(entryType) 211 .status(entryStatus) 212 .source(entrySource) 213 .validity(EntryValidity.VERIFIED); 214 215 if (((user != null) && (proj != null)) && ((!user.isEmpty()) && (!proj.isEmpty()))) { 216 incomplete = incomplete.account(user, proj); 217 } 218 entries.add(incomplete.build()); 219 } 220 } 221 return entries; 222 } 223 224 public static Set<RemoteAddeEntry> createEntriesFrom(final RemoteAddeEntry entry) { 225 Set<RemoteAddeEntry> entries = newLinkedHashSet(EntryType.values().length); 226 RemoteAddeEntry.Builder incomp = 227 new RemoteAddeEntry.Builder(entry.getAddress(), entry.getGroup()) 228 .account(entry.getAccount().getUsername(), entry.getAccount().getProject()) 229 .source(entry.getEntrySource()).status(entry.getEntryStatus()) 230 .validity(entry.getEntryValidity()); 231 for (EntryType type : EnumSet.of(EntryType.IMAGE, EntryType.GRID, EntryType.POINT, EntryType.TEXT, EntryType.RADAR, EntryType.NAV)) { 232 if (!(type == entry.getEntryType())) { 233 entries.add(incomp.type(type).build()); 234 } 235 } 236 logger.trace("built entries={}", entries); 237 return entries; 238 } 239 240 241 /** 242 * Converts the XML contents of {@link IdvResourceManager#RSC_ADDESERVER} 243 * to a {@link Set} of {@link RemoteAddeEntry}s. 244 * 245 * @param root XML to convert. 246 * @param source Used to {@literal "bulk set"} the origin of whatever 247 * {@code RemoteAddeEntry}s get created. 248 * 249 * @return {@code Set} of {@code RemoteAddeEntry}s contained within 250 * {@code root}. 251 */ 252 @SuppressWarnings("unchecked") 253 protected static Set<AddeEntry> convertAddeServerXml(Element root, EntrySource source) { 254 List<Element> serverNodes = findChildren(root, "server"); 255 Set<AddeEntry> es = newLinkedHashSet(serverNodes.size() * 5); 256 for (int i = 0; i < serverNodes.size(); i++) { 257 Element element = serverNodes.get(i); 258 String address = getAttribute(element, "name"); 259 String description = getAttribute(element, "description", ""); 260 261 // loop through each "group" entry. 262 List<Element> groupNodes = findChildren(element, "group"); 263 for (int j = 0; j < groupNodes.size(); j++) { 264 Element group = groupNodes.get(j); 265 266 // convert whatever came out of the "type" attribute into a 267 // valid EntryType. 268 String strType = getAttribute(group, "type"); 269 EntryType type = strToEntryType(strType); 270 271 // the "names" attribute can contain comma-delimited group 272 // names. 273 List<String> names = StringUtil.split(getAttribute(group, "names", ""), ",", true, true); 274 for (String name : names) { 275 if (name.isEmpty()) { 276 continue; 277 } 278 RemoteAddeEntry e = new RemoteAddeEntry 279 .Builder(address, name) 280 .source(source) 281 .type(type) 282 .validity(EntryValidity.VERIFIED) 283 .status(EntryStatus.ENABLED) 284 .validity(EntryValidity.VERIFIED) 285 .status(EntryStatus.ENABLED) 286 .build(); 287 es.add(e); 288 } 289 290 // there's also an optional "name" attribute! woo! 291 String name = getAttribute(group, "name", (String) null); 292 if ((name != null) && (!name.isEmpty())) { 293 294 RemoteAddeEntry e = new RemoteAddeEntry 295 .Builder(address, name) 296 .source(source) 297 .validity(EntryValidity.VERIFIED) 298 .status(EntryStatus.ENABLED) 299 .validity(EntryValidity.VERIFIED) 300 .status(EntryStatus.ENABLED) 301 .build(); 302 es.add(e); 303 } 304 } 305 } 306 return es; 307 } 308 309 /** 310 * Converts a given {@link EntryType} to its {@link String} representation}. 311 * Note that the resulting {@code String} is lowercase. 312 * 313 * @param type The type to convert. Cannot be {@code null}. 314 * 315 * @return 316 * 317 * @throws NullPointerException if {@code type} is {@code null}. 318 */ 319 public static String entryTypeToStr(final EntryType type) { 320 Contract.notNull(type); 321 return type.toString().toLowerCase(); 322 } 323 324 /** 325 * Attempts to convert a {@link String} to a {@link EntryType}. 326 * 327 * @param s Value whose {@code EntryType} is wanted. Cannot be {@code null}. 328 * 329 * @return One of {@code EntryType}. If there was no {@literal "sensible"} 330 * conversion, the method returns {@link EntryType#UNKNOWN}. 331 * 332 * @throws NullPointerException if {@code s} is {@code null}. 333 */ 334 public static EntryType strToEntryType(final String s) { 335 EntryType type = EntryType.UNKNOWN; 336 Contract.notNull(s); 337 try { 338 type = EntryType.valueOf(s.toUpperCase()); 339 } catch (IllegalArgumentException e) { 340 // TODO: anything to do in this situation? 341 } 342 return type; 343 } 344 345 /** 346 * Attempts to convert a {@link String} to an {@link EntrySource}. 347 * 348 * @param s {@code String} representation of an {@code EntrySource}. 349 * Cannot be {@code null}. 350 * 351 * @return Uses {@link EntrySource#valueOf(String)} to convert {@code s} 352 * to an {@code EntrySource} and returns. If no conversion was possible, 353 * returns {@link EntrySource#USER}. 354 * 355 * @throws NullPointerException if {@code s} is {@code null}. 356 */ 357 public static EntrySource strToEntrySource(final String s) { 358 EntrySource source = EntrySource.USER; 359 Contract.notNull(s); 360 try { 361 source = EntrySource.valueOf(s.toUpperCase()); 362 } catch (IllegalArgumentException e) { 363 // TODO: anything to do in this situation? 364 } 365 return source; 366 } 367 368 /** 369 * Attempts to convert a {@link String} to an {@link EntryValidity}. 370 * 371 * @param s {@code String} representation of an {@code EntryValidity}. 372 * Cannot be {@code null}. 373 * 374 * @return Uses {@link EntryValidity#valueOf(String)} to convert 375 * {@code s} to an {@code EntryValidity} and returns. If no conversion 376 * was possible, returns {@link EntryValidity#UNVERIFIED}. 377 * 378 * @throws NullPointerException if {@code s} is {@code null}. 379 */ 380 public static EntryValidity strToEntryValidity(final String s) { 381 EntryValidity valid = EntryValidity.UNVERIFIED; 382 Contract.notNull(s); 383 try { 384 valid = EntryValidity.valueOf(s.toUpperCase()); 385 } catch (IllegalArgumentException e) { 386 // TODO: anything to do in this situation? 387 } 388 return valid; 389 } 390 391 /** 392 * Attempts to convert a {@link String} into an {@link EntryStatus}. 393 * 394 * @param s {@code String} representation of an {@code EntryStatus}. 395 * Cannot be {@code null}. 396 * 397 * @return Uses {@link EntryStatus#valueOf(String)} to convert {@code s} 398 * into an {@code EntryStatus} and returns. If no conversion was possible, 399 * returns {@link EntryStatus#DISABLED}. 400 * 401 * @throws NullPointerException if {@code s} is {@code null}. 402 */ 403 public static EntryStatus strToEntryStatus(final String s) { 404 EntryStatus status = EntryStatus.DISABLED; 405 Contract.notNull(s); 406 try { 407 status = EntryStatus.valueOf(s.toUpperCase()); 408 } catch (IllegalArgumentException e) { 409 // TODO: anything to do in this situation? 410 } 411 return status; 412 } 413 414 /** 415 * Attempts to convert a {@link String} into a member of {@link AddeFormat}. 416 * This method does a little bit of magic with the incoming {@code String}: 417 * <ol> 418 * <li>spaces are replaced with underscores</li> 419 * <li>dashes ({@literal "-"}) are removed</li> 420 * </ol> 421 * This was done because older {@literal "RESOLV.SRV"} files permitted the 422 * {@literal "MCV"} key to contain spaces or dashes, and that doesn't play 423 * so well with Java's enums. 424 * 425 * @param s {@code String} representation of an {@code AddeFormat}. Cannot 426 * be {@code null}. 427 * 428 * @return Uses {@link AddeFormat#valueOf(String)} to convert <i>the modified</i> 429 * {@code String} into an {@code AddeFormat} and returns. If no conversion 430 * was possible, returns {@link AddeFormat#INVALID}. 431 * 432 * @throws NullPointerException if {@code s} is {@code null}. 433 */ 434 public static AddeFormat strToAddeFormat(final String s) { 435 AddeFormat format = AddeFormat.INVALID; 436 Contract.notNull(s); 437 try { 438 format = AddeFormat.valueOf(s.toUpperCase().replace(' ', '_').replace("-", "")); 439 } catch (IllegalArgumentException e) { 440 // TODO: anything to do in this situation? 441 } 442 return format; 443 } 444 445 // TODO(jon): re-add verify flag? 446 protected static Set<RemoteAddeEntry> extractMctableEntries(final String path, final String username, final String project) { 447 Set<RemoteAddeEntry> entries = newLinkedHashSet(); 448 try { 449 InputStream is = IOUtil.getInputStream(path); 450 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 451 String line; 452 453 Map<String, Set<String>> hosts = newMap(); 454 Map<String, String> hostToIp = newMap(); 455 Map<String, String> datasetToHost = newMap(); 456 457 // special case for an local ADDE entries. 458 Set<String> blah = newLinkedHashSet(); 459 blah.add("LOCAL-DATA"); 460 hosts.put("LOCAL-DATA", blah); 461 hostToIp.put("LOCAL-DATA", "LOCAL-DATA"); 462 463 boolean validFile = false; 464 while ((line = reader.readLine()) != null) { 465 routeMatcher.reset(line); 466 hostMatcher.reset(line); 467 468 if (routeMatcher.find()) { 469 String dataset = routeMatcher.group(1); 470 String host = routeMatcher.group(2).toLowerCase(); 471 datasetToHost.put(dataset, host); 472 validFile = true; 473 } 474 else if (hostMatcher.find()) { 475 String name = hostMatcher.group(1).toLowerCase(); 476 String ip = hostMatcher.group(2); 477 478 Set<String> nameSet = hosts.get(ip); 479 if (nameSet == null) { 480 nameSet = newLinkedHashSet(); 481 } 482 nameSet.add(name); 483 hosts.put(ip, nameSet); 484 hostToIp.put(name, ip); 485 hostToIp.put(ip, ip); // HACK :( 486 validFile = true; 487 } 488 } 489 490 if (validFile) { 491 Map<String, String> datasetsToIp = mapDatasetsToIp(datasetToHost, hostToIp); 492 Map<String, String> ipToName = mapIpToName(hosts); 493 List<RemoteAddeEntry> l = mapDatasetsToName(datasetsToIp, ipToName, username, project); 494 entries.addAll(l); 495 } else { 496 entries = Collections.emptySet(); 497 } 498 is.close(); 499 } catch (IOException e) { 500 LogUtil.logException("Reading file: "+path, e); 501 } 502 503 return entries; 504 } 505 506 /** 507 * This method is slightly confusing, sorry! Think of it kind of like a 508 * {@literal "SQL JOIN"}... 509 * 510 * <p>Basically create {@link RemoteAddeEntry}s by using a hostname to 511 * determine which dataset belongs to which IP. 512 * 513 * @param datasetToHost {@code Map} of ADDE groups to host names. 514 * @param hostToIp {@code Map} of host names to IP addresses. 515 * @param username 516 * @param project 517 * 518 * @return 519 */ 520 private static List<RemoteAddeEntry> mapDatasetsToName( 521 final Map<String, String> datasetToHost, final Map<String, String> hostToIp, final String username, final String project) 522 { 523 boolean defaultAcct = false; 524 AddeAccount defAcct = AddeEntry.DEFAULT_ACCOUNT; 525 if (defAcct.getUsername().equalsIgnoreCase(username) && defAcct.getProject().equals(project)) { 526 defaultAcct = true; 527 } 528 List<RemoteAddeEntry> entries = arrList(datasetToHost.size()); 529 for (Entry<String, String> entry : datasetToHost.entrySet()) { 530 String dataset = entry.getKey(); 531 String ip = entry.getValue(); 532 String name = ip; 533 if (hostToIp.containsKey(ip)) { 534 name = hostToIp.get(ip); 535 } 536 RemoteAddeEntry.Builder builder = new RemoteAddeEntry.Builder(name, dataset) 537 .source(EntrySource.MCTABLE); 538 if (!defaultAcct) { 539 builder.account(username, project); 540 } 541 RemoteAddeEntry remoteEntry = builder.build(); 542 logger.trace("built entry={}", remoteEntry); 543 entries.add(builder.build()); 544 } 545 return entries; 546 } 547 548 private static Map<String, String> mapIpToName( 549 final Map<String, Set<String>> map) 550 { 551 assert map != null; 552 553 Map<String, String> ipToName = newMap(map.size()); 554 for (Entry<String, Set<String>> entry : map.entrySet()) { 555 Set<String> names = entry.getValue(); 556 String displayName = ""; 557 for (String name : names) 558 if (name.length() >= displayName.length()) 559 displayName = name; 560 561 if (displayName.isEmpty()) { 562 displayName = entry.getKey(); 563 } 564 ipToName.put(entry.getKey(), displayName); 565 } 566 return ipToName; 567 } 568 569 private static Map<String, String> mapDatasetsToIp(final Map<String, String> datasets, final Map<String, String> hostMap) { 570 assert datasets != null; 571 assert hostMap != null; 572 573 Map<String, String> datasetToIp = newMap(datasets.size()); 574 for (Entry<String, String> entry : datasets.entrySet()) { 575 String dataset = entry.getKey(); 576 String alias = entry.getValue(); 577 if (hostMap.containsKey(alias)) { 578 datasetToIp.put(dataset, hostMap.get(alias)); 579 } 580 } 581 return datasetToIp; 582 } 583 584 /** 585 * Reads a {@literal "RESOLV.SRV"} file and converts the contents into a 586 * {@link Set} of {@link LocalAddeEntry}s. 587 * 588 * @param filename Filename containing desired {@code LocalAddeEntry}s. 589 * Cannot be {@code null}. 590 * 591 * @return {@code Set} of {@code LocalAddeEntry}s contained within 592 * {@code filename}. 593 * 594 * @throws IOException if there was a problem reading from {@code filename}. 595 * 596 * @see #readResolvLine(String) 597 */ 598 public static Set<LocalAddeEntry> readResolvFile(final String filename) throws IOException { 599 Set<LocalAddeEntry> servers = newLinkedHashSet(); 600 BufferedReader br = null; 601 try { 602 br = new BufferedReader(new FileReader(filename)); 603 String line; 604 while ((line = br.readLine()) != null) { 605 line = line.trim(); 606 if (line.isEmpty()) { 607 continue; 608 } 609 servers.add(readResolvLine(line)); 610 } 611 } finally { 612 if (br != null) { 613 br.close(); 614 } 615 } 616 return servers; 617 } 618 619 /** 620 * Converts a {@code String} containing a {@literal "RESOLV.SRV"} entry into 621 * a {@link LocalAddeEntry}. 622 */ 623 public static LocalAddeEntry readResolvLine(String line) { 624 boolean disabled = line.startsWith("#"); 625 if (disabled) { 626 line = line.substring(1); 627 } 628 Pattern commaSplit = Pattern.compile(","); 629 Pattern equalSplit = Pattern.compile("="); 630 631 String[] pairs = commaSplit.split(line.trim()); 632 String[] pair; 633 Map<String, String> keyVals = new HashMap<String, String>(pairs.length); 634 for (int i = 0; i < pairs.length; i++) { 635 if (pairs[i] == null || pairs[i].isEmpty()) { 636 continue; 637 } 638 639 pair = equalSplit.split(pairs[i]); 640 if (pair.length != 2 || pair[0].isEmpty() || pair[1].isEmpty()) { 641 continue; 642 } 643 644 // group 645// if ("N1".equals(pair[0])) { 646//// builder.group(pair[1]); 647// } 648// // descriptor/dataset 649// else if ("N2".equals(pair[0])) { 650//// builder.descriptor(pair[1]); 651// } 652// // data type (only image supported?) 653// else if ("TYPE".equals(pair[0])) { 654//// builder.type(strToEntryType(pair[1])); 655// } 656// // file format 657// else if ("K".equals(pair[0])) { 658//// builder.kind(pair[1].toUpperCase()); 659// } 660// // comment 661// else if ("C".equals(pair[0])) { 662//// builder.name(pair[1]); 663// } 664// // mcv-specific; allows us to infer kind+type? 665// else if ("MCV".equals(pair[0])) { 666//// builder.format(strToAddeFormat(pair[1])); 667// } 668// // realtime ("Y"/"N"/"A") 669// else if ("RT".equals(pair[0])) { 670//// builder.realtime(pair[1]); 671// } 672// // start of file number range 673// else if ("R1".equals(pair[0])) { 674//// builder.start(pair[1]); 675// } 676// // end of file number range 677// else if ("R2".equals(pair[0])) { 678//// builder.end(pair[1]); 679// } 680// // filename mask 681 if ("MASK".equals(pair[0])) { 682 pair[1] = demungeFileMask(pair[1]); 683 } 684 keyVals.put(pair[0], pair[1]); 685 } 686 687 if (keyVals.containsKey("C") && keyVals.containsKey("N1") && keyVals.containsKey("MCV") && keyVals.containsKey("MASK")) { 688 LocalAddeEntry entry = new LocalAddeEntry.Builder(keyVals).build(); 689 EntryStatus status = (disabled) ? EntryStatus.DISABLED : EntryStatus.ENABLED; 690 entry.setEntryStatus(status); 691 return entry; 692 } else { 693 return LocalAddeEntry.INVALID_ENTRY; 694 } 695 } 696 697 /** 698 * Writes a {@link Collection} of {@link LocalAddeEntry}s to a {@literal "RESOLV.SRV"} 699 * file. <b>This method discards the current contents of {@code filename}!</b> 700 * 701 * @param filename Filename that will contain the {@code LocalAddeEntry}s within 702 * {@code entries}. Cannot be {@code null}. 703 * 704 * @param entries {@code Set} of entries to be written to {@code filename}. 705 * Cannot be {@code null}. 706 * 707 * @throws IOException if there was a problem writing to {@code filename}. 708 * 709 * @see #appendResolvFile(String, Collection) 710 */ 711 public static void writeResolvFile(final String filename, final Collection<LocalAddeEntry> entries) throws IOException { 712 writeResolvFile(filename, false, entries); 713 } 714 715 /** 716 * Writes a {@link Collection} of {@link LocalAddeEntry}s to a {@literal "RESOLV.SRV"} 717 * file. This method will <i>append</i> the contents of {@code entries} to 718 * {@code filename}. 719 * 720 * @param filename Filename that will contain the {@code LocalAddeEntry}s within 721 * {@code entries}. Cannot be {@code null}. 722 * 723 * @param entries {@code Collection} of entries to be written to {@code filename}. 724 * Cannot be {@code null}. 725 * 726 * @throws IOException if there was a problem writing to {@code filename}. 727 * 728 * @see #writeResolvFile(String, Collection) 729 */ 730 public static void appendResolvFile(final String filename, final Collection<LocalAddeEntry> entries) throws IOException { 731 writeResolvFile(filename, true, entries); 732 } 733 734 /** 735 * Writes a {@link Collection} of {@link LocalAddeEntry}s to a {@literal "RESOLV.SRV"} 736 * file. 737 * 738 * @param filename Filename that will contain the {@code LocalAddeEntry}s within 739 * {@code entries}. Cannot be {@code null}. 740 * 741 * @param append If {@code true}, append {@code entries} to {@code filename}. Otherwise discards contents of {@code filename}. 742 * 743 * @param entries {@code Collection} of entries to be written to {@code filename}. 744 * Cannot be {@code null}. 745 * 746 * @throws IOException if there was a problem writing to {@code filename}. 747 * 748 * @see #appendResolvFile(String, Collection) 749 * @see #asResolvEntry(LocalAddeEntry) 750 */ 751 private static void writeResolvFile(final String filename, final boolean append, final Collection<LocalAddeEntry> entries) throws IOException { 752 BufferedWriter bw = null; 753 try { 754 bw = new BufferedWriter(new FileWriter(filename)); 755 for (LocalAddeEntry entry : entries) { 756 bw.write(asResolvEntry(entry)+'\n'); 757 } 758 } finally { 759 if (bw != null) { 760 bw.close(); 761 } 762 } 763 } 764 765 /** 766 * De-munges file mask strings. 767 * 768 * @throws NullPointerException if {@code path} is {@code null}. 769 */ 770 public static String demungeFileMask(final String path) { 771 Contract.notNull(path, "how dare you! null paths cannot be munged!"); 772 int index = path.indexOf("/*"); 773 if (index < 0) { 774 return path; 775 } 776 String tmpFileMask = path.substring(0, index); 777 /** Look for "cygwinPrefix" at start of string and munge accordingly */ 778 if (tmpFileMask.length() > cygwinPrefixLength+1 && 779 tmpFileMask.substring(0,cygwinPrefixLength).equals(cygwinPrefix)) { 780 String driveLetter = tmpFileMask.substring(cygwinPrefixLength,cygwinPrefixLength+1).toUpperCase(); 781 return driveLetter + ':' + tmpFileMask.substring(cygwinPrefixLength+1).replace('/', '\\'); 782 } else { 783 return tmpFileMask; 784 } 785 } 786 787 /** 788 * Munges a file mask {@link String} into something {@literal "RESOLV.SRV"} 789 * expects. 790 * 791 * <p>Munging is only needed for Windows users--the process converts 792 * back slashes into forward slashes and prefixes with {@literal "/cygdrive/"}. 793 * 794 * @throws NullPointerException if {@code mask} is {@code null}. 795 */ 796 public static String mungeFileMask(final String mask) { 797 Contract.notNull(mask, "Cannot further munge this mask; it was null upon arriving"); 798 StringBuilder s = new StringBuilder(100); 799 if (mask.length() > 3 && ":".equals(mask.substring(1, 2))) { 800 String newFileMask = mask; 801 String driveLetter = newFileMask.substring(0,1).toLowerCase(); 802 newFileMask = newFileMask.substring(3); 803 newFileMask = newFileMask.replace('\\', '/'); 804 s.append("/cygdrive/").append(driveLetter).append('/').append(newFileMask); 805 } else { 806 s.append("").append(mask); 807 } 808 return s.toString(); 809 } 810 811 /** 812 * Converts a {@link Collection} of {@link LocalAddeEntry}s into a {@link List} 813 * of {@code String}s. 814 * 815 * @param entries {@code Collection} of entries to convert. Should not be {@code null}. 816 * 817 * @return {@code entries} represented as {@code String}s. 818 * 819 * @see #asResolvEntry(LocalAddeEntry) 820 */ 821 public static List<String> asResolvEntries(final Collection<LocalAddeEntry> entries) { 822 List<String> resolvEntries = arrList(entries.size()); 823 for (LocalAddeEntry entry : entries) { 824 resolvEntries.add(asResolvEntry(entry)); 825 } 826 return resolvEntries; 827 } 828 829 /** 830 * Converts a given {@link LocalAddeEntry} into a {@code String} that is 831 * suitable for including in a {@literal "RESOLV.SRV"} file. This method 832 * does <b>not</b> append a newline to the end of the {@code String}. 833 * 834 * @param entry The {@code LocalAddeEntry} to convert. Should not be {@code null}. 835 * 836 * @return {@code entry} as a {@literal "RESOLV.SRV"} entry. 837 */ 838 public static String asResolvEntry(final LocalAddeEntry entry) { 839 AddeFormat format = entry.getFormat(); 840 ServerName servName = format.getServerName(); 841 842 StringBuilder s = new StringBuilder(100); 843 if (entry.getEntryStatus() != EntryStatus.ENABLED) { 844 s.append('#'); 845 } 846 s.append("N1=").append(entry.getGroup().toUpperCase()) 847 .append(",N2=").append(entry.getDescriptor().toUpperCase()) 848 .append(",TYPE=").append(format.getType()) 849 .append(",RT=").append(entry.getRealtimeAsString()) 850 .append(",K=").append(format.getServerName()) 851 .append(",R1=").append(entry.getStart()) 852 .append(",R2=").append(entry.getEnd()) 853 .append(",MCV=").append(format.name()) 854 .append(",C=").append(entry.getName()); 855 856 if (servName == ServerName.LV1B) { 857 s.append(",Q=LALO"); 858 } 859 860 String tmpFileMask = entry.getFileMask(); 861 if (tmpFileMask.length() > 3 && ":".equals(tmpFileMask.substring(1, 2))) { 862 String newFileMask = tmpFileMask; 863 String driveLetter = newFileMask.substring(0,1).toLowerCase(); 864 newFileMask = newFileMask.substring(3); 865 newFileMask = newFileMask.replace('\\', '/'); 866 s.append(",MASK=/cygdrive/").append(driveLetter).append('/').append(newFileMask); 867 } else { 868 s.append(",MASK=").append(tmpFileMask); 869 } 870 // local servers seem to really like trailing commas! 871 return s.append('/').append(format.getFileFilter()).append(',').toString(); 872 } 873}