zaterdag 25 januari 2014

Using Java Enums as a record description, and access them in a generic way, using reflection.

 I love to use java enums in my software. 

You can use them as field-definitions for a datadictionary. 

for example:
public enum _ENUM_FIELDS {
        _SITE_NR('s', "Site"), _COUNTRY('c', "Country");

        public char id;
        public final String text;

        private _ENUM_FIELDS(char id, String text) {
            this.id = id;
            this.text = text;
        }

        @Override
        public String toString() {
            return getString(id, name(), text);
        }
    }
The enum _ENUM_FIELDS has 2 field-definitions, each entry has 3 attributes.

  • char id = the key of the field. This should be unique in a file-definition.
  • String text = The field description.
  • String Name = is not defined, but can be obtained by calling the name() method of the enum.

By using reflection, enums can be handled in a generic way. The class MaxEnum deals with this part. The class MaxEnumSort implements sorting an enum in various ways, based on the 3 attributes for an entry.

You should override the toString() method in the enum, and call the static function getString(), which calls MaxEnumSort, which sorts the fields within a record.

  •  MaxEnum              - handles enums in a generic way using reflection
  •  MaxEnumSort       - Sort an enum in various ways
  • GenericJavaEnumExample - small example to show the use of MaxEnum
  • MaxRecorderIniDD - example which I use in the application I'm writing now


----------------------------------------------------------------

package nl.yokoz.data;

public abstract class MaxEnumSort  {
    public enum _SORT_ORDER {
        _TEXT, _ID, _NAAM;
    }
    public static _SORT_ORDER sortOrder = _SORT_ORDER._NAAM;
    protected static String toString(char id, String name, String text) {
        switch (sortOrder) {
        case _ID:
            return id + "  " + name + "  " + text;
        case _NAAM:
            return name + "  " + id + "  " + text;
        case _TEXT:
            return text + "  " + id + "  " + name;
        }
        return "";
    }

}
----------------------------------------------------------------
package nl.yokoz.data;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import nl.yokoz.data.MaxEnumSort._SORT_ORDER;

public class MaxEnum {

    private Class theEnum;

    public MaxEnum(Class aClass) {
        if (aClass.isEnum()) {
            theEnum = aClass;
        }
    }

    public void toSysOut() {
        final String[] enumFields = getEnumFields();
        if (enumFields != null) {
            System.out.println(theEnum.getName());
            System.out.println("Sorted on: " + MaxEnumSort.sortOrder);
            for (String enumField : enumFields) {
                System.out.println(enumField); // This will call toString in the
                                                // reflected enum. The final
                                                // text comes
                                                // from MaxEnumSort:toString.
            }
            System.out.println();
        }
    }

    public String[] getEnumFields() {
        String[] enumFields = null;
        if (theEnum != null) {
            List enumConstants =Arrays.asList(theEnum.getEnumConstants());
            enumFields = new String[enumConstants.size()];
            int i = 0;
            for (Object enumConstant : enumConstants) {
                enumFields[i++] = enumConstant.toString();
            }
            Arrays.sort(enumFields);
        }
        return enumFields;
    }

    public void showFields(boolean bNoConstants) {
        if (theEnum != null) {
            Field[] fields = getDeclaredFields(bNoConstants);
            for (Field field : fields) {
                System.out.println(field.toString());
            }
        }
    }

    public Field[] getDeclaredFields(boolean bNoConstants) {
        Field[] fields = theEnum.getDeclaredFields();
        if (!bNoConstants) {
            return fields;
        }
        int i = 0;
        int f = 0;
        int[] fieldIndexes = new int[fields.length];
        for (Field field : fields) {
            if (!field.isEnumConstant()) {
                fieldIndexes[f++] = i;
            }
            i++;
        }
        Field[] fieldsNoConstants = new Field[f];
        for (int j = 0; j < f; j++) {
            fieldsNoConstants[j] = fields[fieldIndexes[j]];
        }
        return fieldsNoConstants;
    }

