All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.search.facet.FacetBucket Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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.solr.search.facet;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.solr.common.util.CollectionUtil;
import org.apache.solr.common.util.SimpleOrderedMap;

public class FacetBucket {
  @SuppressWarnings("rawtypes")
  final FacetModule.FacetBucketMerger parent;

  @SuppressWarnings({"rawtypes"})
  final Comparable bucketValue;

  // this is just for internal correlation (the first bucket created is bucket 0,
  // the next bucket 1, across all field buckets)
  final int bucketNumber;

  long count;
  Map subs;

  public FacetBucket(
      @SuppressWarnings("rawtypes") FacetModule.FacetBucketMerger parent,
      @SuppressWarnings("rawtypes") Comparable bucketValue,
      FacetMerger.Context mcontext) {
    this.parent = parent;
    this.bucketValue = bucketValue;
    // TODO: we don't need bucket numbers for all buckets...
    this.bucketNumber = mcontext.getNewBucketNumber();
  }

  public long getCount() {
    return count;
  }

  /** returns the existing merger for the given key, or null if none yet exists */
  FacetMerger getExistingMerger(String key) {
    if (subs == null) return null;
    return subs.get(key);
  }

  private FacetMerger getMerger(String key, Object prototype) {
    FacetMerger merger = null;
    if (subs != null) {
      merger = subs.get(key);
      if (merger != null) return merger;
    }

    merger = parent.createFacetMerger(key, prototype);

    if (merger != null) {
      if (subs == null) {
        subs = new HashMap<>();
      }
      subs.put(key, merger);
    }

    return merger;
  }

  public void mergeBucket(SimpleOrderedMap bucket, FacetMerger.Context mcontext) {
    // todo: for refinements, we want to recurse, but not re-do stats for intermediate buckets

    mcontext.setShardFlag(bucketNumber);

    // drive merging off the received bucket?
    for (int i = 0; i < bucket.size(); i++) {
      String key = bucket.getName(i);
      Object val = bucket.getVal(i);
      if ("count".equals(key)) {
        count += ((Number) val).longValue();
        continue;
      }
      if ("val".equals(key)) {
        // this is taken care of at a higher level...
        continue;
      }

      FacetMerger merger = getMerger(key, val);

      if (merger != null) {
        merger.merge(val, mcontext);
      }
    }
  }

  public SimpleOrderedMap getMergedBucket() {
    SimpleOrderedMap out = new SimpleOrderedMap<>((subs == null ? 0 : subs.size()) + 2);
    if (bucketValue != null) {
      out.add("val", bucketValue);
    }
    out.add("count", count);
    if (subs != null) {
      for (Map.Entry mergerEntry : subs.entrySet()) {
        FacetMerger subMerger = mergerEntry.getValue();
        Object mergedResult = subMerger.getMergedResult();
        if (null != mergedResult) {
          out.add(mergerEntry.getKey(), mergedResult);
        }
      }
    }

    return out;
  }

  public Map getRefinement(
      FacetMerger.Context mcontext, Collection refineTags) {
    if (subs == null) {
      return null;
    }
    Map refinement = null;
    for (String tag : refineTags) {
      FacetMerger subMerger = subs.get(tag);
      if (subMerger != null) {
        Map subRef = subMerger.getRefinement(mcontext);
        if (subRef != null) {
          if (refinement == null) {
            refinement = CollectionUtil.newHashMap(refineTags.size());
          }
          refinement.put(tag, subRef);
        }
      }
    }
    return refinement;
  }

  public Map getRefinement2(
      FacetMerger.Context mcontext, Collection refineTags) {
    // TODO - partial results should turn off refining!!!

    boolean parentMissing = mcontext.bucketWasMissing();

    // TODO: this is a redundant check for many types of facets... only do on field faceting
    if (!parentMissing) {
      // if parent bucket wasn't missing, check if this bucket was.
      // this really only needs checking on certain buckets... (like terms facet)
      boolean sawThisBucket = mcontext.getShardFlag(bucketNumber);
      if (!sawThisBucket) {
        mcontext.setBucketWasMissing(true);
      }
    } else {
      // if parent bucket was missing, then we should be too
      assert !mcontext.getShardFlag(bucketNumber);
    }

    Map refinement = null;

    if (!mcontext.bucketWasMissing()) {
      // this is just a pass-through bucket... see if there is anything to do at all
      if (subs == null || refineTags.isEmpty()) {
        return null;
      }
    } else {
      // for missing bucket, go over all sub-facts
      refineTags = null;
      refinement = CollectionUtil.newHashMap(4);
      if (bucketValue != null) {
        refinement.put("_v", bucketValue);
      }
      refinement.put("_m", 1);
    }

    // TODO: listing things like sub-facets that have no field facets are redundant
    // (we only need facet that have variable values)

    for (Map.Entry sub : subs.entrySet()) {
      if (refineTags != null && !refineTags.contains(sub.getKey())) {
        continue;
      }
      Map subRef = sub.getValue().getRefinement(mcontext);
      if (subRef != null) {
        if (refinement == null) {
          refinement = CollectionUtil.newHashMap(4);
        }
        refinement.put(sub.getKey(), subRef);
      }
    }

    // reset the "bucketMissing" flag on the way back out.
    mcontext.setBucketWasMissing(parentMissing);
    return refinement;
  }
}