View Javadoc
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 net.sf.atmodem4j.spsw;
26  
27  /*
28   * #%L
29   * SPSW Java
30   * %%
31   * Copyright (C) 2009 - 2014 atmodem4j
32   * %%
33   * atmodem4j - A serial port socket wrapper- http://atmodem4j.sourceforge.net/
34   * Copyright (C) 2009-2014, atmodem4j.sf.net, and individual contributors as indicated
35   * by the @authors tag. See the copyright.txt in the distribution for a
36   * full listing of individual contributors.
37   * 
38   * This is free software; you can redistribute it and/or modify it
39   * under the terms of the GNU General Public License as
40   * published by the Free Software Foundation; either version 3 of
41   * the License, or (at your option) any later version.
42   * 
43   * This software is distributed in the hope that it will be useful,
44   * but WITHOUT ANY WARRANTY; without even the implied warranty of
45   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46   * Lesser General Public License for more details.
47   * 
48   * You should have received a copy of the GNU Lesser General Public
49   * License along with this software; if not, write to the Free
50   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
51   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
52   * #L%
53   */
54  
55  import java.io.File;
56  import java.io.IOException;
57  import java.util.Collections;
58  import java.util.Comparator;
59  import java.util.Set;
60  import java.util.TreeSet;
61  import java.util.logging.Level;
62  import java.util.logging.Logger;
63  import java.util.regex.Pattern;
64  
65  /**
66   *
67   * @author scream3r
68   */
69  public class SerialPortList {
70      
71      private final static Logger LOG = Logger.getLogger("net.sf.atmodem4j.spsw");
72  
73      private static final Pattern PORTNAMES_REGEXP;
74      private static final String PORTNAMES_PATH;
75  
76      static {
77          switch (AbstractSerialPortSocket.getOsName()) {
78              case "linux": {
79                  PORTNAMES_REGEXP = Pattern.compile("(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}");
80                  PORTNAMES_PATH = "/dev/";
81                  break;
82              }
83              case "SunOS": {
84                  PORTNAMES_REGEXP = Pattern.compile("[0-9]*|[a-z]*");
85                  PORTNAMES_PATH = "/dev/term/";
86                  break;
87              }
88              case "Mac OS X":
89              case "Darwin": {
90                  PORTNAMES_REGEXP = Pattern.compile("tty.(serial|usbserial|usbmodem).*");
91                  PORTNAMES_PATH = "/dev/";
92                  break;
93              }
94              case "windows": {
95                  PORTNAMES_REGEXP = Pattern.compile("");
96                  PORTNAMES_PATH = "";
97                  break;
98              }
99              default: {
100                 LOG.log(Level.SEVERE, "Unknown OS, os.name: {0} mapped to: {1}", new Object[]{System.getProperty("os.name"), AbstractSerialPortSocket.getOsName()});
101                 PORTNAMES_REGEXP = null;
102                 PORTNAMES_PATH = null;
103                 break;
104             }
105         }
106     }
107 
108     //since 2.1.0 -> Fully rewrited port name comparator
109     private static final Comparator<String> PORTNAMES_COMPARATOR = new Comparator<String>() {
110 
111         @Override
112         public int compare(String valueA, String valueB) {
113 
114             if (valueA.equalsIgnoreCase(valueB)) {
115                 return valueA.compareTo(valueB);
116             }
117 
118             int minLength = Math.min(valueA.length(), valueB.length());
119 
120             int shiftA = 0;
121             int shiftB = 0;
122 
123             for (int i = 0; i < minLength; i++) {
124                 char charA = valueA.charAt(i - shiftA);
125                 char charB = valueB.charAt(i - shiftB);
126                 if (charA != charB) {
127                     if (Character.isDigit(charA) && Character.isDigit(charB)) {
128                         int[] resultsA = getNumberAndLastIndex(valueA, i - shiftA);
129                         int[] resultsB = getNumberAndLastIndex(valueB, i - shiftB);
130 
131                         if (resultsA[0] != resultsB[0]) {
132                             return resultsA[0] - resultsB[0];
133                         }
134 
135                         if (valueA.length() < valueB.length()) {
136                             i = resultsA[1];
137                             shiftB = resultsA[1] - resultsB[1];
138                         } else {
139                             i = resultsB[1];
140                             shiftA = resultsB[1] - resultsA[1];
141                         }
142                     } else {
143                         if (Character.toLowerCase(charA) - Character.toLowerCase(charB) != 0) {
144                             return Character.toLowerCase(charA) - Character.toLowerCase(charB);
145                         }
146                     }
147                 }
148             }
149             return valueA.compareToIgnoreCase(valueB);
150         }
151 
152         /**
153          * Evaluate port <b>index/number</b> from <b>startIndex</b> to the
154          * number end. For example: for port name <b>serial-123-FF</b> you
155          * should invoke this method with <b>startIndex = 7</b>
156          *
157          * @return If port <b>index/number</b> correctly evaluated it value will
158          * be returned<br>
159          * <b>returnArray[0] = index/number</b><br>
160          * <b>returnArray[1] = stopIndex</b><br>
161          *
162          * If incorrect:<br>
163          * <b>returnArray[0] = -1</b><br>
164          * <b>returnArray[1] = startIndex</b><br>
165          *
166          * For this name <b>serial-123-FF</b> result is:
167          * <b>returnArray[0] = 123</b><br>
168          * <b>returnArray[1] = 10</b><br>
169          */
170         private int[] getNumberAndLastIndex(String str, int startIndex) {
171             String numberValue = "";
172             int[] returnValues = {-1, startIndex};
173             for (int i = startIndex; i < str.length(); i++) {
174                 returnValues[1] = i;
175                 char c = str.charAt(i);
176                 if (Character.isDigit(c)) {
177                     numberValue += c;
178                 } else {
179                     break;
180                 }
181             }
182             try {
183                 returnValues[0] = Integer.valueOf(numberValue);
184             } catch (Exception ex) {
185                 //Do nothing
186             }
187             return returnValues;
188         }
189     };
190     //<-since 2.1.0
191 
192     /**
193      * Get sorted array of serial ports in the system using default
194      * settings:<br>
195      *
196      * <b>Search path</b><br>
197      * Windows - ""(always ignored)<br>
198      * Linux - "/dev/"<br>
199      * Solaris - "/dev/term/"<br>
200      * MacOSX - "/dev/"<br>
201      *
202      * <b>RegExp</b><br>
203      * Windows - ""<br>
204      * Linux - "(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm)[0-9]{1,3}"<br>
205      * Solaris - "[0-9]*|[a-z]*"<br>
206      * MacOSX - "tty.(serial|usbserial|usbmodem).*"<br>
207      *
208      * @return String array. If there is no ports in the system String[] with
209      * <b>zero</b> length will be returned (since jSSC-0.8 in previous versions
210      * null will be returned)
211      */
212     public static Set<String> getPortNames(boolean hideBusyPorts) {
213         return getPortNames(PORTNAMES_PATH, PORTNAMES_REGEXP, PORTNAMES_COMPARATOR, hideBusyPorts);
214     }
215 
216     /**
217      * Get sorted array of serial ports in the system located on searchPath
218      *
219      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
220      * The default search paths:<br>
221      * Linux, MacOSX: <b>/dev/</b><br>
222      * Solaris: <b>/dev/term/</b><br>
223      * Windows: <b>this parameter ingored</b>
224      *
225      * @return String array. If there is no ports in the system String[]
226      *
227      * @since 2.3.0
228      */
229     public static Set<String> getPortNames(String searchPath, boolean hideBusyPorts) {
230         return getPortNames(searchPath, PORTNAMES_REGEXP, PORTNAMES_COMPARATOR, hideBusyPorts);
231     }
232 
233     /**
234      * Get sorted array of serial ports in the system matched pattern
235      *
236      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
237      *
238      * @return String array. If there is no ports in the system String[]
239      *
240      * @since 2.3.0
241      */
242     public static Set<String> getPortNames(Pattern pattern, boolean hideBusyPorts) {
243         return getPortNames(PORTNAMES_PATH, pattern, PORTNAMES_COMPARATOR, hideBusyPorts);
244     }
245 
246     /**
247      * Get sorted array of serial ports in the system matched pattern
248      *
249      * @param comparator Comparator for sotring port names <b>(not null)</b>
250      *
251      * @return String array. If there is no ports in the system String[]
252      *
253      * @since 2.3.0
254      */
255     public static Set<String> getPortNames(Comparator<String> comparator, boolean hideBusyPorts) {
256         return getPortNames(PORTNAMES_PATH, PORTNAMES_REGEXP, comparator, hideBusyPorts);
257     }
258 
259     /**
260      * Get sorted array of serial ports in the system located on searchPath,
261      * matched pattern
262      *
263      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
264      * The default search paths:<br>
265      * Linux, MacOSX: <b>/dev/</b><br>
266      * Solaris: <b>/dev/term/</b><br>
267      * Windows: <b>this parameter ingored</b>
268      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
269      *
270      * @return String array. If there is no ports in the system String[]
271      *
272      * @since 2.3.0
273      */
274     public static Set<String> getPortNames(String searchPath, Pattern pattern, boolean hideBusyPorts) {
275         return getPortNames(searchPath, pattern, PORTNAMES_COMPARATOR, hideBusyPorts);
276     }
277 
278     /**
279      * Get sorted array of serial ports in the system located on searchPath and
280      * 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 comparator Comparator for sotring port names <b>(not null)</b>
288      *
289      * @return String array. If there is no ports in the system String[]
290      *
291      * @since 2.3.0
292      */
293     public static Set<String> getPortNames(String searchPath, Comparator<String> comparator, boolean hideBusyPorts) {
294         return getPortNames(searchPath, PORTNAMES_REGEXP, comparator, hideBusyPorts);
295     }
296 
297     /**
298      * Get sorted array of serial ports in the system matched pattern and sorted
299      * by comparator
300      *
301      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
302      * @param comparator Comparator for sotring port names <b>(not null)</b>
303      *
304      * @return String array. If there is no ports in the system String[]
305      *
306      * @since 2.3.0
307      */
308     public static Set<String> getPortNames(Pattern pattern, Comparator<String> comparator, boolean hideBusyPorts) {
309         return getPortNames(PORTNAMES_PATH, pattern, comparator, hideBusyPorts);
310     }
311 
312     /**
313      * Get sorted array of serial ports in the system located on searchPath,
314      * matched pattern and sorted by comparator
315      *
316      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
317      * The default search paths:<br>
318      * Linux, MacOSX: <b>/dev/</b><br>
319      * Solaris: <b>/dev/term/</b><br>
320      * Windows: <b>this parameter ingored</b>
321      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
322      * @param comparator Comparator for sotring port names <b>(not null)</b>
323      * @param hideBusyPorts
324      *
325      * @return String array. If there is no ports in the system String[]
326      *
327      * @since 2.3.0
328      */
329     public static Set<String> getPortNames(String searchPath, Pattern pattern, Comparator<String> comparator, boolean hideBusyPorts) {
330         if (searchPath == null || pattern == null || comparator == null) {
331             return Collections.emptySet();
332         }
333         switch (AbstractSerialPortSocket.getOsName()) {
334             case "windows": 
335                 if (!AbstractSerialPortSocket.isLibLoaded()) {
336                     //Make sure lib is loaded to avoid Link error
337                     AbstractSerialPortSocket.loadNativeLib();
338                 }
339                 return getWindowsPortNames(pattern, comparator, hideBusyPorts);
340             default:
341                 return getUnixBasedPortNames(searchPath, pattern, comparator, hideBusyPorts);
342         }
343     }
344 
345     /**
346      * Get serial port names in Windows
347      *
348      * @since 2.3.0
349      */
350     private static Set<String> getWindowsPortNames(Pattern pattern, Comparator<String> comparator, boolean hideBusyPorts) {
351         try {
352             String[] portNames = getWindowsBasedPortNames(hideBusyPorts);
353             if (portNames == null) {
354                 return Collections.emptySet();
355             }
356             TreeSet<String> ports = new TreeSet<>(comparator);
357             for (String portName : portNames) {
358                 if (pattern.matcher(portName).find()) {
359                     ports.add(portName);
360                 }
361             }
362             return ports;
363         } catch (IOException ex) {
364             throw new RuntimeException(ex);
365         }
366     }
367 
368     /**
369      * Get serial port names like an array of String
370      *
371      * @return unsorted array of String with port names
372      */
373     private static native String[] getWindowsBasedPortNames(boolean hideBusyPorts) throws IOException;
374 
375     /**
376      * Universal method for getting port names of _nix based systems
377      */
378     private static Set<String> getUnixBasedPortNames(String searchPath, Pattern pattern, Comparator<String> comparator, boolean hideBusyPorts) {
379         searchPath = (searchPath.equals("") ? searchPath : (searchPath.endsWith("/") ? searchPath : searchPath + "/"));
380         File dir = new File(searchPath);
381         if (dir.exists() && dir.isDirectory()) {
382             File[] files = dir.listFiles();
383             if (files.length > 0) {
384                 TreeSet<String> portsTree = new TreeSet<>(comparator);
385                 for (File file : files) {
386                     String fileName = file.getName();
387                     if (!file.isDirectory() && !file.isFile() && pattern.matcher(fileName).find()) {
388                         String portName = searchPath + fileName;
389                         if (hideBusyPorts) {
390                             try (SerialPortSocket sp = SerialPortSocket.FACTORY.createSerialPortSocket(portName)) {
391                                 sp.openAsIs();
392                                 portsTree.add(portName);
393                             } catch (IOException ex) {
394                                 LOG.log(Level.FINEST, "find busy ports: "+ portName, ex);
395                             }
396                         } else {
397                             portsTree.add(portName);
398                         }
399                     }
400                 }
401                 return portsTree;
402             }
403         }
404         return Collections.emptySet();
405     }
406 }