/* * 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.codecs.perfield; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.lucene.codecs.FieldsProducer; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.FieldInfos; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.MergeState; import org.apache.lucene.index.Terms; /** * Utility class to update the {@link MergeState} instance to be restricted to a set of fields. * <p> * Warning: the input {@linkplain MergeState} instance will be updated when calling {@link #apply(Collection)}. * <p> * It should be called within a {@code try {...} finally {...}} block to make sure that the mergeState instance is * restored to its original state: * <pre> * PerFieldMergeState pfMergeState = new PerFieldMergeState(mergeState); * try { * doSomething(pfMergeState.apply(fields)); * ... * } finally { * pfMergeState.reset(); * } * </pre> */ final class PerFieldMergeState { private final MergeState in; private final FieldInfos orgMergeFieldInfos; private final FieldInfos[] orgFieldInfos; private final FieldsProducer[] orgFieldsProducers; PerFieldMergeState(MergeState in) { this.in = in; this.orgMergeFieldInfos = in.mergeFieldInfos; this.orgFieldInfos = new FieldInfos[in.fieldInfos.length]; this.orgFieldsProducers = new FieldsProducer[in.fieldsProducers.length]; System.arraycopy(in.fieldInfos, 0, this.orgFieldInfos, 0, this.orgFieldInfos.length); System.arraycopy(in.fieldsProducers, 0, this.orgFieldsProducers, 0, this.orgFieldsProducers.length); } /** * Update the input {@link MergeState} instance to restrict the fields to the given ones. * * @param fields The fields to keep in the updated instance. * @return The updated instance. */ MergeState apply(Collection<String> fields) { in.mergeFieldInfos = new FilterFieldInfos(orgMergeFieldInfos, fields); for (int i = 0; i < orgFieldInfos.length; i++) { in.fieldInfos[i] = new FilterFieldInfos(orgFieldInfos[i], fields); } for (int i = 0; i < orgFieldsProducers.length; i++) { in.fieldsProducers[i] = new FilterFieldsProducer(orgFieldsProducers[i], fields); } return in; } /** * Resets the input {@link MergeState} instance to its original state. * * @return The reset instance. */ MergeState reset() { in.mergeFieldInfos = orgMergeFieldInfos; System.arraycopy(orgFieldInfos, 0, in.fieldInfos, 0, in.fieldInfos.length); System.arraycopy(orgFieldsProducers, 0, in.fieldsProducers, 0, in.fieldsProducers.length); return in; } private static class FilterFieldInfos extends FieldInfos { private final Set<String> filteredNames; private final List<FieldInfo> filtered; // Copy of the private fields from FieldInfos // Renamed so as to be less confusing about which fields we're referring to private final boolean filteredHasVectors; private final boolean filteredHasProx; private final boolean filteredHasPayloads; private final boolean filteredHasOffsets; private final boolean filteredHasFreq; private final boolean filteredHasNorms; private final boolean filteredHasDocValues; private final boolean filteredHasPointValues; FilterFieldInfos(FieldInfos src, Collection<String> filterFields) { // Copy all the input FieldInfo objects since the field numbering must be kept consistent super(toArray(src)); boolean hasVectors = false; boolean hasProx = false; boolean hasPayloads = false; boolean hasOffsets = false; boolean hasFreq = false; boolean hasNorms = false; boolean hasDocValues = false; boolean hasPointValues = false; this.filteredNames = new HashSet<>(filterFields); this.filtered = new ArrayList<>(filterFields.size()); for (FieldInfo fi : src) { if (filterFields.contains(fi.name)) { this.filtered.add(fi); hasVectors |= fi.hasVectors(); hasProx |= fi.getIndexOptions().compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) >= 0; hasFreq |= fi.getIndexOptions() != IndexOptions.DOCS; hasOffsets |= fi.getIndexOptions().compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0; hasNorms |= fi.hasNorms(); hasDocValues |= fi.getDocValuesType() != DocValuesType.NONE; hasPayloads |= fi.hasPayloads(); hasPointValues |= (fi.getPointDimensionCount() != 0); } } this.filteredHasVectors = hasVectors; this.filteredHasProx = hasProx; this.filteredHasPayloads = hasPayloads; this.filteredHasOffsets = hasOffsets; this.filteredHasFreq = hasFreq; this.filteredHasNorms = hasNorms; this.filteredHasDocValues = hasDocValues; this.filteredHasPointValues = hasPointValues; } private static FieldInfo[] toArray(FieldInfos src) { FieldInfo[] res = new FieldInfo[src.size()]; int i = 0; for (FieldInfo fi : src) { res[i++] = fi; } return res; } @Override public Iterator<FieldInfo> iterator() { return filtered.iterator(); } @Override public boolean hasFreq() { return filteredHasFreq; } @Override public boolean hasProx() { return filteredHasProx; } @Override public boolean hasPayloads() { return filteredHasPayloads; } @Override public boolean hasOffsets() { return filteredHasOffsets; } @Override public boolean hasVectors() { return filteredHasVectors; } @Override public boolean hasNorms() { return filteredHasNorms; } @Override public boolean hasDocValues() { return filteredHasDocValues; } @Override public boolean hasPointValues() { return filteredHasPointValues; } @Override public int size() { return filtered.size(); } @Override public FieldInfo fieldInfo(String fieldName) { if (!filteredNames.contains(fieldName)) { // Throw IAE to be consistent with fieldInfo(int) which throws it as well on invalid numbers throw new IllegalArgumentException("The field named '" + fieldName + "' is not accessible in the current " + "merge context, available ones are: " + filteredNames); } return super.fieldInfo(fieldName); } @Override public FieldInfo fieldInfo(int fieldNumber) { FieldInfo res = super.fieldInfo(fieldNumber); if (!filteredNames.contains(res.name)) { throw new IllegalArgumentException("The field named '" + res.name + "' numbered '" + fieldNumber + "' is not " + "accessible in the current merge context, available ones are: " + filteredNames); } return res; } } private static class FilterFieldsProducer extends FieldsProducer { private final FieldsProducer in; private final List<String> filtered; FilterFieldsProducer(FieldsProducer in, Collection<String> filterFields) { this.in = in; this.filtered = new ArrayList<>(filterFields); } @Override public long ramBytesUsed() { return in.ramBytesUsed(); } @Override public Iterator<String> iterator() { return filtered.iterator(); } @Override public Terms terms(String field) throws IOException { if (!filtered.contains(field)) { throw new IllegalArgumentException("The field named '" + field + "' is not accessible in the current " + "merge context, available ones are: " + filtered); } return in.terms(field); } @Override public int size() { return filtered.size(); } @Override public void close() throws IOException { in.close(); } @Override public void checkIntegrity() throws IOException { in.checkIntegrity(); } } }