View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.core5.net;
28  
29  import java.io.Serializable;
30  import java.net.URISyntaxException;
31  
32  import org.apache.hc.core5.annotation.Contract;
33  import org.apache.hc.core5.annotation.ThreadingBehavior;
34  import org.apache.hc.core5.util.Args;
35  import org.apache.hc.core5.util.LangUtils;
36  import org.apache.hc.core5.util.TextUtils;
37  import org.apache.hc.core5.util.Tokenizer;
38  
39  /**
40   * Component that holds all details needed to describe a network connection
41   * to a host. This includes remote host name and port.
42   *
43   * @since 5.0
44   */
45  @Contract(threading = ThreadingBehavior.IMMUTABLE)
46  public final class Host implements NamedEndpoint, Serializable {
47  
48      private static final long serialVersionUID = 1L;
49      private final String name;
50      private final String lcName;
51      private final int port;
52  
53      public Host(final String name, final int port) {
54          super();
55          this.name = Args.notNull(name, "Host name");
56          this.port = Ports.checkWithDefault(port);
57          this.lcName = TextUtils.toLowerCase(this.name);
58      }
59  
60      static Host parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException {
61          final Tokenizer tokenizer = Tokenizer.INSTANCE;
62          final String hostName;
63          final boolean ipv6Brackets = !cursor.atEnd() && s.charAt(cursor.getPos()) == '[';
64          if (ipv6Brackets) {
65              cursor.updatePos(cursor.getPos() + 1);
66              hostName = tokenizer.parseContent(s, cursor, URISupport.IPV6_HOST_TERMINATORS);
67              if (cursor.atEnd() || !(s.charAt(cursor.getPos()) == ']')) {
68                  throw URISupport.createException(s, cursor, "Expected an IPv6 closing bracket ']'");
69              }
70              cursor.updatePos(cursor.getPos() + 1);
71              if (!InetAddressUtils.isIPv6Address(hostName)) {
72                  throw URISupport.createException(s, cursor, "Expected an IPv6 address");
73              }
74          } else {
75              hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_SEPARATORS);
76          }
77          String portText = null;
78          if (!cursor.atEnd() && s.charAt(cursor.getPos()) == ':') {
79              cursor.updatePos(cursor.getPos() + 1);
80              portText = tokenizer.parseContent(s, cursor, URISupport.TERMINATORS);
81          }
82          final int port;
83          if (!TextUtils.isBlank(portText)) {
84              if (!ipv6Brackets && portText.contains(":")) {
85                  throw URISupport.createException(s, cursor, "Expected IPv6 address to be enclosed in brackets");
86              }
87              try {
88                  port = Integer.parseInt(portText);
89              } catch (final NumberFormatException ex) {
90                  throw URISupport.createException(s, cursor, "Port is invalid");
91              }
92          } else {
93              port = -1;
94          }
95          return new Host(hostName, port);
96      }
97  
98      static Host parse(final CharSequence s) throws URISyntaxException {
99          final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
100         return parse(s, cursor);
101     }
102 
103     static void format(final StringBuilder buf, final NamedEndpoint endpoint) {
104         final String hostName = endpoint.getHostName();
105         if (InetAddressUtils.isIPv6Address(hostName)) {
106             buf.append('[').append(hostName).append(']');
107         } else {
108             buf.append(hostName);
109         }
110         if (endpoint.getPort() != -1) {
111             buf.append(":");
112             buf.append(endpoint.getPort());
113         }
114     }
115 
116     static void format(final StringBuilder buf, final Host host) {
117         format(buf, (NamedEndpoint) host);
118     }
119 
120     static String format(final Host host) {
121         final StringBuilder buf = new StringBuilder();
122         format(buf, host);
123         return buf.toString();
124     }
125 
126     public static Host create(final String s) throws URISyntaxException {
127         Args.notEmpty(s, "HTTP Host");
128         final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
129         final Host host = parse(s, cursor);
130         if (TextUtils.isBlank(host.getHostName())) {
131             throw URISupport.createException(s, cursor, "Hostname is invalid");
132         }
133         if (!cursor.atEnd()) {
134             throw URISupport.createException(s, cursor, "Unexpected content");
135         }
136         return host;
137     }
138 
139     @Override
140     public String getHostName() {
141         return name;
142     }
143 
144     @Override
145     public int getPort() {
146         return port;
147     }
148 
149     @Override
150     public boolean equals(final Object o) {
151         if (this == o) {
152             return true;
153         }
154         if (o instanceof Host) {
155             final Host that = (Host) o;
156             return this.lcName.equals(that.lcName) && this.port == that.port;
157         }
158         return false;
159     }
160 
161     @Override
162     public int hashCode() {
163         int hash = LangUtils.HASH_SEED;
164         hash = LangUtils.hashCode(hash, this.lcName);
165         hash = LangUtils.hashCode(hash, this.port);
166         return hash;
167     }
168 
169     @Override
170     public String toString() {
171         return format(this);
172     }
173 
174 }