text:
<file_sep>package video_storm.input;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;

public class KeyInput {
    public boolean[] keys = new boolean[65536];

    public KeyInput() throws LWJGLException {
        Keyboard.create();
    }

    public void get() {
        while(Keyboard.next()) {
            if (Keyboard.getEventKeyState()) {
                keys[Keyboard.getEventKey()] = true;
                System.out.println( "*** " + Keyboard.getEventKey());
            }
            else {
                keys[Keyboard.getEventKey()] = false;
            }
        }
    }

    public void close() {
        Keyboard.destroy();
    }

    public boolean consume( int keySpace ) {
        boolean ans = keys[keySpace];
        keys[keySpace] = false;
        return ans;
    }
}
<file_sep>/**
 * Created on Oct 25, 2003
 */

package craterstudio.math;

import java.util.Random;

public class EasyMath
{
   /**
    * STATIC
    */

   public static final int NO_TRANSFORM                     = 0x00;
   public static final int SQUARED_TRANSFORM                = 0x01;
   public static final int INVERSE_SQUARED_TRANSFORM        = 0x02;
   public static final int SMOOTH_TRANSFORM                 = 0x03;
   public static final int SQUARED_SMOOTH_TRANSFORM         = 0x04;
   public static final int INVERSE_SQUARED_SMOOTH_TRANSFORM = 0x05;

   /**
    * TRANSFORM
    */

//   public static final float transform(float ratio, int type)   {
//      if (type == NO_TRANSFORM)
//         return ratio;
//      if (type == SQUARED_TRANSFORM)
//         return ratio * ratio;
//
//      // inverse
//      if (type == INVERSE_SQUARED_TRANSFORM || type == INVERSE_SQUARED_SMOOTH_TRANSFORM)
//         ratio = 1.0f - ratio;
//
//      if (type == INVERSE_SQUARED_TRANSFORM)
//         return 1.0f - ratio * ratio;
//
//      // smooth
//      ratio = (FastMath.sinDegStrict((ratio * 180.0f - 90.0f)) + 1.0f) * 0.5f;
//
//      if (type == SMOOTH_TRANSFORM)
//         return ratio;
//      if (type == SQUARED_SMOOTH_TRANSFORM)
//         return ratio * ratio;
//      if (type == INVERSE_SQUARED_SMOOTH_TRANSFORM)
//         return 1.0f - ratio * ratio;
//
//      return -1.0f;
//   }

   /**
    *
    */

   public static final float gradients(float cur, float[] keys, float values[])
   {
      int src = keys.length - 1;
      int dst = keys.length - 1;

      for (int i = 1; i < keys.length; i++)
      {
         if (cur < keys[i])
         {
            src = i - 1;
            dst = i - 0;

            return EasyMath.interpolate(cur, keys[src], keys[dst], values[src], values[dst]);
         }
      }

      return -1;
   }

   /**
    * POWER OF TWO
    */

   public static final boolean isPowerOfTwo(int n)
   {
      for (int i = 0; i < 31; i++)
         if (n == (1 << i))
            return true;

      return false;
   }

   public static final int fitInPowerOfTwo(int n)
   {
      if (n < 0)
         throw new IllegalArgumentException("n must be positive");

      for (int i = 1; i < 31; i++) // 2,4,8,16,...
      {
         int pow = (1 << i);
         if (n <= pow)
            return pow;
      }

      // never happens
      return -1;
   }

   public static final int widthInBits(int n)
   {
      if (n == 0)
         return 0;
      if (n < 0)
         return 32;

      for (int i = 30; i >= 0; i--)
         if (n >= (1 << i))
            return i + 1;

      return 0;
   }

   /**
    * CURVE
    */

   public static final double curve(double distance, double topDistance, double margin, double power)
   {
      // Shape: __________________/\_____________________ ( /\ == sin)

      double min = topDistance - margin;
      double max = topDistance;
      double ratio = (distance - min) / (max - min);
      double angle = ratio * 0.5 * Math.PI;
      if (angle < 0.0 || angle > Math.PI)
         return 0.0;
      double sinCurve = Math.sin(angle);
      return Math.pow(sinCurve, power);
   }

   /**
    * LERP
    */

   public static final float lerp(float t, float a, float b)
   {
      return a + t * (b - a);
   }

   public static final double lerp(double t, double a, double b)
   {
      return a + t * (b - a);
   }

   public static final float lerpWithCap(float cur, float min, float max)
   {
      if (cur < 0.0f)
         return min;
      if (cur > 1.0f)
         return max;

      return (cur - min) / (max - min);
   }

   /**
    * INV LERP
    */

   public static final float invLerp(float cur, float min, float max)
   {
      return (cur - min) / (max - min);
   }

   public static final double invLerp(double cur, double min, double max)
   {
      return (cur - min) / (max - min);
   }

   public static final float invLerpWithCap(float cur, float min, float max)
   {
      if (cur < min)
         return 0.0f;
      if (cur > max)
         return 1.0f;
      return invLerp(cur, min, max);
   }

   //

   public static final float invLerp(long cur, long min, long max)
   {
      return (float) (cur - min) / (max - min);
   }

   public static final float invLerpWithCap(long cur, long min, long max)
   {
      if (cur < min)
         return 0.0f;
      if (cur > max)
         return 1.0f;
      return invLerp(cur, min, max);
   }

   /**
    * INTERPOLATE
    */

   public static final float interpolate(float cur, float min, float max, float startValue, float endValue)
   {
      return lerp(invLerp(cur, min, max), startValue, endValue);
   }

//   public static final float interpolate(float cur, float min, float max, float startValue, float endValue, int transform)   {
//      return lerp(transform(invLerp(cur, min, max), transform), startValue, endValue);
//   }

   public static final float interpolateWithCap(float cur, float min, float max, float startValue, float endValue)
   {
      if (cur < min)
         return startValue;
      if (cur > max)
         return endValue;
      return interpolate(cur, min, max, startValue, endValue);
   }

   public static final float interpolate2d(float x, float z, float nw, float ne, float se, float sw)
   {
      // n -= n % dim -> n = 0..dim (local offset)
      x = x - (int) x;
      z = z - (int) z;

      // Which triangle of quad (left | right)
      if (x > z)
         sw = nw + se - ne;
      else
         ne = se + nw - sw;

      // calculate interpolation of selected triangle
      float n = lerp(x, nw, ne);
      float s = lerp(x, sw, se);
      return lerp(z, n, s);
   }

   /**
    * CLAMP
    */

   public static final double clamp(double val, double min, double max)
   {
      return (val < min) ? min : (val > max) ? max : val;
   }

   public static final float clamp(float val, float min, float max)
   {
      return (val < min) ? min : (val > max) ? max : val;
   }

   public static final long clamp(long val, long min, long max)
   {
      return (val < min) ? min : (val > max) ? max : val;
   }

   public static final int clamp(int val, int min, int max)
   {
      return (val < min) ? min : (val > max) ? max : val;
   }

   /**
    * BETWEEN
    */

   public static final boolean isBetween(double val, double min, double max)
   {
      return min <= val && val <= max;
   }

   public static final boolean isBetween(float val, float min, float max)
   {
      return min <= val && val <= max;
   }

   public static final boolean isBetween(long val, long min, long max)
   {
      return min <= val && val <= max;
   }

   public static final boolean isBetween(int val, int min, int max)
   {
      return min <= val && val <= max;
   }

   public static final boolean areBetween(int[] val, int min, int max)
   {
      for (int i = 0; i < val.length; i++)
         if (!(min <= val[i] && val[i] <= max))
            return false;

      return true;
   }

   /**
    * EQUALS
    */

   public static final boolean equals(float a, float b, float error)
   {
      if (a == b)
         return true;

      float diff = a - b;
      return diff * diff < error * error;
   }

   public static final float bounce(float val, float min, float max)
   {
      float range = max - min;
      float range2 = range * 2.0f;
      float steps = (val - min) % range2;
      if (steps > range)
         steps = range2 - steps;
      return steps + min;
   }

   /**
    * MODULO ABS
    */

   public static final float moduloAbs(float val, float max)
   {
      return ((val % max) + max) % max;
   }

   public static final int moduloAbs(int val, int max)
   {
      return ((val % max) + max) % max;
   }

   public static final long moduloAbs(long val, long max)
   {
      return ((val % max) + max) % max;
   }

   /**
    * MODULO RANGE
    */

   public static final float moduloInRange(float val, float min, float max)
   {
      float range = max - min;
      float adjust = (val - min) % range;

      if (adjust >= 0.0F)
         return adjust + min;

      return adjust + min + range;
   }

   public static final int moduloInRange(int val, int min, int max)
   {
      int range = max - min;
      int adjust = (val - min) % range;

      if (adjust >= 0)
         return adjust + min;

      return adjust + min + range;
   }

   public static final long moduloInRange(long val, long min, long max)
   {
      long range = max - min;
      long adjust = (val - min) % range;

      if (adjust >= 0)
         return adjust + min;

      return adjust + min + range;
   }

   /**
    * RANDOM
    */

   public static final float random(Random r)
   {
      return (r.nextFloat() - 0.5F) * 2.0F;
   }

   public static final float random(Random r, float max)
   {
      return r.nextFloat() * max;
   }

   public static final float random(Random r, float min, float max)
   {
      return r.nextFloat() * (max - min) + min;
   }

   public static final float randomRange(Random r, float base, float range)
   {
      return base + EasyMath.random(r) * range;
   }

   public static boolean intersects(int aMin, int aMax, int bMin, int bMax)
   {
      if (isBetween(aMin, bMin, bMax))
         return true;
      if (isBetween(aMax, bMin, bMax))
         return true;
      if (isBetween(bMin, aMin, aMax))
         return true;
      if (isBetween(bMax, aMin, aMax))
         return true;
      return false;
   }

   public static boolean intersects(int[] min, int[] max, int off[], int dim)
   {
      for (int i = 0; i < min.length; i++)
         if (!intersects(off[i], off[i] + dim, min[i], max[i]))
            return false;
      return true;
   }

   private static final int    BIG_ENOUGH_INT   = 16 * 1024;
   private static final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT + 0.0000;
   private static final double BIG_ENOUGH_ROUND = BIG_ENOUGH_INT + 0.5000;
   private static final double BIG_ENOUGH_CEIL  = BIG_ENOUGH_INT + 0.9999;

   public static int fastFloor(float x)
   {
      return (int) (x + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT;
   }

   public static int fastRound(float x)
   {
      return (int) (x + BIG_ENOUGH_ROUND) - BIG_ENOUGH_INT;
   }

   public static int fastCeil(float x)
   {
      return (int) (x + BIG_ENOUGH_CEIL) - BIG_ENOUGH_INT;
   }

   public static void main(String[] args)
   {
      System.out.println(fastFloor(+13.4f));
      System.out.println(fastFloor(-13.4f));
      System.out.println(fastFloor(+13.6f));
      System.out.println(fastFloor(-13.6f));
      System.out.println();
      System.out.println(fastRound(+13.4f));
      System.out.println(fastRound(-13.4f));
      System.out.println(fastRound(+13.6f));
      System.out.println(fastRound(-13.6f));
      System.out.println();
      System.out.println(fastCeil(+13.4f));
      System.out.println(fastCeil(-13.4f));
      System.out.println(fastCeil(+13.6f));
      System.out.println(fastCeil(-13.6f));
   }
}<file_sep>package video_storm;

import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import video_storm.input.KeyInput;

public class Main
{
    static KeyInput keyinput;
    static MoviePlayer player = null;