    public void toSysOutAll() {
        for (_SORT_ORDER sortOrder : MaxEnumSort._SORT_ORDER.values()) {
            MaxEnumSort.sortOrder = sortOrder;
            toSysOut();
        }
    }

}
----------------------------------------------------------------

package nl.yokoz.maxrecorder.db.dd;

import nl.yokoz.data.MaxEnum;
import nl.yokoz.data.MaxEnumSort;

public class GenericJavaEnumExample extends MaxEnumSort {

    public enum _ENUM_FIELDS {
        _SITE_NR('s', "Site"), _COUNTRY('c', "Country"), _TIMEZONE('t', "TimeZone"), _LOCATION_ACTIVE('a', "Location Active");

        public char id;
        public final String text;

        private _ENUM_FIELDS(char id, String text) {
            this.id = id;
            this.text = text;
        }

        @Override
        public String toString() {
            return getString(id, name(), text);
        }

    }

    public static String getString(char id, String name, String text) {
        return toString(id, name, text); // call toString in MaxEnumSort, the
                                            // parent class.

    }

    public static void main(String[] args) {
        final MaxEnum maxEnum = new MaxEnum(GenericJavaEnumExample._ENUM_FIELDS.class);
        maxEnum.showFields(true);
        maxEnum.toSysOutAll();
    }

}

The output when running main() is:

public char nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS.id
public final java.lang.String nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS.text
private static final nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS[] nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS.ENUM$VALUES

nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS
Sorted on: _TEXT
Country  c  _COUNTRY
Location Active  a  _LOCATION_ACTIVE
Site  s  _SITE_NR
TimeZone  t  _TIMEZONE

nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS
Sorted on: _ID
a  _LOCATION_ACTIVE  Location Active
c  _COUNTRY  Country
s  _SITE_NR  Site
t  _TIMEZONE  TimeZone

nl.yokoz.maxrecorder.db.dd.GenericJavaEnumExample$_ENUM_FIELDS
Sorted on: _NAAM
_COUNTRY  c  Country
_LOCATION_ACTIVE  a  Location Active
_SITE_NR  s  Site
_TIMEZONE  t  TimeZone



----------------------------------------------------------------

package nl.yokoz.maxrecorder.db.dd;

import nl.yokoz.data.MaxEnum;
import nl.yokoz.data.MaxEnumSort;

public class MaxRecorderIniDD extends MaxEnumSort {

    public enum _FIELDS {
        _OUTPUTFILE_PREFIX('o', "OutputFilePrefix"), _SITE_NR('s', "Site"), _COUNTRY('c', "Country"), _TIMEZONE('t', "TimeZone"), _LOCATION_ACTIVE('a', "Location Active"), _AWAKE_HOUR('a', "AwakeHour"), _AWAKE_MINUTE('b', "AwakeMinuut"), _NO_SIZECHECK('c', "noSizeCheck"), _SHOW_TIMESTAMP('t', "ShowTimestamp"), _SLEEP_HOUR('h', "SleepHour"), _SLEEP_MINUTE('m', "SleepMinuut"), _EXTENTIE('e', "Extentie"), _CALC_FILENAME('q', "CalcFileName"), _IMAGE_SIZE('s', "Image size"), _LOCATIE_NR('l', "Locatie"), _CAPTURE_INTERVAL_SECONDS('i', "CaptureIntervalSeconds"), _OUTPUTFILE_POSTPREFIX('p', "OutputFilePostFix"), _LOCATION_NAME('n', "LocationName");

        public char id;
        public final String text;

        private _FIELDS(char id, String text) {
            this.id = id;
            this.text = text;
        }

        @Override
        public String toString() {
            return getString(id, name(), text);
        }

    }

    public static String getString(char id, String name, String text) {

         // call toString in MaxEnumSort, the parent class.
         return toString(id, name, text);

    }

    public static void main(String[] args) {
        final MaxEnum maxEnum = new MaxEnum(_FIELDS.class);
        // enable the following line, when you want to change the default
        // sortorder.
        // MaxEnumSort.sortOrder = _SORT_ORDER._TEXT;
        maxEnum.toSysOut();
        maxEnum.showFields(false);
        maxEnum.toSysOutAll();
    }

}

