RSS

Category Archives: Java

Effective OpenAL with LWJGL 3

Effective OpenAL with LWJGL 3

Jesus Bloody Christ it’s been a while.

So, a lot of you are likely interested in developing on Java with LWJGL3 instead of LWJGL 2.9.*; as you should be. LWJGL3 has support for a lot of modern industry trends with older versions did not; such as multi-monitor support without back flipping through flaming hoops, or basically anything involving GLFW. It’s still in beta, I know, but it’s a solid piece of work and the team on it is dedicated enough to make it a reliable and standing dependency for modern projects.

Except for every now and then, when it happens to be missing some minor things. Or, more importantly, when there’s a dearth of documentation or tutorials on a new trick you’re pulling.

I can contribute, at least in part, to both of those.

OpenAL is the audio world’s equivalent to OpenGL; it’s a sophisticated and sleek interface to sound hardware. Many common effects and utilities, such as 3D sound, are built into it directly; and it interfaces sublimely with code already designed for OpenGL. Additionally, it’s also a very tight interface that does not take long at all to learn.

In the past, I would suggest using JavaSound for Java game audio, which is also a tight API, but it lacks these features. Most major audio filters have to be built into it rather directly and often by your own hand; and there’s no official guarantee of hardware optimization. However, what LWJGL3’s OpenAL interface now lacks can easily be supported by readily-present JavaSound features; such as the audio system’s file loader.

This entry is on, step by step, how one would do such a thing.

Let’s start with a basic framework. I’ve tried to keep a balance between minimal dependencies and staying on-topic, so I’ll suggest that you have both LWJGL3 (most recent version, preferably), and Apache Commons IO, as dependency libraries.

class Lesson {
    public Lesson() throws Exception {
        //Start by acquiring the default device
        long device = ALC10.alcOpenDevice((ByteBuffer)null);

        //Create a handle for the device capabilities, as well.
        ALCCapabilities deviceCaps = ALC.createCapabilities(device);
        // Create context (often already present, but here, necessary)
        IntBuffer contextAttribList = BufferUtils.createIntBuffer(16);

        // Note the manner in which parameters are provided to OpenAL...
        contextAttribList.put(ALC_REFRESH);
        contextAttribList.put(60);

        contextAttribList.put(ALC_SYNC);
        contextAttribList.put(ALC_FALSE);

        // Don't worry about this for now; deals with effects count
        contextAttribList.put(ALC_MAX_AUXILIARY_SENDS);
        contextAttribList.put(2);

        contextAttribList.put(0);
        contextAttribList.flip();
        
        //create the context with the provided attributes
        long newContext = ALC10.alcCreateContext(device, contextAttribList);
        
        if(!ALC10.alcMakeContextCurrent(newContext)) {
            throw new Exception("Failed to make context current");
        }
        
        AL.createCapabilities(deviceCaps);
        
        
        //define listener
        AL10.alListener3f(AL10.AL_VELOCITY, 0f, 0f, 0f);
        AL10.alListener3f(AL10.AL_ORIENTATION, 0f, 0f, -1f);
        
        
        IntBuffer buffer = BufferUtils.createIntBuffer(1);
        AL10.alGenBuffers(buffer);
        
        //We'll get to this next!
        long time = createBufferData(buffer.get(0));
        
        //Define a source
        int source = AL10.alGenSources();
        AL10.alSourcei(source, AL10.AL_BUFFER, buffer.get(0));
        AL10.alSource3f(source, AL10.AL_POSITION, 0f, 0f, 0f);
        AL10.alSource3f(source, AL10.AL_VELOCITY, 0f, 0f, 0f);
        
        //fun stuff
        AL10.alSourcef(source, AL10.AL_PITCH, 1);
        AL10.alSourcef(source, AL10.AL_GAIN, 1f);
        AL10.alSourcei(source, AL10.AL_LOOPING, AL10.AL_FALSE);
        
        //Trigger the source to play its sound
        AL10.alSourcePlay(source);
        
        try {
            Thread.sleep(time); //Wait for the sound to finish
        } catch(InterruptedException ex) {}
        
        AL10.alSourceStop(source); //Demand that the sound stop
        
        //and finally, clean up
        AL10.alDeleteSources(source);
        

    }

}