	public static void main(String[] args) {
        Window window = null;

        try {
            player = new MoviePlayer("C:\\indispot\\indiespot-media-0.8.04-all\\sample_h264.mp4");

            window = new Window(1400, 800);

            Canvas canvas = new Canvas();
            canvas.set(new MovieCanvas(player.getMovieWidth(), player.getMovieHeight()));

            keyinput = new KeyInput();

            while (!Display.isCloseRequested()) {
                processInput();

                player.tick(canvas.movieCanvas);

                canvas.setView(Display.getWidth(), Display.getHeight());
                canvas.render();

                Display.update();
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        finally {
            if (keyinput != null)
                keyinput.close();

            if (player != null)
                player.close();

            if (window != null)
                window.close();
        }

		System.exit(0);
	}

    private static void processInput() {
        keyinput.get();

        if (keyinput.consume(Keyboard.KEY_SPACE)) {
            player.togglePause();
        }
        else if (keyinput.consume(Keyboard.KEY_TAB)) {
            State.menuMode = !State.menuMode;
            System.out.println( "*** menu mode" );
        }
    }
}<file_sep>/*
 * Copyright (c) 2012, Riven
 *
 * 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 Riven 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.
 */

package net.indiespot.media.test;

import static org.lwjgl.opengl.GL11.*;

import java.io.File;
import java.nio.ByteBuffer;

import net.indiespot.media.Movie;
import net.indiespot.media.impl.OpenALAudioRenderer;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.glu.GLU;

import craterstudio.math.EasyMath;
import craterstudio.text.TextValues;

public class TestGameLoop {
	public static void main(String path) throws Exception {

		final File movieFile = new File(path);
		Movie movie = Movie.open(movieFile);

		OpenALAudioRenderer audioRenderer = new OpenALAudioRenderer();
		audioRenderer.init(movie.audioStream(), movie.framerate());

		//

		Display.setDisplayMode(new DisplayMode(800, 600));
		Display.setResizable(true);
		Display.setTitle("TestGame");
		Display.setVSyncEnabled(false);
		AL.create();

		// create display
		{
			for (int samples = 8; samples >= 0; samples--) {
				try {
					Display.create(new PixelFormat(/* bpp */32, /* alpha */8, /* depth */24,/* stencil */8,/* samples */4));
					break;
				} catch (LWJGLException exc) {
					System.out.println("Failed to create Display with " + samples + " samples");
				}
			}
		}

		// setup projection matrix
		{
			displayWidth = Display.getWidth();
			displayHeight = Display.getHeight();
			{
				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				GLU.gluPerspective(60.0f, displayWidth / (float) displayHeight, 0.01f, 100.0f);

				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();

				glViewport(0, 0, displayWidth, displayHeight);
			}
		}

		// create texture holding video frame
		{
			textureHandle = glGenTextures();
			glBindTexture(GL_TEXTURE_2D, textureHandle);

			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

			int wPot = EasyMath.fitInPowerOfTwo(movie.width());
			int hPot = EasyMath.fitInPowerOfTwo(movie.height());
			texWidthUsedRatio = (float) movie.width() / wPot;
			texHeightUsedRatio = (float) movie.height() / hPot;

			// 'tmpbuf' should be null, but some drivers are too buggy
			ByteBuffer tmpbuf = BufferUtils.createByteBuffer(wPot * hPot * 3);
			glTexImage2D(GL_TEXTURE_2D, 0/* level */, GL_RGB, wPot, hPot, 0/* border */, GL_RGB, GL_UNSIGNED_BYTE, tmpbuf);
			tmpbuf = null;
		}

		// game loop

		long textureReceiveTook = 0;
		long textureUpdateTook = 0;
		long textureRenderTook = 0;

		long started = System.nanoTime();
		long startedLastSecond = System.nanoTime();
		int videoFramesLastSecond = 0;
		int renderFramesLastSecond = 0;

		while (!Display.isCloseRequested()) {

			audioRenderer.tick(movie);

			glClearColor(0, 0, 0, 1);
			glClear(GL_COLOR_BUFFER_BIT);

			// position camera, make it sway
			{
				glMatrixMode(GL_MODELVIEW);
				{
					glLoadIdentity();

					long elapsed = (System.nanoTime() - started) / 1_000_000L;
					float angle = 90 + (float) Math.sin(elapsed * 0.001) * 15;

					// inverse camera transformations
					glRotatef(-angle, 0, 1, 0);
					glRotatef(-15f, 0, 0, 1);
					glTranslatef(-3, -1.7f, -0);
				}
			}

			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, textureHandle);

			if (movie.isTimeForNextFrame()) {
				// grab the next frame from the video stream
				textureReceiveTook = System.nanoTime();
				textureBuffer = movie.videoStream().readFrameInto(textureBuffer);
				textureReceiveTook = System.nanoTime() - textureReceiveTook;

				if (textureBuffer == null) {
					break;
				}

				textureUpdateTook = System.nanoTime();
				glTexSubImage2D(GL_TEXTURE_2D, 0/* level */, 0, 0, movie.width(), movie.height(), GL_RGB, GL_UNSIGNED_BYTE, textureBuffer);
				textureUpdateTook = System.nanoTime() - textureUpdateTook;

				// signal the AV-sync that we processed a frame
				movie.onUpdatedVideoFrame();

				videoFramesLastSecond++;
			}

			// render scene
			textureRenderTook = System.nanoTime();
			{
				glEnable(GL_BLEND);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

				float h = (float) movie.height() / movie.width() * 2;

				glPushMatrix();

				glBegin(GL_QUADS);
				{
					glColor4f(1, 1, 1, 1);

					// render movie screen

					glTexCoord2f(0 * texWidthUsedRatio, 1 * texHeightUsedRatio);
					glVertex3f(0, 0.1f + h * 0, +1);

					glTexCoord2f(1 * texWidthUsedRatio, 1 * texHeightUsedRatio);
					glVertex3f(0, 0.1f + h * 0, -1);

					glTexCoord2f(1 * texWidthUsedRatio, 0 * texHeightUsedRatio);
					glVertex3f(0, 0.1f + h * 1, -1);

					glTexCoord2f(0 * texWidthUsedRatio, 0 * texHeightUsedRatio);
					glVertex3f(0, 0.1f + h * 1, +1);

					// radiosity / blur on the floor

					for (int i = -15; i <= +15; i++) {
						glColor4f(1, 1, 1, 0.025f);
						glTexCoord2f(0 * texWidthUsedRatio, 1 * texHeightUsedRatio);
						glVertex3f(0, 0, +1);

						glTexCoord2f(1 * texWidthUsedRatio, 1 * texHeightUsedRatio);
						glVertex3f(0, 0, -1);

						glColor4f(1, 1, 1, 0.0f);
						glTexCoord2f(1 * texWidthUsedRatio, 0 * texHeightUsedRatio);
						glVertex3f(0, -0.5f * h + Math.abs(i) * 0.01f, -1 + (i * 4) * 0.01f);

						glTexCoord2f(0 * texWidthUsedRatio, 0 * texHeightUsedRatio);
						glVertex3f(0, -0.5f * h + Math.abs(i) * 0.01f, +1 + (i * 4) * 0.01f);
					}
				}
				glEnd();
				glDisable(GL_BLEND);
				glDisable(GL_TEXTURE_2D);

				glPopMatrix();
			}
			glFlush();
			textureRenderTook = System.nanoTime() - textureRenderTook;

			renderFramesLastSecond++;
			if (System.nanoTime() > startedLastSecond + 1_000_000_000L) {
				startedLastSecond += 1_000_000_000L;

				String a = TextValues.formatNumber(textureReceiveTook / 1_000_000.0, 1);
				String b = TextValues.formatNumber(textureUpdateTook / 1_000_000.0, 1);
				String c = TextValues.formatNumber(textureRenderTook / 1_000_000.0, 1);
				Display.setTitle(//
				   "rendering " + renderFramesLastSecond + "fps, " + //
				      "video " + videoFramesLastSecond + "fps, " + //
				      "read blocking: " + a + "ms, " + //
				      "texture update: " + b + "ms, " + //
				      "rendering: " + c + "ms");

				renderFramesLastSecond = 0;
				videoFramesLastSecond = 0;
			}

			Display.update();
		}

		movie.close();
		audioRenderer.close();

		Display.destroy();
		AL.destroy();

		System.exit(0);
	}

	static int displayWidth;
	static int displayHeight;

	static int textureHandle;
	static ByteBuffer textureBuffer;

	static float texWidthUsedRatio;
	static float texHeightUsedRatio;
}
<file_sep>/*
 * Created on Aug 7, 2005
 */
package craterstudio.util;

import java.util.List;
import java.util.ArrayList;

public class Pool<T> {
	private final int maxSize;

	Pool() {
		this.creator = null;
		this.pool = null;
		this.maxSize = -1;
	}

	public Pool(PoolHandler<T> creator) {
		this(creator, 0, -1);
	}

	public Pool(PoolHandler<T> creator, int maxSize) {
		this(creator, 0, maxSize);
	}

	public Pool(PoolHandler<T> creator, int initSize, int maxSize) {
		this.creator = creator;
		this.maxSize = maxSize;

		this.pool = new ArrayList<T>();
		for (int i = 0; i < initSize; i++) {
			pool.add(this.create());
		}
	}

	private T create() {
		T item = creator.create();
		creator.clean(item);
		createCount++;
		return item;
	}

	private int createCount;
	private final List<T> pool;
	private final PoolHandler<T> creator;

	/**
	 * Tries to take an object from the pool, otherwise creates a new one.
	 */

	public T aquire() {
		return pool.isEmpty() ? this.create() : ListUtil.removeLast(pool);
	}

	/**
	 * Adds the object to the pool, if it fits.
	 */

	public T release(T item) {
		if (item == null) {
			return null;
		}

		creator.clean(item);

		if (true) {
			this.verifyUniqueRelease(item);
		}

		if (maxSize == -1 || (maxSize != -1 && pool.size() < maxSize)) {

			pool.add(item);
		}

		return null;
	}

	private void verifyUniqueRelease(T toAdd) {
		// don't use pool.contains() because it uses obj.equals(item), which might
		// return true
		for (T item : pool) {
			if (toAdd == item) {
				throw new IllegalStateException();
			}
		}
	}

	/**
	 * Ensure the pool holds this many objects.
	 */

	public void ensure(int amount) {
		if (maxSize != -1 && amount > maxSize)
			throw new IllegalStateException("Amount (" + amount + ") is larger than the maximum size (" + maxSize + ")");

		int create = pool.size() - amount;
		for (int i = 0; i < create; i++) {
			pool.add(this.create());
		}
	}

	/**
	 * Flushes the pool.
	 */

	public void flush() {
		pool.clear();
	}

	public int createdObjectsCount() {
		return this.createCount;
	}

	public int linkedObjectsCount() {
		return pool.size();
	}

	/**
    *
    */

	public final Pool<T> asThreadSafePool() {
		final Pool<T> backing = this;

		return new Pool<T>() {
			private final Object lock = new Object();

			public T aquire() {
				synchronized (lock) {
					return backing.aquire();
				}
			}

			public T release(T t) {
				synchronized (lock) {
					return backing.release(t);
				}
			}

			public void ensure(int amount) {
				synchronized (lock) {
					backing.ensure(amount);
				}
			}

			public void flush() {
				synchronized (lock) {
					backing.flush();
				}
			}

			public int createdObjectsCount() {
				synchronized (lock) {
					return backing.createdObjectsCount();
				}
			}

			public int linkedObjectsCount() {
				synchronized (lock) {
					return backing.linkedObjectsCount();
				}
			}
		};
	}
}<file_sep>/*
 * Created on Jul 21, 2010
 */

package craterstudio.streams;

import java.io.IOException;
import java.io.InputStream;

public class AbstractInputStream extends InputStream
{
   protected final InputStream backing;

   public AbstractInputStream(InputStream backing)
   {
      this.backing = backing;
   }

   @Override
   public int read() throws IOException
   {
      byte[] one = new byte[1];
      return (this.read(one) == -1) ? 1 : one[0] & 0xFF;
   }

   @Override
   public int read(byte[] buf) throws IOException
   {
      return this.read(buf, 0, buf.length);
   }

   @Override
   public int read(byte[] buf, int off, int len) throws IOException
   {
      return this.backing.read(buf, off, len);
   }

   @Override
   public long skip(long n) throws IOException
   {
      byte[] temp = new byte[1024];
      long counter = 0;
      while (n > 0)
      {
         int take = (int) Math.min(temp.length, n);
         int got = this.read(temp, 0, take);
         if (got == -1)
            break;
         counter += got;
      }
      return counter == 0 ? -1 : counter;
   }

   @Override
   public void close() throws IOException
   {
      this.backing.close();
   }
}<file_sep>package video_storm;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import video_storm.input.KeyInput;
import video_storm.input.MouseInput;

public class MainTest {
    public static void main( String[] args ) throws LWJGLException {
        new Window(640, 480);

        KeyInput k = new KeyInput();
        MouseInput m = new MouseInput();

        System.out.println( "*** " + Keyboard.KEY_SPACE );

        while(true) {
            k.get();
            m.get();
            Display.update();
        }
    }
}
<file_sep>/*
 * Created on Jul 21, 2010
 */

package craterstudio.streams;

import java.io.IOException;
import java.io.OutputStream;

public class AbstractOutputStream extends OutputStream {
   protected OutputStream backing;

   public AbstractOutputStream(OutputStream backing) {
      this.backing = backing;
   }

   @Override
   public void write(int b) throws IOException {
      byte[] one = new byte[1];
      one[0] = (byte) b;
      this.write(one, 0, one.length);
   }

   @Override
   public void write(byte[] b) throws IOException {
      this.write(b, 0, b.length);
   }

   @Override
   public void write(byte[] b, int off, int len) throws IOException {
      this.backing.write(b, off, len);
   }

   @Override
   public void flush() throws IOException {
      this.backing.flush();
   }

   @Override
   public void close() throws IOException {
      this.backing.close();
   }
}
<file_sep>/*
 * Created on Sep 18, 2005
 */

package craterstudio.text;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Calendar;

public class TextValues {
	/**
	 * PARSE
	 */

	public static boolean isBoolean(String value) {
		if (value == null)
			return false;
		if (value.equalsIgnoreCase("true"))
			return true;
		if (value.equalsIgnoreCase("false"))
			return true;
		return false;
	}

	public static boolean isInt(String value) {
		if (value == null) {
			return false;
		}

		try {
			Integer.parseInt(value);
			return true;
		} catch (NumberFormatException exc) {
			return false;
		}
	}

	public static boolean isLong(String value) {
		if (value == null) {
			return false;
		}

		try {
			Long.parseLong(value);
			return true;
		} catch (NumberFormatException exc) {
			return false;
		}
	}

	public static boolean isFloat(String value) {
		if (value == null) {
			return false;
		}

		try {
			Float.parseFloat(value);
			return true;
		} catch (NumberFormatException exc) {
			return false;
		}
	}

	public static boolean isDouble(String value) {
		if (value == null) {
			return false;
		}

		try {
			Double.parseDouble(value);
			return true;
		} catch (NumberFormatException exc) {
			return false;
		}
	}

	public static boolean tryParseBoolean(String value, boolean defaultValue) {
		if (value == null) {
			return defaultValue;
		}

		return Boolean.parseBoolean(value);
	}

	public static int tryParseInt(String value, int defaultValue) {
		if (value == null) {
			return defaultValue;
		}

		try {
			return Integer.parseInt(value);
		} catch (NumberFormatException exc) {
			return defaultValue;
		}
	}

	public static long tryParseLong(String value, long defaultValue) {
		if (value == null) {
			return defaultValue;
		}

		try {
			return Long.parseLong(value);
		} catch (NumberFormatException exc) {
			return defaultValue;
		}
	}

	public static float tryParseFloat(String value, float defaultValue) {
		if (value == null) {
			return defaultValue;
		}

		try {
			return Float.parseFloat(value);
		} catch (NumberFormatException exc) {
			return defaultValue;
		}
	}

	public static double tryParseDouble(String value, double defaultValue) {
		if (value == null) {
			return defaultValue;
		}

		try {
			return Double.parseDouble(value);
		} catch (NumberFormatException exc) {
			return defaultValue;
		}
	}

	public static final String[] trim(String[] src) {
		String[] dst = new String[src.length];
		for (int i = 0; i < dst.length; i++)
			dst[i] = src[i].trim();
		return dst;
	}

	//

	public static final boolean[] parseBooleans(String[] value) {
		boolean[] data = new boolean[value.length];
		for (int i = 0; i < data.length; i++)
			if (value[i].equals("true"))
				data[i] = true;
			else if (value[i].equals("false"))
				data[i] = false;
			else
				throw new IllegalArgumentException("value: " + value[i]);
		return data;
	}

	public static final long[] parseLongs(String[] value) {
		long[] data = new long[value.length];
		for (int i = 0; i < data.length; i++)
			data[i] = Long.parseLong(value[i], 10);
		return data;
	}

	public static final double[] parseDoubles(String[] value) {
		double[] data = new double[value.length];
		for (int i = 0; i < data.length; i++)
			data[i] = Double.parseDouble(value[i]);
		return data;
	}