The output when running main() is:
nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS
Sorted on: _NAAM
_AWAKE_HOUR  a  AwakeHour
_AWAKE_MINUTE  b  AwakeMinuut
_CALC_FILENAME  q  CalcFileName
_CAPTURE_INTERVAL_SECONDS  i  CaptureIntervalSeconds
_COUNTRY  c  Country
_EXTENTIE  e  Extentie
_IMAGE_SIZE  s  Image size
_LOCATIE_NR  l  Locatie
_LOCATION_ACTIVE  a  Location Active
_LOCATION_NAME  n  LocationName
_NO_SIZECHECK  c  noSizeCheck
_OUTPUTFILE_POSTPREFIX  p  OutputFilePostFix
_OUTPUTFILE_PREFIX  o  OutputFilePrefix
_SHOW_TIMESTAMP  t  ShowTimestamp
_SITE_NR  s  Site
_SLEEP_HOUR  h  SleepHour
_SLEEP_MINUTE  m  SleepMinuut
_TIMEZONE  t  TimeZone

public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._OUTPUTFILE_PREFIX
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._SITE_NR
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._COUNTRY
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._TIMEZONE
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._LOCATION_ACTIVE
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._AWAKE_HOUR
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._AWAKE_MINUTE
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._NO_SIZECHECK
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._SHOW_TIMESTAMP
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._SLEEP_HOUR
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._SLEEP_MINUTE
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._EXTENTIE
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._CALC_FILENAME
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._IMAGE_SIZE
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._LOCATIE_NR
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._CAPTURE_INTERVAL_SECONDS
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._OUTPUTFILE_POSTPREFIX
public static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS._LOCATION_NAME
public char nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS.id
public final java.lang.String nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS.text
private static final nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS[] nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS.ENUM$VALUES

nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS
Sorted on: _TEXT
AwakeHour  a  _AWAKE_HOUR
AwakeMinuut  b  _AWAKE_MINUTE
CalcFileName  q  _CALC_FILENAME
CaptureIntervalSeconds  i  _CAPTURE_INTERVAL_SECONDS
Country  c  _COUNTRY
Extentie  e  _EXTENTIE
Image size  s  _IMAGE_SIZE
Locatie  l  _LOCATIE_NR
Location Active  a  _LOCATION_ACTIVE
LocationName  n  _LOCATION_NAME
OutputFilePostFix  p  _OUTPUTFILE_POSTPREFIX
OutputFilePrefix  o  _OUTPUTFILE_PREFIX
ShowTimestamp  t  _SHOW_TIMESTAMP
Site  s  _SITE_NR
SleepHour  h  _SLEEP_HOUR
SleepMinuut  m  _SLEEP_MINUTE
TimeZone  t  _TIMEZONE
noSizeCheck  c  _NO_SIZECHECK

nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS
Sorted on: _ID
a  _AWAKE_HOUR  AwakeHour
a  _LOCATION_ACTIVE  Location Active
b  _AWAKE_MINUTE  AwakeMinuut
c  _COUNTRY  Country
c  _NO_SIZECHECK  noSizeCheck
e  _EXTENTIE  Extentie
h  _SLEEP_HOUR  SleepHour
i  _CAPTURE_INTERVAL_SECONDS  CaptureIntervalSeconds
l  _LOCATIE_NR  Locatie
m  _SLEEP_MINUTE  SleepMinuut
n  _LOCATION_NAME  LocationName
o  _OUTPUTFILE_PREFIX  OutputFilePrefix
p  _OUTPUTFILE_POSTPREFIX  OutputFilePostFix
q  _CALC_FILENAME  CalcFileName
s  _IMAGE_SIZE  Image size
s  _SITE_NR  Site
t  _SHOW_TIMESTAMP  ShowTimestamp
t  _TIMEZONE  TimeZone

