/*
 * 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.index;


import java.io.IOException;
import java.util.List;

import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.packed.PackedInts;

A wrapper for CompositeIndexReader providing access to DocValues.

NOTE: for multi readers, you'll get better performance by gathering the sub readers using IndexReader.getContext() to get the atomic leaves and then operate per-LeafReader, instead of using this class.

NOTE: This is very costly.

@lucene.experimental
@lucene.internal
/** * A wrapper for CompositeIndexReader providing access to DocValues. * * <p><b>NOTE</b>: for multi readers, you'll get better * performance by gathering the sub readers using * {@link IndexReader#getContext()} to get the * atomic leaves and then operate per-LeafReader, * instead of using this class. * * <p><b>NOTE</b>: This is very costly. * * @lucene.experimental * @lucene.internal */
public class MultiDocValues {
No instantiation
/** No instantiation */
private MultiDocValues() {}
Returns a NumericDocValues for a reader's norms (potentially merging on-the-fly).

This is a slow way to access normalization values. Instead, access them per-segment with LeafReader.getNormValues(String)

/** Returns a NumericDocValues for a reader's norms (potentially merging on-the-fly). * <p> * This is a slow way to access normalization values. Instead, access them per-segment * with {@link LeafReader#getNormValues(String)} * </p> */
public static NumericDocValues getNormValues(final IndexReader r, final String field) throws IOException { final List<LeafReaderContext> leaves = r.leaves(); final int size = leaves.size(); if (size == 0) { return null; } else if (size == 1) { return leaves.get(0).reader().getNormValues(field); } FieldInfo fi = FieldInfos.getMergedFieldInfos(r).fieldInfo(field); //TODO avoid merging if (fi == null || fi.hasNorms() == false) { return null; } return new NumericDocValues() { private int nextLeaf; private NumericDocValues currentValues; private LeafReaderContext currentLeaf; private int docID = -1; @Override public int nextDoc() throws IOException { while (true) { if (currentValues == null) { if (nextLeaf == leaves.size()) { docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(nextLeaf); currentValues = currentLeaf.reader().getNormValues(field); nextLeaf++; continue; } int newDocID = currentValues.nextDoc(); if (newDocID == NO_MORE_DOCS) { currentValues = null; continue; } else { docID = currentLeaf.docBase + newDocID; return docID; } } } @Override public int docID() { return docID; } @Override public int advance(int targetDocID) throws IOException { if (targetDocID <= docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { currentValues = null; docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(readerIndex); currentValues = currentLeaf.reader().getNormValues(field); if (currentValues == null) { return nextDoc(); } nextLeaf = readerIndex+1; } int newDocID = currentValues.advance(targetDocID - currentLeaf.docBase); if (newDocID == NO_MORE_DOCS) { currentValues = null; return nextDoc(); } else { docID = currentLeaf.docBase + newDocID; return docID; } } @Override public boolean advanceExact(int targetDocID) throws IOException { if (targetDocID < docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { throw new IllegalArgumentException("Out of range: " + targetDocID); } currentLeaf = leaves.get(readerIndex); currentValues = currentLeaf.reader().getNormValues(field); nextLeaf = readerIndex+1; } docID = targetDocID; if (currentValues == null) { return false; } return currentValues.advanceExact(targetDocID - currentLeaf.docBase); } @Override public long longValue() throws IOException { return currentValues.longValue(); } @Override public long cost() { // TODO return 0; } }; }
Returns a NumericDocValues for a reader's docvalues (potentially merging on-the-fly)
/** Returns a NumericDocValues for a reader's docvalues (potentially merging on-the-fly) */
public static NumericDocValues getNumericValues(final IndexReader r, final String field) throws IOException { final List<LeafReaderContext> leaves = r.leaves(); final int size = leaves.size(); if (size == 0) { return null; } else if (size == 1) { return leaves.get(0).reader().getNumericDocValues(field); } boolean anyReal = false; for(LeafReaderContext leaf : leaves) { FieldInfo fieldInfo = leaf.reader().getFieldInfos().fieldInfo(field); if (fieldInfo != null) { DocValuesType dvType = fieldInfo.getDocValuesType(); if (dvType == DocValuesType.NUMERIC) { anyReal = true; break; } } } if (anyReal == false) { return null; } return new NumericDocValues() { private int nextLeaf; private NumericDocValues currentValues; private LeafReaderContext currentLeaf; private int docID = -1; @Override public int docID() { return docID; } @Override public int nextDoc() throws IOException { while (true) { while (currentValues == null) { if (nextLeaf == leaves.size()) { docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(nextLeaf); currentValues = currentLeaf.reader().getNumericDocValues(field); nextLeaf++; } int newDocID = currentValues.nextDoc(); if (newDocID == NO_MORE_DOCS) { currentValues = null; continue; } else { docID = currentLeaf.docBase + newDocID; return docID; } } } @Override public int advance(int targetDocID) throws IOException { if (targetDocID <= docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { currentValues = null; docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(readerIndex); currentValues = currentLeaf.reader().getNumericDocValues(field); nextLeaf = readerIndex+1; if (currentValues == null) { return nextDoc(); } } int newDocID = currentValues.advance(targetDocID - currentLeaf.docBase); if (newDocID == NO_MORE_DOCS) { currentValues = null; return nextDoc(); } else { docID = currentLeaf.docBase + newDocID; return docID; } } @Override public boolean advanceExact(int targetDocID) throws IOException { if (targetDocID < docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { throw new IllegalArgumentException("Out of range: " + targetDocID); } currentLeaf = leaves.get(readerIndex); currentValues = currentLeaf.reader().getNumericDocValues(field); nextLeaf = readerIndex+1; } docID = targetDocID; if (currentValues == null) { return false; } return currentValues.advanceExact(targetDocID - currentLeaf.docBase); } @Override public long longValue() throws IOException { return currentValues.longValue(); } @Override public long cost() { // TODO return 0; } }; }
Returns a BinaryDocValues for a reader's docvalues (potentially merging on-the-fly)
/** Returns a BinaryDocValues for a reader's docvalues (potentially merging on-the-fly) */
public static BinaryDocValues getBinaryValues(final IndexReader r, final String field) throws IOException { final List<LeafReaderContext> leaves = r.leaves(); final int size = leaves.size(); if (size == 0) { return null; } else if (size == 1) { return leaves.get(0).reader().getBinaryDocValues(field); } boolean anyReal = false; for(LeafReaderContext leaf : leaves) { FieldInfo fieldInfo = leaf.reader().getFieldInfos().fieldInfo(field); if (fieldInfo != null) { DocValuesType dvType = fieldInfo.getDocValuesType(); if (dvType == DocValuesType.BINARY) { anyReal = true; break; } } } if (anyReal == false) { return null; } return new BinaryDocValues() { private int nextLeaf; private BinaryDocValues currentValues; private LeafReaderContext currentLeaf; private int docID = -1; @Override public int nextDoc() throws IOException { while (true) { while (currentValues == null) { if (nextLeaf == leaves.size()) { docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(nextLeaf); currentValues = currentLeaf.reader().getBinaryDocValues(field); nextLeaf++; } int newDocID = currentValues.nextDoc(); if (newDocID == NO_MORE_DOCS) { currentValues = null; continue; } else { docID = currentLeaf.docBase + newDocID; return docID; } } } @Override public int docID() { return docID; } @Override public int advance(int targetDocID) throws IOException { if (targetDocID <= docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { currentValues = null; docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(readerIndex); currentValues = currentLeaf.reader().getBinaryDocValues(field); nextLeaf = readerIndex+1; if (currentValues == null) { return nextDoc(); } } int newDocID = currentValues.advance(targetDocID - currentLeaf.docBase); if (newDocID == NO_MORE_DOCS) { currentValues = null; return nextDoc(); } else { docID = currentLeaf.docBase + newDocID; return docID; } } @Override public boolean advanceExact(int targetDocID) throws IOException { if (targetDocID < docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { throw new IllegalArgumentException("Out of range: " + targetDocID); } currentLeaf = leaves.get(readerIndex); currentValues = currentLeaf.reader().getBinaryDocValues(field); nextLeaf = readerIndex+1; } docID = targetDocID; if (currentValues == null) { return false; } return currentValues.advanceExact(targetDocID - currentLeaf.docBase); } @Override public BytesRef binaryValue() throws IOException { return currentValues.binaryValue(); } @Override public long cost() { // TODO return 0; } }; }
Returns a SortedNumericDocValues for a reader's docvalues (potentially merging on-the-fly)

This is a slow way to access sorted numeric values. Instead, access them per-segment with LeafReader.getSortedNumericDocValues(String)

/** Returns a SortedNumericDocValues for a reader's docvalues (potentially merging on-the-fly) * <p> * This is a slow way to access sorted numeric values. Instead, access them per-segment * with {@link LeafReader#getSortedNumericDocValues(String)} * </p> * */
public static SortedNumericDocValues getSortedNumericValues(final IndexReader r, final String field) throws IOException { final List<LeafReaderContext> leaves = r.leaves(); final int size = leaves.size(); if (size == 0) { return null; } else if (size == 1) { return leaves.get(0).reader().getSortedNumericDocValues(field); } boolean anyReal = false; final SortedNumericDocValues[] values = new SortedNumericDocValues[size]; final int[] starts = new int[size+1]; long totalCost = 0; for (int i = 0; i < size; i++) { LeafReaderContext context = leaves.get(i); SortedNumericDocValues v = context.reader().getSortedNumericDocValues(field); if (v == null) { v = DocValues.emptySortedNumeric(context.reader().maxDoc()); } else { anyReal = true; } values[i] = v; starts[i] = context.docBase; totalCost += v.cost(); } starts[size] = r.maxDoc(); if (anyReal == false) { return null; } final long finalTotalCost = totalCost; return new SortedNumericDocValues() { private int nextLeaf; private SortedNumericDocValues currentValues; private LeafReaderContext currentLeaf; private int docID = -1; @Override public int nextDoc() throws IOException { while (true) { if (currentValues == null) { if (nextLeaf == leaves.size()) { docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(nextLeaf); currentValues = values[nextLeaf]; nextLeaf++; } int newDocID = currentValues.nextDoc(); if (newDocID == NO_MORE_DOCS) { currentValues = null; continue; } else { docID = currentLeaf.docBase + newDocID; return docID; } } } @Override public int docID() { return docID; } @Override public int advance(int targetDocID) throws IOException { if (targetDocID <= docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { currentValues = null; docID = NO_MORE_DOCS; return docID; } currentLeaf = leaves.get(readerIndex); currentValues = values[readerIndex]; nextLeaf = readerIndex+1; } int newDocID = currentValues.advance(targetDocID - currentLeaf.docBase); if (newDocID == NO_MORE_DOCS) { currentValues = null; return nextDoc(); } else { docID = currentLeaf.docBase + newDocID; return docID; } } @Override public boolean advanceExact(int targetDocID) throws IOException { if (targetDocID < docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, leaves); if (readerIndex >= nextLeaf) { if (readerIndex == leaves.size()) { throw new IllegalArgumentException("Out of range: " + targetDocID); } currentLeaf = leaves.get(readerIndex); currentValues = values[readerIndex]; nextLeaf = readerIndex+1; } docID = targetDocID; if (currentValues == null) { return false; } return currentValues.advanceExact(targetDocID - currentLeaf.docBase); } @Override public long cost() { return finalTotalCost; } @Override public int docValueCount() { return currentValues.docValueCount(); } @Override public long nextValue() throws IOException { return currentValues.nextValue(); } }; }
Returns a SortedDocValues for a reader's docvalues (potentially doing extremely slow things).

This is an extremely slow way to access sorted values. Instead, access them per-segment with LeafReader.getSortedDocValues(String)

/** Returns a SortedDocValues for a reader's docvalues (potentially doing extremely slow things). * <p> * This is an extremely slow way to access sorted values. Instead, access them per-segment * with {@link LeafReader#getSortedDocValues(String)} * </p> */
public static SortedDocValues getSortedValues(final IndexReader r, final String field) throws IOException { final List<LeafReaderContext> leaves = r.leaves(); final int size = leaves.size(); if (size == 0) { return null; } else if (size == 1) { return leaves.get(0).reader().getSortedDocValues(field); } boolean anyReal = false; final SortedDocValues[] values = new SortedDocValues[size]; final int[] starts = new int[size+1]; long totalCost = 0; for (int i = 0; i < size; i++) { LeafReaderContext context = leaves.get(i); SortedDocValues v = context.reader().getSortedDocValues(field); if (v == null) { v = DocValues.emptySorted(); } else { anyReal = true; totalCost += v.cost(); } values[i] = v; starts[i] = context.docBase; } starts[size] = r.maxDoc(); if (anyReal == false) { return null; } else { IndexReader.CacheHelper cacheHelper = r.getReaderCacheHelper(); IndexReader.CacheKey owner = cacheHelper == null ? null : cacheHelper.getKey(); OrdinalMap mapping = OrdinalMap.build(owner, values, PackedInts.DEFAULT); return new MultiSortedDocValues(values, starts, mapping, totalCost); } }
Returns a SortedSetDocValues for a reader's docvalues (potentially doing extremely slow things).

This is an extremely slow way to access sorted values. Instead, access them per-segment with LeafReader.getSortedSetDocValues(String)

/** Returns a SortedSetDocValues for a reader's docvalues (potentially doing extremely slow things). * <p> * This is an extremely slow way to access sorted values. Instead, access them per-segment * with {@link LeafReader#getSortedSetDocValues(String)} * </p> */
public static SortedSetDocValues getSortedSetValues(final IndexReader r, final String field) throws IOException { final List<LeafReaderContext> leaves = r.leaves(); final int size = leaves.size(); if (size == 0) { return null; } else if (size == 1) { return leaves.get(0).reader().getSortedSetDocValues(field); } boolean anyReal = false; final SortedSetDocValues[] values = new SortedSetDocValues[size]; final int[] starts = new int[size+1]; long totalCost = 0; for (int i = 0; i < size; i++) { LeafReaderContext context = leaves.get(i); SortedSetDocValues v = context.reader().getSortedSetDocValues(field); if (v == null) { v = DocValues.emptySortedSet(); } else { anyReal = true; totalCost += v.cost(); } values[i] = v; starts[i] = context.docBase; } starts[size] = r.maxDoc(); if (anyReal == false) { return null; } else { IndexReader.CacheHelper cacheHelper = r.getReaderCacheHelper(); IndexReader.CacheKey owner = cacheHelper == null ? null : cacheHelper.getKey(); OrdinalMap mapping = OrdinalMap.build(owner, values, PackedInts.DEFAULT); return new MultiSortedSetDocValues(values, starts, mapping, totalCost); } }
Implements SortedDocValues over n subs, using an OrdinalMap
@lucene.internal
/** * Implements SortedDocValues over n subs, using an OrdinalMap * @lucene.internal */
public static class MultiSortedDocValues extends SortedDocValues {
docbase for each leaf: parallel with values
/** docbase for each leaf: parallel with {@link #values} */
public final int docStarts[];
leaf values
/** leaf values */
public final SortedDocValues values[];
ordinal map mapping ords from values to global ord space
/** ordinal map mapping ords from <code>values</code> to global ord space */
public final OrdinalMap mapping; private final long totalCost; private int nextLeaf; private SortedDocValues currentValues; private int currentDocStart; private int docID = -1;
Creates a new MultiSortedDocValues over values
/** Creates a new MultiSortedDocValues over <code>values</code> */
public MultiSortedDocValues(SortedDocValues values[], int docStarts[], OrdinalMap mapping, long totalCost) throws IOException { assert docStarts.length == values.length + 1; this.values = values; this.docStarts = docStarts; this.mapping = mapping; this.totalCost = totalCost; } @Override public int docID() { return docID; } @Override public int nextDoc() throws IOException { while (true) { while (currentValues == null) { if (nextLeaf == values.length) { docID = NO_MORE_DOCS; return docID; } currentDocStart = docStarts[nextLeaf]; currentValues = values[nextLeaf]; nextLeaf++; } int newDocID = currentValues.nextDoc(); if (newDocID == NO_MORE_DOCS) { currentValues = null; continue; } else { docID = currentDocStart + newDocID; return docID; } } } @Override public int advance(int targetDocID) throws IOException { if (targetDocID <= docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, docStarts); if (readerIndex >= nextLeaf) { if (readerIndex == values.length) { currentValues = null; docID = NO_MORE_DOCS; return docID; } currentDocStart = docStarts[readerIndex]; currentValues = values[readerIndex]; nextLeaf = readerIndex+1; } int newDocID = currentValues.advance(targetDocID - currentDocStart); if (newDocID == NO_MORE_DOCS) { currentValues = null; return nextDoc(); } else { docID = currentDocStart + newDocID; return docID; } } @Override public boolean advanceExact(int targetDocID) throws IOException { if (targetDocID < docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, docStarts); if (readerIndex >= nextLeaf) { if (readerIndex == values.length) { throw new IllegalArgumentException("Out of range: " + targetDocID); } currentDocStart = docStarts[readerIndex]; currentValues = values[readerIndex]; nextLeaf = readerIndex+1; } docID = targetDocID; if (currentValues == null) { return false; } return currentValues.advanceExact(targetDocID - currentDocStart); } @Override public int ordValue() throws IOException { return (int) mapping.getGlobalOrds(nextLeaf-1).get(currentValues.ordValue()); } @Override public BytesRef lookupOrd(int ord) throws IOException { int subIndex = mapping.getFirstSegmentNumber(ord); int segmentOrd = (int) mapping.getFirstSegmentOrd(ord); return values[subIndex].lookupOrd(segmentOrd); } @Override public int getValueCount() { return (int) mapping.getValueCount(); } @Override public long cost() { return totalCost; } }
Implements MultiSortedSetDocValues over n subs, using an OrdinalMap
@lucene.internal
/** * Implements MultiSortedSetDocValues over n subs, using an OrdinalMap * @lucene.internal */
public static class MultiSortedSetDocValues extends SortedSetDocValues {
docbase for each leaf: parallel with values
/** docbase for each leaf: parallel with {@link #values} */
public final int docStarts[];
leaf values
/** leaf values */
public final SortedSetDocValues values[];
ordinal map mapping ords from values to global ord space
/** ordinal map mapping ords from <code>values</code> to global ord space */
public final OrdinalMap mapping; private final long totalCost; private int nextLeaf; private SortedSetDocValues currentValues; private int currentDocStart; private int docID = -1;
Creates a new MultiSortedSetDocValues over values
/** Creates a new MultiSortedSetDocValues over <code>values</code> */
public MultiSortedSetDocValues(SortedSetDocValues values[], int docStarts[], OrdinalMap mapping, long totalCost) throws IOException { assert docStarts.length == values.length + 1; this.values = values; this.docStarts = docStarts; this.mapping = mapping; this.totalCost = totalCost; } @Override public int docID() { return docID; } @Override public int nextDoc() throws IOException { while (true) { while (currentValues == null) { if (nextLeaf == values.length) { docID = NO_MORE_DOCS; return docID; } currentDocStart = docStarts[nextLeaf]; currentValues = values[nextLeaf]; nextLeaf++; } int newDocID = currentValues.nextDoc(); if (newDocID == NO_MORE_DOCS) { currentValues = null; continue; } else { docID = currentDocStart + newDocID; return docID; } } } @Override public int advance(int targetDocID) throws IOException { if (targetDocID <= docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, docStarts); if (readerIndex >= nextLeaf) { if (readerIndex == values.length) { currentValues = null; docID = NO_MORE_DOCS; return docID; } currentDocStart = docStarts[readerIndex]; currentValues = values[readerIndex]; nextLeaf = readerIndex+1; } int newDocID = currentValues.advance(targetDocID - currentDocStart); if (newDocID == NO_MORE_DOCS) { currentValues = null; return nextDoc(); } else { docID = currentDocStart + newDocID; return docID; } } @Override public boolean advanceExact(int targetDocID) throws IOException { if (targetDocID < docID) { throw new IllegalArgumentException("can only advance beyond current document: on docID=" + docID + " but targetDocID=" + targetDocID); } int readerIndex = ReaderUtil.subIndex(targetDocID, docStarts); if (readerIndex >= nextLeaf) { if (readerIndex == values.length) { throw new IllegalArgumentException("Out of range: " + targetDocID); } currentDocStart = docStarts[readerIndex]; currentValues = values[readerIndex]; nextLeaf = readerIndex+1; } docID = targetDocID; if (currentValues == null) { return false; } return currentValues.advanceExact(targetDocID - currentDocStart); } @Override public long nextOrd() throws IOException { long segmentOrd = currentValues.nextOrd(); if (segmentOrd == NO_MORE_ORDS) { return segmentOrd; } else { return mapping.getGlobalOrds(nextLeaf-1).get(segmentOrd); } } @Override public BytesRef lookupOrd(long ord) throws IOException { int subIndex = mapping.getFirstSegmentNumber(ord); long segmentOrd = mapping.getFirstSegmentOrd(ord); return values[subIndex].lookupOrd(segmentOrd); } @Override public long getValueCount() { return mapping.getValueCount(); } @Override public long cost() { return totalCost; } } }