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