The beginning is not unlike the creation of an OpenGL interface; you need to define an OpenAL context and make it current for the thread. Passing a null byte buffer to alcOpenDevice will provide you with the default device, which is usually what you’re after. (It is actually possible to interface with, say, multiple sets of speakers selectively, or the headphones instead of the speaker system, if you would like; but that’s another topic.)

Much like graphics devices, every audio device has its own set of capabilities. We’ll want a handle on those, as well. It’s safe to say that if a speaker can do it, OpenAL is capable of it; but not all speakers (or microphones) are created the same.

After this, OpenAL will want to know something of what we’re expecting it to manage. Note that it’s all passed over as a solid int buffer. We’re providing it with a notion of what features it will need to enact, or at least emulate; with a sequence of identifiers followed by parameters, terminated with a null. I haven’t begun to touch all that is possible here, but this attribute list should be enough for most uses.

After that, create the context, make it current, check to see that it didn’t blow up in your face, and register the capabilities. (Feel free to play with this once you’ve got the initial example going.)

So, before I get to the part where JavaSound comes in, let’s start with the nature of how OpenAL views sound. Sound, in its view, has three components: a listener, a source, and an actual buffer.

The listener would be either you or your program user; however, the program would want to know a little about your properties. Are you located something to the left or right? Are you moving (or virtually moving)? I usually set this first as it is likely to be constant across all sounds (kind of like a graphics context).

Next, we have a method of my own creation that builds and registers the audio file. Forgive me for the delay, but that’s where JavaSound’s features (in the core JKD) come in, and I’m deferring it to later in the discussion. You will note that the audio buffers have to be registered with OpenAL; as it needs to prepare for the data. There’s a solid chance that you will have sound-processor-local memory, much like graphics memory, and it will have to be managed accordingly by that processor before you can chuck any data at it.

Let’s look at that audio buffer creator.

     private long createBufferData(int p) throws UnsupportedAudioFileException, IOException {
        //shortcut finals:
        final int MONO = 1, STEREO = 2;
        
        AudioInputStream stream = null;
        stream = AudioSystem.getAudioInputStream(Lesson3.class.getResource("I Can Change — LCD Soundsystem.wav"));
        
        AudioFormat format = stream.getFormat();
        if(format.isBigEndian()) throw new UnsupportedAudioFileException("Can't handle Big Endian formats yet");
        
        //load stream into byte buffer
        int openALFormat = -1;
        switch(format.getChannels()) {
            case MONO:
                switch(format.getSampleSizeInBits()) {
                    case 8:
                        openALFormat = AL10.AL_FORMAT_MONO8;
                        break;
                    case 16:
                        openALFormat = AL10.AL_FORMAT_MONO16;
                        break;
                }
                break;
            case STEREO:
                switch(format.getSampleSizeInBits()) {
                    case 8:
                        openALFormat = AL10.AL_FORMAT_STEREO8;
                        break;
                    case 16:
                        openALFormat = AL10.AL_FORMAT_STEREO16;
                        break;
                }
                break;
        }
        
        //load data into a byte buffer
        //I've elected to use IOUtils from Apache Commons here, but the core
        //notion is to load the entire stream into the byte array--you can
        //do this however you would like.
        byte[] b = IOUtils.toByteArray(stream);
        ByteBuffer data = BufferUtils.createByteBuffer(b.length).put(b);
        data.flip();
        
        //load audio data into appropriate system space....
        AL10.alBufferData(p, openALFormat, data, (int)format.getSampleRate());
        
        //and return the rough notion of length for the audio stream!
        return (long)(1000f * stream.getFrameLength() / format.getFrameRate());
    }

We’re hijacking a lot of the older JavaSound API utilities for this. OpenAL, much like OpenGL, isn’t really “open”, nor is it technically a “library”. So, having something around for handling audio data is helpful, and why bother writing our own when it’s already built into the JDK?

For JavaSound, you work with either Clips, or (more frequently) AudioInputStreams. You can read most audio file formats directly via AudioSystem.getAudioInputStream(…); in this case, I’ve elected to use a WAV format of LCD Soundsystem’s “I Can Change”, because James Murphy is a god damned genius. However, you can use anything you would like; to get it to work with this just drop it in the same source directory.

Next up, grab the format of the sound with AudioStream.getFormat(). This will provide you with a lot of valuable information about the stream. If it’s a big endian stream (which most wave files are not), you might need to convert it to little endian or make proper alterations to OpenAL. I’ve glossed over this, as endian-ness is not really a part of the tutorial and there are plenty of good byte-management tutorials out there.

