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