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