I’ve elected to use format to check for the mono/stereo status (more are actually possible), and whether the sound is 8-bit or more frequently 16-bit. (Technically 32- or even 64- bit sound is possible; but there is actually a resolution to the cochlea of the ear, and you’re not going to bump into that outside of labs with very funny looking equipment. Even Blu-ray doesn’t go above 24-bit. Seriously, there’s generally just no point in bothering.)

Afterward, we load the stream into a byte array (I’m using IOUtils for this for brevity, but you can do it however you like), and the byte array into a ByteBuffer. Flip the buffer, and punch it over to OpenAL, which will take care of the rest of the work with it. Afterwards, we will eventually need the length of the audio stream, so calculate it as shown and send it back to the calling method.

After the buffer’s been created and the length of it is known, we’ve got to create a source for it! This is where most of the cooler built-in effects show up. alGenSources() creates a framework for the source; alSourcei(source, AL10.AL_BUFFER, buffer.get(0)) ties it to the sound buffer. You’ll also see that I set up AL_GAIN and AL_PITCH, which are fun to play with.

You’re almost done!

To actually play the buffer, you use the source. alSourcePlay(source) starts it. After that, I have the Thread sleep for the calculated length of the sound, just so we have time to hear it. At the end, I call alSourceStop(source) to demand an end to the source.

Lastly, I delete all sources. You might also want to delete devices, if you’ve done anything silly with them; this is very low-level access. You now have everything you need to load audio into your games and programs, and if you happen to bump into an SPI for a preferred format, it will now also be enough to get you going on OpenAL as well.

 
Leave a comment

Posted by on July 4, 2016 in Java, Programming

 

Tags: , , , , , , , , , ,

Apache Commons DecompositionSolvers

Apache Commons DecompositionSolvers

Jesus it’s been too long since I got back to this!

Anyway, right, the

DecompositionSolver

Intro / Why-I-Need-To-Worry-About-This

Linear algebra exists for a reason; namely, us. Suppose we’re attempting to find the coordinate values, which under a certain transform, become a specific value. Let’s keep it simple and call it:

 x + 2y + 3z = 1
2x + 4y + 3z = 2
4x           = 3

As I’m sure you can remember from grade school, you have the same number of equations as unknowns, so it is almost certainly solvable. We just subtract two of the first equation from the second, four of the first equation form the third, four of the second from the third, one of the second from the first, and a quarter of the third from the first. Then we maybe divide the third by eight and the second by three, and presto,

x = 3/4
y = 1/8
z = 0

Unfortunately, as programmers, we both know that this is much easier done in practice than in theory; and when you’re automating  a task, a working theory is the only thing that really counts.

So, those of you who have already taken linear algebra (quite possibly all of you) may be familiar with a much easier way of representing this problem:

┌1 2 3┐┌x┐   ┌1┐
│2 4 3││y│ = │2│
└4 0 0┘└z┘   └3┘

A decomposition basically solves this, through a sequence of steps on both sides that reduces the original matrix to an identity matrix, while having the right-hand matrix undergo the same operations. This is commonly written as an augmented matrix, like so:

┌1 2 3│1┐
│2 4 3│2│
└4 0 0│3┘

Matrix reduction is a heck of a lot more straightforward than the nonsense I spouted a few paragraphs back, though going into its details is a bit off topic here. Our final matrix, after the reduction, looks like this:

┌1 0 0│3/4┐
│0 1 0│1/8│
└0 0 1│ 0 ┘

How Do We Do This in Java?

Not just Java, actually; this is specifically about the Apache Commons Math3 decomposition solver interface.

One of the tricks with reduction is that there are a lot of different, equally effective, ways to go about it; and like any other algorithm, the efficiency depends, in large part, on the initial state of your matrix. My personal favorite is the LU Decomposition. (Or, if you prefer a link that isn’t a video, look here.)

First I recommend making a Maven project out of your Java project, presuming that it isn’t already fitting that form factor. Afterwards, open up pom.xml, and add this:

<dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-math3</artifactId>
        <version>3.5</version>
    </dependency>
</dependencies>

right after the close of the build tag. Your project is now pulling classes from across the internet, on Apache Commons Math3. Later on, you may want the version number to be a bit higher; for now I’m using version 3.5.

So, you’ll note that you have access to a host of new classes, all in some subpackage of org.apache.commons.math3. Import org.apache.commons.math3.linear.* into your class file.