	//

	public static final int[] parseInts(String[] value) {
		return TextValues.parseInts(value, 0, value.length, 10);
	}

	public static final int[] parseInts(String[] value, int radix) {
		return TextValues.parseInts(value, 0, value.length, radix);
	}

	public static final int[] parseInts(String[] value, int off, int len) {
		return TextValues.parseInts(value, off, len, 10);
	}

	public static final int[] parseInts(String[] value, int off, int len, int radix) {
		int[] data = new int[len];
		for (int i = 0; i < len; i++)
			data[i] = Integer.parseInt(value[off + i], radix);
		return data;
	}

	public static final float[] parseFloats(String[] value) {
		return TextValues.parseFloats(value, 0, value.length);
	}

	public static final float[] parseFloats(String[] value, int off, int len) {
		float[] data = new float[len];

		for (int i = 0; i < len; i++)
			data[i] = Float.parseFloat(value[off + i]);

		return data;
	}

	public static final String nanosToTime(long nanos) {
		if (nanos == 0)
			return "n/a"; // so small it cannot even be the time elapsed between
			              // two System.nanoTime() calls

		if (nanos < 10000)
			return nanos + "ns";

		long micros = nanos / 1000L;
		if (micros < 10000)
			return micros + "us";

		long millis = micros / 1000L;
		if (millis < 10000)
			return millis + "ms";

		return millis / 1000 + "s";
	}

	public static final String nanosAsMicroSeconds(long nanos) {
		return TextValues.formatNumber(nanos / 1000000000.0, 6) + "s";
	}

	/**
	 * VALUE OF
	 */

	public static final String valueOf(int[] buf) {
		return TextValues.valueOf(buf, 0, buf.length);
	}

	public static final String valueOf(float[] buf) {
		return TextValues.valueOf(buf, 0, buf.length);
	}

	public static final String valueOf(int[] buf, int off, int len) {
		if (len == 0)
			return "";

		StringBuffer buffer = new StringBuffer();

		for (int i = 0; i < len; i++) {
			buffer.append(buf[off + i]);
			buffer.append(", ");
		}

		buffer.setLength(buffer.length() - 2);

		return buffer.toString();
	}

	public static final String valueOf(float[] buf, int off, int len) {
		if (len == 0)
			return "";

		StringBuffer buffer = new StringBuffer();

		for (int i = 0; i < len; i++) {
			buffer.append(buf[off + i]);
			buffer.append(", ");
		}

		buffer.setLength(buffer.length() - 2);

		return buffer.toString();
	}

	/**
	 * HEX
	 */

	private static char[] hexTableUpper = "0123456789ABCDEF".toCharArray();

	// private static char[] hexTableLower = "0123456789abcdef".toCharArray();

	private static final int h2i(int cur) {
		if (cur >= '0' && cur <= '9')
			return cur - '0';
		if (cur >= 'a' && cur <= 'f')
			return cur - 'a' + 10;
		if (cur >= 'A' && cur <= 'F')
			return cur - 'A' + 10;
		throw new IllegalArgumentException("cur=" + cur);
	}

	public static final String hexDecode(String hex) {
		if (hex == null)
			return null;
		if (hex.length() % 2 != 0)
			throw new IllegalArgumentException();

		char[] digits = new char[hex.length() / 2];
		for (int k = 0, p = 0; k < digits.length; k++)
			digits[k] = (char) ((h2i(hex.charAt(p++)) << 4) | h2i(hex.charAt(p++)));
		return new String(digits);
	}

	public static final String hexEncode(String str) {
		if (str == null)
			return null;

		char[] hex = new char[str.length() * 2];
		for (int i = 0, p = 0; i < str.length(); i++, p += 2) {
			char c = str.charAt(i);
			hex[p + 0] = hexTableUpper[(c & 0xF0) >> 4];
			hex[p + 1] = hexTableUpper[(c & 0x0F) >> 0];
		}
		return new String(hex);
	}

	//

	public static int hexDecodeDigit(char c) {
		if (c >= '0' && c <= '9')
			return c - '0';
		if (c >= 'a' && c <= 'f')
			return 10 + (c - 'a');
		if (c >= 'A' && c <= 'F')
			return 10 + (c - 'A');
		throw new IllegalStateException();
	}

	//

	public static final String hexRawEncode(byte[] raw) {
		char[] ca = new char[raw.length * 2];
		for (int i = 0; i < raw.length; i++)
			for (int k = 0; k < 2; k++)
				ca[(i << 1) + (1 - k)] = hexTableUpper[((raw[i] & 0xFF) >>> (k << 2)) & 0x0F];
		return new String(ca);
	}

	public static final byte[] hexRawDecode(String raw) {
		if (raw.length() % 2 != 0)
			throw new IllegalStateException();

		byte[] data = new byte[raw.length() >> 1];
		for (int i = 0; i < data.length; i++)
			data[i] = (byte) Integer.parseInt(raw.substring(i << 1, (i << 1) + 2), 16);
		return data;
	}

	//

	public static final String hexValueOf(byte hex) {
		char[] digits = new char[2];
		for (int i = 0; i < 2; i++)
			digits[1 - i] = hexTableUpper[(hex >>> (i << 2)) & 0x0F];
		return new String(digits);
	}

	public static final String hexValueOf(short hex) {
		char[] digits = new char[4];
		for (int i = 0; i < 4; i++)
			digits[3 - i] = hexTableUpper[(hex >>> (i << 2)) & 0x0F];
		return new String(digits);
	}

	public static final String hexValueOf(int hex) {
		char[] digits = new char[8];
		for (int i = 0; i < 8; i++)
			digits[7 - i] = hexTableUpper[(hex >>> (i << 2)) & 0x0F];
		return new String(digits);
	}

	public static final String hexValueOf(long hex) {
		char[] digits = new char[16];
		for (int i = 0; i < 16; i++)
			digits[15 - i] = hexTableUpper[(int) ((hex >>> (i << 2))) & 0x0F];
		return new String(digits);
	}

	/**
	 * BIN
	 */

	public static final String binValueOf(byte hex) {
		char[] digits = new char[8];
		for (int i = 0; i < 8; i++)
			digits[7 - i] = (char) ('0' + ((hex >>> i) & 0x01));
		return new String(digits);
	}

	public static final String binValueOf(short hex) {
		char[] digits = new char[3216];
		for (int i = 0; i < 16; i++)
			digits[15 - i] = (char) ('0' + ((hex >>> i) & 0x01));
		return new String(digits);
	}

	public static final String binValueOf(int hex) {
		char[] digits = new char[32];
		for (int i = 0; i < 32; i++)
			digits[31 - i] = (char) ('0' + ((hex >>> i) & 0x01));
		return new String(digits);
	}

	public static final String binValueOf(long hex) {
		char[] digits = new char[64];
		for (int i = 0; i < 64; i++)
			digits[63 - i] = (char) ('0' + ((hex >>> i) & 0x01));
		return new String(digits);
	}

	/**
	 * FORMAT
	 */

	private static final String zeros = new String("0000000000000000000000000000000000");

	public static final int parseIntMagnitude(String s) {
		long value = parseLongMagnitude(s);
		if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE)
			throw new IllegalStateException("value too large too fit in integer");
		return (int) value;
	}

	public static final long parseLongMagnitude(String s) {
		s = s.trim();
		if (s.isEmpty())
			return 0;

		long mul = 1;

		switch (s.charAt(s.length() - 1)) {
		// bytes: tera, giga, mega, kilo
			case 'T':
				mul *= 1024L;
			case 'G':
				mul *= 1024L;
			case 'M':
				mul *= 1024L;
			case 'K':
				mul *= 1024L;
				break;

			// milliseconds: years, <<_NO_MONTHS_>> weeks, days, hours, minutes,
			// seconds
			case 'y':
				mul *= 365L;
			case 'w':
				mul *= 7L;
			case 'd':
				mul *= 24L;
			case 'h':
				mul *= 60L;
			case 'm':
				mul *= 60L;
			case 's':
				mul *= 1000L;
				break;

			// no magnitude set
			default:
				break;
		}

		if (mul != 1) // magnitude set, strip magnitude
			s = s.substring(0, s.length() - 1);
		return Long.parseLong(s) * mul;
	}

	public static final String magnitudePeriods(int value, char sep) {
		boolean neg = value < 0;
		if (neg)
			value = -value;

		String left = String.valueOf(value);

		String build = "";
		while (left.length() > 3) {
			build = sep + left.substring(left.length() - 3) + build;
			left = left.substring(0, left.length() - 3);
		}

		return (neg ? "-" : "") + left + build;
	}

	public static final String formatCurrencyInCents(int amount, boolean formatForUSD, boolean magnitudeSeparators) {
		int full = amount / 100;
		int cent = amount % 100;

		char mags = formatForUSD ? ',' : '.';
		char sepe = formatForUSD ? '.' : ',';

		String post = sepe + formatNumber(cent, 2);
		if (magnitudeSeparators)
			return magnitudePeriods(full, mags) + post;
		return String.valueOf(full) + post;
	}

	public static final String formatNumber(int value, int minbfor) {
		boolean negative = value < 0;
		String sign = "";

		if (negative) {
			sign = "-";
			value = -value;
		}

		String s = String.valueOf(value);
		if (s.length() < minbfor)
			s = zeros.substring(0, minbfor - s.length()) + s;
		return sign + s;
	}

	public static final String formatNumber(double value, int decimals) {
		return formatNumber(value, 1, decimals);
	}

	public static final String formatNumber(double value, int minbfor, int decimals) {
		if (Double.isNaN(value))
			return "NaN";
		if (Double.isInfinite(value))
			return "Infinity";

		NumberFormat df = DecimalFormat.getInstance();
		df.setGroupingUsed(false);
		df.setMinimumFractionDigits(decimals);
		df.setMaximumFractionDigits(decimals);
		df.setMinimumIntegerDigits(value < 0.0 ? minbfor - 1 : minbfor);
		df.setMaximumIntegerDigits(Integer.MAX_VALUE);
		return df.format(value);
	}

	public static final String formatedValueOf(float[] value, int before, int after) {
		if (value.length == 0)
			return "";

		StringBuffer buffer = new StringBuffer();

		for (int i = 0; i < value.length; i++) {
			buffer.append(TextValues.formatNumber(value[i], before, after));
			buffer.append(", ");
		}

		buffer.setLength(buffer.length() - 2);

		return buffer.toString();
	}

	/**
	 * NUMBER FORMAT
	 */

	/**
	 * MAG
	 */

	public static final String formatWithMagnitude(double value, int decimals) {
		return TextValues.formatWithMagnitude(value, decimals, 1000.0);
	}

	public static final String formatWithMagnitudeBytes(double value, int decimals) {
		return TextValues.formatWithMagnitude(value, decimals, 1024.0);
	}

	private static final String[] mags = new String[] { "n", "u", "m", "", "k", "M", "G", "T", "P" };

	private static final String formatWithMagnitude(double value, int decimals, double magSize) {
		if (value == 0.0)
			return TextValues.formatNumber(value, decimals);

		int mag = 3;
		boolean minus = value < 0.0F;

		// always make it positive
		if (minus)
			value = -value;

		if (value > 1.0F) {
			// find mag
			while ((value / magSize) > 1.0f) {
				mag++;
				value /= magSize;
			}
		} else {
			// find mag
			do {
				mag--;
				value *= magSize;
			} while ((value * magSize) < 1.0f);
		}

		// make negative if it was negative
		if (minus)
			value = -value;

		return TextValues.formatNumber(value, decimals) + mags[mag];
	}

	/**
	 * FORMAT TIME
	 */

	public static final String getCurrentDate() {
		return formatDateTime(System.currentTimeMillis(), "Y-M-D");
	}

	public static final String getCurrentTime() {
		return formatDateTime(System.currentTimeMillis(), "h:m:s");
	}

	public static final String getCurrentDateTime() {
		return formatDateTime(System.currentTimeMillis(), "Y-M-D h:m:s");
	}

	public static final String formatDateTime(long timestamp, String format) {
		Calendar cc = Calendar.getInstance();
		cc.setTimeInMillis(timestamp);

		int yyyy = cc.get(Calendar.YEAR);
		int mm = cc.get(Calendar.MONTH) + 1;
		int dd = cc.get(Calendar.DAY_OF_MONTH);
		int h = cc.get(Calendar.HOUR_OF_DAY);
		int m = cc.get(Calendar.MINUTE);
		int s = cc.get(Calendar.SECOND);
		int ms = cc.get(Calendar.MILLISECOND);

		String yyyy0 = TextValues.formatNumber(yyyy, 4, 0);
		String mm0 = TextValues.formatNumber(mm, 2, 0);
		String dd0 = TextValues.formatNumber(dd, 2, 0);
		String h0 = TextValues.formatNumber(h, 2, 0);
		String m0 = TextValues.formatNumber(m, 2, 0);
		String s0 = TextValues.formatNumber(s, 2, 0);
		String ms0 = TextValues.formatNumber(ms, 3, 0);

		StringBuffer sb = new StringBuffer();

		for (int i = 0; i < format.length(); i++) {
			switch (format.charAt(i)) {
				case 'Y':
					sb.append(yyyy0);
					break;
				case 'M':
					sb.append(mm0);
					break;
				case 'D':
					sb.append(dd0);
					break;
				case 'h':
					sb.append(h0);
					break;
				case 'm':
					sb.append(m0);
					break;
				case 's':
					sb.append(s0);
					break;
				case 'S':
					sb.append(ms0);
					break;

				default:
					sb.append(format.charAt(i));
					break;
			}
		}

		return sb.toString();
	}

	public static final String formatElapsedTime(long ms, String format) {
		long s = ms / 1000;

		long ss = s % 60;
		long mm = (s / 60) % 60;
		long hh = (s / 60) / 60;

		String ss0 = TextValues.formatNumber(ss, 2, 0);
		String mm0 = TextValues.formatNumber(mm, 2, 0);
		String hh0 = TextValues.formatNumber(hh, 2, 0);

		StringBuffer sb = new StringBuffer();

		for (int i = 0; i < format.length(); i++) {
			switch (format.charAt(i)) {
				case 'S':
					sb.append(ss0);
					break;
				case 'M':
					sb.append(mm0);
					break;
				case 'H':
					sb.append(hh0);
					break;
				default:
					sb.append(format.charAt(i));
					break;
			}
		}

		return sb.toString();
	}
}<file_sep>package craterstudio.util.concur;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import craterstudio.func.Transformer;

public class LockStepExecutor<I, O> {
	private final ExecutorService service;

