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 }