/** Whether our configuration state includes the given field. */
 private boolean includes(FieldMetaData fmd) {
   if ((hasFetchGroupDefault() && fmd.isInDefaultFetchGroup())
       || hasFetchGroupAll()
       || hasField(fmd.getFullName(false))
       || hasExtendedLookupPath(fmd)) return true;
   String[] fgs = fmd.getCustomFetchGroups();
   for (int i = 0; i < fgs.length; i++) if (hasFetchGroup(fgs[i])) return true;
   return false;
 }
  public int requiresFetch(FieldMetaData fm) {
    if (!includes(fm)) return FETCH_NONE;

    Class<?> type = fm.getRelationType();
    if (type == null) return FETCH_LOAD;
    if (_availableDepth == 0) return FETCH_NONE;

    // we can skip calculating recursion depth if this is a top-level conf:
    // the field is in our fetch groups, so can't possibly not select
    if (_parent == null) return FETCH_LOAD;

    String fieldName = fm.getFullName(false);
    int rdepth = getAvailableRecursionDepth(fm, type, fieldName, false);
    if (rdepth != FetchGroup.DEPTH_INFINITE && rdepth <= 0) return FETCH_NONE;

    if (StringUtils.equals(_directRelationOwner, fieldName)) return FETCH_REF;
    return FETCH_LOAD;
  }
  /**
   * Return the available recursion depth via the given field for the given type.
   *
   * @param traverse whether we're traversing the field
   */
  private int getAvailableRecursionDepth(
      FieldMetaData fm, Class<?> type, String fromField, boolean traverse) {
    // see if there's a previous limit
    int avail = Integer.MIN_VALUE;
    for (FetchConfigurationImpl f = this; f != null; f = f._parent) {
      if (StringUtils.equals(f._fromField, fromField)
          && ImplHelper.isAssignable(f._fromType, type)) {
        avail = f._availableRecursion;
        if (traverse) avail = reduce(avail);
        break;
      }
    }
    if (avail == 0) return 0;

    // calculate fetch groups max
    ClassMetaData meta = fm.getDefiningMetaData();
    int max = Integer.MIN_VALUE;
    if (fm.isInDefaultFetchGroup())
      max = meta.getFetchGroup(FetchGroup.NAME_DEFAULT).getRecursionDepth(fm);
    String[] groups = fm.getCustomFetchGroups();
    int cur;
    for (int i = 0; max != FetchGroup.DEPTH_INFINITE && i < groups.length; i++) {
      // ignore custom groups that are inactive in this configuration
      if (!this.hasFetchGroup(groups[i])) continue;
      cur = meta.getFetchGroup(groups[i]).getRecursionDepth(fm);
      if (cur == FetchGroup.DEPTH_INFINITE || cur > max) max = cur;
    }
    // reduce max if we're traversing a self-type relation
    if (traverse
        && max != Integer.MIN_VALUE
        && ImplHelper.isAssignable(meta.getDescribedType(), type)) max = reduce(max);

    // take min/defined of previous avail and fetch group max
    if (avail == Integer.MIN_VALUE && max == Integer.MIN_VALUE) {
      int def = FetchGroup.RECURSION_DEPTH_DEFAULT;
      return (traverse && ImplHelper.isAssignable(meta.getDescribedType(), type)) ? def - 1 : def;
    }
    if (avail == Integer.MIN_VALUE || avail == FetchGroup.DEPTH_INFINITE) return max;
    if (max == Integer.MIN_VALUE || max == FetchGroup.DEPTH_INFINITE) return avail;
    return Math.min(max, avail);
  }
  public FetchConfiguration traverse(FieldMetaData fm) {
    Class<?> type = fm.getRelationType();
    if (type == null) return this;

    FetchConfigurationImpl clone = newInstance(_state);
    clone._parent = this;
    clone._availableDepth = reduce(_availableDepth);
    clone._fromField = fm.getFullName(false);
    clone._fromType = type;
    clone._availableRecursion = getAvailableRecursionDepth(fm, type, fm.getFullName(false), true);
    if (StringUtils.equals(_directRelationOwner, fm.getFullName(false))) clone._load = false;
    else clone._load = _load;

    FieldMetaData owner = fm.getMappedByMetaData();
    if (owner != null && owner.getTypeCode() == JavaTypes.PC)
      clone._directRelationOwner = owner.getFullName(false);

    return clone;
  }
 private boolean hasExtendedLookupPath(FieldMetaData fmd) {
   return getExtendedPathLookup()
       && (hasField(fmd.getRealName())
           || (_fromField != null && hasField(_fromField + "." + fmd.getName())));
 }