	public LockStepExecutor(ExecutorService service) {
		this.service = service;
	}

	private final List<I> inputs = new ArrayList<>();
	private final List<O> outputs = new ArrayList<>();
	private final List<Future<O>> futures = new ArrayList<>();

	public void addInput(I input) {
		this.inputs.add(input);
	}

	public List<O> lockstep(final Transformer<I, O> task) {

		futures.clear();
		for (final I input : inputs) {
			Callable<O> call = new Callable<O>() {
				@Override
				public O call() throws Exception {
					return task.transform(input);
				}
			};
			futures.add(this.service.submit(call));
		}
		inputs.clear();

		outputs.clear();
		for (Future<O> future : futures) {
			O output;
			try {
				output = future.get();
			} catch (InterruptedException | ExecutionException exc) {
				throw new IllegalStateException(exc);
			}
			outputs.add(output);
		}

		return outputs;
	}
}
<file_sep>/*
 * Copyright (c) 2012, Riven
 *
 * 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 Riven 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.
 */

package net.indiespot.media.impl;

import org.lwjgl.opengl.Display;

import craterstudio.text.TextValues;
import craterstudio.util.HighLevel;

class VSyncSleeper {
	private static final long INTERVAL = 1_000_000_000L / 60;

	private long nextPostSync;
	private long errorMargin;

	public void setSyncErrorMargin(int millis) {
		this.errorMargin = (millis * 1_000_000L);
	}

	public void measureSyncTimestamp(int frames) {
		// ensure GPU cannot use async updates
		nextPostSync = System.nanoTime();
		for (int i = 0; i < frames; i++) {
			Display.update();
			long now = System.nanoTime();
			System.out.println("VSyncSleeper: " + TextValues.formatNumber((now - nextPostSync) / 1_000_000.0, 2) + "ms");
			nextPostSync = now;
		}
	}

	private void debugMeasureSyncPredictionError() {
		/**
		 * result: even over thousands of frames, the error is within 1ms
		 */
		for (int i = 0; true; i++) {
			Display.update();

			System.out.println("vsync prediction error: " + TextValues.formatNumber(//
			   (System.nanoTime() - (nextPostSync + (i + 1) * INTERVAL)) / 1_000_000.0, 2) + "ms");
		}
	}

	public void sleepUntilBeforeVsync() {
		this.calcNextPostVync(System.nanoTime());

		long wakeupAt = this.nextPostSync - this.errorMargin;
		if (wakeupAt < System.nanoTime()) {
			return;
		}

		// System.out.println("VSyncSleeper: wait for " +
		// TextValues.formatNumber((wakeupAt - System.nanoTime()) / 1_000_000.0,
		// 2) + "ms");

		while (System.nanoTime() < wakeupAt) {
			HighLevel.sleep(1);
		}
	}

	private void calcNextPostVync(long now) {
		long next = nextPostSync;
		while (next < now) {
			next += INTERVAL;
		}
		nextPostSync = next;
	}
}
<file_sep>/*
 * Copyright (c) 2012, Riven
 *
 * 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 Riven 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.
 */

package net.indiespot.media;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

import craterstudio.util.concur.SimpleBlockingQueue;

import net.indiespot.media.impl.FFmpeg;
import net.indiespot.media.impl.VideoMetadata;

public class Movie implements Closeable {

	public static Movie open(File movieFile) throws IOException {
		VideoMetadata metadata = FFmpeg.extractMetadata(movieFile);

		InputStream rgb24Stream = FFmpeg.extractVideoAsRGB24(movieFile);
		InputStream wav16Stream = FFmpeg.extractAudioAsWAV(movieFile);

		AudioStream audioStream = new AudioStream(wav16Stream);
		VideoStream videoStream = new VideoStream(rgb24Stream, metadata);

		videoStream = new ThreadedVideoStream(videoStream, metadata, 3);

		return new Movie(metadata, videoStream, audioStream);
	}

	private Movie(VideoMetadata metadata, VideoStream videoStream, AudioStream audioStream) {
		this.metadata = metadata;
		this.videoStream = videoStream;
		this.audioStream = audioStream;
	}

	//

	private final VideoMetadata metadata;

	public int width() {
		return metadata.width;
	}

	public int height() {
		return metadata.height;
	}

	public float framerate() {
		return metadata.framerate;
	}

	//

	private final VideoStream videoStream;
	private final AudioStream audioStream;

	public VideoStream videoStream() {
		return videoStream;
	}

	public AudioStream audioStream() {
		return audioStream;
	}

	//

	private long initFrame;
	private long frameInterval;

	public void init() {
		initFrame = System.nanoTime();
		audioIndex = 0;
		videoIndex = 0;
		frameInterval = (long) (1000_000_000L / metadata.framerate);
	}

	//

	private static final int AUDIO_UNAVAILABLE = -1;
	private static final int AUDIO_TERMINATED = -2;

	private int audioIndex;
	private int videoIndex;

	public void onMissingAudio() {
		audioIndex = AUDIO_UNAVAILABLE;
	}

	public void onEndOfAudio() {
		audioIndex = AUDIO_TERMINATED;
	}

	public void onRenderedAudioBuffer() {
		this.audioIndex++;
	}

	public void onUpdatedVideoFrame() {
		this.videoIndex++;
	}

	public boolean isTimeForNextFrame() {

		switch (audioIndex) {
			case AUDIO_TERMINATED:
				// reached end of audio
				return true;

			case AUDIO_UNAVAILABLE:
				// sync video with clock
				return videoIndex * frameInterval <= System.nanoTime() - initFrame;

			default:
				// sync video with audio
				return videoIndex <= audioIndex;
		}
	}

	@Override
	public void close() throws IOException {
		audioStream().close();
		videoStream().close();
	}
}

class ThreadedVideoStream extends VideoStream {
	private final VideoStream backing;
	private final SimpleBlockingQueue<ByteBuffer> emptyQueue, filledQueue;

	public ThreadedVideoStream(final VideoStream backing, VideoMetadata metadata, int buffers) {
		super(backing.videoStream, metadata);

		this.backing = backing;
		this.emptyQueue = new SimpleBlockingQueue<>();
		this.filledQueue = new SimpleBlockingQueue<>();

		for (int i = 0; i < buffers; i++) {
			this.emptyQueue.put(ByteBuffer.allocateDirect(metadata.width * metadata.height * 3));
		}

		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					ByteBuffer rgbBuffer = emptyQueue.take();

					rgbBuffer.clear();
					if (backing.readFrameInto(rgbBuffer) == null) {
						break;
					}
					rgbBuffer.flip();

					filledQueue.put(rgbBuffer);
				}

				filledQueue.put(null);
			}
		}).start();
	}

	public ByteBuffer readFrameInto(ByteBuffer rgbBuffer) {
		if (rgbBuffer != null) {
			this.emptyQueue.put(rgbBuffer);
		}
		return this.filledQueue.take();
	}

	@Override
	public void close() throws IOException {
		this.backing.close();
	}
}
<file_sep>/*
 * Created on Aug 7, 2005
 */
package craterstudio.util;

public interface PoolHandler<T>
{
   public T create();

   public void clean(T t);
}
<file_sep>/*
 * Created on 16-mei-2005
 */
package craterstudio.io;

import java.io.IOException;

public interface TransferListener
{
   public void transferInitiated(int expectedBytes);

   public void transfered(int bytes);

   public void transferFinished(IOException potentialException);
}<file_sep>/**
 * Created on 18-mei-2003
 */

package craterstudio.util;

import java.awt.Component;
import java.awt.Dialog;
import java.awt.Frame;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicLong;

import javax.swing.JDialog;

//import craterstudio.data.tuples.Duo;
//import craterstudio.func.Condition;
import craterstudio.io.Streams;
import craterstudio.text.Text;

public class HighLevel {
	public static String convertThrowableToString(Throwable t) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		PrintStream stream = new PrintStream(baos);
		t.printStackTrace(stream);
		return Text.utf8(baos.toByteArray());
	}

	public static List<File> lookupClassPath() {
		ClassLoader parent = HighLevel.class.getClassLoader();

		List<File> cp = new ArrayList<File>();

		if (parent instanceof URLClassLoader) {
			URL[] urls = ((URLClassLoader) parent).getURLs();

			for (URL url : urls) {
				String full = url.toExternalForm();

				if (!full.startsWith("file:")) {
					continue;
				}

				cp.add(new File(Text.after(full, "file:")));
			}
		}

		return cp;
	}

	public static Runnable createMainLauncher(String classpath, String mainclass, String... args) throws Exception {
		return createMainLauncher(Text.split(classpath, File.pathSeparatorChar), mainclass, args);
	}

	public static Runnable createMainLauncher(String[] classpath, String mainclass, final String... args) throws Exception {
		URL[] cpURLs = new URL[classpath.length];

		try {
			for (int i = 0; i < cpURLs.length; i++) {
				cpURLs[i] = new File(classpath[i]).toURI().toURL();
			}
		} catch (MalformedURLException exc) {
			throw new IllegalArgumentException(exc);
		}

		URLClassLoader cl = new URLClassLoader(cpURLs);
		Class<?>[] params = new Class[] { String[].class };
		final Method method = cl.loadClass(mainclass).getMethod("main", params);

		return new Runnable() {
			@Override
			public void run() {
				try {
					method.invoke(null, new Object[] { args });
				} catch (InvocationTargetException exc) {
					if (!(exc.getCause() instanceof ThreadDeath))
						throw new IllegalStateException(exc.getCause());
				} catch (Exception exc) {
					exc.printStackTrace();
				}
			}
		};
	}

	public static final int iteratorElements(Iterator<?> it) {
		int counter = 0;
		while (it.hasNext()) {
			it.next();
			counter++;
		}
		return counter;
	}

	public static final <T> T iteratorElement(Iterator<T> it, int n) {
		if (n < 0)
			throw new IllegalArgumentException("n (" + n + ") must be >= 0");

		int m = n;

		while (it.hasNext()) {
			T t = it.next();
			if ((n--) == 0)
				return t;
		}

		throw new NoSuchElementException("element @ " + m + " not available");
	}

	public static final <T> int indexOfIterator(Iterator<T> it, T find) {
		int i = 0;
		while (it.hasNext()) {
			if (it.next().equals(find))
				return i;
			i++;
		}
		return -1;
	}

	public static final <T> int lastIndexOfIterator(Iterator<T> it, T find) {
		int last = -1;
		int i = 0;
		while (it.hasNext()) {
			if (it.next().equals(find))
				last = i;
			i++;
		}
		return last;
	}

	//

	public static final Thread startDaemon(Runnable task) {
		Thread t = new Thread(task);
		t.setDaemon(true);
		t.start();
		return t;
	}

	private static final AtomicLong threadOff = new AtomicLong();

	public static final Thread start(Runnable task) {
		Thread t = new Thread(task);
		t.start();
		return t;
	}

	public static final Thread start(Runnable task, long stacksize) {
		return start(task, "CustomStackSize-" + threadOff.getAndIncrement(), stacksize);
	}

	public static final Thread start(Runnable task, String name, long stacksize) {
		Thread t = new Thread(Thread.currentThread().getThreadGroup(), task, name, stacksize);
		t.start();
		return t;
	}

	public static final void join(Thread t) {
		do {
			try {
				t.join();

				break;
			} catch (InterruptedException exc) {
				// ignore
			}
		} while (true);
	}

	public static final void wait(Object lock) {
		try {
			lock.wait();
		} catch (InterruptedException exc) {
			// ignore
		}
	}

	public static final void wait(Object lock, long ms) {
		try {
			lock.wait(ms);
		} catch (InterruptedException exc) {
			// ignore
		}
	}

//	public static final void wait(Object lock, long waitMS, Condition con, boolean consumeInterrupts) {
//		synchronized (lock) {
//			while (con == null || (con != null && !con.pass())) {
//				try {
//					lock.wait(waitMS);
//				} catch (InterruptedException exc) {
//					if (consumeInterrupts) {
//						Thread.interrupted();
//					}
//				}
//
//				if (con == null)
//					break;
//			}
//		}
//	}

	public static final void notify(Object lock) {
		synchronized (lock) {
			lock.notify();
		}
	}

	public static final void notifyAll(Object lock) {
		synchronized (lock) {
			lock.notifyAll();
		}
	}