We can solve the above problem by creating a RealMatrix of the initial matrix, potentially like so:

RealMatrix matrix = new Array2DRowRealMatrix(new double[][]{
    {1.0, 2.0, 3.0},
    {2.0, 4.0, 3.0},
    {4.0, 0.0, 0.0}
});

But don’t get me wrong, there are literally dozens of ways to create a RealMatrix.

Next, create a RealVector, describing the other side of the equation, perhaps like so:

RealVector vector = new ArrayRealVector(new double[]{
    1.0,
    2.0,
    3.0
});

We now have a matrix and vector representation of the two sides of our equation.

Working with RealMatrix and RealVector

If you’re an experienced programmer, you probably expect some kind of Command Pattern to show up next. It’s certainly what I would do, if I needed to duplicated the exact operations in the exact order on more than one piece of base data. Fortunately, something like it has already been implemented by Apache.

If you look up the Apache Commons Math3 javadocs, you’ll notice that while RealMatrix has a lot of handy operations, they generally just involve polling for data, not actually operating on it. Commons has made the wise move to encapsulate operations in their own classes, rather than just their own methods. There are many dozen other classes, such as MatrixUtils (remember that one!), which both generate and operate on RealMatrix and RealVector classes.

In this instance, turn to DecompositionSolver. It’s meant for tasks just like our own, and there are many subclasses. As I said, my preference is LUDecomposition, but that is only capable of handling square matrices. Since our matrix is square, that’s fine; in other cases when your matrix doesn’t fit the profile, look through EigenDecomposition, SingularValueDecomposition, or some other utility.

For LUDecomposition, we’ll want to do something like this:

DecompositionSolver solver = new LUDecomposition(matrix).getSolver();

The work has been done, as one initialization, LUDecomposition doesn’t just store the matrix as a property; it determines from it the exact sequence of operations necessary to turn it into an identity matrix.

Once you have your solver, you can get your final right-hand vector via:

solver.solve(vector);

which will provide you with:

┌3/4┐
│1/8│
└ 0 ┘

Final Source Code

Here’s a working example of how such a program might work.

 package oberlin.math3;

import java.io.*;
import java.util.*;

import org.apache.commons.math3.linear.*;

public class MatrixReducer {
    
    public static void main(String...args) {
        new MatrixReducer();
    }
    
