lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sar...@apache.org
Subject [05/48] lucene-solr:jira/lucene-2562: Add luke as a sub-module.
Date Tue, 17 Jul 2018 21:12:40 GMT
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java
new file mode 100644
index 0000000..378abd9
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.documents;
+
+import org.apache.lucene.index.PostingsEnum;
+import org.apache.lucene.util.BytesRef;
+
+import java.io.IOException;
+
+/**
+ * Holder for a term's position information, and optionally, offsets and payloads.
+ */
+public final class TermPosting {
+
+  // position
+  private int position = -1;
+
+  // start and end offset (optional)
+  private int startOffset = -1;
+  private int endOffset = -1;
+
+  // payload (optional)
+  private BytesRef payload;
+
+  static TermPosting of(int position, PostingsEnum penum) throws IOException {
+    TermPosting posting = new TermPosting();
+
+    // set position
+    posting.position = position;
+
+    // set offset (if available)
+    int sOffset = penum.startOffset();
+    int eOffset = penum.endOffset();
+    if (sOffset >= 0 && eOffset >= 0) {
+      posting.startOffset = sOffset;
+      posting.endOffset = eOffset;
+    }
+
+    // set payload (if available)
+    if (penum.getPayload() != null) {
+      posting.payload = BytesRef.deepCopyOf(penum.getPayload());
+    }
+
+    return posting;
+  }
+
+  public int getPosition() {
+    return position;
+  }
+
+  public int getStartOffset() {
+    return startOffset;
+  }
+
+  public int getEndOffset() {
+    return endOffset;
+  }
+
+  public BytesRef getPayload() {
+    return payload;
+  }
+
+  @Override
+  public String toString() {
+    return "TermPosting{" +
+        "position=" + position +
+        ", startOffset=" + startOffset +
+        ", endOffset=" + endOffset +
+        ", payload=" + payload +
+        '}';
+  }
+
+  private TermPosting() {
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java
new file mode 100644
index 0000000..595784c
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.documents;
+
+import org.apache.lucene.index.PostingsEnum;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.luke.util.BytesRefUtils;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+
+/**
+ * Holder for term vector entry representing the term and their number of occurrences, and optionally, positions in the document field.
+ */
+public final class TermVectorEntry {
+
+  private final String termText;
+  private final long freq;
+  private final List<TermVectorPosition> positions;
+
+  /**
+   * Returns a new term vector entry representing the specified term, and optionally, positions.
+   *
+   * @param te - positioned terms iterator
+   * @return term vector entry
+   * @throws IOException
+   */
+  static TermVectorEntry of(@Nonnull TermsEnum te) throws IOException {
+    String termText = BytesRefUtils.decode(te.term());
+
+    List<TermVectorEntry.TermVectorPosition> tvPositions = new ArrayList<>();
+    PostingsEnum pe = te.postings(null, PostingsEnum.OFFSETS);
+    pe.nextDoc();
+    int freq = pe.freq();
+    for (int i = 0; i < freq; i++) {
+      int pos = pe.nextPosition();
+      if (pos < 0) {
+        // no position information available
+        continue;
+      }
+      TermVectorPosition tvPos = TermVectorPosition.of(pos, pe);
+      tvPositions.add(tvPos);
+    }
+
+    return new TermVectorEntry(termText, te.totalTermFreq(), tvPositions);
+  }
+
+  private TermVectorEntry(String termText, long freq, List<TermVectorPosition> positions) {
+    this.termText = termText;
+    this.freq = freq;
+    this.positions = positions;
+  }
+
+  /**
+   * Returns the string representation for this term.
+   */
+  public String getTermText() {
+    return termText;
+  }
+
+  /**
+   * Returns the number of occurrences of this term in the document field.
+   */
+  public long getFreq() {
+    return freq;
+  }
+
+  /**
+   * Returns the list of positions for this term in the document field.
+   */
+  public List<TermVectorPosition> getPositions() {
+    return positions;
+  }
+
+  @Override
+  public String toString() {
+    String positionsStr = positions.stream()
+        .map(TermVectorPosition::toString)
+        .collect(Collectors.joining(","));
+
+    return "TermVectorEntry{" +
+        "termText='" + termText + '\'' +
+        ", freq=" + freq +
+        ", positions=" + positionsStr +
+        '}';
+  }
+
+  /**
+   * Holder for position information for a term vector entry.
+   */
+  public static final class TermVectorPosition {
+    private final int position;
+    private final int startOffset;
+    private final int endOffset;
+
+    /**
+     * Returns a new position entry representing the specified posting, and optionally, start and end offsets.
+     * @param pos - term position
+     * @param pe - positioned postings iterator
+     * @return position entry
+     * @throws IOException
+     */
+    static TermVectorPosition of(int pos, @Nonnull PostingsEnum pe) throws IOException {
+      int sOffset = pe.startOffset();
+      int eOffset = pe.endOffset();
+      if (sOffset >= 0 && eOffset >= 0) {
+        return new TermVectorPosition(pos, sOffset, eOffset);
+      }
+      return new TermVectorPosition(pos);
+    }
+
+    /**
+     * Returns the position for this term in the document field.
+     */
+    public int getPosition() {
+      return position;
+    }
+
+    /**
+     * Returns the start offset for this term in the document field.
+     * Empty Optional instance is returned if no offset information available.
+     */
+    public OptionalInt getStartOffset() {
+      return startOffset >= 0 ? OptionalInt.of(startOffset) : OptionalInt.empty();
+    }
+
+    /**
+     * Returns the end offset for this term in the document field.
+     * Empty Optional instance is returned if no offset information available.
+     */
+    public OptionalInt getEndOffset() {
+      return endOffset >= 0 ? OptionalInt.of(endOffset) : OptionalInt.empty();
+    }
+
+    @Override
+    public String toString() {
+      return "TermVectorPosition{" +
+          "position=" + position +
+          ", startOffset=" + startOffset +
+          ", endOffset=" + endOffset +
+          '}';
+    }
+
+    private TermVectorPosition(int position) {
+      this(position, -1, -1);
+    }
+
+    private TermVectorPosition(int position, int startOffset, int endOffset) {
+      this.position = position;
+      this.startOffset = startOffset;
+      this.endOffset = endOffset;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java
new file mode 100644
index 0000000..baa20c3
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.documents;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.PostingsEnum;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An utility class to access to the term vectors.
+ */
+final class TermVectorsAdapter {
+
+  private static Logger logger = LoggerFactory.getLogger(TermVectorsAdapter.class);
+
+  private IndexReader reader;
+
+  TermVectorsAdapter(@Nonnull IndexReader reader) {
+    this.reader = reader;
+  }
+
+  /**
+   * Returns the term vectors for the specified field in the specified document.
+   * If no term vector is available for the field, empty list is returned.
+   *
+   * @param docid - document id
+   * @param field - field name
+   * @return list of term vector elements
+   * @throws IOException
+   */
+  List<TermVectorEntry> getTermVector(int docid, String field) throws IOException {
+    Terms termVector = reader.getTermVector(docid, field);
+    if (termVector == null) {
+      // no term vector available
+      logger.warn("No term vector indexed for doc: #{} and field: {}", docid, field);
+      return Collections.emptyList();
+    }
+
+    List<TermVectorEntry> res = new ArrayList<>();
+    TermsEnum te = termVector.iterator();
+    while (te.next() != null) {
+      res.add(TermVectorEntry.of(te));
+    }
+    return res;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java
new file mode 100644
index 0000000..9913be3
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * A dedicated interface for Luke's Overview tab.
+ */
+public interface Overview {
+
+  /**
+   * Returns the currently opened index directory path,
+   * or the root directory path if multiple index directories are opened.
+   */
+  String getIndexPath();
+
+  /**
+   * Returns the number of fields in this index.
+   */
+  int getNumFields();
+
+  /**
+   * Returns the number of documents in this index.
+   */
+  int getNumDocuments();
+
+  /**
+   * Returns the total number of terms in this index.
+   *
+   * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index
+   */
+  long getNumTerms();
+
+  /**
+   * Returns true if this index includes deleted documents.
+   */
+  boolean hasDeletions();
+
+  /**
+   * Returns the number of deleted documents in this index.
+   */
+  int getNumDeletedDocs();
+
+  /**
+   * Returns true if the index is optimized.
+   * Empty Optional instance is returned if multiple indexes are opened.
+   */
+  Optional<Boolean> isOptimized();
+
+  /**
+   * Returns the version number when this index was opened.
+   * Empty Optional instance is returned if multiple indexes are opened.
+   */
+  Optional<Long> getIndexVersion();
+
+  /**
+   * Returns the string representation for the Lucene segment version when the index was created.
+   * Empty Optional instance is returned if multiple indexes are opened.
+   *
+   * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index
+   */
+  Optional<String> getIndexFormat();
+
+  /**
+   * Returns the currently opened {@link org.apache.lucene.store.Directory} implementation class name.
+   * Empty Optional instance is returned if multiple indexes are opened.
+   */
+  Optional<String> getDirImpl();
+
+  /**
+   * Returns the information of the commit point that reader has opened.
+   *
+   * Empty Optional instance is returned if multiple indexes are opened.
+   */
+  Optional<String> getCommitDescription();
+
+  /**
+   * Returns the user provided data for the commit point.
+   * Empty Optional instance is returned if multiple indexes are opened.
+   *
+   * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index
+   */
+  Optional<String> getCommitUserData();
+
+  /**
+   * Returns all fields with the number of terms for each field sorted by {@link TermCountsOrder}
+   *
+   * @param order - the sort order
+   * @return the ordered map of terms and their frequencies
+   * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index
+   */
+  Map<String, Long> getSortedTermCounts(TermCountsOrder order);
+
+  /**
+   * Returns the top indexed terms with their statistics for the specified field.
+   *
+   * @param field - the field name
+   * @param numTerms - the max number of terms to be returned
+   * @return the list of top terms and their document frequencies
+   * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index
+   */
+  List<TermStats> getTopTerms(String field, int numTerms);
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java
new file mode 100644
index 0000000..5c09ce2
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+import org.apache.lucene.index.IndexReader;
+
+public class OverviewFactory {
+
+  public Overview newInstance(IndexReader reader, String indexPath) {
+    return new OverviewImpl(reader, indexPath);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java
new file mode 100644
index 0000000..063febc
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.luke.models.LukeModel;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.luke.util.IndexUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public final class OverviewImpl extends LukeModel implements Overview {
+
+  private final String indexPath;
+
+  private final TermCounts termCounts;
+
+  private final TopTerms topTerms;
+
+  /**
+   * Constructs an OverviewImpl that holds the given {@link IndexReader}.
+   *
+   * @param reader - the index reader
+   * @param indexPath - the (root) index directory path
+   * @throws LukeException - if an internal error is occurred when accessing index
+   */
+  public OverviewImpl(@Nonnull IndexReader reader, @Nonnull String indexPath) {
+    super(reader);
+    this.indexPath = indexPath;
+    try {
+      this.termCounts = new TermCounts(reader);
+    } catch (IOException e) {
+      throw new LukeException("An error occurred when collecting term statistics.");
+    }
+    this.topTerms = new TopTerms(reader);
+  }
+
+  @Override
+  public String getIndexPath() {
+    return indexPath;
+  }
+
+  @Override
+  public int getNumFields() {
+    return IndexUtils.getFieldInfos(reader).size();
+  }
+
+  @Override
+  public int getNumDocuments() {
+    return reader.numDocs();
+  }
+
+  @Override
+  public long getNumTerms() {
+    return termCounts.numTerms();
+  }
+
+  @Override
+  public boolean hasDeletions() {
+    return reader.hasDeletions();
+  }
+
+  @Override
+  public int getNumDeletedDocs() {
+    return reader.numDeletedDocs();
+  }
+
+  @Override
+  public Optional<Boolean> isOptimized() {
+    if (commit != null) {
+      return Optional.of(commit.getSegmentCount() == 1);
+    }
+    return Optional.empty();
+  }
+
+  @Override
+  public Optional<Long> getIndexVersion() {
+    if (reader instanceof DirectoryReader) {
+      return Optional.of(((DirectoryReader) reader).getVersion());
+    }
+    return Optional.empty();
+  }
+
+  @Override
+  public Optional<String> getIndexFormat() {
+    if (dir == null) {
+      return Optional.empty();
+    }
+    try {
+      return Optional.of(IndexUtils.getIndexFormat(dir));
+    } catch (IOException e) {
+      throw new LukeException("Index format not available.", e);
+    }
+  }
+
+  @Override
+  public Optional<String> getDirImpl() {
+    if (dir == null) {
+      return Optional.empty();
+    }
+    return Optional.of(dir.getClass().getName());
+  }
+
+  @Override
+  public Optional<String> getCommitDescription() {
+    if (commit == null) {
+      return Optional.empty();
+    }
+    return Optional.of(
+        commit.getSegmentsFileName()
+            + " (generation=" + commit.getGeneration()
+            + ", segs=" + commit.getSegmentCount() + ")");
+  }
+
+  @Override
+  public Optional<String> getCommitUserData() {
+    if (commit == null) {
+      return Optional.empty();
+    }
+    try {
+      return Optional.of(IndexUtils.getCommitUserData(commit));
+    } catch (IOException e) {
+      throw new LukeException("Commit user data not available.", e);
+    }
+  }
+
+  @Override
+  public Map<String, Long> getSortedTermCounts(@Nullable TermCountsOrder order) {
+    if (order == null) {
+      order = TermCountsOrder.COUNT_DESC;
+    }
+    return termCounts.sortedTermCounts(order);
+  }
+
+  @Override
+  public List<TermStats> getTopTerms(@Nonnull String field, int numTerms) {
+    if (numTerms < 0) {
+      throw new IllegalArgumentException(String.format("'numTerms' must be a positive integer: %d is not accepted.", numTerms));
+    }
+    try {
+      return topTerms.getTopTerms(field, numTerms);
+    } catch (Exception e) {
+      throw new LukeException(String.format("Top terms for field %s not available.", field), e);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java
new file mode 100644
index 0000000..e81e9cb
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.luke.util.IndexUtils;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * An utility class that collects term counts terms for all fields in a index.
+ */
+final class TermCounts {
+
+  private final Map<String, Long> termCountMap;
+
+  TermCounts(@Nonnull IndexReader reader) throws IOException {
+    termCountMap = IndexUtils.countTerms(reader, IndexUtils.getFieldNames(reader));
+  }
+
+  /**
+   * Returns the total number of terms in this index.
+   */
+  long numTerms() {
+    return termCountMap.values().stream().mapToLong(Long::longValue).sum();
+  }
+
+  /**
+   * Returns all fields with the number of terms for each field sorted by {@link TermCountsOrder}
+   * @param order - sort order
+   */
+  Map<String, Long> sortedTermCounts(@Nonnull TermCountsOrder order){
+    Comparator<Map.Entry<String, Long>> comparator;
+    switch (order) {
+      case NAME_ASC:
+        comparator = Map.Entry.comparingByKey();
+        break;
+      case NAME_DESC:
+        comparator = Map.Entry.<String, Long>comparingByKey().reversed();
+        break;
+      case COUNT_ASC:
+        comparator = Map.Entry.comparingByValue();
+        break;
+      case COUNT_DESC:
+        comparator = Map.Entry.<String, Long>comparingByValue().reversed();
+        break;
+      default:
+        comparator = Map.Entry.comparingByKey();
+    }
+    return sortedTermCounts(comparator);
+  }
+
+  private Map<String, Long> sortedTermCounts(Comparator<Map.Entry<String, Long>> comparator) {
+    return termCountMap.entrySet().stream()
+        .sorted(comparator)
+        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java
new file mode 100644
index 0000000..a5976ba
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+/**
+ * Sort orders for fields with their term counts
+ */
+public enum TermCountsOrder {
+  /**
+   * Ascending order by the field name
+   */
+  NAME_ASC,
+
+  /**
+   * Descending order by the field name
+   */
+  NAME_DESC,
+
+  /**
+   * Ascending order by the count of terms
+   */
+  COUNT_ASC,
+
+  /**
+   * Descending order by the count of terms
+   */
+  COUNT_DESC
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java
new file mode 100644
index 0000000..b97afe7
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+import org.apache.lucene.luke.util.BytesRefUtils;
+
+/**
+ * Holder for statistics for a term in a specific field.
+ */
+public final class TermStats {
+
+  private final String decodedTermText;
+
+  private final String field;
+
+  private final int docFreq;
+
+  /**
+   * Returns a TermStats instance representing the specified {@link org.apache.lucene.misc.TermStats} value.
+   */
+  static TermStats of(org.apache.lucene.misc.TermStats stats) {
+    String termText = BytesRefUtils.decode(stats.termtext);
+    return new TermStats(termText, stats.field, stats.docFreq);
+  }
+
+  private TermStats(String decodedTermText, String field, int docFreq) {
+    this.decodedTermText = decodedTermText;
+    this.field = field;
+    this.docFreq = docFreq;
+  }
+
+  /**
+   * Returns the string representation for this term.
+   */
+  public String getDecodedTermText() {
+    return decodedTermText;
+  }
+
+  /**
+   * Returns the field name.
+   */
+  public String getField() {
+    return field;
+  }
+
+  /**
+   * Returns the document frequency of this term.
+   */
+  public int getDocFreq() {
+    return docFreq;
+  }
+
+  @Override
+  public String toString() {
+    return "TermStats{" +
+        "decodedTermText='" + decodedTermText + '\'' +
+        ", field='" + field + '\'' +
+        ", docFreq=" + docFreq +
+        '}';
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java
new file mode 100644
index 0000000..ab23e1b
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.overview;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.misc.HighFreqTerms;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * An utility class that collects terms and their statistics in a specific field.
+ */
+final class TopTerms {
+
+  private final IndexReader reader;
+
+  private final Map<String, List<TermStats>> topTermsCache;
+
+  TopTerms(@Nonnull IndexReader reader) {
+    this.reader = reader;
+    this.topTermsCache = new WeakHashMap<>();
+  }
+
+  /**
+   * Returns the top indexed terms with their statistics for the specified field.
+   *
+   * @param field - the field name
+   * @param numTerms - the max number of terms to be returned
+   * @throws Exception - if an error occurs when collecting term statistics
+   */
+  List<TermStats> getTopTerms(String field, int numTerms) throws Exception {
+
+    if (!topTermsCache.containsKey(field) || topTermsCache.get(field).size() < numTerms) {
+      org.apache.lucene.misc.TermStats[] stats =
+          HighFreqTerms.getHighFreqTerms(reader, numTerms, field, new HighFreqTerms.DocFreqComparator());
+
+      List<TermStats> topTerms = Arrays.stream(stats)
+          .map(TermStats::of)
+          .collect(Collectors.toList());
+
+      // cache computed statistics for later uses
+      topTermsCache.put(field, topTerms);
+    }
+
+    return ImmutableList.copyOf(topTermsCache.get(field));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java
new file mode 100644
index 0000000..709afaa
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.lucene.queries.mlt.MoreLikeThis;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Configurations for MoreLikeThis query.
+ */
+public final class MLTConfig {
+
+  private final List<String> fields;
+
+  private final int maxDocFreq;
+
+  private final int minDocFreq;
+
+  private final int minTermFreq;
+
+  public static class Builder {
+
+    private final List<String> fields = new ArrayList<>();
+    private int maxDocFreq = MoreLikeThis.DEFAULT_MAX_DOC_FREQ;
+    private int minDocFreq = MoreLikeThis.DEFAULT_MIN_DOC_FREQ;
+    private int minTermFreq = MoreLikeThis.DEFAULT_MIN_TERM_FREQ;
+
+    public Builder fields(Collection<String> val) {
+      fields.addAll(val);
+      return this;
+    }
+
+    public Builder maxDocFreq(int val) {
+      maxDocFreq = val;
+      return this;
+    }
+
+    public Builder minDocFreq(int val) {
+      minDocFreq = val;
+      return this;
+    }
+
+    public Builder minTermFreq(int val) {
+      minTermFreq = val;
+      return this;
+    }
+
+    public MLTConfig build() {
+      return new MLTConfig(this);
+    }
+  }
+
+  private MLTConfig(Builder builder) {
+    this.fields = ImmutableList.copyOf(builder.fields);
+    this.maxDocFreq = builder.maxDocFreq;
+    this.minDocFreq = builder.minDocFreq;
+    this.minTermFreq = builder.minTermFreq;
+  }
+
+  public String[] getFieldNames() {
+    return fields.toArray(new String[fields.size()]);
+  }
+
+  public int getMaxDocFreq() {
+    return maxDocFreq;
+  }
+
+  public int getMinDocFreq() {
+    return minDocFreq;
+  }
+
+  public int getMinTermFreq() {
+    return minTermFreq;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java
new file mode 100644
index 0000000..9c20bd6
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.lucene.document.DateTools;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.stream.Collectors;
+
+/**
+ * Configurations for query parser.
+ */
+public final class QueryParserConfig {
+
+  public enum Operator {
+    AND, OR
+  }
+
+  private final boolean useClassicParser;
+
+  private final boolean enablePositionIncrements;
+
+  private final boolean allowLeadingWildcard;
+
+  private final DateTools.Resolution dateResolution;
+
+  private final Operator defaultOperator;
+
+  private final float fuzzyMinSim;
+
+  private final int fuzzyPrefixLength;
+
+  private final Locale locale;
+
+  private final TimeZone timeZone;
+
+  private final int phraseSlop;
+
+  // classic parser only configurations
+  private final boolean autoGenerateMultiTermSynonymsPhraseQuery;
+
+  private final boolean autoGeneratePhraseQueries;
+
+  private final boolean splitOnWhitespace;
+
+  // standard parser only configurations
+  private final Map<String, Class<? extends Number>> typeMap;
+
+  public static class Builder {
+    private boolean useClassicParser = true;
+    private boolean enablePositionIncrements = true;
+    private boolean allowLeadingWildcard = false;
+    private DateTools.Resolution dateResolution = DateTools.Resolution.MILLISECOND;
+    private Operator defaultOperator = Operator.OR;
+    private float fuzzyMinSim = 2f;
+    private int fuzzyPrefixLength = 0;
+    private Locale locale = Locale.getDefault();
+    private TimeZone timeZone = TimeZone.getDefault();
+    private int phraseSlop = 0;
+    private boolean autoGenerateMultiTermSynonymsPhraseQuery = false;
+    private boolean autoGeneratePhraseQueries = false;
+    private boolean splitOnWhitespace = false;
+    private Map<String, Class<? extends Number>> typeMap = new HashMap<>();
+
+    public Builder useClassicParser(boolean value) {
+      useClassicParser = value;
+      return this;
+    }
+
+    public Builder enablePositionIncrements(boolean value) {
+      enablePositionIncrements = value;
+      return this;
+    }
+
+    public Builder allowLeadingWildcard(boolean value) {
+      allowLeadingWildcard = value;
+      return this;
+    }
+
+    public Builder dateResolution(DateTools.Resolution value) {
+      dateResolution = value;
+      return this;
+    }
+
+    public Builder defaultOperator(Operator op) {
+      defaultOperator = op;
+      return this;
+    }
+
+    public Builder fuzzyMinSim(float val) {
+      fuzzyMinSim = val;
+      return this;
+    }
+
+    public Builder fuzzyPrefixLength(int val) {
+      fuzzyPrefixLength = val;
+      return this;
+    }
+
+    public Builder locale(Locale val) {
+      locale = val;
+      return this;
+    }
+
+    public Builder timeZone(TimeZone val) {
+      timeZone = val;
+      return this;
+    }
+
+    public Builder phraseSlop(int val) {
+      phraseSlop = val;
+      return this;
+    }
+
+    public Builder autoGenerateMultiTermSynonymsPhraseQuery(boolean val) {
+      autoGenerateMultiTermSynonymsPhraseQuery = val;
+      return this;
+    }
+
+    public Builder autoGeneratePhraseQueries(boolean val) {
+      autoGeneratePhraseQueries = val;
+      return this;
+    }
+
+    public Builder splitOnWhitespace(boolean val) {
+      splitOnWhitespace = val;
+      return this;
+    }
+
+    public Builder typeMap(Map<String, Class<? extends Number>> val) {
+      typeMap = val;
+      return this;
+    }
+
+    public QueryParserConfig build() {
+      return new QueryParserConfig(this);
+    }
+  }
+
+  private QueryParserConfig(Builder builder) {
+    this.useClassicParser = builder.useClassicParser;
+    this.enablePositionIncrements = builder.enablePositionIncrements;
+    this.allowLeadingWildcard = builder.allowLeadingWildcard;
+    this.dateResolution = builder.dateResolution;
+    this.defaultOperator = builder.defaultOperator;
+    this.fuzzyMinSim = builder.fuzzyMinSim;
+    this.fuzzyPrefixLength = builder.fuzzyPrefixLength;
+    this.locale = builder.locale;
+    this.timeZone = builder.timeZone;
+    this.phraseSlop = builder.phraseSlop;
+    this.autoGenerateMultiTermSynonymsPhraseQuery = builder.autoGenerateMultiTermSynonymsPhraseQuery;
+    this.autoGeneratePhraseQueries = builder.autoGeneratePhraseQueries;
+    this.splitOnWhitespace = builder.splitOnWhitespace;
+    this.typeMap = ImmutableMap.copyOf(builder.typeMap);
+  }
+
+  public boolean isUseClassicParser() {
+    return useClassicParser;
+  }
+
+  public boolean isAutoGenerateMultiTermSynonymsPhraseQuery() {
+    return autoGenerateMultiTermSynonymsPhraseQuery;
+  }
+
+  public boolean isEnablePositionIncrements() {
+    return enablePositionIncrements;
+  }
+
+  public boolean isAllowLeadingWildcard() {
+    return allowLeadingWildcard;
+  }
+
+  public boolean isAutoGeneratePhraseQueries() {
+    return autoGeneratePhraseQueries;
+  }
+
+  public boolean isSplitOnWhitespace() {
+    return splitOnWhitespace;
+  }
+
+  public DateTools.Resolution getDateResolution() {
+    return dateResolution;
+  }
+
+  public Operator getDefaultOperator() {
+    return defaultOperator;
+  }
+
+  public float getFuzzyMinSim() {
+    return fuzzyMinSim;
+  }
+
+  public int getFuzzyPrefixLength() {
+    return fuzzyPrefixLength;
+  }
+
+  public Locale getLocale() {
+    return locale;
+  }
+
+  public TimeZone getTimeZone() {
+    return timeZone;
+  }
+
+  public int getPhraseSlop() {
+    return phraseSlop;
+  }
+
+  public Map<String, Class<? extends Number>> getTypeMap() {
+    return typeMap;
+  }
+
+  @Override
+  public String toString() {
+    return "QueryParserConfig: [" +
+        String.format(" default operator=%s;", defaultOperator.name()) +
+        String.format(" enable position increment=%s;", enablePositionIncrements) +
+        String.format(" allow leading wildcard=%s; ", allowLeadingWildcard) +
+        String.format(" split whitespace=%s;", splitOnWhitespace) +
+        String.format(" generate phrase query=%s;", autoGeneratePhraseQueries) +
+        String.format(" generate multiterm sysnonymsphrase query=%s;", autoGenerateMultiTermSynonymsPhraseQuery) +
+        String.format(" phrase slop=%d;", phraseSlop) +
+        String.format(" date resolution=%s;", dateResolution.name()) +
+        String.format(" locale=%s;", locale.toString()) +
+        String.format(" time zone=%s;", timeZone.getID()) +
+        String.format(" numeric types=%s;", String.join(",", getTypeMap().entrySet().stream()
+            .map(e -> e.getKey() + "=" + e.getValue().toString()).collect(Collectors.toSet()))) +
+        "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java
new file mode 100644
index 0000000..4ee5707
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * A dedicated interface for Luke's Search tab.
+ */
+public interface Search {
+
+  /**
+   * Returns all field names in this index.
+   */
+  Collection<String> getFieldNames();
+
+  /**
+   * Returns field names those are sortable.
+   */
+  Collection<String> getSortableFieldNames();
+
+  /**
+   * Returns field names those are searchable.
+   */
+  Collection<String> getSearchableFieldNames();
+
+  /**
+   * Returns field names those are searchable by range query.
+   */
+  Collection<String> getRangeSearchableFieldNames();
+
+  /**
+   * Returns the current query.
+   */
+  Query getCurrentQuery();
+
+  /**
+   * Parses the specified query expression with given configurations.
+   *
+   * @param expression - query expression
+   * @param defField - default field for the query
+   * @param analyzer - analyzer for parsing query expression
+   * @param config - query parser configuration
+   * @param rewrite - if true, re-written query is returned
+   * @return parsed query
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  Query parseQuery(String expression, String defField, Analyzer analyzer, QueryParserConfig config, boolean rewrite);
+
+  /**
+   * Creates the MoreLikeThis query for the specified document with given configurations.
+   *
+   * @param docid - document id
+   * @param mltConfig - MoreLikeThis configuration
+   * @param analyzer - analyzer for analyzing the document
+   * @return MoreLikeThis query
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  Query mltQuery(int docid, MLTConfig mltConfig, Analyzer analyzer);
+
+  /**
+   * Searches this index by the query with given configurations.
+   *
+   * @param query - search query
+   * @param simConfig - similarity configuration
+   * @param fieldsToLoad - field names to load
+   * @param pageSize - page size
+   * @return search results
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  SearchResults search(Query query, SimilarityConfig simConfig, Set<String> fieldsToLoad, int pageSize);
+
+  /**
+   * Searches this index by the query with given sort criteria and configurations.
+   *
+   * @param query - search query
+   * @param simConfig - similarity configuration
+   * @param sort - sort criteria
+   * @param fieldsToLoad - fields to load
+   * @param pageSize - page size
+   * @return search results
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  SearchResults search(Query query, SimilarityConfig simConfig, Sort sort, Set<String> fieldsToLoad, int pageSize);
+
+  /**
+   * Returns the next page for the current query.
+   *
+   * @return search results, or empty if there are no more results
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  Optional<SearchResults> nextPage();
+
+  /**
+   * Returns the previous page for the current query.
+   *
+   * @return search results, or empty if there are no more results.
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  Optional<SearchResults> prevPage();
+
+  /**
+   * Explains the document for the specified query.
+   *
+   * @param query - query
+   * @param docid - document id to be explained
+   * @return explanations
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  Explanation explain(Query query, int docid);
+
+  /**
+   * Returns possible {@link SortField}s for the specified field.
+   *
+   * @param name - field name
+   * @return list of possible sort types
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  List<SortField> guessSortTypes(String name);
+
+  /**
+   * Returns the {@link SortField} for the specified field with the sort type and order.
+   *
+   * @param name - field name
+   * @param type - string representation for a type
+   * @param reverse - if true, descending order is used
+   * @return sort type, or empty if the type is incompatible with the field
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  Optional<SortField> getSortType(String name, String type, boolean reverse);
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java
new file mode 100644
index 0000000..d6751a6
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+import org.apache.lucene.index.IndexReader;
+
+public class SearchFactory {
+
+  public Search newInstance(IndexReader reader) {
+    return new SearchImpl(reader);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java
new file mode 100644
index 0000000..74e8738
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java
@@ -0,0 +1,447 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.luke.models.LukeModel;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.luke.util.IndexUtils;
+import org.apache.lucene.queries.mlt.MoreLikeThis;
+import org.apache.lucene.queryparser.classic.ParseException;
+import org.apache.lucene.queryparser.classic.QueryParser;
+import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
+import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
+import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig;
+import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSortField;
+import org.apache.lucene.search.SortedSetSortField;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.similarities.BM25Similarity;
+import org.apache.lucene.search.similarities.ClassicSimilarity;
+import org.apache.lucene.search.similarities.Similarity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.text.NumberFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class SearchImpl extends LukeModel implements Search {
+
+  private static final Logger logger = LoggerFactory.getLogger(SearchImpl.class);
+
+  private static final int DEFAULT_PAGE_SIZE = 10;
+
+  private final IndexSearcher searcher;
+
+  private int pageSize = DEFAULT_PAGE_SIZE;
+
+  private int currentPage = -1;
+
+  private long totalHits = -1;
+
+  private ScoreDoc[] docs = new ScoreDoc[0];
+
+  private Query query;
+
+  private Sort sort;
+
+  private Set<String> fieldsToLoad;
+
+  /**
+   * Constructs a SearchImpl that holds given {@link IndexReader}
+   * @param reader - the index reader
+   */
+  public SearchImpl(@Nonnull IndexReader reader) {
+    super(reader);
+    this.searcher = new IndexSearcher(reader);
+  }
+
+  @Override
+  public Collection<String> getSortableFieldNames() {
+    return IndexUtils.getFieldNames(reader).stream()
+        .map(f -> IndexUtils.getFieldInfo(reader, f))
+        .filter(info -> !info.getDocValuesType().equals(DocValuesType.NONE))
+        .map(info -> info.name)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public Collection<String> getSearchableFieldNames() {
+    return IndexUtils.getFieldNames(reader).stream()
+        .map(f -> IndexUtils.getFieldInfo(reader, f))
+        .filter(info -> !info.getIndexOptions().equals(IndexOptions.NONE))
+        .map(info -> info.name)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public Collection<String> getRangeSearchableFieldNames() {
+    return IndexUtils.getFieldNames(reader).stream()
+        .map(f -> IndexUtils.getFieldInfo(reader, f))
+        .filter(info -> info.getPointDimensionCount() > 0)
+        .map(info -> info.name)
+        .collect(Collectors.toSet());
+  }
+
+  @Override
+  public Query getCurrentQuery() {
+    return this.query;
+  }
+
+  @Override
+  public Query parseQuery(@Nonnull String expression, @Nonnull String defField, @Nonnull Analyzer analyzer,
+                          @Nonnull QueryParserConfig config, boolean rewrite) {
+
+    Query query = config.isUseClassicParser() ?
+        parseByClassicParser(expression, defField, analyzer, config) :
+        parseByStandardParser(expression, defField, analyzer, config);
+
+    if (rewrite) {
+      try {
+        query = query.rewrite(reader);
+      } catch (IOException e) {
+        throw new LukeException(String.format("Failed to rewrite query: %s", query.toString()), e);
+      }
+    }
+
+    return query;
+  }
+
+  private Query parseByClassicParser(@Nonnull String expression, @Nonnull String defField, @Nonnull Analyzer analyzer,
+                                     @Nonnull QueryParserConfig config) {
+    QueryParser parser = new QueryParser(defField, analyzer);
+
+    switch (config.getDefaultOperator()) {
+      case OR:
+        parser.setDefaultOperator(QueryParser.Operator.OR);
+        break;
+      case AND:
+        parser.setDefaultOperator(QueryParser.Operator.AND);
+        break;
+    }
+
+    parser.setAutoGenerateMultiTermSynonymsPhraseQuery(config.isAutoGenerateMultiTermSynonymsPhraseQuery());
+    parser.setAutoGeneratePhraseQueries(config.isAutoGeneratePhraseQueries());
+    parser.setEnablePositionIncrements(config.isEnablePositionIncrements());
+    parser.setAllowLeadingWildcard(config.isAllowLeadingWildcard());
+    parser.setSplitOnWhitespace(config.isSplitOnWhitespace());
+    parser.setDateResolution(config.getDateResolution());
+    parser.setFuzzyMinSim(config.getFuzzyMinSim());
+    parser.setFuzzyPrefixLength(config.getFuzzyPrefixLength());
+    parser.setLocale(config.getLocale());
+    parser.setTimeZone(config.getTimeZone());
+    parser.setPhraseSlop(config.getPhraseSlop());
+
+    try {
+      return parser.parse(expression);
+    } catch (ParseException e) {
+      throw new LukeException(String.format("Failed to parse query expression: %s", expression), e);
+    }
+
+  }
+
+  private Query parseByStandardParser(@Nonnull String expression, @Nonnull String defField, @Nonnull Analyzer analyzer,
+                                      @Nonnull QueryParserConfig config) {
+    StandardQueryParser parser = new StandardQueryParser(analyzer);
+
+    switch (config.getDefaultOperator()) {
+      case OR:
+        parser.setDefaultOperator(StandardQueryConfigHandler.Operator.OR);
+        break;
+      case AND:
+        parser.setDefaultOperator(StandardQueryConfigHandler.Operator.AND);
+        break;
+    }
+
+    parser.setEnablePositionIncrements(config.isEnablePositionIncrements());
+    parser.setAllowLeadingWildcard(config.isAllowLeadingWildcard());
+    parser.setDateResolution(config.getDateResolution());
+    parser.setFuzzyMinSim(config.getFuzzyMinSim());
+    parser.setFuzzyPrefixLength(config.getFuzzyPrefixLength());
+    parser.setLocale(config.getLocale());
+    parser.setTimeZone(config.getTimeZone());
+    parser.setPhraseSlop(config.getPhraseSlop());
+
+    if (config.getTypeMap() != null) {
+      Map<String, PointsConfig> pointsConfigMap = new HashMap<>();
+
+      for (Map.Entry<String, Class<? extends Number>> entry : config.getTypeMap().entrySet()) {
+        String field = entry.getKey();
+        Class<? extends Number> type = entry.getValue();
+        PointsConfig pc;
+        if (type == Integer.class || type == Long.class) {
+          pc = new PointsConfig(NumberFormat.getIntegerInstance(Locale.ROOT), type);
+        } else if (type == Float.class || type == Double.class) {
+          pc = new PointsConfig(NumberFormat.getNumberInstance(Locale.ROOT), type);
+        } else {
+          logger.warn(String.format("Ignored invalid number type: %s.", type.getName()));
+          continue;
+        }
+        pointsConfigMap.put(field, pc);
+      }
+
+      parser.setPointsConfigMap(pointsConfigMap);
+    }
+
+    try {
+      return parser.parse(expression, defField);
+    } catch (QueryNodeException e) {
+      throw new LukeException(String.format("Failed to parse query expression: %s", expression), e);
+    }
+
+  }
+
+  @Override
+  public Query mltQuery(int docid, MLTConfig mltConfig, Analyzer analyzer) {
+    MoreLikeThis mlt = new MoreLikeThis(reader);
+
+    mlt.setAnalyzer(analyzer);
+    mlt.setFieldNames(mltConfig.getFieldNames());
+    mlt.setMinDocFreq(mltConfig.getMinDocFreq());
+    mlt.setMaxDocFreq(mltConfig.getMaxDocFreq());
+    mlt.setMinTermFreq(mltConfig.getMinTermFreq());
+
+    try {
+      return mlt.like(docid);
+    } catch (IOException e) {
+      throw new LukeException("Failed to create MLT query for doc: " + docid);
+    }
+  }
+
+  @Override
+  public SearchResults search(
+      @Nonnull Query query, @Nonnull SimilarityConfig simConfig, @Nullable Set<String> fieldsToLoad, int pageSize) {
+    return search(query, simConfig, null, fieldsToLoad, pageSize);
+  }
+
+  @Override
+  public SearchResults search(
+      @Nonnull Query query, @Nonnull SimilarityConfig simConfig, @Nullable Sort sort, @Nullable Set<String> fieldsToLoad, int pageSize) {
+    if (pageSize < 0) {
+      throw new LukeException(new IllegalArgumentException("Negative integer is not acceptable for page size."));
+    }
+
+    // reset internal status to prepare for a new search session
+    this.docs = new ScoreDoc[0];
+    this.currentPage = 0;
+    this.pageSize = pageSize;
+    this.query = query;
+    this.sort = sort;
+    this.fieldsToLoad = fieldsToLoad == null ? null : ImmutableSet.copyOf(fieldsToLoad);
+    searcher.setSimilarity(createSimilarity(simConfig));
+
+    try {
+      return search();
+    } catch (IOException e) {
+      throw new LukeException("Search Failed.", e);
+    }
+  }
+
+  private SearchResults search() throws IOException {
+    // execute search
+    ScoreDoc after = docs.length == 0 ? null : docs[docs.length - 1];
+    TopDocs topDocs = sort == null ?
+        searcher.searchAfter(after, query, pageSize) :
+        searcher.searchAfter(after, query, pageSize, sort);
+
+    // reset total hits for the current query
+    this.totalHits = topDocs.totalHits;
+
+    // cache search results for later use
+    ScoreDoc[] newDocs = new ScoreDoc[docs.length + topDocs.scoreDocs.length];
+    System.arraycopy(docs, 0, newDocs, 0, docs.length);
+    System.arraycopy(topDocs.scoreDocs, 0, newDocs, docs.length, topDocs.scoreDocs.length);
+    this.docs = newDocs;
+
+    return SearchResults.of(topDocs.totalHits, topDocs.scoreDocs, currentPage * pageSize, searcher, fieldsToLoad);
+  }
+
+  @Override
+  public Optional<SearchResults> nextPage() {
+    if (currentPage < 0 || query == null) {
+      throw new LukeException(new IllegalStateException("Search session not started."));
+    }
+
+    // proceed to next page
+    currentPage += 1;
+
+    if (totalHits == 0 || currentPage * pageSize >= totalHits) {
+      logger.warn("No more next search results are available.");
+      return Optional.empty();
+    }
+
+    try {
+
+      if (currentPage * pageSize < docs.length) {
+        // if cached results exist, return that.
+        int from = currentPage * pageSize;
+        int to = Math.min(from + pageSize, docs.length);
+        ScoreDoc[] part = Arrays.copyOfRange(docs, from, to);
+        return Optional.of(SearchResults.of(totalHits, part, from, searcher, fieldsToLoad));
+      } else {
+        return Optional.of(search());
+      }
+
+    } catch (IOException e) {
+      throw new LukeException("Search Failed.", e);
+    }
+  }
+
+
+  @Override
+  public Optional<SearchResults> prevPage() {
+    if (currentPage < 0 || query == null) {
+      throw new LukeException(new IllegalStateException("Search session not started."));
+    }
+
+    // return to previous page
+    currentPage -= 1;
+
+    if (currentPage < 0) {
+      logger.warn("No more previous search results are available.");
+      return Optional.empty();
+    }
+
+    try {
+      // there should be cached results for this page
+      int from = currentPage * pageSize;
+      int to = Math.min(from + pageSize, docs.length);
+      ScoreDoc[] part = Arrays.copyOfRange(docs, from, to);
+      return Optional.of(SearchResults.of(totalHits, part, from, searcher, fieldsToLoad));
+    } catch (IOException e) {
+      throw new LukeException("Search Failed.", e);
+    }
+  }
+
+  private Similarity createSimilarity(@Nonnull SimilarityConfig config) {
+    Similarity similarity;
+
+    if (config.isUseClassicSimilarity()) {
+      ClassicSimilarity tfidf = new ClassicSimilarity();
+      tfidf.setDiscountOverlaps(config.isDiscountOverlaps());
+      similarity = tfidf;
+    } else {
+      BM25Similarity bm25 = new BM25Similarity(config.getK1(), config.getB());
+      bm25.setDiscountOverlaps(config.isDiscountOverlaps());
+      similarity = bm25;
+    }
+
+    return similarity;
+  }
+
+  @Override
+  public List<SortField> guessSortTypes(String name) {
+    FieldInfo finfo = IndexUtils.getFieldInfo(reader, name);
+    if (finfo == null) {
+      throw new LukeException("No such field: " + name, new IllegalArgumentException());
+    }
+
+    DocValuesType dvType = finfo.getDocValuesType();
+
+    switch (dvType) {
+      case NONE:
+        return Collections.emptyList();
+
+      case NUMERIC:
+        return Lists.newArrayList(
+            new SortField(name, SortField.Type.INT),
+            new SortField(name, SortField.Type.LONG),
+            new SortField(name, SortField.Type.FLOAT),
+            new SortField(name, SortField.Type.DOUBLE));
+
+      case SORTED_NUMERIC:
+        return Lists.newArrayList(
+            new SortedNumericSortField(name, SortField.Type.INT),
+            new SortedNumericSortField(name, SortField.Type.LONG),
+            new SortedNumericSortField(name, SortField.Type.FLOAT),
+            new SortedNumericSortField(name, SortField.Type.DOUBLE));
+
+      case SORTED:
+        return Lists.newArrayList(
+            new SortField(name, SortField.Type.STRING),
+            new SortField(name, SortField.Type.STRING_VAL));
+
+      case SORTED_SET:
+        return Collections.singletonList(new SortedSetSortField(name, false));
+
+      default:
+        return Collections.singletonList(new SortField(name, SortField.Type.DOC));
+    }
+
+  }
+
+  @Override
+  public Optional<SortField> getSortType(@Nonnull String name, @Nonnull String type, boolean reverse) {
+    List<SortField> candidates = guessSortTypes(name);
+    if (candidates.isEmpty()) {
+      logger.warn(String.format("No available sort types for: %s", name));
+      return Optional.empty();
+    }
+
+    // TODO should be refactored...
+    for (SortField sf : candidates) {
+      if (sf instanceof SortedSetSortField) {
+        return Optional.of(new SortedSetSortField(sf.getField(), reverse));
+      } else if (sf instanceof SortedNumericSortField) {
+        SortField.Type sfType = ((SortedNumericSortField) sf).getNumericType();
+        if (sfType.name().equals(type)) {
+          return Optional.of(new SortedNumericSortField(sf.getField(), sfType, reverse));
+        }
+      } else {
+        SortField.Type sfType = sf.getType();
+        if (sfType.name().equals(type)) {
+          return Optional.of(new SortField(sf.getField(), sfType, reverse));
+        }
+      }
+    }
+    return Optional.empty();
+  }
+
+  @Override
+  public Explanation explain(Query query, int docid) {
+    try {
+      return searcher.explain(query, docid);
+    } catch (IOException e) {
+      throw new LukeException(String.format("Failed to create explanation for doc: %d for query: \"%s\"", docid, query.toString()), e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java
new file mode 100644
index 0000000..dc262cc
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.ScoreDoc;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Holder for a search result page.
+ */
+public final class SearchResults {
+
+  private long totalHits = 0;
+
+  private int offset = 0;
+
+  private List<Doc> hits = new ArrayList<>();
+
+  /**
+   * Creates a search result page for the given raw Lucene hits.
+   *
+   * @param totalHits - total number of hits for this query
+   * @param docs - array of hits
+   * @param offset - offset of the current page
+   * @param searcher - index searcher
+   * @param fieldsToLoad - fields to load
+   * @return the search result page
+   * @throws IOException
+   */
+  static SearchResults of(long totalHits, @Nonnull ScoreDoc[] docs, int offset,
+                          @Nonnull IndexSearcher searcher, Set<String> fieldsToLoad)
+      throws IOException {
+    SearchResults res = new SearchResults();
+
+    res.totalHits = totalHits;
+
+    for (ScoreDoc sd : docs) {
+      Document luceneDoc = (fieldsToLoad == null) ?
+          searcher.doc(sd.doc) : searcher.doc(sd.doc, fieldsToLoad);
+      res.hits.add(Doc.of(sd.doc, sd.score, luceneDoc));
+      res.offset = offset;
+    }
+
+    return res;
+  }
+
+  /**
+   * Returns the total number of hits for this query.
+   */
+  public long getTotalHits() {
+    return totalHits;
+  }
+
+  /**
+   * Returns the offset of the current page.
+   */
+  public int getOffset() {
+    return offset;
+  }
+
+  /**
+   * Returns the documents of the current page.
+   */
+  public List<Doc> getHits() {
+    return ImmutableList.copyOf(hits);
+  }
+
+  /**
+   * Returns the size of the current page.
+   */
+  public int size() {
+    return hits.size();
+  }
+
+  private SearchResults() {
+  }
+
+  /**
+   * Holder for a hit.
+   */
+  public static class Doc {
+    private int docId;
+    private float score;
+    private Map<String, String[]> fieldValues = new HashMap<>();
+
+    /**
+     * Creates a hit.
+     *
+     * @param docId - document id
+     * @param score - score of this document for the query
+     * @param luceneDoc - raw Lucene document
+     * @return the hit
+     */
+    static Doc of(int docId, float score, @Nonnull Document luceneDoc) {
+      Doc doc = new Doc();
+      doc.docId = docId;
+      doc.score = score;
+      Set<String> fields = luceneDoc.getFields().stream().map(IndexableField::name).collect(Collectors.toSet());
+      for (String f : fields) {
+        doc.fieldValues.put(f, luceneDoc.getValues(f));
+      }
+      return doc;
+    }
+
+    /**
+     * Returns the document id.
+     */
+    public int getDocId() {
+      return docId;
+    }
+
+    /**
+     * Returns the score of this document for the current query.
+     */
+    public float getScore() {
+      return score;
+    }
+
+    /**
+     * Returns the field data of this document.
+     */
+    public Map<String, String[]> getFieldValues() {
+      return ImmutableMap.copyOf(fieldValues);
+    }
+
+    private Doc() {
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java
new file mode 100644
index 0000000..0487f55
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.search;
+
+/**
+ * Configurations for Similarity.
+ */
+public final class SimilarityConfig {
+
+  private final boolean useClassicSimilarity;
+
+  /* BM25Similarity parameters */
+
+  private final float k1;
+
+  private final float b;
+
+  /* Common parameters */
+
+  private final boolean discountOverlaps;
+
+  public static class Builder {
+    private boolean useClassicSimilarity = false;
+    private float k1 = 1.2f;
+    private float b = 0.75f;
+    private boolean discountOverlaps = true;
+
+    public Builder useClassicSimilarity(boolean val) {
+      useClassicSimilarity = val;
+      return this;
+    }
+
+    public Builder k1(float val) {
+      k1 = val;
+      return this;
+    }
+
+    public Builder b(float val) {
+      b = val;
+      return this;
+    }
+
+    public Builder discountOverlaps (boolean val) {
+      discountOverlaps = val;
+      return this;
+    }
+
+    public SimilarityConfig build() {
+      return new SimilarityConfig(this);
+    }
+  }
+
+  private SimilarityConfig(Builder builder) {
+    this.useClassicSimilarity = builder.useClassicSimilarity;
+    this.k1 = builder.k1;
+    this.b = builder.b;
+    this.discountOverlaps = builder.discountOverlaps;
+  }
+
+  public boolean isUseClassicSimilarity() {
+    return useClassicSimilarity;
+  }
+
+  public float getK1() {
+    return k1;
+  }
+
+  public float getB() {
+    return b;
+  }
+
+  public boolean isDiscountOverlaps() {
+    return discountOverlaps;
+  }
+
+  public String toString() {
+    return "SimilarityConfig: [" +
+        String.format(" use classic similarity=%s;", useClassicSimilarity) +
+        String.format(" discount overlaps=%s;", discountOverlaps) +
+        String.format(" k1=%f;", k1) +
+        String.format(" b=%f;", b) +
+        "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java
new file mode 100644
index 0000000..f94d584
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.tools;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.CheckIndex;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.search.Query;
+
+import java.io.PrintStream;
+import java.util.Collection;
+
+/**
+ * A dedicated interface for Luke's various index manipulations.
+ */
+public interface IndexTools {
+
+  /**
+   * Execute force merges.
+   *
+   * <p>
+   * Merges are executed until there are <i>maxNumSegments</i> segments. <br>
+   * When <i>expunge</i> is true, <i>maxNumSegments</i> parameter is ignored.
+   * </p>
+   *
+   * @param expunge - if true, only segments having deleted documents are merged
+   * @param maxNumSegments - max number of segments
+   * @param ps - information stream
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  void optimize(boolean expunge, int maxNumSegments, PrintStream ps);
+
+  /**
+   * Check the current index status.
+   *
+   * @param ps information stream
+   * @return index status
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  CheckIndex.Status checkIndex(PrintStream ps);
+
+  /**
+   * Try to repair the corrupted index using previously returned index status.
+   *
+   * <p>This method must be called with the return value from {@link IndexTools#checkIndex(PrintStream)}.</p>
+   *
+   * @param st - index status
+   * @param ps - information stream
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  void repairIndex(CheckIndex.Status st, PrintStream ps);
+
+  /**
+   * Add new document to this index.
+   *
+   * @param doc - document to be added
+   * @param analyzer - analyzer for parsing to document
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  void addDocument(Document doc, Analyzer analyzer);
+
+  /**
+   * Delete documents from this index by the specified query.
+   *
+   * @param query - query for deleting
+   * @throws LukeException - if an internal error occurs when accessing index
+   */
+  void deleteDocuments(Query query);
+
+  /**
+   * Returns preset {@link Field} classes.
+   */
+  Collection<Class<? extends Field>> getPresetFields();
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/dc72c4a0/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java
----------------------------------------------------------------------
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java
new file mode 100644
index 0000000..657a47a
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.luke.models.tools;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.store.Directory;
+
+public class IndexToolsFactory {
+
+  public IndexTools newInstance(Directory dir) {
+    return new IndexToolsImpl(dir, false, false);
+  }
+
+  public IndexTools newInstance(IndexReader reader, boolean useCompound, boolean keepAllCommits) {
+    return new IndexToolsImpl(reader, useCompound, keepAllCommits);
+  }
+
+}


Mime
View raw message