nl.yokoz.maxrecorder.db.dd.MaxRecorderIniDD$_FIELDS
Sorted on: _NAAM
_AWAKE_HOUR  a  AwakeHour
_AWAKE_MINUTE  b  AwakeMinuut
_CALC_FILENAME  q  CalcFileName
_CAPTURE_INTERVAL_SECONDS  i  CaptureIntervalSeconds
_COUNTRY  c  Country
_EXTENTIE  e  Extentie
_IMAGE_SIZE  s  Image size
_LOCATIE_NR  l  Locatie
_LOCATION_ACTIVE  a  Location Active
_LOCATION_NAME  n  LocationName
_NO_SIZECHECK  c  noSizeCheck
_OUTPUTFILE_POSTPREFIX  p  OutputFilePostFix
_OUTPUTFILE_PREFIX  o  OutputFilePrefix
_SHOW_TIMESTAMP  t  ShowTimestamp
_SITE_NR  s  Site
_SLEEP_HOUR  h  SleepHour
_SLEEP_MINUTE  m  SleepMinuut
_TIMEZONE  t  TimeZone

vrijdag 17 januari 2014

Detecting USB-devices with Java 7.0 under Linux

For my backup-program I wanted to know which USB-disks are available on my system, which runs Linux. USB support in Java is limited to third party libraries, for example, JUSB.

But I found another solution.

The first step is to use the Linux command 'mount'. It shows which partitions are attached to your system.

arjen@linux-ta51:~> mount

      Partial output:
  • /dev/sda7 on /develop type ext3
  • /dev/sda13 on /home type ext4
  • /dev/sda11 on /data type ext4
  • /dev/sdc5 on /bigdata type ext4                    = Usb hard-disk 1
  • /dev/sdb1 on /media/3TB type ext4              = Usb hard-disk 2
  • /dev/sdd1 on /media/USB DISK type vfat    = Usb stick

My system has only 1 hard-disk, so I ignore the lines which start with  '/dev/sda'.
If your system has 2 hard-disks, you should ignore '/dev/sdb' too.

The next step is using the API WatchService, introduced with Java 7.0.
You register the directories with this service, and get notifications, when plugging or unplugging an USB-device.

I've tested this approach in Eclipse with my Usb-stick at /dev/sdd1, and it works!
The usb-stick is mounted on /media/USB DISK. 


You should watch the directory /media, and not /media/USB DISK, to get the notifications.

The Demo-program starts the WatchService in a separate thread. There's no GUI to end this thread. The Demo-program doesn't use the first step, 'mount'.
See the source of MaxDrive for an example of using 'mount'. There is a main procedure for testing this.



package nl.yokoz.maxwatchdir;

import java.nio.file.Path;
import java.nio.file.Paths;

import nl.yokoz.io.file.util.dir.MaxWatchDir;

public class MaxWatchDirDemo extends MaxWatchDir {

    public MaxWatchDirDemo(Path dir) {
        super(dir);
    }

    public static void main(String[] args) {
        Path dir = Paths.get("/media/USB DISK");
        new MaxWatchDirDemo(dir).start();
    }

    @Override
    protected boolean onModify(Path child) {
        return false;
    }

    @Override
    protected boolean onCreate(Path child) {
        sysOut(child, true);
        return false;
    }

    @Override
    protected boolean onDelete(Path child) {
        sysOut(child, false);
        return false;
    }

    private void sysOut(Path child, boolean bPlugged) {
        String un;
        if (!bPlugged) {
            un = "un";
        } else {
            un = "";
        }
        System.out.format("Usb-stick %s: %splugged\n", child, un);
    }

}



----------------------------------------------------------------------



package nl.yokoz.io.file.util.dir;

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * http://docs.oracle.com/javase/tutorial/displayCode.html?code=http://docs.oracle.com/javase/tutorial/essential/io/examples/WatchDir.java
 *
 */

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;

class MaxFileName {

    private final String fileName;

    public MaxFileName(String aFileName) {
        fileName = aFileName;
    }

