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.http.conn.scheme;
28
29 import java.util.Locale;
30
31 import org.apache.http.annotation.Immutable;
32
33 import org.apache.http.util.LangUtils;
34
35 /**
36 * Encapsulates specifics of a protocol scheme such as "http" or "https". Schemes are identified
37 * by lowercase names. Supported schemes are typically collected in a {@link SchemeRegistry
38 * SchemeRegistry}.
39 * <p/>
40 * For example, to configure support for "https://" URLs, you could write code like the following:
41 * <pre>
42 * Scheme https = new Scheme("https", 443, new MySecureSocketFactory());
43 * SchemeRegistry registry = new SchemeRegistry();
44 * registry.register(https);
45 * </pre>
46 *
47 * @since 4.0
48 */
49 @Immutable
50 public final class Scheme {
51
52 /** The name of this scheme, in lowercase. (e.g. http, https) */
53 private final String name;
54
55 /** The socket factory for this scheme */
56 private final SchemeSocketFactory socketFactory;
57
58 /** The default port for this scheme */
59 private final int defaultPort;
60
61 /** Indicates whether this scheme allows for layered connections */
62 private final boolean layered;
63
64 /** A string representation, for {@link #toString toString}. */
65 private String stringRep;
66 /*
67 * This is used to cache the result of the toString() method
68 * Since the method always generates the same value, there's no
69 * need to synchronize, and it does not affect immutability.
70 */
71
72 /**
73 * Creates a new scheme.
74 * Whether the created scheme allows for layered connections
75 * depends on the class of <code>factory</code>.
76 *
77 * @param name the scheme name, for example "http".
78 * The name will be converted to lowercase.
79 * @param port the default port for this scheme
80 * @param factory the factory for creating sockets for communication
81 * with this scheme
82 *
83 * @since 4.1
84 */
85 @SuppressWarnings("deprecation")
86 public Scheme(final String name, final int port, final SchemeSocketFactory factory) {
87 if (name == null) {
88 throw new IllegalArgumentException("Scheme name may not be null");
89 }
90 if ((port <= 0) || (port > 0xffff)) {
91 throw new IllegalArgumentException("Port is invalid: " + port);
92 }
93 if (factory == null) {
94 throw new IllegalArgumentException("Socket factory may not be null");
95 }
96 this.name = name.toLowerCase(Locale.ENGLISH);
97 this.defaultPort = port;
98 if (factory instanceof SchemeLayeredSocketFactory) {
99 this.layered = true;
100 this.socketFactory = factory;
101 } else if (factory instanceof LayeredSchemeSocketFactory) {
102 this.layered = true;
103 this.socketFactory = new SchemeLayeredSocketFactoryAdaptor2((LayeredSchemeSocketFactory) factory);
104 } else {
105 this.layered = false;
106 this.socketFactory = factory;
107 }
108 }
109
110 /**
111 * Creates a new scheme.
112 * Whether the created scheme allows for layered connections
113 * depends on the class of <code>factory</code>.
114 *
115 * @param name the scheme name, for example "http".
116 * The name will be converted to lowercase.
117 * @param factory the factory for creating sockets for communication
118 * with this scheme
119 * @param port the default port for this scheme
120 *
121 * @deprecated (4.1) Use {@link #Scheme(String, int, SchemeSocketFactory)}
122 */
123 @Deprecated
124 public Scheme(final String name,
125 final SocketFactory factory,
126 final int port) {
127
128 if (name == null) {
129 throw new IllegalArgumentException
130 ("Scheme name may not be null");
131 }
132 if (factory == null) {
133 throw new IllegalArgumentException
134 ("Socket factory may not be null");
135 }
136 if ((port <= 0) || (port > 0xffff)) {
137 throw new IllegalArgumentException
138 ("Port is invalid: " + port);
139 }
140
141 this.name = name.toLowerCase(Locale.ENGLISH);
142 if (factory instanceof LayeredSocketFactory) {
143 this.socketFactory = new SchemeLayeredSocketFactoryAdaptor(
144 (LayeredSocketFactory) factory);
145 this.layered = true;
146 } else {
147 this.socketFactory = new SchemeSocketFactoryAdaptor(factory);
148 this.layered = false;
149 }
150 this.defaultPort = port;
151 }
152
153 /**
154 * Obtains the default port.
155 *
156 * @return the default port for this scheme
157 */
158 public final int getDefaultPort() {
159 return defaultPort;
160 }
161
162
163 /**
164 * Obtains the socket factory.
165 * If this scheme is {@link #isLayered layered}, the factory implements
166 * {@link LayeredSocketFactory LayeredSocketFactory}.
167 *
168 * @return the socket factory for this scheme
169 *
170 * @deprecated (4.1) Use {@link #getSchemeSocketFactory()}
171 */
172 @Deprecated
173 public final SocketFactory getSocketFactory() {
174 if (this.socketFactory instanceof SchemeSocketFactoryAdaptor) {
175 return ((SchemeSocketFactoryAdaptor) this.socketFactory).getFactory();
176 } else {
177 if (this.layered) {
178 return new LayeredSocketFactoryAdaptor(
179 (LayeredSchemeSocketFactory) this.socketFactory);
180 } else {
181 return new SocketFactoryAdaptor(this.socketFactory);
182 }
183 }
184 }
185
186 /**
187 * Obtains the socket factory.
188 * If this scheme is {@link #isLayered layered}, the factory implements
189 * {@link LayeredSocketFactory LayeredSchemeSocketFactory}.
190 *
191 * @return the socket factory for this scheme
192 *
193 * @since 4.1
194 */
195 public final SchemeSocketFactory getSchemeSocketFactory() {
196 return this.socketFactory;
197 }
198
199 /**
200 * Obtains the scheme name.
201 *
202 * @return the name of this scheme, in lowercase
203 */
204 public final String getName() {
205 return name;
206 }
207
208 /**
209 * Indicates whether this scheme allows for layered connections.
210 *
211 * @return <code>true</code> if layered connections are possible,
212 * <code>false</code> otherwise
213 */
214 public final boolean isLayered() {
215 return layered;
216 }
217
218 /**
219 * Resolves the correct port for this scheme.
220 * Returns the given port if it is valid, the default port otherwise.
221 *
222 * @param port the port to be resolved,
223 * a negative number to obtain the default port
224 *
225 * @return the given port or the defaultPort
226 */
227 public final int resolvePort(int port) {
228 return port <= 0 ? defaultPort : port;
229 }
230
231 /**
232 * Return a string representation of this object.
233 *
234 * @return a human-readable string description of this scheme
235 */
236 @Override
237 public final String toString() {
238 if (stringRep == null) {
239 StringBuilder buffer = new StringBuilder();
240 buffer.append(this.name);
241 buffer.append(':');
242 buffer.append(Integer.toString(this.defaultPort));
243 stringRep = buffer.toString();
244 }
245 return stringRep;
246 }
247
248 @Override
249 public final boolean equals(Object obj) {
250 if (this == obj) return true;
251 if (obj instanceof Scheme) {
252 Scheme that = (Scheme) obj;
253 return this.name.equals(that.name)
254 && this.defaultPort == that.defaultPort
255 && this.layered == that.layered;
256 } else {
257 return false;
258 }
259 }
260
261 @Override
262 public int hashCode() {
263 int hash = LangUtils.HASH_SEED;
264 hash = LangUtils.hashCode(hash, this.defaultPort);
265 hash = LangUtils.hashCode(hash, this.name);
266 hash = LangUtils.hashCode(hash, this.layered);
267 return hash;
268 }
269
270 }