1 /* jSSC (Java Simple Serial Connector) - serial port communication library.
2  * © Alexey Sokolov (scream3r), 2010-2014.
3  *
4  * This file is part of jSSC.
5  *
6  * jSSC is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * jSSC is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with jSSC.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * If you use jSSC in public project you can inform me about this by e-mail,
20  * of course if you want it.
21  *
22  * e-mail: scream3r.org@gmail.com
23  * web-site: http://scream3r.org | http://code.google.com/p/java-simple-serial-connector/
24  */
25 package jssc;
26 
27 import java.io.File;
28 import java.util.Comparator;
29 import java.util.TreeSet;
30 import java.util.regex.Pattern;
31 
32 /**
33  *
34  * @author scream3r
35  */
36 public class SerialPortList {
37 
38     private static SerialNativeInterface serialInterface;
39     private static final Pattern PORTNAMES_REGEXP;
40     private static final String PORTNAMES_PATH;
41 
42     static {
43         serialInterface = new SerialNativeInterface();
44         switch (SerialNativeInterface.getOsType()) {
45             case SerialNativeInterface.OS_LINUX: {
46                 PORTNAMES_REGEXP = Pattern.compile("(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}");
47                 PORTNAMES_PATH = "/dev/";
48                 break;
49             }
50             case SerialNativeInterface.OS_SOLARIS: {
51                 PORTNAMES_REGEXP = Pattern.compile("[0-9]*|[a-z]*");
52                 PORTNAMES_PATH = "/dev/term/";
53                 break;
54             }
55             case SerialNativeInterface.OS_MAC_OS_X: {
56                 PORTNAMES_REGEXP = Pattern.compile("tty.(serial|usbserial|usbmodem).*");
57                 PORTNAMES_PATH = "/dev/";
58                 break;
59             }
60             case SerialNativeInterface.OS_FREEBSD: {
61                 PORTNAMES_REGEXP = Pattern.compile("cuaU[0-9]{1,3}$");
62                 PORTNAMES_PATH = "/dev/";
63                 break;
64             }
65             case SerialNativeInterface.OS_WINDOWS: {
66                 PORTNAMES_REGEXP = Pattern.compile("");
67                 PORTNAMES_PATH = "";
68                 break;
69             }
70             default: {
71                 PORTNAMES_REGEXP = null;
72                 PORTNAMES_PATH = null;
73                 break;
74             }
75         }
76     }
77 
78     //since 2.1.0 -> Fully rewrited port name comparator
79     private static final Comparator<String> PORTNAMES_COMPARATOR = new Comparator<String>() {
80 
81         @Override
82         public int compare(String valueA, String valueB) {
83 
84             if(valueA.equalsIgnoreCase(valueB)){
85                 return valueA.compareTo(valueB);
86             }
87 
88             int minLength = Math.min(valueA.length(), valueB.length());
89 
90             int shiftA = 0;
91             int shiftB = 0;
92 
93             for(int i = 0; i < minLength; i++){
94                 char charA = valueA.charAt(i - shiftA);
95                 char charB = valueB.charAt(i - shiftB);
96                 if(charA != charB){
97                     if(Character.isDigit(charA) && Character.isDigit(charB)){
98                         int[] resultsA = getNumberAndLastIndex(valueA, i - shiftA);
99                         int[] resultsB = getNumberAndLastIndex(valueB, i - shiftB);
100 
101                         if(resultsA[0] != resultsB[0]){
102                             return resultsA[0] - resultsB[0];
103                         }
104 
105                         if(valueA.length() < valueB.length()){
106                             i = resultsA[1];
107                             shiftB = resultsA[1] - resultsB[1];
108                         }
109                         else {
110                             i = resultsB[1];
111                             shiftA = resultsB[1] - resultsA[1];
112                         }
113                     }
114                     else {
115                         if(Character.toLowerCase(charA) - Character.toLowerCase(charB) != 0){
116                             return Character.toLowerCase(charA) - Character.toLowerCase(charB);
117                         }
118                     }
119                 }
120             }
121             return valueA.compareToIgnoreCase(valueB);
122         }
123 
124         /**
125          * Evaluate port <b>index/number</b> from <b>startIndex</b> to the number end. For example:
126          * for port name <b>serial-123-FF</b> you should invoke this method with <b>startIndex = 7</b>
127          *
128          * @return If port <b>index/number</b> correctly evaluated it value will be returned<br>
129          * <b>returnArray[0] = index/number</b><br>
130          * <b>returnArray[1] = stopIndex</b><br>
131          *
132          * If incorrect:<br>
133          * <b>returnArray[0] = -1</b><br>
134          * <b>returnArray[1] = startIndex</b><br>
135          *
136          * For this name <b>serial-123-FF</b> result is:
137          * <b>returnArray[0] = 123</b><br>
138          * <b>returnArray[1] = 10</b><br>
139          */
140         private int[] getNumberAndLastIndex(String str, int startIndex) {
141             String numberValue = "";
142             int[] returnValues = {-1, startIndex};
143             for(int i = startIndex; i < str.length(); i++){
144                 returnValues[1] = i;
145                 char c = str.charAt(i);
146                 if(Character.isDigit(c)){
147                     numberValue += c;
148                 }
149                 else {
150                     break;
151                 }
152             }
153             try {
154                 returnValues[0] = Integer.valueOf(numberValue);
155             }
156             catch (Exception ex) {
157                 //Do nothing
158             }
159             return returnValues;
160         }
161     };
162     //<-since 2.1.0
163 
164     /**
165      * Get sorted array of serial ports in the system using default settings:<br>
166      *
167      * <b>Search path</b><br>
168      * Windows - ""(always ignored)<br>
169      * Linux - "/dev/"<br>
170      * Solaris - "/dev/term/"<br>
171      * MacOSX - "/dev/"<br>
172      *
173      * <b>RegExp</b><br>
174      * Windows - ""<br>
175      * Linux - "(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm)[0-9]{1,3}"<br>
176      * Solaris - "[0-9]*|[a-z]*"<br>
177      * MacOSX - "tty.(serial|usbserial|usbmodem).*"<br>
178      *
179      * @return String array. If there is no ports in the system String[]
180      * with <b>zero</b> length will be returned (since jSSC-0.8 in previous versions null will be returned)
181      */
getPortNames()182     public static String[] getPortNames() {
183         return getPortNames(PORTNAMES_PATH, PORTNAMES_REGEXP, PORTNAMES_COMPARATOR);
184     }
185 
186     /**
187      * Get sorted array of serial ports in the system located on searchPath
188      *
189      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
190      * The default search paths:<br>
191      * Linux, MacOSX: <b>/dev/</b><br>
192      * Solaris: <b>/dev/term/</b><br>
193      * Windows: <b>this parameter ingored</b>
194      *
195      * @return String array. If there is no ports in the system String[]
196      *
197      * @since 2.3.0
198      */
getPortNames(String searchPath)199     public static String[] getPortNames(String searchPath) {
200         return getPortNames(searchPath, PORTNAMES_REGEXP, PORTNAMES_COMPARATOR);
201     }
202 
203     /**
204      * Get sorted array of serial ports in the system matched pattern
205      *
206      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
207      *
208      * @return String array. If there is no ports in the system String[]
209      *
210      * @since 2.3.0
211      */
getPortNames(Pattern pattern)212     public static String[] getPortNames(Pattern pattern) {
213         return getPortNames(PORTNAMES_PATH, pattern, PORTNAMES_COMPARATOR);
214     }
215 
216     /**
217      * Get sorted array of serial ports in the system matched pattern
218      *
219      * @param comparator Comparator for sotring port names <b>(not null)</b>
220      *
221      * @return String array. If there is no ports in the system String[]
222      *
223      * @since 2.3.0
224      */
getPortNames(Comparator<String> comparator)225     public static String[] getPortNames(Comparator<String> comparator) {
226         return getPortNames(PORTNAMES_PATH, PORTNAMES_REGEXP, comparator);
227     }
228 
229     /**
230      * Get sorted array of serial ports in the system located on searchPath, matched pattern
231      *
232      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
233      * The default search paths:<br>
234      * Linux, MacOSX: <b>/dev/</b><br>
235      * Solaris: <b>/dev/term/</b><br>
236      * Windows: <b>this parameter ingored</b>
237      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
238      *
239      * @return String array. If there is no ports in the system String[]
240      *
241      * @since 2.3.0
242      */
getPortNames(String searchPath, Pattern pattern)243     public static String[] getPortNames(String searchPath, Pattern pattern) {
244         return getPortNames(searchPath, pattern, PORTNAMES_COMPARATOR);
245     }
246 
247     /**
248      * Get sorted array of serial ports in the system located on searchPath and sorted by comparator
249      *
250      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
251      * The default search paths:<br>
252      * Linux, MacOSX: <b>/dev/</b><br>
253      * Solaris: <b>/dev/term/</b><br>
254      * Windows: <b>this parameter ingored</b>
255      * @param comparator Comparator for sotring port names <b>(not null)</b>
256      *
257      * @return String array. If there is no ports in the system String[]
258      *
259      * @since 2.3.0
260      */
getPortNames(String searchPath, Comparator<String> comparator)261     public static String[] getPortNames(String searchPath, Comparator<String> comparator) {
262         return getPortNames(searchPath, PORTNAMES_REGEXP, comparator);
263     }
264 
265     /**
266      * Get sorted array of serial ports in the system matched pattern and sorted by comparator
267      *
268      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
269      * @param comparator Comparator for sotring port names <b>(not null)</b>
270      *
271      * @return String array. If there is no ports in the system String[]
272      *
273      * @since 2.3.0
274      */
getPortNames(Pattern pattern, Comparator<String> comparator)275     public static String[] getPortNames(Pattern pattern, Comparator<String> comparator) {
276         return getPortNames(PORTNAMES_PATH, pattern, comparator);
277     }
278 
279     /**
280      * Get sorted array of serial ports in the system located on searchPath, matched pattern and sorted by comparator
281      *
282      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
283      * The default search paths:<br>
284      * Linux, MacOSX: <b>/dev/</b><br>
285      * Solaris: <b>/dev/term/</b><br>
286      * Windows: <b>this parameter ingored</b>
287      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
288      * @param comparator Comparator for sotring port names <b>(not null)</b>
289      *
290      * @return String array. If there is no ports in the system String[]
291      *
292      * @since 2.3.0
293      */
getPortNames(String searchPath, Pattern pattern, Comparator<String> comparator)294     public static String[] getPortNames(String searchPath, Pattern pattern, Comparator<String> comparator) {
295         if(searchPath == null || pattern == null || comparator == null){
296             return new String[]{};
297         }
298         if(SerialNativeInterface.getOsType() == SerialNativeInterface.OS_WINDOWS){
299             return getWindowsPortNames(pattern, comparator);
300         }
301         return getUnixBasedPortNames(searchPath, pattern, comparator);
302     }
303 
304     /**
305      * Get serial port names in Windows
306      *
307      * @since 2.3.0
308      */
getWindowsPortNames(Pattern pattern, Comparator<String> comparator)309     private static String[] getWindowsPortNames(Pattern pattern, Comparator<String> comparator) {
310         String[] portNames = serialInterface.getSerialPortNames();
311         if(portNames == null){
312             return new String[]{};
313         }
314         TreeSet<String> ports = new TreeSet<String>(comparator);
315         for(String portName : portNames){
316             if(pattern.matcher(portName).find()){
317                 ports.add(portName);
318             }
319         }
320         return ports.toArray(new String[ports.size()]);
321     }
322 
323     /**
324      * Universal method for getting port names of _nix based systems
325      */
getUnixBasedPortNames(String searchPath, Pattern pattern, Comparator<String> comparator)326     private static String[] getUnixBasedPortNames(String searchPath, Pattern pattern, Comparator<String> comparator) {
327         searchPath = (searchPath.equals("") ? searchPath : (searchPath.endsWith("/") ? searchPath : searchPath + "/"));
328         String[] returnArray = new String[]{};
329         File dir = new File(searchPath);
330         if(dir.exists() && dir.isDirectory()){
331             File[] files = dir.listFiles();
332             if(files.length > 0){
333                 TreeSet<String> portsTree = new TreeSet<String>(comparator);
334                 for(File file : files){
335                     String fileName = file.getName();
336                     if(!file.isDirectory() && !file.isFile() && pattern.matcher(fileName).find()){
337                         String portName = searchPath + fileName;
338                         long portHandle = serialInterface.openPort(portName, false);//Open port without TIOCEXCL
339                         if(portHandle < 0 && portHandle != SerialNativeInterface.ERR_PORT_BUSY){
340                             continue;
341                         }
342                         else if(portHandle != SerialNativeInterface.ERR_PORT_BUSY) {
343                             serialInterface.closePort(portHandle);
344                         }
345                         portsTree.add(portName);
346                     }
347                 }
348                 returnArray = portsTree.toArray(returnArray);
349             }
350         }
351         return returnArray;
352     }
353 }
354