//	public static final void sleep(long waitMS, Condition con, boolean consumeInterrupts) {
//		while (!con.pass()) {
//			HighLevel.sleep(waitMS, consumeInterrupts);
//		}
//	}

	/**
	 * FIELD TO STRING
	 */

	public static final String fieldToString(Class<?> clazz, int fieldValue, String def) {
		try {
			for (Field field : clazz.getFields())
				if (field.getInt(null) == fieldValue)
					return field.getName();

			for (Field field : clazz.getDeclaredFields())
				if (Modifier.isPublic(field.getModifiers()))
					if (field.getInt(null) == fieldValue)
						return field.getName();
		} catch (Exception exc) {
			if (def == null)
				throw new IllegalArgumentException("Field-value (" + fieldValue + ") not found in class " + clazz.getName());

			return def;
		}

		if (def == null)
			throw new IllegalArgumentException("Field-value (" + fieldValue + ") not found in class " + clazz.getName());

		return def;
	}

	public static final List<String> fieldToStrings(Class<?> clazz, int fieldValue) {
		try {
			List<String> fieldNames = new LinkedList<String>();

			for (Field field : clazz.getFields())
				if (field.getInt(null) == fieldValue)
					fieldNames.add(field.getName());

			for (Field field : clazz.getDeclaredFields())
				if (Modifier.isPublic(field.getModifiers()))
					if (field.getInt(null) == fieldValue)
						fieldNames.add(field.getName());

			if (!fieldNames.isEmpty())
				return fieldNames;
		} catch (Exception exc) {
			throw new IllegalArgumentException(exc);
		}

		throw new IllegalArgumentException("Field-value (" + fieldValue + ") not found in class " + clazz.getName());
	}

	public static final int stringToField(Class<?> clazz, String fieldName, int def) {
		try {
			Field field = clazz.getField(fieldName);
			if (field == null)
				throw new IllegalStateException();
			if (field.getType() != int.class)
				throw new IllegalStateException();
			return ((Integer) field.get(null)).intValue();
		} catch (Exception exc) {
			return def;
		}
	}

	/**
	 * PROPERTIES BY PATH
	 */

	public static final void setPropertyByPath(Object obj, String path, Object val) {
		propertyPathBy(obj, path, val, true);
	}

	public static final Object getPropertyByPath(Object obj, String path) {
		return propertyPathBy(obj, path, null, false);
	}

	private static final Object propertyPathBy(Object obj, String path, Object val, boolean set) {
		String orgPath = path;

		while (true) {
			int dot = path.indexOf('.');

			if (dot == -1) {
				if (set) {
					setProperty(obj, path, val);
					return null;
				}

				return getProperty(obj, path);
			}

			obj = getProperty(obj, path.substring(0, dot));
			if (obj == null)
				throw new NullPointerException("\"" + path.substring(0, dot) + "\"==null in \"" + orgPath + "\"");
			path = path.substring(dot + 1);
		}
	}

	/**
	 * PROPERTIES
	 */

	public static final void setProperty(Object obj, String property, Object val) {
		String methodName = "set" + Text.capitalize(property);

		for (Method method : obj.getClass().getMethods()) {
			if (!method.getName().equals(methodName))
				continue;

			if (method.getReturnType() != void.class)
				throw new IllegalStateException("Invalid property: " + property + ", return-type must be void");

			if (method.getParameterTypes().length != 1)
				throw new IllegalStateException("Invalid property: " + property + ", must have 1 parameter");

			try {
				method.invoke(obj, new Object[] { val });
				return;
			} catch (IllegalArgumentException exc) {
				throw new IllegalStateException("" + exc.getMessage() + " prop=" + property + ", val=" + val.getClass().getSimpleName() + ", par=" + method.getParameterTypes()[0].getSimpleName());
			} catch (Exception exc) {
				throw new IllegalStateException(exc);
			}
		}

		throw new IllegalStateException("Couldn't find property: " + property + " as " + methodName);
	}

	public static final Object getProperty(Object obj, String property) {
		for (int i = 0; i < 2; i++) {
			String methodName = ((i == 0) ? "get" : "is") + Text.capitalize(property);

			for (Method method : obj.getClass().getMethods()) {
				if (!method.getName().equals(methodName))
					continue;

				if (method.getReturnType() == void.class)
					throw new IllegalStateException("Invalid property: " + property + ", return-type must not be void");

				if (method.getParameterTypes().length != 0)
					throw new IllegalStateException("Invalid property: " + property + ", must have 0 parameters");

				try {
					return method.invoke(obj, new Object[0]);
				} catch (Exception exc) {
					throw new IllegalStateException(exc);
				}
			}
		}

		throw new IllegalStateException("Couldn't find property: " + property);
	}

	/**
	 * GC
	 */

	public static final long gc() {
		long preUsed = HighLevel.memUsed();
		for (int i = 0; i < 4; i++)
			System.gc();
		long postUsed = HighLevel.memUsed();

		return preUsed - postUsed;
	}

	public static long memUsed() {
		Runtime r = Runtime.getRuntime();
		return r.totalMemory() - r.freeMemory();
	}

	//

	/**
    *
    */

//	public static final Duo<byte[]> executeProcess(String... cmds) {
//		try {
//			Process proc = Runtime.getRuntime().exec(cmds);
//			ByteArrayOutputStream stdout = new ByteArrayOutputStream();
//			ByteArrayOutputStream stderr = new ByteArrayOutputStream();
//			Streams.asynchronousTransfer(proc.getInputStream(), stdout);
//			Streams.asynchronousTransfer(proc.getErrorStream(), stderr);
//			int exitCode = proc.waitFor();
//			if (exitCode != 0)
//				throw new IllegalStateException("exit-code: " + exitCode + " cmds=" + Arrays.toString(cmds));
//
//			return new Duo<byte[]>(stdout.toByteArray(), stderr.toByteArray());
//		} catch (Exception exc) {
//			throw new IllegalStateException(exc);
//		}
//	}

	/**
	 * SLEEP
	 */

	public static final void busySleep(long ms) {
		busyNanoSleep(ms * 1000000L);
	}

	public static final void busyNanoSleep(long ns) {
		long end = System.nanoTime() + ns;
		while (System.nanoTime() < end)
			Thread.yield();
	}

	public static final void sleep() {
		long tenYears = 10 * 365L * 24L * 3600L * 1000L;

		try {
			Thread.sleep(tenYears);
		} catch (InterruptedException exc) {
			return;
		}
	}

	public static final void sleep(long ms) {
		sleep(ms, false);
	}

	public static final void sleep(long ms, boolean consumeInterrupts) {
		try {
			Thread.sleep(ms);
		} catch (InterruptedException exc) {
			if (consumeInterrupts) {
				Thread.currentThread().interrupt();
				Thread.interrupted();
			}
		}
	}

	public static final int sizeof(Class<?> prim) {
		if (prim == byte.class || prim == boolean.class)
			return 1;
		if (prim == short.class || prim == char.class)
			return 2;
		if (prim == int.class || prim == float.class)
			return 4;
		if (prim == long.class || prim == double.class)
			return 8;
		return -1;
	}

	public static JDialog createDialogFromComponentParent(Component c, boolean modal, boolean root) {
		Component target = null;

		while (c.getParent() != null) {
			c = c.getParent();

			if (c instanceof Frame) {
				target = c;
				if (!root)
					break;
			}

			if (c instanceof Dialog) {
				target = c;
				if (!root)
					break;
			}
		}

		if (target instanceof Frame)
			return new JDialog((Frame) target, modal);
		if (target instanceof Dialog)
			return new JDialog((Dialog) target, modal);

		throw new IllegalStateException("Couldn't find root surface");
	}

	//

	public static Field findDeclaredField(Class<?> clazz, String name) {
		do {
			for (Field fld : clazz.getDeclaredFields())
				if (fld.getName().equals(name))
					return fld;

			clazz = clazz.getSuperclass();
		} while (clazz != null);

		return null;
	}

	public static Field[] findDeclaredFields(Class<?> clazz, String name) {
		List<Field> result = new ArrayList<Field>();

		do {
			for (Field fld : clazz.getDeclaredFields())
				if (fld.getName().equals(name))
					result.add(fld);

			clazz = clazz.getSuperclass();
		} while (clazz != null);

		return result.toArray(new Field[result.size()]);
	}

	//

	public static Method findDeclaredMethod(Class<?> clazz, String name) {
		do {
			for (Method mthd : clazz.getDeclaredMethods())
				if (mthd.getName().equals(name))
					return mthd;

			clazz = clazz.getSuperclass();
		} while (clazz != null);

		return null;
	}

	public static Method[] findDeclaredMethods(Class<?> clazz, String name) {
		Class<?> clz = clazz;
		List<Method> result = new ArrayList<Method>();

		try {
			do {
				outer: for (Method mthd1 : clazz.getDeclaredMethods()) {
					if (!mthd1.getName().equals(name))
						continue; // not interested

					for (Method mthd2 : result) {
						// can still be overridden with WIDER modifiers
						// if(mthd1.getModifiers() != mthd2.getModifiers())
						// continue;

						if (!mthd1.getReturnType().equals(mthd2.getReturnType()))
							continue; // not equal

						Class<?>[] c1 = mthd1.getParameterTypes();
						Class<?>[] c2 = mthd2.getParameterTypes();
						if (!Arrays.equals(c1, c2))
							continue; // not equal

						// is overriden by superclass, don't mention
						continue outer;
					}

					// found new method
					result.add(mthd1);
				}

				clazz = clazz.getSuperclass();
			} while (clazz != null);
		} catch (AccessControlException exc) {
			result.clear();
			for (Method mthd : clz.getMethods())
				if (mthd.getName().equals(name))
					result.add(mthd);
		}

		return result.toArray(new Method[result.size()]);
	}

	//

	public static Field[] forceAccess(Field... flds) {
		try {
			for (Field fld : flds)
				if (!fld.isAccessible())
					fld.setAccessible(true);
		} catch (AccessControlException exc) {
			// ignore
		}

		return flds;
	}

	public static Method[] forceAccess(Method... mthds) {
		try {
			for (Method mthd : mthds)
				if (!mthd.isAccessible())
					mthd.setAccessible(true);
			return mthds;
		} catch (AccessControlException exc) {
			// ignore
		}

		return mthds;
	}

	public static List<URL> getBootClassPath() {
		String a = System.getProperty("sun.boot.class.path");
		String b = System.getProperty("path.separator");

		List<URL> urls = new ArrayList<URL>();
		for (String p : Text.split(a, b)) {
			try {
				urls.add(new File(p).toURI().toURL());
			} catch (IOException exc) {
				exc.printStackTrace();
			}
		}
		return urls;
	}

	public static List<URL> getClassPath() {
		String a = System.getProperty("java.class.path");
		String b = System.getProperty("path.separator");

		List<URL> urls = new ArrayList<URL>();
		for (String p : Text.split(a, b)) {
			try {
				urls.add(new File(p).toURI().toURL());
			} catch (IOException exc) {
				exc.printStackTrace();
			}
		}
		return urls;
	}

	public static List<URL> getClassPath0() {
		try {
			ClassLoader loader = HighLevel.class.getClassLoader();

			if (loader instanceof URLClassLoader) {
				URLClassLoader ucl = (URLClassLoader) loader;
				return Arrays.asList(ucl.getURLs());
			}
		} catch (Exception exc) {
			exc.printStackTrace();
		}

		return null;
	}

	public static List<File> getJarsOnClassPath() {
		List<File> files = new ArrayList<File>();

		try {
			ClassLoader loader = HighLevel.class.getClassLoader();

			if (loader instanceof URLClassLoader) {
				URLClassLoader ucl = (URLClassLoader) loader;
				for (URL url : ucl.getURLs()) {
					String full = url.toString();
					if (!full.startsWith("file:")) {
						continue;
					}

					String path = Text.after(full, "file:");

					File file = new File(path);
					if (!file.exists() || file.isDirectory()) {
						continue;
					}

					files.add(file);
				}
			}
		} catch (Exception exc) {
			exc.printStackTrace();
		}

		return files;
	}
}<file_sep>package video_storm;

import craterstudio.math.EasyMath;
import net.indiespot.media.Movie;
import org.lwjgl.BufferUtils;
import org.lwjgl.util.glu.GLU;

import java.nio.ByteBuffer;

import static org.lwjgl.opengl.GL11.*;

public class MovieCanvas {
    int textureHandle;
    float texWidthUsedRatio, texHeightUsedRatio;

    int movieWidth, movieHeight;

    ByteBuffer textureBuffer;

    public MovieCanvas(int movieWidth, int movieHeight) {
        this.movieWidth = movieWidth;
        this.movieHeight = movieHeight;

        setupTexture();
    }

    public void render(int screenWidth, int screenHeight) {
        setProjection(screenWidth, screenHeight);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        float finalWidth = 1, finalHeight = 1;

        float movieRatio = (float)movieHeight / (float) movieWidth;

        if ( movieRatio < (float)screenHeight / (float)screenWidth) {
            finalWidth = screenWidth;
            finalHeight = movieRatio * screenWidth;
        }
        else {
            finalWidth = (float)movieWidth / (float) movieHeight * screenHeight;
            finalHeight = screenHeight;
        }

        renderMovieScreen(finalWidth, finalHeight, State.menuMode );
    }

    private void setProjection(int screenWidth, int screenHeight) {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();

        if (State.menuMode ) {
            GLU.gluPerspective( 60.0f, screenWidth / ( float ) screenHeight, 0.01f, 10000.0f );
            GLU.gluLookAt( 400, -210, -1600f, -400, 0, 0, 0, -1, 0 );
        }
        else {
            GLU.gluOrtho2D( -screenWidth / 2, screenWidth / 2, screenHeight / 2, -screenHeight / 2 );
        }
    }

    private void renderMovieScreen(float width, float height, boolean renderBlurr) {
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, textureHandle);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        glBegin(GL_QUADS);
        {
            glColor4f(1, 1, 1, 1);

            // render movie screen
            float z = 1;

            glTexCoord2f(0 * texWidthUsedRatio, 1 * texHeightUsedRatio);
            glVertex3f(-width/2, height/2, z);

            glTexCoord2f(1 * texWidthUsedRatio, 1 * texHeightUsedRatio);
            glVertex3f(width/2, height/2, z);

            glTexCoord2f(1 * texWidthUsedRatio, 0 * texHeightUsedRatio);
            glVertex3f(width/2, -height/2, z);

            glTexCoord2f(0 * texWidthUsedRatio, 0 * texHeightUsedRatio);
            glVertex3f(-width/2, -height/2, z);

            if (renderBlurr)
                blurrOnFloor(width, height);
        }

        glEnd();

        glDisable(GL_BLEND);
        glDisable(GL_TEXTURE_2D);
    }

    private void blurrOnFloor(float screenW, float screenH) {

        for (int i = -15; i <= 15; i++) {
            glColor4f(1, 1, 1, 0.015f);

            glTexCoord2f(0 * texWidthUsedRatio,
                         1 * texHeightUsedRatio);
                                                        glVertex3f( -screenW/2,
                                                                    screenH/2,
                                                                    0);

            glTexCoord2f(1 * texWidthUsedRatio,
                         1 * texHeightUsedRatio);

                                                        glVertex3f( screenW/2,
                                                                    screenH/2,
                                                                0);

//            glColor4f(1, 1, 1, 0.0f);
            glTexCoord2f(1 * texWidthUsedRatio,
                         0 * texHeightUsedRatio);

                                                        glVertex3f( screenW/2 + i * 5,
                                                                    screenH/2,
                                                                    -screenH);

            glTexCoord2f(   0 * texWidthUsedRatio,
                            0 * texHeightUsedRatio);

                                                        glVertex3f( -screenW/2 + i * 5,
                                                                    screenH/2,
                                                                    -screenH);
        }
    }

    private void setupTexture() {
        textureHandle = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, textureHandle);

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        int wPot = EasyMath.fitInPowerOfTwo( movieWidth );
        int hPot = EasyMath.fitInPowerOfTwo( movieHeight );
        texWidthUsedRatio = (float) movieWidth / wPot;
        texHeightUsedRatio = (float) movieHeight / hPot;

        // 'tmpbuf' should be null, but some drivers are too buggy
        ByteBuffer tmpbuf = BufferUtils.createByteBuffer( wPot * hPot * 3 );
        glTexImage2D(GL_TEXTURE_2D, 0/* level */, GL_RGB, wPot, hPot, 0/* border */, GL_RGB, GL_UNSIGNED_BYTE, tmpbuf);
    }

    public boolean readMovieFrame(Movie movie) {
        textureBuffer = movie.videoStream().readFrameInto(textureBuffer);

        if (textureBuffer == null)
            return false;

        glTexSubImage2D(GL_TEXTURE_2D, 0/* level */, 0, 0, movie.width(), movie.height(), GL_RGB, GL_UNSIGNED_BYTE, textureBuffer);

        return true;
    }
}
<file_sep>/*
 * Copyright (c) 2012, Riven
 *
 * 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 Riven 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.
 */

