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 }