001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.test;
018    
019    import java.io.IOException;
020    import java.net.DatagramSocket;
021    import java.net.ServerSocket;
022    import java.util.NoSuchElementException;
023    import java.util.concurrent.atomic.AtomicInteger;
024    
025    
026    /**
027     * Finds currently available server ports.
028     *
029     * @see <a href="http://www.iana.org/assignments/currentMinPort-numbers">IANA.org</a>
030     */
031    public final class AvailablePortFinder {
032        /**
033         * The minimum server currentMinPort number. Set at 1024 to avoid returning privileged
034         * currentMinPort numbers.
035         */
036        public static final int MIN_PORT_NUMBER = 1024;
037    
038        /**
039         * The maximum server currentMinPort number.
040         */
041        public static final int MAX_PORT_NUMBER = 49151;
042    
043    
044        /**
045         * Incremented to the next lowest available port when getNextAvailable() is called.
046         */
047        private static AtomicInteger currentMinPort = new AtomicInteger(MIN_PORT_NUMBER);
048    
049        /**
050         * Creates a new instance.
051         */
052        private AvailablePortFinder() {
053            // Do nothing
054        }
055    
056        /**
057         * Gets the next available currentMinPort starting at the lowest currentMinPort number. This is the preferred
058         * method to use. The port return is immediately marked in use and doesn't rely on the caller actually opening
059         * the port.
060         *
061         * @throws NoSuchElementException if there are no ports available
062         */
063        public static synchronized int getNextAvailable() {
064            int next = getNextAvailable(currentMinPort.get());
065            currentMinPort.set(next + 1);
066            return next;
067        }
068    
069        /**
070         * Gets the next available currentMinPort starting at a currentMinPort.
071         *
072         * @param fromPort the currentMinPort to scan for availability
073         * @throws NoSuchElementException if there are no ports available
074         */
075        public static synchronized int getNextAvailable(int fromPort) {
076            if (fromPort < currentMinPort.get() || fromPort > MAX_PORT_NUMBER) {
077                throw new IllegalArgumentException("Invalid start currentMinPort: " + fromPort);
078            }
079    
080            for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) {
081                if (available(i)) {
082                    return i;
083                }
084            }
085    
086            throw new NoSuchElementException("Could not find an available currentMinPort above " + fromPort);
087        }
088    
089        /**
090         * Checks to see if a specific currentMinPort is available.
091         *
092         * @param port the currentMinPort to check for availability
093         */
094        public static boolean available(int port) {
095            if (port < currentMinPort.get() || port > MAX_PORT_NUMBER) {
096                throw new IllegalArgumentException("Invalid start currentMinPort: " + port);
097            }
098    
099            ServerSocket ss = null;
100            DatagramSocket ds = null;
101            try {
102                ss = new ServerSocket(port);
103                ss.setReuseAddress(true);
104                ds = new DatagramSocket(port);
105                ds.setReuseAddress(true);
106                return true;
107            } catch (IOException e) {
108                // Do nothing
109            } finally {
110                if (ds != null) {
111                    ds.close();
112                }
113    
114                if (ss != null) {
115                    try {
116                        ss.close();
117                    } catch (IOException e) {
118                        /* should not be thrown */
119                    }
120                }
121            }
122    
123            return false;
124        }
125    }