package net.indiespot.media.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Pattern;

import craterstudio.io.Streams;
import craterstudio.streams.NullOutputStream;
import craterstudio.text.RegexUtil;
import craterstudio.text.TextValues;

public class FFmpeg {

	public static String FFMPEG_PATH;
	public static boolean FFMPEG_VERBOSE = false;

	static {
		String resourceName = "./lib/ffmpeg/ffmpeg";
		if (Extractor.isMac) {
			resourceName += "-mac";
		} else {
			resourceName += Extractor.is64bit ? "64" : "32";
			if (Extractor.isWindows) {
				resourceName += ".exe";
			}
		}

		FFMPEG_PATH = resourceName;

		if (!new File(FFMPEG_PATH).exists()) {
			throw new IllegalStateException("Failed to find ffmpeg: " + new File(FFMPEG_PATH).getAbsolutePath());
		}
	}

	public static VideoMetadata extractMetadata(File srcMovieFile) throws IOException {
		Process process = new ProcessBuilder().command(//
		   FFMPEG_PATH, //
		   "-i", srcMovieFile.getAbsolutePath(),//
		   "-f", "null"//
		).start();
		Streams.asynchronousTransfer(process.getInputStream(), System.out, true, false);

		int width = -1;
		int height = -1;
		float framerate = -1;

		try {
			InputStream stderr = process.getErrorStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(stderr));
			for (String line; (line = br.readLine()) != null;) {
				System.out.println("ffmpeg: " + line);

				// Look for:
				// "	Stream #0:0: Video: vp6f, yuv420p, 320x240, 314 kb/s, 30 tbr, 1k tbn, 1k tbc"
				// ----------------------------------------------------------^

				if (line.trim().startsWith("Stream #") && line.contains("Video:")) {
					framerate = Float.parseFloat(RegexUtil.findFirst(line, Pattern.compile("\\s(\\d+(\\.\\d+)?)\\stbr,"), 1));
					int[] wh = TextValues.parseInts(RegexUtil.find(line, Pattern.compile("\\s(\\d+)x(\\d+)[\\s,]"), 1, 2));
					width = wh[0];
					height = wh[1];
				}
			}

			if (framerate == -1) {
				throw new IllegalStateException("failed to find framerate of video");
			}
			return new VideoMetadata(width, height, framerate);
		} finally {
			Streams.safeClose(process);
		}
	}

	public static InputStream extractVideoAsRGB24(File srcMovieFile) throws IOException {
		return streamData(new ProcessBuilder().command(//
		   FFMPEG_PATH, //
		   "-i", srcMovieFile.getAbsolutePath(), //
		   // "-ss", "00:05:00.00", //
		   "-f", "rawvideo", //
		   "-pix_fmt", "rgb24", //
		   "-" //
		));
	}

	//

	public static InputStream extractAudioAsWAV(File srcMovieFile) throws IOException {
		return streamData(new ProcessBuilder().command(//
		   FFMPEG_PATH, //
		   "-i", srcMovieFile.getAbsolutePath(), //
		   "-acodec", "pcm_s16le", //
		   "-ac", "2", //
		   // "-ss", "00:05:00.00", //
		   "-f", "wav", //
		   "-" //
		));
	}

	//

	private static InputStream streamData(ProcessBuilder pb) throws IOException {
		Process process = pb.start();
		Streams.asynchronousTransfer(process.getErrorStream(), FFMPEG_VERBOSE ? System.err : new NullOutputStream(), true, false);
		return process.getInputStream();
	}
}
<file_sep>package video_storm;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;

public class Window {

    public Window(int width, int height) throws LWJGLException {
        Display.setDisplayMode( new DisplayMode( width, height ) );
        Display.setResizable(true);
        Display.setTitle("VideoStorm v1.0 (by Ace)");
        Display.setVSyncEnabled(false);

        Display.create(new PixelFormat(/* bpp */32, /* alpha */8, /* depth */24,/* stencil */8,/* samples */4));
    }

    public void close() {
        Display.destroy();
    }
}
<file_sep>/*
 * Created on 4 jun 2010
 */

package craterstudio.streams;

import java.io.InputStream;

import craterstudio.io.Streams;

public class BinaryLineInputStream extends AbstractInputStream
{
   public BinaryLineInputStream(InputStream backing)
   {
      super(backing);
   }

   public String readLine()
   {
      return Streams.binaryReadLineAsString(this.backing);
   }
}
<file_sep>/*
 * Created on Sep 7, 2004
 */
package craterstudio.text;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
import java.util.Set;

import craterstudio.data.ByteList;
import craterstudio.data.tuples.Pair;

public class Text {
	public static boolean isNullOrEmpty(String value) {
		return (value==null) || value.isEmpty();
	}

	//

	public static String trimToNull(String value) {
		if (value == null)
			return null;
		value = value.trim();
		if (value.isEmpty())
			return null;
		return value;
	}

	public static String nullToEmpty(String value) {
		return (value == null) ? "" : value;
	}

	public static String concat(String... args) {
		int len = 0;
		for (int i = 0; i < args.length; i++)
			len += (args[i] = (args[i] == null ? "null" : args[i])).length();

		char[] chars = new char[len];
		for (int i = 0, p = 0; i < args.length; i++)
			for (int k = 0; k < args[i].length(); k++)
				chars[p++] = args[i].charAt(k);
		return new String(chars);
	}

	private static final char[] table = "0123456789abcdef".toCharArray();

	public static String hashAsHex(byte[] hash) {
		char[] table = Text.table;
		char[] digits = new char[hash.length << 1];
		for (int k = 0; k < hash.length; k++) {
			int h = hash[k];
			digits[(k << 1) | 1] = table[h & 0x0F];
			digits[k << 1] = table[(h & 0xF0) >> 4];
		}
		return new String(digits);
	}

	//

	private static final char[] generatecode_default_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890".toCharArray();

	public static String generateRandomCode(int len) {
		return generateRandomCode(len, generatecode_default_chars);
	}

	public static String generateRandomCode(int len, char[] chars) {
		return generateRandomCode(len, chars, new SecureRandom());
	}

	public static String generateRandomCode(int len, char[] chars, Random r) {
		char[] dst = new char[len];

		for (int i = 0; i < dst.length; i++)
			dst[i] = chars[r.nextInt(chars.length)];

		return new String(dst);
	}

	private static final int UUID_LENGTH = 16;
	private static final int UUID_MAX_ELEMENTS = 1024;
	private static Set<String> UUID_SET = new HashSet<String>();
	private static LinkedList<String> UUID_LIST = new LinkedList<String>();

	public static final synchronized String generateUniqueId() {
		// generate new UUID
		String uuid;
		do {
			uuid = Text.generateRandomCode(UUID_LENGTH);
		} while (!UUID_SET.add(uuid));

		// clean up oldest UUID
		UUID_LIST.addLast(uuid);
		if (UUID_LIST.size() > UUID_MAX_ELEMENTS)
			UUID_SET.remove(UUID_LIST.removeFirst());

		// return
		return uuid;
	}

	/**
	 * ALIGN STRING
	 */

	public static final int ALIGN_LEFT = 0;
	public static final int ALIGN_CENTER = 1;
	public static final int ALIGN_RIGHT = 2;

	public static final String formatString(String text, int length) {
		return Text.formatString(text, length, ALIGN_LEFT, ' ');
	}

	public static final String formatString(String text, int length, char c) {
		return Text.formatString(text, length, ALIGN_LEFT, c);
	}

	public static final String formatString(String text, int length, int type) {
		return Text.formatString(text, length, type, ' ');
	}

	public static final String formatString(String text, int length, int type, char c) {
		if (text.length() > length) {
			if (text.length() >= 3)
				return text.substring(0, length - 3) + "...";
			return "<?>";
		}

		final char[] array = new char[length];

		if (type == Text.ALIGN_LEFT) {
			int split = text.length();

			for (int i = 0; i < split; i++)
				array[i] = text.charAt(i);

			for (int i = split; i < length; i++)
				array[i] = c;
		}

		if (type == Text.ALIGN_CENTER)
			throw new UnsupportedOperationException();

		if (type == Text.ALIGN_RIGHT) {
			int split = length - text.length();

			for (int i = 0; i < split; i++)
				array[i] = c;

			for (int i = split; i < length; i++)
				array[i] = text.charAt(i - split);
		}

		return new String(array);
	}

	/**
    *
    */

	public static final boolean onlyNumbers(String val) {
		char[] arr = val.toCharArray();

		for (int i = 0; i < arr.length; i++) {
			char c = arr[i];
			if (c < '0' || c > '9')
				return false;
		}

		return true;
	}

	public static final boolean onlyLetters(String val) {
		char[] arr = val.toCharArray();

		for (int i = 0; i < arr.length; i++) {
			char c = arr[i];
			if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
				return false;
		}

		return true;
	}

	/**
	 * CONVERT
	 */

	public static final String ascii(byte[] raw, int off, int len) {
		char[] str = new char[len];
		for (int i = 0; i < len; i++)
			str[i] = (char) raw[off + i];
		return new String(str);
	}

	public static final String ascii(byte[] raw) {
		char[] str = new char[raw.length];
		for (int i = 0; i < str.length; i++)
			str[i] = (char) raw[i];
		return new String(str);
	}

	public static final byte[] ascii(String str) {
		byte[] raw = new byte[str.length()];
		for (int i = 0; i < raw.length; i++)
			raw[i] = (byte) str.charAt(i);
		return raw;
	}

	public static final void ascii(OutputStream out, byte[] tmp, String str) throws IOException {
		int pos = 0;
		for (int i = 0; i < str.length(); i++) {
			tmp[pos++] = (byte) str.charAt(i);
			if (pos == tmp.length) {
				out.write(tmp, 0, pos);
				pos = 0;
			}
		}
		out.write(tmp, 0, pos);
	}

	public static String toLower(String s) {
		char[] ca = s.toCharArray();
		for (int i = 0; i < ca.length; i++) {
			ca[i] = toLower(ca[i]);
		}
		return new String(ca);
	}

	public static String toUpper(String s) {
		char[] ca = s.toCharArray();
		for (int i = 0; i < ca.length; i++) {
			ca[i] = toUpper(ca[i]);
		}
		return new String(ca);
	}

	public static char toLower(char c) {
		return (c >= 'A' && c <= 'Z') ? (char) (c + ('a' - 'A')) : c;
	}

	public static char toUpper(char c) {
		return (c >= 'a' && c <= 'z') ? (char) (c - ('a' - 'A')) : c;
	}

	//

	public static final String utf8(byte[] raw) {
		return Text.utf8(raw, 0, raw.length);
	}

	public static final String utf8(byte[] raw, int off, int len) {
		try {
			return new String(raw, off, len, "UTF-8");
		} catch (UnsupportedEncodingException exc) {
			throw new IllegalStateException();
		}
	}

	public static final byte[] utf8(String str) {
		try {
			return str.getBytes("UTF-8");
		} catch (UnsupportedEncodingException exc) {
			throw new IllegalStateException();
		}
	}

	//

	private static final String encoding = "ISO-8859-1"; // "UTF-8";

	public static final String convert(byte[] data) {
		return convert(data, encoding);
	}

	public static final String convert(byte[] data, String encoding) {
		try {
			return new String(data, encoding);
		} catch (Exception exc) {
			return null;
		}
	}

	public static final String convert(byte[] data, int offset, int length) {
		try {
			return new String(data, offset, length, encoding);
		} catch (Exception exc) {
			return null;
		}
	}

	public static final byte[] convert(String value) {
		try {
			return value.getBytes(encoding);
		} catch (Exception exc) {
			return null;
		}
	}

	/**
	 * CHOP LAST
	 */

	public static String chopLast(String value, int chars) {
		if (chars < 0 || chars > value.length())
			throw new IllegalArgumentException("invalid chars: " + chars + " / " + value.length());
		return value.substring(0, value.length() - chars);
	}

	public static StringBuilder chopLast(StringBuilder value, int chars) {
		if (chars < 0 || chars > value.length())
			throw new IllegalArgumentException("invalid chars: " + chars + " / " + value.length());
		value.setLength(value.length() - chars);
		return value;
	}

	/**
	 * FLIP
	 */

	public static final String flip(String value) {
		char[] arr = value.toCharArray();

		int half = arr.length / 2;
		int end = arr.length - 1;

		for (int i = 0; i < half; i++) {
			char c = arr[i];
			arr[i] = arr[end - i];
			arr[end - i] = c;
		}

		return String.valueOf(arr);
	}

	public static String firstOccurence(String text, String... finds) {
		int min = Integer.MAX_VALUE;
		String first = null;
		for (String find : finds) {
			int i = text.indexOf(find);
			if (i != -1 && i < min) {
				min = i;
				first = find;
			}
		}
		return first;
	}

	public static Pair<String, Integer> firstOccurenceWithIndex(String text, String... finds) {
		int min = Integer.MAX_VALUE;
		String first = null;
		for (String find : finds) {
			int i = text.indexOf(find);
			if (i != -1 && i < min) {
				min = i;
				first = find;
			}
		}
		if (first == null)
			return null;
		return new Pair<String, Integer>(first, Integer.valueOf(min));
	}

	/**
	 * SPLIT
	 */

	public static final String[] depthAwareSplit(String value, char split, char open, char close) {
		List<String> list = new ArrayList<String>();

		int depth = 0;
		int lastStart = 0;

		for (int i = 0; i < value.length(); i++) {
			char c = value.charAt(i);

			if (c == open) {
				depth++;
				continue;
			}

			if (c == close) {
				depth--;
				continue;
			}

			if (c != split || depth != 0) {
				continue;
			}

			list.add(value.substring(lastStart, i));
			lastStart = i + 1;
		}

		if (lastStart != value.length())
			list.add(value.substring(lastStart));

		return list.toArray(new String[list.size()]);
	}

