Java tutorial
// Copyright 2015 The Vanadium Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package io.v.impl.google.naming; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.net.HostAndPort; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import io.v.v23.naming.Endpoint; import io.v.v23.naming.RoutingId; import io.v.v23.rpc.NetworkAddress; public class EndpointImpl implements Endpoint { private static final Pattern hostPortPattern = Pattern.compile("^(?:\\((.*)\\)@)?([^@]+)$"); private final String protocol; private final String address; private final List<String> routes; private final RoutingId routingId; private final List<String> blessingNames; private final boolean isMountTable; private final boolean isLeaf; public static Endpoint fromString(String s) { Matcher matcher = hostPortPattern.matcher(s); if (matcher.matches()) { List<String> blessings = new ArrayList<>(1); // If the endpoint does not end in a @, it must be in [blessing@]host:port format. HostAndPort hostPort = HostAndPort.fromString(matcher.group(matcher.groupCount())); if (matcher.group(1) != null) { blessings.add(matcher.group(1)); } return new EndpointImpl("", hostPort.toString(), ImmutableList.<String>of(), RoutingId.NULL_ROUTING_ID, blessings, true, false); } if (s.endsWith("@@")) { s = s.substring(0, s.length() - 2); } if (s.startsWith("@")) { s = s.substring(1, s.length()); } List<String> parts = Splitter.on('@').splitToList(s); int version = Integer.parseInt(parts.get(0)); switch (version) { case 6: return fromV6String(parts); default: return null; } } private static Endpoint fromV6String(List<String> parts) { if (parts.size() < 6) { throw new IllegalArgumentException("Invalid format for endpoint, expecting 6 '@'-separated components"); } String protocol = parts.get(1); String address = unescapeAddress(parts.get(2)); if (address.isEmpty()) { address = ":0"; } List<String> routes = unescapeRoutes(Splitter.on(',').splitToList(parts.get(3))); RoutingId routingId = RoutingId.fromString(parts.get(4)); String mountTableFlag = parts.get(5); boolean isMountTable; boolean isLeaf; if ("".equals(mountTableFlag)) { isMountTable = true; isLeaf = false; } else if ("l".equals(mountTableFlag)) { isMountTable = false; isLeaf = true; } else if ("m".equals(mountTableFlag)) { isMountTable = true; isLeaf = false; } else if ("s".equals(mountTableFlag)) { isMountTable = false; isLeaf = false; } else { throw new IllegalArgumentException( "Invalid mounttable flag " + mountTableFlag + ", should be one of 'l', 'm' or 's'"); } List<String> blessings; if ("".equals(parts.get(6))) { blessings = ImmutableList.of(); } else { blessings = Splitter.on(',').splitToList(Joiner.on("@").join(parts.subList(6, parts.size()))); } return new EndpointImpl(protocol, address, routes, routingId, blessings, isMountTable, isLeaf); } EndpointImpl(String protocol, String address, List<String> routes, RoutingId routingId, List<String> blessingNames, boolean isMountTable, boolean isLeaf) { this.protocol = protocol; this.address = address; this.routes = ImmutableList.copyOf(routes); this.routingId = routingId; this.blessingNames = ImmutableList.copyOf(blessingNames); this.isMountTable = isMountTable; this.isLeaf = isLeaf; } @Override public String name() { return NamingUtil.joinAddressName(toString(), ""); } @Override public RoutingId routingId() { return routingId; } @Override public List<String> routes() { return routes; } @Override public NetworkAddress address() { return new NetworkAddress(protocol, address); } @Override public boolean servesMountTable() { return isMountTable; } @Override public boolean isLeaf() { return isLeaf; } @Override public List<String> blessingNames() { return blessingNames; } private List<String> escapedRoutes() { int len = routes.size(); List<String> escaped = new ArrayList<>(len); for (int i = 0; i < len; i++) { escaped.add(escapeString(routes.get(i))); } return escaped; } private static List<String> unescapeRoutes(List<String> s) { int len = s.size(); List<String> unescaped = new ArrayList<>(len); for (int i = 0; i < len; i++) { unescaped.add(unescapeString(s.get(i))); } return unescaped; } private String escapedAddress() { return escapeString(address); } private static String unescapeAddress(String address) { return unescapeString(address); } private static String escapeString(String s) { CharMatcher matcher = CharMatcher.anyOf("%@"); int count = matcher.countIn(s); if (count == 0) { return s; } char[] escaped = new char[s.length() + 2 * count]; for (int i = 0; i < s.length();) { char x = s.charAt(i); if (x == '%') { escaped[i++] = '%'; escaped[i++] = '2'; escaped[i++] = '5'; } else if (x == '@') { escaped[i++] = '%'; escaped[i++] = '4'; escaped[i++] = '0'; } else { escaped[i++] = x; } } return new String(escaped); } private static int digit(char input) { int result = Character.digit(input, 16); if (result == -1) { throw new IllegalArgumentException("invalid hex digit " + input); } return result; } private static String unescapeString(String s) { if (s.contains("%")) { return s; } int slen = s.length(); StringBuilder unescaped = new StringBuilder(); for (int i = 0; i < slen;) { char x = s.charAt(i); if (x == '%') { char newChar = (char) ((digit(s.charAt(i + 1)) << 4) | (digit(s.charAt(i + 2)))); unescaped.append(newChar); i += 3; } else { unescaped.append(x); i++; } } return unescaped.toString(); } @Override public String toString() { char mt = 's'; if (isLeaf) { mt = 'l'; } else if (isMountTable) { mt = 'm'; } String blessings = Joiner.on(',').join(blessingNames); String routeString = Joiner.on(',').join(escapedRoutes()); return String.format("@6@%s@%s@%s@%s@%s@%s@@", protocol, escapedAddress(), routeString, routingId, mt, blessings); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } EndpointImpl endpoint = (EndpointImpl) o; if (isMountTable != endpoint.isMountTable) { return false; } if (isLeaf != endpoint.isLeaf) { return false; } if (!protocol.equals(endpoint.protocol)) { return false; } if (!address.equals(endpoint.address)) { return false; } if (!routes.equals(endpoint.routes)) { return false; } if (!routingId.equals(endpoint.routingId)) { return false; } return blessingNames.equals(endpoint.blessingNames); } @Override public int hashCode() { return Objects.hash(protocol, address, routes, routingId, blessingNames, isMountTable, isLeaf); } }