diff --git a/lucene/src/java/org/apache/lucene/util/fst2/FST.java b/lucene/src/java/org/apache/lucene/util/fst2/FST.java
new file mode 100644
index 0000000..14604a9
--- /dev/null
+++ b/lucene/src/java/org/apache/lucene/util/fst2/FST.java
@@ -0,0 +1,457 @@
+package org.apache.lucene.util.fst2;
+
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.IntsRef;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* @formatter:off */
+/**
+ * A finite state transducer (FST) or a finite state automaton (FSA) stored in a compact
+ * byte[] array.
+ * 
+ * <h2>Logical representation</h2>
+ * 
+ * <p>A state machine logically consists of states and labeled arcs (transitions) between these
+ * states. In the representation used in this class, states are <i>implicit</i>: a state consists
+ * of an label-ordered list of transitions; the last transition of a state has a special
+ * flag indicating it is ending the current state. Because states are implicit, accepting states
+ * are not represented at all. Instead, the "input acceptance" flag is moved to an arc 
+ * leading to such a state. Terminal states are not represented and arcs pointing to such states
+ * point to an invalid address instead.</p>
+ * 
+ * <p>The above representation is equivalent to 
+ * <a href="http://en.wikipedia.org/wiki/Mealy_machine">Mealy's automata</a>.</p>
+ * 
+ * <h2>Constructing a transducer (FST)</h2>
+ *
+ * TODO: fill me in with an example.
+ * 
+ * <h2>Constructing an acceptor automaton (FSA)</h2>
+ * 
+ * TODO: fill me in with an example.
+ *
+ * <h2>Traversals</h2>
+ *
+ * TODO: fill me in with examples of pointers to code: 
+ * matching an input sequence. Checking for the longest matching
+ * prefix. Collecting prefix completions.
+ *
+ * <h2>Physical representation</h2>
+ * 
+ * <p>Each state is stored as a sequence of transitions. A state's transitions 
+ * can be stored in a variable-length, packed format or "expanded" into a fixed-offset lookup
+ * array to allow binary search lookups. In all cases, transition labels within a state are 
+ * lexicographically sorted.</p>  
+ * 
+ * <p>A single transition consists of a label (input it accepts), the output it should 
+ * accumulate if followed, a pointer to the target state's first transition and 
+ * a set of flags. Flag bits are used, among others, to encode the information about 
+ * the last transition of the current state and about the finality of the 
+ * current transition.</p>
+ * 
+ * <h2>Notes</h2>
+ * 
+ * <p>The storage layout ideas are somewhat similar to what's used by 
+ * <a href="http://sourceforge.net/projects/morfologik">Morfologik</a>.</p>
+ *
+ * @see "http://en.wikipedia.org/wiki/Finite-state_machine"
+ * @lucene.experimental
+ */
+/* @formatter:on */
+public class FST<O> {
+  /**
+   * Arc label type/ size.
+   */
+  public static enum ArcLabel {
+    BYTE1, BYTE2, BYTE4;
+  }
+
+  /**
+   * Transducer's output algebra.
+   */
+  public static class OutputAlgebra<T> {
+  }
+
+  /**
+   * An empty output (effectively a state machine that is not a transducer and encodes
+   * no information on arcs).
+   */
+  public static class Empty extends OutputAlgebra<Void> {
+    Empty() {}
+  }
+
+  /**
+   * A positive (greater than zero) integer output.
+   */
+  public static class PositiveInt extends OutputAlgebra<Integer> {
+    PositiveInt() {}
+  }
+
+  /**
+   * FST or FSA builder from sorted data.
+   */
+  public static class FSTBuilder<O> {
+    private final ArcLabel arcLabel;
+
+    protected FSTBuilder(ArcLabel arcLabel) {
+      this.arcLabel = arcLabel;
+    }
+
+    public FSTBuilder<O> add(BytesRef bytesRef, O output) {
+      return null;
+    }
+
+    public FSTBuilder<O> add(CharSequence chs, O output) {
+      return null;
+    }
+
+    public FSTBuilder<O> add(IntsRef intsRef, O output) {
+      return null;
+    }
+
+    public FST<O> build() {
+      return new FST<O>(arcLabel);
+    }
+
+    /**
+     * Provides a builder for a finite state automaton (no output on transitions).
+     */
+    public static FSABuilder fsa(ArcLabel arcLabel) {
+      return new FSABuilder(arcLabel);
+    }
+
+    /**
+     * Provides a builder for finite state transducers (output on transitions).
+     */
+    public static <E, T extends OutputAlgebra<E>> FSTBuilder<E> fst(ArcLabel arcLabel, Class<T> outputAlgebra) {
+      return new FSTBuilder<E>(arcLabel);
+    }
+  }
+
+  /**
+   * FSA builder provides additional methods without the output argument.
+   */
+  public static class FSABuilder extends FSTBuilder<Empty> {
+    public FSABuilder(ArcLabel arcLabel) {
+      super(arcLabel);
+    }
+
+    public FSABuilder add(BytesRef bytesRef) {
+      assert super.arcLabel == ArcLabel.BYTE1;
+      return null;
+    }
+
+    public FSABuilder add(CharSequence chs) {
+      assert super.arcLabel == ArcLabel.BYTE2;
+      return null;
+    }
+
+    public FSABuilder add(IntsRef intsRef) {
+      assert super.arcLabel == ArcLabel.BYTE4;
+      return null;
+    }
+  }
+
+  /**
+   * An arc in the state machine.
+   */
+  public static class Arc<O> implements Cloneable {
+    private final FST<O> fst;
+
+    /*
+     * Arc and FST are closely coupled, but linked explicitly (not object-level nested).
+     */
+    Arc(FST<O> fst) {
+      this.fst = fst;
+    }
+
+    public boolean hasNext() {
+      return !isLast();
+    }
+
+    public boolean isLast() {
+      return false; // fst.isLast(this);
+    }
+
+    public Arc<O> next() {
+      return null; // fst.next(this);
+    }
+
+    public int getLabel() {
+      return 0;
+    }
+
+    public O getOutput() {
+      return null;
+    }
+
+    // TODO: ideally, we could override clone() with a covariant and add cloneFrom(), but covariants
+    // are 1.6 and we must stick to 1.5.
+    public Arc<O> copy() {
+      return new Arc<O>(fst).copyFrom(this);
+    }
+
+    public Arc<O> copyFrom(Arc<O> source) {
+      return this;
+    }
+
+    /** 
+     * Follow the target pointer from the current arc to the first arc of the target's
+     * state.
+     * 
+     * Destructive for <code>this</code>. 
+     */
+    public Arc<O> follow() {
+      assert this.canFollow();
+      return this;
+    }
+
+    /** 
+     * Find an arc labeled with the given symbol within the current state
+     * (must be lexicographically higher than the current arc's label).
+     * If found, follows to the target state's first arc.
+     * 
+     * Destructive for <code>this</code>.
+     */
+    public Arc<O> follow(int label) {
+      return follow(this, label);
+    }
+
+    /**
+     * Follow a chain of labels from the current arc's state. Returns the first
+     * arc of the state pointed to by that sequence or <code>null</code> if a mismatch
+     * occurs.
+     *  
+     * Destructive for <code>this</code>.
+     */
+    public Arc<O> follow(int... labels) {
+      return follow(this, labels);
+    }
+    
+    /**
+     * Same as {@link #follow(int...)}.
+     * 
+     * Destructive for <code>scratch</code>.
+     */
+    public Arc<O> follow(Arc<O> scratch, int... labels) {
+      return follow(scratch, labels, 0, labels.length);
+    }
+
+    /**
+     * Same as {@link #follow(int...)}.
+     * 
+     * Destructive for <code>this</code>.
+     */
+    public Arc<O> follow(int [] labels, int start, int length) {
+      return follow(this, labels, start, length);
+    }
+
+    /**
+     * Same as {@link #follow(int...)}.
+     * 
+     * Destructive for <code>scratch</code>.
+     */
+    public Arc<O> follow(Arc<O> scratch, int [] labels, int start, int length) {
+      final int end = start + length;
+      for (int i = start; scratch != null && i < end; i++) {
+        scratch = scratch.follow(labels[i]);
+      }
+      return scratch;
+    }
+
+    /** 
+     * Find an arc labeled with the given symbol within the current state
+     * (must be lexicographically higher than the current arc's label).
+     * If found, follows to the target state's first arc.
+     * 
+     * Destructive for <code>scratch</code>, does not change <code>this</code>.
+     */
+    public Arc<O> follow(Arc<O> scratch, int label) {
+      return find(scratch, label) == null ? null : scratch.follow();
+    }
+
+    /** 
+     * Skip to an arc labeled with the given symbol within the current state 
+     * (must be lexicographically higher than the current arc's label).
+     * 
+     * Destructive for <code>this</code>.
+     */
+    public Arc<O> find(int label) {
+      return find(this, label);
+    }
+
+    /** 
+     * Skip to an arc labeled with the given symbol within the current state. Note: this method
+     * will seek only forward (lexicographically higher) arcs within scratch's state.
+     * 
+     * Destructive for <code>scratch</code>, does not change <code>this</code>.
+     */
+    public Arc<O> find(Arc<O> scratch, int label) {
+     return null;
+    }
+
+    /**
+     * Returns <code>true</code> if following this arc accepts the input path 
+     * from the root. 
+     */
+    public boolean isAccepting() {
+      return false;
+    }
+
+    /**
+     * If <code>true</code> this arc has a target node to which we can follow. Accepting arcs
+     * pointing at the dummy terminal state cannot be followed.
+     */
+    public boolean canFollow() {
+      return false;
+    }
+  }
+
+  /** Arc label type. */
+  private ArcLabel arcLabel;
+
+  /** First arc of the root state. */
+  private Arc<O> root;
+  
+  /** */
+  public FST(ArcLabel arcLabel) {
+    this.arcLabel = arcLabel;
+  }
+  
+  /** Returns a clone of the root state's first arc. */
+  public Arc<O> getRoot() {
+    return getRoot(newArc());
+  }
+
+  /** Fills the argument with the root state's first arc. */
+  public Arc<O> getRoot(Arc<O> buffer) {
+    return buffer.copyFrom(root);
+  }
+
+  /** Create a new buffer for an arc object. */
+  public Arc<O> newArc() {
+    return new Arc<O>(this);
+  }
+
+  /** Create a new array of arc objects (prefilled). */
+  public Arc<O> [] newArcArray(int size) {
+    @SuppressWarnings("unchecked")
+    final Arc<O> [] array = (Arc<O>[]) new Arc [size];
+    while (--size >= 0)
+      array[size] = newArc();
+    return array;
+  }
+
+  /** */
+  @SuppressWarnings("unused")
+  private static void main() {
+    {
+      /*
+       * Create an acceptor machine (FSA). Add a few input sequences.
+       */
+      FST<Empty> fsa = FSTBuilder.fsa(FST.ArcLabel.BYTE1)
+        .add(new BytesRef("ab"))
+        .add(new BytesRef("abc"))
+        .add(new BytesRef("ac"))
+        .build();
+
+
+      // Get the root state's first arc clone (if the automaton is empty, this can return null).
+      Arc<Empty> arc = fsa.getRoot();
+  
+      // the same, but reusing an existing object. also shows how to get a clean arc buffer from an fst.
+      arc = fsa.getRoot(fsa.newArc());
+
+      
+      // Dump all outgoing transitions from arc's state.
+      for (Arc<Empty> tmp = arc.copy(); tmp.hasNext(); tmp.next()) {
+        int label = tmp.getLabel();     // transition label here.
+        Empty output = tmp.getOutput(); // FSAs have a constant empty output.
+      }
+
+      
+      // Follow a chain of labels from the root. null will be returned on mismatch.
+      int [] sequence = {'a', 'b'};
+      Arc<Empty> a = arc.copy().follow(sequence);
+      assert a != null;
+
+
+      // Check if a given sequence is accepted from the current arc's state. 
+      // This translates into: follow to the symbol before
+      // the last one, then check if an outgoing transition exists and is marked as accepting.
+      a = arc.copy().follow(sequence, 0, sequence.length - 1);
+      boolean accepted = 
+               a != null 
+            && (a = a.find(sequence.length - 1)) != null 
+            && a.isAccepting();
+
+
+      // Collect the suffixes starting at the given state (collect the right language).
+      // Non-recursive, minimal gc overhead (arcs buffered and reused).
+      a = arc.copy().find('a').follow(); // or
+      a = arc.copy().follow('a');
+
+      int depth = 0;
+      Arc<Empty> [] arcs = fsa.newArcArray(10);
+      int [] labels = new int [10];
+
+      // TODO: expand arcs and buffer on demand.
+      arcs[0].copyFrom(a);
+      while (depth > 0) {
+        final Arc<Empty> current = arcs[depth];
+        labels[depth] = current.getLabel();
+
+        // We're hitting an accepting arc right now. Collect.
+        if (current.isAccepting()) {
+          // labels[0..depth] is an accepted sequence.
+        }
+
+        // Advance to the next state if we can follow the current arc.
+        // If we can't, go back and skip to the next arc in order.
+        if (current.canFollow()) {
+          arcs[depth].follow(arcs[depth + 1]); // or arcs[depth + 1].copyFrom(arcs[depth]).follow();
+          depth++;
+        } else {
+          while (depth >= 0 && arcs[depth].isLast()) 
+            depth--;
+          if (depth >= 0)
+            arcs[depth].next();
+        }
+      }
+    }
+
+    {
+      /*
+       * Create a transducer on byte2 labels and with positive inputs algebra. Add a few sequences.
+       */
+      FST<Integer> fst = FSTBuilder.fst(FST.ArcLabel.BYTE2, PositiveInt.class)
+        .add(new IntsRef(new int [] {0, 1, 2}, 0, 3), 10)
+        .add(new IntsRef(new int [] {0, 2, 3}, 0, 3), 5)
+        .add(new IntsRef(new int [] {1, 2, 3}, 0, 3), 2)
+        .build();
+
+      // Dump all outgoing transitions from the root state.
+      Arc<Integer> arc = fst.getRoot();
+      for (Arc<Integer> tmp = arc.copy(); tmp.hasNext(); tmp.next()) {
+        int label = tmp.getLabel();     // transition label here.
+        Integer output = tmp.getOutput(); // FSAs have a constant empty output.
+      }
+    }
+  }
+}