	public static final String normalizeLinebreaks(String value) {
		value = Text.replace(value, "\r\n", "\n");
		value = Text.replace(value, '\r', '\n');

		return value;
	}

	public static final String[] splitOnLines(String value) {
		return Text.split(normalizeLinebreaks(value), '\n');
	}

	public static final String[] split(String value, char d) {
		String[] buf = new String[count(value, d) + 1];

		split(value, d, buf);

		return buf;
	}

	public static final String[] splitOnUnicodeWhitespace(String value) {
		List<String> parts = new ArrayList<String>();

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < value.length(); i++) {
			if (Character.isWhitespace(value.charAt(i))) {
				parts.add(sb.toString());
				sb.setLength(0);
			} else {
				sb.append(value.charAt(i));
			}
		}
		parts.add(sb.toString());
		sb.setLength(0);

		return parts.toArray(new String[parts.size()]);
	}

	public static final String[] split(String value, String d) {
		List<String> parts = new ArrayList<String>();

		String[] buf = new String[2];
		String[] pair;

		while ((pair = splitPair(value, d, buf)) != null) {
			parts.add(pair[0]);
			value = pair[1];
		}

		parts.add(value);

		return parts.toArray(new String[parts.size()]);
	}

	public static final int split(String value, char d, String[] buf) {
		int count = 0, n = value.length(), lastSplit = 0;

		for (int i = 0; i < n; i++) {
			if (value.charAt(i) == d) {
				buf[count++] = value.substring(lastSplit, i);

				lastSplit = i + 1;
			}
		}

		buf[count++] = value.substring(lastSplit, n);

		return count;
	}

	/**
	 * PAIR
	 */

	public static final String[] splitPair(String pair, char d) {
		return Text.splitPair(pair, d, new String[2]);
	}

	public static final String[] splitPair(String pair, String d) {
		return Text.splitPair(pair, d, new String[2]);
	}

	public static final String[] splitPair(String pair, char d, String[] result) {
		int split = pair.indexOf(d);
		if (split == -1)
			return null;

		result[0] = pair.substring(0, split);
		result[1] = pair.substring(split + 1);

		return result;
	}

	public static final String[] splitPair(String pair, String d, String[] result) {
		int split = pair.indexOf(d);
		if (split == -1)
			return null;

		result[0] = pair.substring(0, split);
		result[1] = pair.substring(split + d.length());

		return result;
	}

	public static final String[] splitOnUppercase(String input) {
		StringBuilder part = new StringBuilder();
		List<String> parts = new ArrayList<String>();
		for (char c : input.toCharArray()) {
			if (Character.isUpperCase(c)) {
				if (part.length() > 0)
					parts.add(part.toString());
				part.setLength(0);
			}
			part.append(c);
		}
		if (part.length() > 0)
			parts.add(part.toString());
		return parts.toArray(new String[parts.size()]);
	}

	/**
	 * CHOP
	 */

	public static final String[] chop(String value, int size) {
		int fullParts = value.length() / size;
		int extra = ((value.length() % size) != 0) ? 1 : 0;

		String[] part = new String[fullParts + extra];

		for (int i = 0; i < fullParts; i++)
			part[i] = value.substring(i * size, (i + 1) * size);

		if (extra != 0)
			part[fullParts] = value.substring(fullParts * size);

		return part;
	}

	/**
	 * JOIN
	 */

	public static final String join(String[] value) {
		return Text.join(value, 0, value.length);
	}

	public static final String join(String[] value, char c) {
		return Text.join(value, 0, value.length, c);
	}

	public static final String join(String[] value, String s) {
		return Text.join(value, 0, value.length, s);
	}

	public static final String join(String[] value, int off, int len) {
		StringBuilder buffer = new StringBuilder();
		for (int i = 0; i < len; i++)
			buffer.append(value[off + i]);
		return buffer.toString();
	}

	public static final String join(String[] value, int off, int len, char c) {
		StringBuilder buffer = new StringBuilder();

		for (int i = 0; i < len; i++) {
			buffer.append(value[off + i]);
			if (i != len - 1)
				buffer.append(c);
		}

		return buffer.toString();
	}

	public static final String join(String[] value, int off, int len, String s) {
		StringBuilder buffer = new StringBuilder();

		for (int i = 0; i < len; i++) {
			buffer.append(value[off + i]);
			if (i != len - 1)
				buffer.append(s);
		}

		return buffer.toString();
	}

	public static final String[] multiSplit(String value, char... cs) {
		int[] in = new int[cs.length];
		int or = 0;
		for (int i = 0; i < in.length; i++)
			if ((or |= in[i] = value.indexOf(cs[i], i == 0 ? 0 : in[i - 1] + 1)) < 0)
				return null;

		String[] parts = new String[cs.length + 1];
		for (int i = 0; i < parts.length - 1; i++)
			parts[i] = value.substring(i == 0 ? 0 : in[i - 1] + 1, in[i]);
		parts[parts.length - 1] = value.substring(in[in.length - 1] + 1);

		return parts;
	}

	// INPUT: "abc def_ghi jkl_mno pqr" & [" ","_"]
	// OUTPUT: ["abc","def","ghi","jkl","mno pqr"]

	public static final String[] multiSplit(String value, String... ss) {
		int[] in = new int[ss.length];
		int or = 0;
		for (int i = 0; i < ss.length; i++)
			if ((or |= in[i] = value.indexOf(ss[i], i == 0 ? 0 : in[i - 1] + ss[i - 1].length())) < 0)
				return null;

		String[] parts = new String[ss.length + 1];
		for (int i = 0; i < parts.length - 1; i++)
			parts[i] = value.substring(i == 0 ? 0 : in[i - 1] + ss[i - 1].length(), in[i]);
		parts[parts.length - 1] = value.substring(in[in.length - 1] + ss[ss.length - 1].length());

		return parts;
	}

	public static final String[] multiSplitAll(String value, char... cs) {
		List<String> results = new ArrayList<String>();

		while (value != null) {
			String[] result = Text.multiSplit(value, cs);

			if (result == null) {
				results.add(value);
				value = null;
			} else {
				for (int i = 0; i < result.length - 1; i++)
					results.add(result[i]);
				value = result[result.length - 1];
			}
		}

		return results.toArray(new String[results.size()]);
	}

	public static final String[] multiSplitLoop_bugged(String value, char... cs) {
		List<String> results = new ArrayList<String>();

		while (value != null) {
			String[] result = Text.multiSplit(value, cs);

			if (result == null) {
				results.add(value);
				value = null;
			} else {
				for (int i = 0; i < result.length - 1; i++)
					results.add(result[i]);
				value = result[result.length - 1];
			}
		}

		return results.toArray(new String[results.size()]);
	}

	public static final String[] multiSplitLoop_bugged(String value, String... ss) {
		List<String> results = new ArrayList<String>();

		while (value != null) {
			String[] result = Text.multiSplit(value, ss);

			if (result == null) {
				results.add(value);
				value = null;
			} else {
				for (int i = 0; i < result.length - 1; i++)
					results.add(result[i]);
				value = result[result.length - 1];
			}
		}

		return results.toArray(new String[results.size()]);
	}

	/**
	 * BEFORE / AFTER / BETWEEN
	 */

	public static String before(String text, char find) {
		int offset = text.indexOf(find);
		if (offset == -1)
			return null;
		return text.substring(0, offset);
	}

	public static String before(String text, String find) {
		int offset = text.indexOf(find);
		if (offset == -1)
			return null;
		return text.substring(0, offset);
	}

	public static String beforeLast(String text, char find) {
		int offset = text.lastIndexOf(find);
		if (offset == -1)
			return null;
		return text.substring(0, offset);
	}

	public static String beforeLast(String text, String find) {
		int offset = text.lastIndexOf(find);
		if (offset == -1)
			return null;
		return text.substring(0, offset);
	}

	//

	public static String after(String text, char find) {
		int offset = text.indexOf(find);
		if (offset == -1)
			return null;
		return text.substring(offset + 1);
	}

	public static String after(String text, String find) {
		int offset = text.indexOf(find);
		if (offset == -1)
			return null;
		return text.substring(offset + find.length());
	}

	public static String afterLast(String text, char find) {
		int offset = text.lastIndexOf(find);
		if (offset == -1)
			return null;
		return text.substring(offset + 1);
	}

	public static String afterLast(String text, String find) {
		int offset = text.lastIndexOf(find);
		if (offset == -1)
			return null;
		return text.substring(offset + find.length());
	}

	//

	public static String betweenOuter(String text, char start, char end) {
		String after = Text.after(text, start);
		if (after == null)
			return null;
		return Text.beforeLast(after, end);
	}

	public static String betweenOuter(String text, String start, String end) {
		String after = Text.after(text, start);
		if (after == null)
			return null;
		return Text.beforeLast(after, end);
	}

	//

	public static String between(String text, char start, char end) {
		String after = Text.after(text, start);
		if (after == null)
			return null;
		return Text.before(after, end);
	}

	public static String between(String text, String start, String end) {
		String after = Text.after(text, start);
		if (after == null)
			return null;
		return Text.before(after, end);
	}

	public static String between(String text, char start, char end, String def) {
		return Text.replaceOnNull(Text.between(text, start, end), def);
	}

	public static String between(String text, String start, String end, String def) {
		return Text.replaceOnNull(Text.between(text, start, end), def);
	}

	public static String[] multiBetween(String text, String start, String end) {
		List<String> matches = new ArrayList<String>();

		while (true) {
			String afterStart = Text.after(text, start);
			if (afterStart == null)
				break;
			String[] matchAndNext = Text.splitPair(afterStart, end);
			if (matchAndNext == null)
				break;
			matches.add(matchAndNext[0]);
			text = matchAndNext[1];
		}

		return matches.toArray(new String[matches.size()]);
	}

	//

	private static String replaceOnNull(String text, String replace) {
		return (text == null) ? replace : text;
	}

	//

	public static String beforeIfAny(String text, char find) {
		return Text.replaceOnNull(Text.before(text, find), text);
	}

	public static String beforeIfAny(String text, String find) {
		return Text.replaceOnNull(Text.before(text, find), text);
	}

	public static String beforeLastIfAny(String text, char find) {
		return Text.replaceOnNull(Text.beforeLast(text, find), text);
	}

	public static String beforeLastIfAny(String text, String find) {
		return Text.replaceOnNull(Text.beforeLast(text, find), text);
	}

	//

	public static String afterIfAny(String text, char find) {
		return Text.replaceOnNull(Text.after(text, find), text);
	}

	public static String afterIfAny(String text, String find) {
		return Text.replaceOnNull(Text.after(text, find), text);
	}

	public static String afterLastIfAny(String text, char find) {
		return Text.replaceOnNull(Text.afterLast(text, find), text);
	}

	public static String afterLastIfAny(String text, String find) {
		return Text.replaceOnNull(Text.afterLast(text, find), text);
	}

	//

	public static String before(String text, char find, String def) {
		return Text.replaceOnNull(Text.before(text, find), def);
	}

	public static String before(String text, String find, String def) {
		return Text.replaceOnNull(Text.before(text, find), def);
	}

	public static String beforeLast(String text, char find, String def) {
		return Text.replaceOnNull(Text.beforeLast(text, find), def);
	}

	public static String beforeLast(String text, String find, String def) {
		return Text.replaceOnNull(Text.beforeLast(text, find), def);
	}

	public static String after(String text, char find, String def) {
		return Text.replaceOnNull(Text.after(text, find), def);
	}

	public static String after(String text, String find, String def) {
		return Text.replaceOnNull(Text.after(text, find), def);
	}

	public static String afterLast(String text, char find, String def) {
		return Text.replaceOnNull(Text.afterLast(text, find), def);
	}

	public static String afterLast(String text, String find, String def) {
		return Text.replaceOnNull(Text.afterLast(text, find), def);
	}

	/**
    *
    */

	public static final int[] indicesOf(String s, char c) {
		int[] p = new int[Text.count(s, c)];

		for (int i = 0, j = 0; i < s.length(); i++)
			if (s.charAt(i) == c)
				p[j++] = i;

		return p;
	}

	public static final int[] indicesOf(String s, String m) {
		if (m.length() == 0)
			throw new IllegalArgumentException("empty match");

		int[] p = new int[Text.count(s, m)];
		int i = 0;

		int offset = 0;
		while (true) {
			int off = s.indexOf(m, offset);
			if (off == -1)
				break;
			p[i++] = off;
			offset = off + m.length();
		}
		return p;
	}

	/**
	 * COUNT
	 */

	public static final int count(String s, char d) {
		int n = s.length(), x = 0;

		for (int i = 0; i < n; i++)
			if (s.charAt(i) == d)
				x++;

		return x;
	}

	public static final int count(String s, String d) {
		int count = 0, offset = 0;
		while (true) {
			int off = s.indexOf(d, offset);
			if (off == -1)
				break;
			count++;
			offset = off + d.length();
		}
		return count;
	}

	public static String repeat(String s, int count) {
		if (count < 0)
			throw new IllegalArgumentException();
		if (count == 0)
			return "";
		if (count == 1)
			return s;

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < count; i++)
			sb.append(s);
		return sb.toString();
	}

	public static String repeat(char c, int count) {
		if (count < 0)
			throw new IllegalArgumentException();
		if (count == 0)
			return "";
		if (count == 1)
			return String.valueOf(c);

		char[] cs = new char[count];
		for (int i = 0; i < count; i++)
			cs[i] = c;
		return new String(cs);
	}

	/**
	 * REPLACE
	 */

	public static final String replaceBetweenIfAny(String s, String open, String close, String replaceWith) {
		int i = s.indexOf(open);
		if (i == -1 || s.indexOf(close, i + open.length()) == -1)
			return s; // early escape

		String a = Text.before(s, open);
		if (a == null)
			return s; // not so early escape
		String b = Text.after(Text.after(s, open), close);
		if (b == null)
			return s;
		return a + open + replaceWith + close + b;
	}

	public static final String replaceAllBetween(String s, String open, String close, String replaceWith) {
		if (s.indexOf(open) == -1 || s.indexOf(close) == -1)
			return s; // early escape

		String remaining = s;
		StringBuilder done = new StringBuilder();

		while (true) {
			int indexOfOpen = remaining.indexOf(open);
			if (indexOfOpen == -1)
				break;

			int indexOfClose = remaining.indexOf(close, indexOfOpen + open.length());
			if (indexOfClose == -1)
				break;

			String before = remaining.substring(0, indexOfOpen);
			done.append(before);
			if (replaceWith != null)
				done.append(replaceWith);
			remaining = remaining.substring(indexOfClose + close.length());
		}

		return done.append(remaining).toString();
	}

	public static final String replace(String s, char a, char b) {
		if (s.indexOf(a) == -1)
			return s; // early escape

		char[] array = s.toCharArray();
		for (int i = 0; i < array.length; i++)
			if (array[i] == a)
				array[i] = b;
		return new String(array);
	}

	public static final String replace(String s, String a, String b) {
		if (s.indexOf(a) == -1)
			return s; // early escape

		StringBuilder sb = new StringBuilder();

		int len = a.length();
		int off = 0;

		while (true) {
			int found = s.indexOf(a, off);

			if (found == -1) {
				sb.append(s.substring(Math.min(off, s.length())));

				break;
			}

			sb.append(s.substring(off, found));
			sb.append(b);

			off += (found - off) + len;
		}

		return sb.toString();
	}

	public static final String replaceIfEquals(String s, String a, String b) {
		if (s == null)
			return (a == null) ? b : s;
		return s.equals(a) ? b : s;
	}

	/**
	 * REMOVE
	 */

	public static final String removeInnerIfAny(String s, String open, String close) {
		String a = Text.before(s, open);
		if (a == null)
			return s;
		String b = Text.after(s, open);
		if (b.startsWith(close))
			return s;
		String c = Text.after(b, close);
		if (c == null)
			return s;
		return a + open + close + c;
	}

	public static final String removeOuterIfAny(String s, String open, String close) {
		String a = Text.before(s, open);
		if (a == null)
			return s;
		String b = Text.after(Text.after(s, open), close);
		if (b == null)
			return s;
		return a + b;
	}

	public static final String removeAllInner(String s, String open, String close) {
		while (true) {
			String replaced = Text.removeInnerIfAny(s, open, close);
			if (replaced == s)
				break;
			s = replaced;
		}
		return s;
	}

	public static final String removeAllOuter(String s, String open, String close) {
		while (true) {
			String replaced = Text.removeOuterIfAny(s, open, close);
			if (replaced == s)
				break;
			s = replaced;
		}
		return s;
	}

	public static final String remove(String s, char c) {
		int count = Text.count(s, c);
		if (count == 0)
			return s;

		char[] curr = s.toCharArray();
		char[] next = new char[curr.length - count];

		for (int i = 0, j = 0; i < curr.length; i++)
			if (curr[i] != c)
				next[j++] = curr[i];

		return new String(next);
	}

	public static final String remove(String s, char... cs) {
		int count = 0;
		for (char c : cs)
			count += Text.count(s, c);
		if (count == 0)
			return s;

		char[] curr = s.toCharArray();
		char[] next = new char[curr.length - count];

		outer: for (int i = 0, j = 0; i < curr.length; i++) {
			for (int k = 0; k < cs.length; k++)
				if (curr[i] == cs[k])
					continue outer;

			next[j++] = curr[i];
		}

		return new String(next);
	}

	public static final String remove(String s, String find) {
		return Text.replace(s, find, "");
	}

	public static final String convertWhiteSpaceTo(String s, char c) {
		char[] cs = s.toCharArray();
		for (int i = 0; i < cs.length; i++)
			if (cs[i] <= ' ')
				cs[i] = c;
		return new String(cs);
	}

	//

	public static final int indexOfWhiteSpace(String s) {
		for (int i = 0; i < s.length(); i++)
			if (s.charAt(i) <= ' ')
				return i;
		return -1;
	}

	public static final int lastIndexOfWhiteSpace(String s) {
		for (int i = s.length() - 1; i >= 0; i--)
			if (s.charAt(i) <= ' ')
				return i;
		return -1;
	}

	public static final String[] splitOnWhiteSpace(String s) {
		s = Text.convertWhiteSpaceTo(s, ' ');
		s = Text.removeDuplicates(s, ' ');
		return Text.split(s, ' ');
	}

	public static final String[] splitPairOnWhiteSpace(String s) {
		s = Text.convertWhiteSpaceTo(s, ' ');
		s = Text.removeDuplicates(s, ' ');
		return Text.splitPair(s, ' ');
	}

	public static final String trimBefore(String s) {
		for (int i = 0; i < s.length(); i++)
			if (s.charAt(i) > ' ')
				return s.substring(i);
		return "";
	}

	public static final String removeDuplicates(String s, char c) {
		if (s.indexOf(c) == -1)
			return s; // early escape
		if (s.indexOf(c) == s.lastIndexOf(c))
			return s; // early escape

		char[] array = s.toCharArray();

		StringBuilder buffer = new StringBuilder();

		boolean duplicate = false;

		for (int i = 0; i < array.length; i++) {
			if (array[i] != c) {
				buffer.append(array[i]);
				duplicate = false;
				continue;
			}

			if (duplicate)
				continue;

			buffer.append(array[i]);
			duplicate = true;
		}

		return buffer.toString();
	}

	/**
	 * CONTAINS
	 */

	public static final boolean contains(String[] array, String search) {
		if (search == null) {
			for (String s : array)
				if (s == null)
					return true;
		} else {
			for (String s : array)
				if (search.equals(s))
					return true;
		}

		return false;
	}

	/**
	 * CAPITALIZE
	 */

	public static final String capitalize(String value) {
		if (value.length() == 0)
			return value;

		if (value.length() == 1)
			return value.toUpperCase();

		return value.substring(0, 1).toUpperCase() + value.substring(1);
	}

	public static final String decapitalize(String value) {
		if (value.length() == 0)
			return value;

		if (value.length() == 1)
			return value.toLowerCase();

		return value.substring(0, 1).toLowerCase() + value.substring(1);
	}

	/**
	 * ARGS
	 */

	public static final Map<String, String> parseProperties(String value) {
		Map<String, String> map = new HashMap<String, String>();

		String[] lines = Text.splitOnLines(value);

		for (String line : lines) {
			line = Text.beforeIfAny(line, '#');
			line = line.trim();
			if (line.length() == 0)
				continue;

			String[] pair = Text.splitPair(line, '=');
			if (pair == null)
				continue;

			String key = pair[0].trim();
			String val = pair[1].trim();

			map.put(key, val);
		}

		return map;
	}

	public static final Map<String, String> parseArgs(String value, String sep, String split) {
		String[] pairs = Text.split(value, sep);
		String[] part = new String[2];

		Map<String, String> map = new HashMap<String, String>();

		for (String pair : pairs) {
			if (!pair.contains(String.valueOf(split)))
				continue;

			Text.splitPair(pair, split, part);
			map.put(part[0], part[1]);
		}

		return map;
	}

	public static final Map<String, String> parseArgs(String value, char sep, char split) {
		String[] pairs = Text.split(value, sep);
		String[] pair = new String[2];

		Map<String, String> map = new HashMap<String, String>();

		for (String p : pairs) {
			if (Text.splitPair(p, split, pair) == null)
				map.put(p, "");
			else
				map.put(pair[0], pair[1]);
		}

		return map;
	}

	public static final String joinArgs(Map<String, String> map, String sep, String split) {
		StringBuilder sb = new StringBuilder();

		for (String key : map.keySet()) {
			sb.append(key);
			sb.append(split);
			sb.append(map.get(key));
			sb.append(sep);
		}

		if (sb.length() > 0) {
			sb.setLength(sb.length() - 1);
		}

		return sb.toString();
	}

	public static final String joinArgs(Map<String, String> map, char sep, char split) {
		StringBuilder sb = new StringBuilder();

		for (String key : map.keySet()) {
			sb.append(key);
			sb.append(split);
			sb.append(map.get(key));
			sb.append(sep);
		}

		if (sb.length() > 0) {
			sb.setLength(sb.length() - 1);
		}

		return sb.toString();
	}

	/**
	 * DECODE URL
	 */

	public static final String decodeURL(String encoded, String charset) {
		return Text.decodeURL(encoded, charset, true);
	}

	public static final String decodeURL(String encoded, String charset, boolean decodePlus) {
		if (decodePlus) {
			encoded = Text.replace(encoded, '+', ' ');
		}

		StringBuilder chars = new StringBuilder();
		ByteList hex = new ByteList();
		Charset decoder = Charset.forName(charset);

		String[] parts = Text.split(encoded, '%');
		for (int i = 0; i < parts.length; i++) {
			String part = parts[i];
			if (i == 0)
				chars.append(part);
			else if (part.isEmpty())
				continue;
			else if (part.startsWith("u")) {
				if (hex.size() > 0) {
					chars.append(decoder.decode(ByteBuffer.wrap(hex.toArray())).toString());
					hex.clear();
				}

				try {
					int unicode = 0;
					unicode |= chr2oct(part.charAt(1)) << 12;
					unicode |= chr2oct(part.charAt(2)) << 8;
					unicode |= chr2oct(part.charAt(3)) << 4;
					unicode |= chr2oct(part.charAt(4)) << 0;
					chars.append((char) unicode);
					chars.append(part.substring(5));
				} catch (IllegalStateException exc) {
					chars.append(part);
				}
			} else {
				try {
					int unicode = 0;
					unicode |= chr2oct(part.charAt(0)) << 4;
					unicode |= chr2oct(part.charAt(1)) << 0;
					hex.add((byte) unicode);
					if (part.length() == 2)
						continue;
				} catch (IllegalStateException exc) {
					chars.append(part);
				}

				if (hex.size() > 0) {
					chars.append(Charset.forName(charset).decode(ByteBuffer.wrap(hex.toArray())).toString());
					hex.clear();
				}
				chars.append(part.substring(2));
			}
		}

		if (hex.size() > 0) {
			chars.append(Charset.forName(charset).decode(ByteBuffer.wrap(hex.toArray())).toString());
			hex.clear();
		}

		return chars.toString();
	}

	private static int chr2oct(char c) {
		if (c >= '0' && c <= '9')
			return c - '0';
		if (c >= 'a' && c <= 'f')
			return 10 + c - 'a';
		if (c >= 'A' && c <= 'F')
			return 10 + c - 'A';
		throw new IllegalStateException();
	}

	/**
    *
    */

	public static final void mainArgsLookup(String[] args, Map<String, String> params) {
		for (String key : params.keySet()) {
			String val = Text.mainArgsLookup(args, key);

			if (val == null) {
				if (params.get(key) == null)
					throw new IllegalStateException("parameter for -" + key + " not found, and no default value");
			} else {
				params.put(key, val);
			}
		}
	}

	public static final String mainArgsLookup(String[] args, String key) {
		return Text.mainArgsLookup(args, key, null);
	}

	public static final String mainArgsLookup(String[] args, String key, String def) {
		return Text.mainArgsLookup(args, key, def, 0);
	}

	public static final List<String> mainArgsLookups(String[] args, String key) {
		key = "-" + key;

		List<String> matches = new ArrayList<String>();
		for (int i = 0; i < args.length - 1; i++)
			if (args[i].equals(key))
				matches.add(args[i += 1]);
		return matches;
	}

	public static final String mainArgsLookup(String[] args, String key, String def, int skip) {
		key = "-" + key;

		for (int i = 0; i < args.length - 1; i++)
			if (args[i].equals(key) && (skip-- == 0))
				return args[i + 1];
		return def;
	}

	public static boolean areCharsAt(String s, char c, int off1, int off2) {
		if (s.charAt(off1) == c)
			if (s.charAt(off2) == c)
				return true;
		return false;
	}

	public static boolean areDigits(String s, int off, int len) {
		int end = off + len;
		for (int i = off; i < end; i++)
			if (s.charAt(i) < '0' || s.charAt(i) > '9')
				return false;
		return true;
	}

	/**
	 * ROMAN
	 */

	public static final String rot13(String input) {
		char[] out = input.toCharArray();
		int i = 0;
		for (char c : out) {
			if (c >= 'a' && c <= 'z') {
				c -= 'a';
				c += 13;
				c %= 26;
				c += 'a';
			} else if (c >= 'A' && c <= 'Z') {
				c -= 'A';
				c += 13;
				c %= 26;
				c += 'A';
			} else if (c >= '0' && c <= '9') {
				c -= '0';
				c += 5;
				c %= 10;
				c += '0';
			}
			out[i++] = c;
		}
		return new String(out);
	}