    public boolean isFile() {
        return newFile().isFile();
    }

    public String getParent() {
        return newFile().getParent();
    }

    public boolean hasParent() {
        final String parent = getParent();
        return !parent.equalsIgnoreCase("/");
    }

    private File newFile() {
        return new File(fileName);
    }

}

/**
 * Watch a directory (or tree) for changes to files.
 */
public abstract class MaxWatchDir extends Thread {

    public enum MaxWatchEventKinds {
        _CREATE(ENTRY_CREATE.toString()), _MODIFY(ENTRY_MODIFY.toString()), _DELETE(ENTRY_DELETE.toString()), _OVERFLOW(OVERFLOW.toString());

        private final String name;

        private MaxWatchEventKinds(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name.toString();
        }

        public static MaxWatchEventKinds parse(Kind kind) {
            for (MaxWatchEventKinds aKind : values()) {
                if (aKind.toString().equalsIgnoreCase(kind.name())) {
                    return aKind;
                }
            }
            return null;
        }
    }

    abstract protected boolean onModify(Path child);

    abstract protected boolean onDelete(Path child);

    abstract protected boolean onCreate(Path child);

    private WatchService watcher;
    private Map keys;
    private boolean recursive;
    private boolean trace = false;

    public MaxWatchDir() {
        super("MaxWatchDir");
        try {
            watcher = FileSystems.getDefault().newWatchService();
            keys = new HashMap();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public MaxWatchDir(Path dir) {
        this(dir, false);
    }

    public MaxWatchDir(Path dir, boolean recursive) {
        this();
        this.recursive = recursive;
        if (recursive) {
            System.out.format("Scanning %s ...\n", dir);
            registerRecursive(dir);
            System.out.println("Done.");
        } else {
            add(dir);
        }
    }

    private void add(Path dir) {
        add(dir.toString());
    }

    /**
     * Register the given directory with the WatchService
     */
    protected boolean register(Path dir) {
        WatchKey key;
        try {
            key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        } catch (IOException e) {
            return false;
        }
        if (trace) {
            Path prev = keys.get(key);
            if (prev == null) {
                System.out.format("register: %s\n", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %s\n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
        return true;
    }

    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    protected void registerRecursive(final Path start) {
        // register directory and sub-directories
        try {
            Files.walkFileTree(start, new SimpleFileVisitor() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
                    register(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Process all events for keys queued to the watcher
     */
    private void processEvents() {
        // enable trace after initial registration
        trace = true;
        for (;;) {
            // wait for key to be signalled
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }
            if (onWatchEvent(key)) {
                // reset key and remove from set if directory no longer
                // accessible
                boolean valid = key.reset();
                if (!valid) {
                    keys.remove(key);
                    // all directories are inaccessible
                    if (keys.isEmpty()) {
                        break;
                    }
                }
            }
        }
    }

    private boolean onWatchEvent(WatchKey key) {
        Path dir = keys.get(key);
        if (dir == null) {
            System.err.println("WatchKey not recognized!!");
            return false;
        }
        for (WatchEvent event : key.pollEvents()) {
            // Context for directory entry event is the file name of entry
            WatchEvent ev = cast(event);
            Path name = ev.context();
            Path child = dir.resolve(name);
            MaxWatchEventKinds kind = MaxWatchEventKinds.parse(event.kind());
            if (kind == null) {
                continue; // this should be unreachable
            }
            showEvent(event, child);
            switch (kind) {
            case _MODIFY:
                if (onModify(child)) {
                }
                break;
            case _OVERFLOW:
                continue;
            case _DELETE:
                if (onDelete(child)) {
                }
                break;
            case _CREATE:
                if (onCreate(child)) {
                }
                // if directory is created, and watching recursively, then
                // register it and its sub-directories
                if (recursive) {
                    if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
                        registerRecursive(child);
                    }
                }
                break;
            }
        }
        return true;
    }

    private void showEvent(WatchEvent event, Path child) {
        System.out.format("%s: %s\n", event.kind().name(), child);
    }

    @SuppressWarnings("unchecked")
    static WatchEvent cast(WatchEvent event) {
        return (WatchEvent) event;
    }

    @Override
    public void run() {
        processEvents();
    }

    public boolean add(String entry2watch) {
        MaxFileName maxFileName = new MaxFileName(entry2watch);
        Path path2register;
        if (maxFileName.isFile()) {
            path2register = Paths.get(maxFileName.getParent());
        } else {
            if (maxFileName.hasParent()) {
                path2register = Paths.get(maxFileName.getParent());
            } else {
                path2register = Paths.get(maxFileName.toString());
            }
        }
        return register(path2register);
    }

    public void stopIt() {
        interrupt();
    }

}

------------------------------------------------------------------------


package nl.yokoz.io.file.util.drive;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;

import nl.yokoz.exec.MaxExecPgm;
import nl.yokoz.string.MaxString;
import nl.yokoz.util.collections.MaxArrayList;

public class MaxDrive {

    private static final String DEV_SD = "/dev/sd";
    private String partition;
    private String path;

    public static void main(String[] args) {
        boolean bExcludeRoot = true;
        MaxArrayList drives = MaxDrive.getDrives(bExcludeRoot);
        MaxArrayList portableDrives = MaxDrive.getPortableDrives(drives);
        for (MaxDrive maxDrive : portableDrives) {
            System.out.println(maxDrive.toString());
        }
    }

    private static MaxArrayList getPortableDrives(MaxArrayList drives) {
        MaxArrayList portableDrives = new MaxArrayList(drives.size());
        for (MaxDrive maxDrive : drives) {
            if (maxDrive.isPortable()) {
                portableDrives.add(maxDrive);
            }
        }
        return portableDrives;
    }

    public static MaxArrayList getPortableDrives() {
        return getPortableDrives(MaxDrive.getDrives(true));
    }

    private boolean isPortable() {
        return !partition.startsWith("a");
    }

    public static MaxArrayList getDrives(boolean bExcludeRoot) {
        MaxArrayList drives = new MaxArrayList();
        try {
            BufferedReader reader = MaxExecPgm.execGetBufferedReader("mount");
            String line = null;
            while ((line = reader.readLine()) != null) {
                MaxDrive drive = MaxDrive.parse(line);
                if (drive != null) {
                    if (bExcludeRoot) {
                        if (drive.isRootDevice()) {
                            continue;
                        }
                    }
                    drives.add(drive);
                }
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return drives;
    }

    private boolean isRootDevice() {
        return path.compareTo("/") == 0;
    }

    /**
     *
     * @param line
     *

     *

     *            /dev/sda12 on / type ext4 (rw,relatime,data=ordered)

     *            /dev/sdb5 on /bigdata type ext4 (rw,relatime,data=ordered)

     *            /dev/sda11 on /data type ext4 (rw,relatime,data=ordered)

     *            /dev/sda13 on /home type ext4 (rw,relatime,data=ordered)

     *            /dev/sdc1 on /media/3TB type ext4 (rw,relatime,data=ordered)

     *            /dev/sdd1 on /media/USB DISK type vfat

     *
     * @return
     */
    private static MaxDrive parse(String line) {
        if (!line.startsWith(DEV_SD)) {
            return null;
        }
        MaxDrive drive = new MaxDrive();
        StringTokenizer st = new StringTokenizer(line);
        int tokensCount = st.countTokens() - 6;
        int fieldNr = 1;
        while (st.hasMoreTokens()) {
            MaxString token = MaxString._new(st.nextToken());
            switch (fieldNr) {
            case 1:
                drive.partition = token.substringRight(DEV_SD.length() + 1);
                break;
            case 3:
                if (tokensCount > 0) {
                    for (int i = 0; i < tokensCount; i++) {
                        token.appendString(" " + st.nextToken());
                    }
                }
                drive.path = token.toString();
                return drive;
            }
            fieldNr++;
        }
        return null;
    }

    @Override
    public String toString() {
        return getPartition() + "   " + path;
    }

    public final String getPartition() {
        return DEV_SD + partition;
    }

    public final String getPath() {
        return path;
    }

}


-------------------------------------------------------------------------------

package nl.yokoz.exec;

import java.io.*;

public class MaxExecPgm {

     public static BufferedReader execGetBufferedReader(String cmd) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return reader;
    }


}

-------------------------------------------------------------------------------

 package nl.yokoz.util.collections;

import java.util.ArrayList;

public class MaxArrayList extends ArrayList {

    private static final long serialVersionUID = 1L;

    public MaxArrayList() {
    }

    public MaxArrayList(int size) {
        super(size);
    }

    public E firstElement() {
        return get(0);
    }

    public E lastElement() {
        return get(size() - 1);
    }

}


-------------------------------------------------------------------------------

package nl.yokoz.string;

import java.io.File;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.StringTokenizer;
import java.util.TimeZone;

final public class MaxString {

    private static final String _ZERO = "0";
    public static char DOT = '.';
    public static char COMMA = ',';
    private String s;
    public final static String _EMPTY_STRING = "";

    public MaxString() {
        set2empty();
    }

    private void set2empty() {
        s = _EMPTY_STRING;
    }

    public MaxString(String aString) {
        if (aString != null) {
            if (aString.length() != 0) {
                s = aString;
                return;
            }
        }
        set2empty();
    }

    public MaxString(int aInt) {
        this(String.valueOf(aInt));
    }

    public MaxString(MaxString aMaxString) {
        this(aMaxString.s);
    }

    public MaxString(Object o) {
        if (o == null) {
            set2empty();
            return;
        }
        s = o.toString();
        if (Boolean.class.isInstance(o)) {
            Boolean b = new Boolean(s);
            if (!b) {
                s = _ZERO;
            } else {
                s = "1";
            }
        }
    }

    public static MaxString _new(String aString) {
        return new MaxString(aString);
    }

    public static MaxString _new(long aLong) {
        return new MaxString(String.valueOf(aLong));
    }

    public static MaxString _new(int anInt) {
        return new MaxString(String.valueOf(anInt));
    }

    public static MaxString _new(Object o) {
        if (o != null) {
            return new MaxString(o);
        }
        return new MaxString();
    }

    public boolean isEmpty() {
        return s == _EMPTY_STRING;
    }

    public boolean isNotEmpty() {
        return !isEmpty();
    }

    public boolean hasData() {
        return isNotEmpty();
    }

    public boolean isInteger() {
        try {
            Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return false;
        }
        return true;
    }

    public int toInt() {
        int i;
        try {
            i = Integer.parseInt(s);
        } catch (NumberFormatException e) {
            i = 0;
        }
        return i;
    }

    public float toFloat() {
        return toInt();
    }

    public BigDecimal toBigDecimal() {
        return new BigDecimal(toInt());
    }

    public long toLong() {
        long l = -1;
        try {
            l = Long.parseLong(s);
        } catch (NumberFormatException e) {
         //   l = 0;
        }
        return l;
    }

    public static String getDateString() {
        Calendar cal = Calendar.getInstance(TimeZone.getDefault());
        String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(DATE_FORMAT);
        sdf.setTimeZone(TimeZone.getDefault());
        return sdf.format(cal.getTime()).replace(":", "").replace("-", "");
    }

    public static String appendFileSeparatorChar(String s) {
        String separatorChar = String.valueOf(File.separatorChar);
        if (!s.endsWith(separatorChar)) {
            s += separatorChar;
        }
        return s;
    }

    public void appendSpace() {
        appendString(" ");
    }

    public void appendString(String aString) {
        s = s + aString;
    }

    public void appendChar(char aChar) {
        s = s + String.valueOf(aChar);
    }

    public void prependString(String aString) {
        s = aString + s;
    }

    public String capitalize() {
        return toUpperCase(firstElement().s) + toLowerCase(substringRight(2));
    }

    public static String toUpperCase(String string) {
        return string.toUpperCase();
    }

    public static String toLowerCase(String string) {
        return string.toLowerCase();
    }

    public int getLength() {
        return s.length();
    }

    public char toChar() {
        char c;
        try {
            c = s.charAt(0);
        } catch (NumberFormatException e) {
            c = ' ';
        }
        return c;
    }

    public String appendFileSeparatorChar() {
        s = appendFileSeparatorChar(s);
        return s;
    }

    public static MaxString appendFileSeparatorChar(MaxString s) {
        return new MaxString(appendFileSeparatorChar(s.s));
    }

    public boolean toBool() {
        return toInt() != 0;
    }

    public static long getDateAsLong() {
        Calendar cal = Calendar.getInstance(TimeZone.getDefault());
        return cal.getTimeInMillis();
    }

    public int nextInt() {
        return toInt() + 1;
    }

    public MaxString eatSpaces() {
        return eatChar(' ');
    }

    public StringTokenizer getStringTokenizer() {
        return new StringTokenizer(s, " ");
    }

    public boolean isZero() {
        return s.compareTo(_ZERO) == 0;
    }

    public boolean compareTo(MaxString value) {
        return s.compareTo(value.s) == 0;
    }

    public StringTokenizer getStringTokenizer(String delimiter) {
        return new StringTokenizer(s, delimiter);
    }

    public MaxString eatChar(char c) {
        StringTokenizer st = getStringTokenizer(String.valueOf(c));
        String newValue = "";
        while (st.hasMoreTokens()) {
            newValue += st.nextToken();
        }
        return new MaxString(newValue);
    }

    public static int toInt(String value) {
        return new MaxString(value).toInt();
    }

    @Override
    public String toString() {
        return s;
    }

    public final void setText(String string) {
        s = string;
    }

    public boolean startsWith(String string) {
        return s.startsWith(string);
    }

    public boolean endsWith(String string) {
        return s.endsWith(string);
    }

    public int length() {
        return s.length();
    }

    private String substring(int beginIndex, int eindIndex) {
        return s.substring(beginIndex, eindIndex);
    }

    public String substringLeft(int lengte) {
        return substring(0, lengte);
    }

    public String substringRight(int beginIndex) {
        return substring(beginIndex - 1, length());
    }

    public int indexOf(char c) {
        return s.indexOf(c);
    }

    public int indexOf(String string) {
        return s.indexOf(string);
    }

    public int lastIndexOf(String string) {
        return s.lastIndexOf(string);
    }

    public void appendString(MaxString string) {
        appendString(string.toString());
    }

    public void sysOut() {
        System.out.println(s);
    }

    public void writerOut(PrintWriter writer) {
        writer.println(s);
    }

    public void empty() {
        set2empty();
    }

    public String trim() {
        return s.trim();
    }

    public static int toChar(String key) {
        return new MaxString(key).toChar();
    }

    public void appendInt(int anInt) {
        appendString(String.valueOf(anInt));
    }

    public void appendIntComma(int anInt) {
        appendInt(anInt);
        appendComma();
    }

    public void appendComma() {
        appendChar(COMMA);
    }

    public MaxString firstElement() {
        return new MaxString(substringLeft(1));
    }

    public static MaxString _new() {
        return new MaxString();
    }

    public String increase() {
        int i = toInt();
        i++;
        if (i < 10) {
            set2Zero();
        } else {
            set2empty();
        }
        appendInt(i);
        return s;
    }

    public MaxString set2Zero() {
        s = _ZERO;
        return this;
    }

    public boolean isInt() {
        int i = -1;
        try {
            i = Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return false;
        }
        return i != -1;
    }

    public boolean isBool() {
        int i = -1;
        try {
            i = Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return false;
        }
        switch (i) {
        case 0:
        case 1:
            return true;
        }
        return false;
    }

    public void eatLastChar() {
        s = substringLeft(length() - 1);
    }

    public char lastChar() {
        return s.charAt(length() - 1);
    }

    public boolean isLong() {
        if (toLong() == -1) {
            return false;
        }
        return true;
    }

}