    public MatrixReducer() {
        try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
                Scanner scanner = new Scanner(System.in)) {
            writer.write("\nEnter first row of three numbers: ");
            writer.flush();
            
            RealVector vector1 = new ArrayRealVector(new double[]{scanner.nextDouble(), scanner.nextDouble(), scanner.nextDouble()});
            
            writer.write("\nEnter second row of three numbers: ");
            writer.flush();
            
            RealVector vector2 = new ArrayRealVector(new double[]{scanner.nextDouble(), scanner.nextDouble(), scanner.nextDouble()});

            writer.write("\nEnter third row of three numbers: ");
            writer.flush();
            
            RealVector vector3 = new ArrayRealVector(new double[]{scanner.nextDouble(), scanner.nextDouble(), scanner.nextDouble()});
            
            
            //create matrix
            RealMatrix matrix = MatrixUtils.createRealIdentityMatrix(3);
            matrix.setRowVector(0, vector1);
            matrix.setRowVector(1, vector2);
            matrix.setRowVector(2, vector3);
            
            //get other side
            writer.write("\nEnter vector on right side (3 entries):");
            writer.flush();
            
            RealVector vector = new ArrayRealVector(new double[]{scanner.nextDouble(), scanner.nextDouble(), scanner.nextDouble()});
            
            
            writer.write("Solving...");
            writer.flush();
            
            DecompositionSolver solver = new LUDecomposition(matrix).getSolver();
            matrix = solver.solve(matrix);
            vector = solver.solve(vector);
            
            writer.write("Solution: \n");
            writer.flush();
            
            writer.write("┌" + matrix.getEntry(0, 0) + " " + matrix.getEntry(0, 1) + " "
                    + matrix.getEntry(0, 2) + "┐┌x┐   ┌" + Double.toString(vector.getEntry(0)) + "┐\n");
            writer.write("│" + matrix.getEntry(1, 0) + " " + matrix.getEntry(1, 1) + " "
                    + matrix.getEntry(1, 2) + "││y│ = │" + Double.toString(vector.getEntry(1)) + "│\n");
            writer.write("└" + matrix.getEntry(2, 0) + " " + matrix.getEntry(2, 1) + " "
                    + matrix.getEntry(2, 2) + "┘└z┘   └" + Double.toString(vector.getEntry(2)) + "┘\n");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
Leave a comment

Posted by on June 30, 2015 in Java, Programming

 

Tags: , , , , , , , , , ,

The NIO.2 Watcher

So, I’ve been working on a side project involving the Builder tutorial. It roughly (not entirely, but roughly) works out as a machine-operated interpreter, that is, code altered by machine before being translated. After that it does something even more awesome, but it’s only capable of triggering the compilation, after alteration, through a utility that isn’t as well known as it should be.

The Watcher Utility

As of Java 7, we got the NIO.2 classes. These included Path (which most of you are probably familiar with), Files, FileSystem, asynchronous channels, and a host of other goodies. One of them was the Watch Service API.

What Watch ultimately amounts to is a device that can trigger an event any time an arbitrary subset of data is altered in some way. The easiest possible example is monitoring a directory for changes, but this is, gloriously, not exclusive. In classic Java nomenclature, one might think of it as a sort of PathEventListener, in a way; but it’s capable of a bit more than that particular name implies. It doesn’t have to be associated with Paths, and unlike most listeners, it’s less about monitoring for user-generated interrupts, and more about monitoring for system-wide circumstances, including secondary effects.

Using a Watcher

Watchers keep internal collections of keys, each one associated with a source object. This registration is typically located on the source object, at least directly. The best, and most correct, way to do this is through direct implementation of the Watchable interface. Many JDK classes, such as Path, already implement this. Once implemented, you would use the method:

Watchable.register(WatchService watchService, Kind<?>...events)

This method registers all events, of the specified types, on the provided WatchService object. Every time one of them occurs, the key is flagged as signaled, and in its own time the WatchService will retrieve the data from that key and operate.

Note that a Path can be many things. It could be a path to a directory on your machine, which is of program concern. It could be a path to a printer tray, or a server, or even a kitchen appliance (think polling the status of an automated espresso machine). In this example, I will be showing a manner in which a directory path can register to be watched for alterations.

WatchEvent.Kind interface

This can be thought of, for old school Java programmers, as the class type of a Watch event. Most of the frequently used keys are in java.nio.file.StandardWatchEventKinds, but as an interface, it is fully customizable. They only require two methods to be overridden, that is, WatchEvent.Kind.name(), which simply returns a String value representing the type of event; and WatchEvent.Kind.type(), which returns a Class that describes the location context of the event.

WatchEvent.Kind.type() may return null, and it won’t break anything; but after getting a feel for the results of StandardWatchEventKinds, you might consider implementing it. As an example, for ENTRY_CREATE, ENTRY_MODIFY, and ENTRY_DELETE, the context is a relative path between the Path being watched, and the item that has changed. (Knowing that a random item was deleted is of little if any use, without knowing which one.)

Implementing a WatchService

Most of the WatchServices you are likely to use are stock in the JDK. I’m going to start with one of them; in a later blog, I’ll probably create one from scratch, but it really is better to start simple.

For the common case of monitoring a directory, FileSystem.newWatchService() covers everything you need. It is important to get a watcher for the correct type of FileSystem, though; as many of you know, Java is capable, as of version 7, of taking advantage of the numerous file system-specific capabilities. The safest way to do it is through:

WatchService watcher = FileSystem.getDefault().newWatchService();

But there may be many points in which you intend to grab a watcher from a file system of a specific, or even custom, type. This is fine, but be aware of the extra layer of debugging.

Afterward, each path can be registered with the watch service through its Path.register(…) method. Be certain to include every variety of WatchEvent.Kind that you want to watch for. It may be tempting to simply register for every single standard type every time, but I encourage you, as a matter of practice, to consider whether you’re really concerned about each Kind before including it. They do, technically, cost a small amount of system resources; and while it may not be noticeable for small projects, when you’re dealing with massive file hierarchies it can become a concern.

When polling for changes, it is mildly more complicated than it is with Listeners. The watcher must be polled for WatchKey objects. WatchKeys are generated when a watched alteration occurs. They all have a state, which is continuously either ready, meaning the associated Watchable is valid but without events; signaled, meaning that at least one event has occurred and been registered with this WatchKey; and invalid, meaning that it is no longer sensible to consider the associated Watchable a candidate for events.

There’s more than one way to get the next signaled WatchKey, but one of the most efficient methods is WatchService.take(). This will always return a signaled WatchKey. It is a blocking method, so use it with that in mind; if no WatchKeys are yet signaled, it will wait until one is before returning.

Once you have a WatchKey, a secondary loop examines every sequential change that has occurred. (If you’re curious, if a WatchEvent occurs for a WatchKey that is already signaled, it is added to the stack and no other alterations are made; if it occurs while the WatchKey is ready, it initiates the stack and WatchKey is flipped to signaled). This is done via WatchKey.pollEvents(). For each event, you may examine the WatchEvent, and act on it accordingly.

After all is said and done, and the WatchKey has zero events left to parse, call WatchKey.reset(). This attempts to flip the WatchKey back to the ready state; if it fails (if the key is now invalid), the method returns false. This might signal, as an example, that the watched path no longer exists.

Example

Any WachService manager must be running continuously. The antipattern approach is to simply use a while-true block; but in general, it is less hazardous to make it its own thread.

import java.io.IOException;
import java.nio.*;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;

public class DirectoryWatcher implements Runnable {
    
    private WatchService watcher;
    private volatile boolean isRunning = false;
    
    public DirectoryWatcher() throws IOException {
        watcher = FileSystems.getDefault().newWatchService();
    }
    
    /**
     * Begins watching provided path for changes.
     * 
     * @param path
     * @throws IOException 
     */
    public void register(Path path) throws IOException {
        //register the provided path with the watch service
        path.register(watcher,    StandardWatchEventKinds.ENTRY_CREATE,
                                StandardWatchEventKinds.ENTRY_MODIFY,
                                StandardWatchEventKinds.ENTRY_DELETE);
    }

    @Override
    public void run() {
        isRunning = true;
        
        while(isRunning) {
            //retrieve the next WatchKey
            try {
                WatchKey key = watcher.take();
                
                key.pollEvents().stream().forEach(event -> {
                    final Kind<?> kind = event.kind();
                    
                    if(kind != StandardWatchEventKinds.OVERFLOW) {
                        final Path path = ((WatchEvent<Path>)event).context();
                        System.out.println(kind + " event occurred on '" + path + "'");
                    }
                });
                
                if(!key.reset()) {
                    //the key should be valid now; but if it is not,
                    //then the directory was likely deleted.
                    break;
                }
                    
            } catch (InterruptedException e) {
                continue;
            }
            
            Thread.yield();
        }
    }
    
    public void stop() {
        this.isRunning = false;
    }
}

Simple enough, yes?

The register(…) method may be a little redundant; however, the run() method is where the meat is. WatchKeys are retrieved with WatchService.take(); afterward, in a parallel stream, each WatchEvent associated with that key is looped through. (When an event is of type OVERFLOW, it usually means that data on the event has been lost; not optimal, but the best course of action here is to continue to the next key.)

In this instance, the event is simply reported to the terminal, but this lambda expression is where you would take arbitrary actions according to the event. It is also possible to use an external iteration to do this, if you need to change values or perform another non-lambda-kosher action.

After all events have been iterated through, WatchKey.reset() is called, and checked. In the event that it returns false, something has happened to our directory, and the thread has become a potential resource leak; so it is shut down automatically. Otherwise, the thread then yields to other threads, and repeats itself.

Here’s a small Main class that I’ve built to use this. A single path parameter will be the directory to monitor; or it will simply watch for $HOME/watchtest.

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

public class Main {

    public static void main(String[] args) throws IOException {
        final String location = (args.length > 0) ? args[1] : 
            System.getProperty("user.home") + "/watchtest";
        
        final Path path = Paths.get(location);
        DirectoryWatcher watcher = new DirectoryWatcher();
        watcher.register(path);
        (new Thread(watcher)).start();
        
        //wait a set amount of time, then stop the program
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //continue
        }
        watcher.stop();
    }

}

Try running it, and making a few changes to your select directory. See what it does.

And That’s It!

The next real question is how to create your own WatchService; which is totally doable. Generally, though, it isn’t necessary. The next time I come back to this subject, I’ll be going over that, possibly starting with WatchKey.Kinds. First, though, I need to get back to the project that I started this for, and I need to continue the Build Tool tutorial, so it might be a bit.

Good coding!

 
Leave a comment

Posted by on March 11, 2015 in Java, NIO.2, Programming

 

Tags: , , , , , , , , , , , , , , , , ,