//	public static String traverse(String input, String[] delimiters, int[] maxLengths, TextTransformer transformer) {
//		if (maxLengths.length + 1 != delimiters.length)
//			throw new IllegalStateException();
//		String[] matches = new String[maxLengths.length];
//
//		StringBuilder rewritten = null;
//
//		int offset = 0;
//
//		outer: while (!input.isEmpty()) {
//			int[] ios = new int[delimiters.length];
//
//			for (int i = 0; i < delimiters.length; i++) {
//				int off = (i == 0) ? offset : (ios[i - 1] + delimiters[i - 1].length());
//				ios[i] = input.indexOf(delimiters[i], off);
//				if (ios[i] == -1) {
//					// not found
//					break outer;
//				}
//
//				if (i == 0) {
//					continue;
//				}
//
//				String ps = delimiters[i - 1];
//				int pio = ios[i - 1];
//
//				// too long?
//				int len = (ios[i] - pio) - ps.length();
//				if (len > maxLengths[i - 1]) {
//					// advance
//					offset = ios[0] + 1;
//					continue outer;
//				}
//
//				matches[i - 1] = input.substring(pio + ps.length(), ios[i]);
//			}
//
//			// match!
//
//			if (rewritten == null)
//				rewritten = new StringBuilder();
//
//			// merge!
//			String[] merged = new String[(matches.length << 1) + 1];
//			for (int i = 0; i < merged.length; i++)
//				merged[i] = ((i & 1) == 0) ? delimiters[i >> 1] : matches[i >> 1];
//			transformer.transform(merged);
//
//			rewritten.append(input.substring(0, ios[0]));
//			rewritten.append(join(merged));
//			input = input.substring(ios[ios.length - 1] + delimiters[ios.length - 1].length());
//			offset = 0;
//		}
//
//		if (rewritten != null) {
//			input = rewritten.append(input).toString();
//		}
//
//		return input;
//	}
}