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

com.atlan.model.search.Suggestions Maven / Gradle / Ivy

// Generated by delombok at Wed Oct 16 22:16:04 UTC 2024
/* SPDX-License-Identifier: Apache-2.0
   Copyright 2023 Atlan Pte. Ltd. */
package com.atlan.model.search;

import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import com.atlan.Atlan;
import com.atlan.AtlanClient;
import com.atlan.exception.AtlanException;
import com.atlan.exception.NotFoundException;
import com.atlan.model.assets.Asset;
import com.atlan.model.core.AssetMutationResponse;
import com.atlan.model.core.AtlanTag;
import com.atlan.model.fields.AtlanField;
import com.atlan.serde.Serde;
import com.atlan.util.ParallelBatch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/**
 * Suggestion abstraction mechanism, to simplify finding suggestions for metadata enrichment
 * for a given asset. This works purely by looking at other assets with the same name (and type)
 * that have this metadata populated, and rank-ordering any such metadata by how frequently it
 * occurs across other assets of the same type with the same name.
 */
public class Suggestions {
    private static final String AGG_DESCRIPTION = "group_by_description";
    private static final String AGG_USER_DESCRIPTION = "group_by_userDescription";
    private static final String AGG_OWNER_USERS = "group_by_ownerUsers";
    private static final String AGG_OWNER_GROUPS = "group_by_ownerGroups";
    private static final String AGG_ATLAN_TAGS = "group_by_tags";
    private static final String AGG_TERMS = "group_by_terms";


    public enum TYPE {
        /**
         * System-level description suggestions.
         */
        SystemDescription, /**
         * User-provided description suggestions.
         */
        UserDescription, /**
         * Suggestions for individual users who could be owners.
         */
        IndividualOwners, /**
         * Suggestions for groups who could be owners.
         */
        GroupOwners, /**
         * Suggestions for Atlan tags to assign to the asset.
         */
        Tags, /**
         * Suggestions for terms to assign to the asset.
         */
        Terms;
    }

    /**
     * Build a suggestion finder for the provided asset.
     *
     * @param asset for which to find suggestions
     * @return the start of a suggestion finder for the provided asset, against the default tenant
     */
    public static SuggestionsBuilder finder(Asset asset) {
        return finder(Atlan.getDefaultClient(), asset);
    }

    /**
     * Build a suggestion finder against the provided Atlan tenant for the provided asset.
     *
     * @param client connectivity to an Atlan tenant
     * @param asset for which to find suggestions
     * @return the start of a suggestion finder for the provided asset, against the specified tenant
     */
    public static SuggestionsBuilder finder(AtlanClient client, Asset asset) {
        return _internal().client(client).asset(asset).includeArchived(false).maxSuggestions(5);
    }

    /**
     * Client through which to find suggestions.
     */
    AtlanClient client;
    /**
     * Asset for which to find suggestions.
     */
    Asset asset;
    /**
     * Whether to include archived assets as part of suggestions (true) or not (false, default).
     */
    Boolean includeArchived;
    /**
     * Which type(s) of suggestions to include in the search and results.
     */
    Collection includes;
    /**
     * Maximum number of suggestions to return (default: 5).
     */
    Integer maxSuggestions;
    /**
     * By default, we will only look for suggestions on other assets with exactly the same
     * type. You may want to expand this, for example, for suggested metadata for tables you might also want to look
     * at Views. You can add any additional types here you want to consider where an asset with the same name as
     * this asset is likely have similar metadata (and thus be valid for providing suggestions).
     */
    Collection withOtherTypes;
    /**
     * By default, we will only match on the name (exactly) of the provided asset.
     * You may want to expand this, for example, to look for assets with the same name as well as with
     * some other context, for example, looking only at columns with the same name that are also in parent
     * tables that have the same name. (Columns like 'ID' may otherwise be insufficiently unique to have
     * very useful suggestions.)
     */
    Collection wheres;
    /**
     * By default, we will only match on the name (exactly) of the provided asset.
     * You may want to expand this, for example, to look for assets with the same name as well as without
     * some other context, for example, looking only at columns with the same name that are not in a particular
     * schema (e.g. one used purely for testing purposes).
     */
    Collection whereNots;


    public static class SuggestionsBuilder {
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private AtlanClient client;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private Asset asset;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private Boolean includeArchived;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private java.util.ArrayList includes;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private Integer maxSuggestions;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private java.util.ArrayList withOtherTypes;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private java.util.ArrayList wheres;
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        private java.util.ArrayList whereNots;

        /**
         * Find the requested suggestions and return the results
         * (but do not make any changes to the asset itself).
         *
         * @throws AtlanException on any issue interacting with the APIs
         */
        public SuggestionResponse get() throws AtlanException {
            List allTypes = new ArrayList<>();
            allTypes.add(asset.getTypeName());
            if (withOtherTypes != null && !withOtherTypes.isEmpty()) {
                allTypes.addAll(withOtherTypes);
            }
            FluentSearch.FluentSearchBuilder builder =  // We only care about the aggregations, not results
            client.assets.select(includeArchived).where(Asset.TYPE_NAME.in(allTypes)).where(Asset.NAME.eq(asset.getName())).pageSize(0).minSomes(1);
            if (wheres != null && !wheres.isEmpty()) {
                for (Query condition : wheres) {
                    builder.where(condition);
                }
            }
            if (whereNots != null && !whereNots.isEmpty()) {
                for (Query condition : whereNots) {
                    builder.whereNot(condition);
                }
            }
            for (TYPE include : includes) {
                switch (include) {
                case SystemDescription: 
                    builder.whereSome(Asset.DESCRIPTION.hasAnyValue()).aggregate(AGG_DESCRIPTION, Asset.DESCRIPTION.bucketBy(maxSuggestions, true));
                    break;
                case UserDescription: 
                    builder.whereSome(Asset.USER_DESCRIPTION.hasAnyValue()).aggregate(AGG_USER_DESCRIPTION, Asset.USER_DESCRIPTION.bucketBy(maxSuggestions, true));
                    break;
                case IndividualOwners: 
                    builder.whereSome(Asset.OWNER_USERS.hasAnyValue()).aggregate(AGG_OWNER_USERS, Asset.OWNER_USERS.bucketBy(maxSuggestions));
                    break;
                case GroupOwners: 
                    builder.whereSome(Asset.OWNER_GROUPS.hasAnyValue()).aggregate(AGG_OWNER_GROUPS, Asset.OWNER_GROUPS.bucketBy(maxSuggestions));
                    break;
                case Tags: 
                    builder.whereSome(Asset.ATLAN_TAGS.hasAnyValue()).aggregate(AGG_ATLAN_TAGS, Asset.ATLAN_TAGS.bucketBy(maxSuggestions));
                    break;
                case Terms: 
                    builder.whereSome(Asset.ASSIGNED_TERMS.hasAnyValue()).aggregate(AGG_TERMS, Asset.ASSIGNED_TERMS.bucketBy(maxSuggestions));
                    break;
                default: 
                }
                // Do nothing -- unknown type
            }
            IndexSearchRequest request = builder.toRequest();
            IndexSearchResponse response = request.search(client);
            Map aggregations = response.getAggregations();
            SuggestionResponse.SuggestionResponseBuilder responseBuilder = SuggestionResponse.builder();
            for (TYPE include : includes) {
                switch (include) {
                case SystemDescription: 
                    responseBuilder.systemDescriptions(getDescriptions(aggregations.get(AGG_DESCRIPTION), Asset.DESCRIPTION));
                    break;
                case UserDescription: 
                    responseBuilder.userDescriptions(getDescriptions(aggregations.get(AGG_USER_DESCRIPTION), Asset.USER_DESCRIPTION));
                    break;
                case IndividualOwners: 
                    responseBuilder.ownerUsers(getOthers(aggregations.get(AGG_OWNER_USERS)));
                    break;
                case GroupOwners: 
                    responseBuilder.ownerGroups(getOthers(aggregations.get(AGG_OWNER_GROUPS)));
                    break;
                case Tags: 
                    responseBuilder.atlanTags(getTags(client, aggregations.get(AGG_ATLAN_TAGS)));
                    break;
                case Terms: 
                    responseBuilder.assignedTerms(getTerms(aggregations.get(AGG_TERMS)));
                    break;
                }
            }
            return responseBuilder.build();
        }

        /**
         * Find the requested suggestions and apply the top suggestions as
         * changes to the asset.
         * Note: this will NOT validate whether there is any existing value for what
         * you are setting, so will clobber any existing value with the suggestion.
         * If you want to be certain you are only updating empty values, you should ensure
         * you are only building a finder for suggestions for values that do not already
         * exist on the asset in question.
         *
         * @throws AtlanException on any issue interacting with the APIs
         */
        public AssetMutationResponse apply() throws AtlanException {
            return apply(false);
        }

        /**
         * Find the requested suggestions and apply the top suggestions as
         * changes to the asset.
         * Note: this will NOT validate whether there is any existing value for what
         * you are setting, so will clobber any existing value with the suggestion.
         * If you want to be certain you are only updating empty values, you should ensure
         * you are only building a finder for suggestions for values that do not already
         * exist on the asset in question.
         *
         * @param allowMultiple if true, allow multiple suggestions to be applied to the asset (up to maxSuggestions requested), i.e. for owners, terms and tags
         * @throws AtlanException on any issue interacting with the APIs
         */
        public AssetMutationResponse apply(boolean allowMultiple) throws AtlanException {
            Apply result = _apply(allowMultiple);
            return result.getAsset().save(result.getIncludesTags());
        }

        /**
         * Find the requested suggestions and apply the top suggestions as
         * changes to the asset within the provided batch.
         * Note: this will NOT validate whether there is any existing value for what
         * you are setting, so will clobber any existing value with the suggestion. Also,
         * to ensure tags are applied you MUST set your provided batch up to replace tags
         * BEFORE using it here.
         * If you want to be certain you are only updating empty values, you should ensure
         * you are only building a finder for suggestions for values that do not already
         * exist on the asset in question.
         *
         * @throws AtlanException on any issue interacting with the APIs
         */
        public AssetMutationResponse apply(ParallelBatch batch) throws AtlanException {
            return apply(batch, false);
        }

        /**
         * Find the requested suggestions and apply the top suggestions as
         * changes to the asset within the provided batch.
         * Note: this will NOT validate whether there is any existing value for what
         * you are setting, so will clobber any existing value with the suggestion.
         * If you want to be certain you are only updating empty values, you should ensure
         * you are only building a finder for suggestions for values that do not already
         * exist on the asset in question.
         *
         * @param allowMultiple if true, allow multiple suggestions to be applied to the asset (up to maxSuggestions requested), i.e. for owners, terms and tags
         * @throws AtlanException on any issue interacting with the APIs
         */
        public AssetMutationResponse apply(ParallelBatch batch, boolean allowMultiple) throws AtlanException {
            return batch.add(_apply(allowMultiple).getAsset());
        }

        private Apply _apply(boolean allowMultiple) throws AtlanException {
            SuggestionResponse response = get();
            Asset.AssetBuilder builder = asset.trimToRequired();
            String descriptionToApply = getDescriptionToApply(response);
            // Note: only ever set the description over a user-provided description (never the system-sourced
            // description)
            builder.userDescription(descriptionToApply);
            if (response.getOwnerGroups() != null && !response.getOwnerGroups().isEmpty()) {
                if (allowMultiple) {
                    builder.ownerGroups(response.getOwnerGroups().stream().map(SuggestionResponse.SuggestedItem::getValue).collect(Collectors.toSet()));
                } else {
                    builder.ownerGroup(response.getOwnerGroups().get(0).getValue());
                }
            }
            if (response.getOwnerUsers() != null && !response.getOwnerUsers().isEmpty()) {
                if (allowMultiple) {
                    builder.ownerUsers(response.getOwnerUsers().stream().map(SuggestionResponse.SuggestedItem::getValue).collect(Collectors.toSet()));
                } else {
                    builder.ownerUser(response.getOwnerUsers().get(0).getValue());
                }
            }
            boolean includesTags = false;
            if (response.getAtlanTags() != null && !response.getAtlanTags().isEmpty()) {
                includesTags = true;
                if (allowMultiple) {
                    builder.atlanTags(response.getAtlanTags().stream().map(t -> AtlanTag.builder().typeName(t.getValue()).propagate(false).build()).collect(Collectors.toSet()));
                } else {
                    builder.atlanTag(AtlanTag.builder().typeName(response.getAtlanTags().get(0).getValue()).propagate(false).build());
                }
            }
            if (response.getAssignedTerms() != null && !response.getAssignedTerms().isEmpty()) {
                if (allowMultiple) {
                    builder.assignedTerms(response.getAssignedTerms().stream().map(SuggestionResponse.SuggestedTerm::getValue).collect(Collectors.toSet()));
                } else {
                    builder.assignedTerm(response.getAssignedTerms().get(0).getValue());
                }
            }
            return new Apply(builder.build(), includesTags);
        }

        private static List getDescriptions(AggregationResult res, AtlanField field) {
            List results = new ArrayList<>();
            if (res instanceof AggregationBucketResult) {
                AggregationBucketResult result = (AggregationBucketResult) res;
                for (AggregationBucketDetails bucket : result.getBuckets()) {
                    long count = bucket.getDocCount();
                    String value = bucket.getSourceValue(field).toString();
                    if (!value.isBlank()) {
                        results.add(new SuggestionResponse.SuggestedItem(count, value));
                    }
                }
            }
            return results;
        }

        private static List getTerms(AggregationResult res) {
            List results = new ArrayList<>();
            if (res instanceof AggregationBucketResult) {
                AggregationBucketResult result = (AggregationBucketResult) res;
                for (AggregationBucketDetails bucket : result.getBuckets()) {
                    long count = bucket.getDocCount();
                    String value = bucket.getKey().toString();
                    if (!value.isBlank()) {
                        results.add(new SuggestionResponse.SuggestedTerm(count, value));
                    }
                }
            }
            return results;
        }

        private static List getTags(AtlanClient client, AggregationResult res) throws AtlanException {
            List results = new ArrayList<>();
            if (res instanceof AggregationBucketResult) {
                AggregationBucketResult result = (AggregationBucketResult) res;
                for (AggregationBucketDetails bucket : result.getBuckets()) {
                    long count = bucket.getDocCount();
                    String value = bucket.getKey().toString();
                    if (!value.isBlank()) {
                        String name;
                        try {
                            name = client.getAtlanTagCache().getNameForId(value);
                        } catch (NotFoundException e) {
                            name = Serde.DELETED_AUDIT_OBJECT;
                        }
                        if (name == null) {
                            name = Serde.DELETED_AUDIT_OBJECT;
                        }
                        results.add(new SuggestionResponse.SuggestedItem(count, name));
                    }
                }
            }
            return results;
        }

        private static List getOthers(AggregationResult res) {
            List results = new ArrayList<>();
            if (res instanceof AggregationBucketResult) {
                AggregationBucketResult result = (AggregationBucketResult) res;
                for (AggregationBucketDetails bucket : result.getBuckets()) {
                    long count = bucket.getDocCount();
                    String value = bucket.getKey().toString();
                    if (!value.isBlank()) {
                        results.add(new SuggestionResponse.SuggestedItem(count, value));
                    }
                }
            }
            return results;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        SuggestionsBuilder() {
        }

        /**
         * Client through which to find suggestions.
         * @return {@code this}.
         */
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder client(final AtlanClient client) {
            this.client = client;
            return this;
        }

        /**
         * Asset for which to find suggestions.
         * @return {@code this}.
         */
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder asset(final Asset asset) {
            this.asset = asset;
            return this;
        }

        /**
         * Whether to include archived assets as part of suggestions (true) or not (false, default).
         * @return {@code this}.
         */
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder includeArchived(final Boolean includeArchived) {
            this.includeArchived = includeArchived;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder include(final TYPE include) {
            if (this.includes == null) this.includes = new java.util.ArrayList();
            this.includes.add(include);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder includes(final java.util.Collection includes) {
            if (includes == null) {
                throw new java.lang.NullPointerException("includes cannot be null");
            }
            if (this.includes == null) this.includes = new java.util.ArrayList();
            this.includes.addAll(includes);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder clearIncludes() {
            if (this.includes != null) this.includes.clear();
            return this;
        }

        /**
         * Maximum number of suggestions to return (default: 5).
         * @return {@code this}.
         */
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder maxSuggestions(final Integer maxSuggestions) {
            this.maxSuggestions = maxSuggestions;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder withOtherType(final String withOtherType) {
            if (this.withOtherTypes == null) this.withOtherTypes = new java.util.ArrayList();
            this.withOtherTypes.add(withOtherType);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder withOtherTypes(final java.util.Collection withOtherTypes) {
            if (withOtherTypes == null) {
                throw new java.lang.NullPointerException("withOtherTypes cannot be null");
            }
            if (this.withOtherTypes == null) this.withOtherTypes = new java.util.ArrayList();
            this.withOtherTypes.addAll(withOtherTypes);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder clearWithOtherTypes() {
            if (this.withOtherTypes != null) this.withOtherTypes.clear();
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder where(final Query where) {
            if (this.wheres == null) this.wheres = new java.util.ArrayList();
            this.wheres.add(where);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder wheres(final java.util.Collection wheres) {
            if (wheres == null) {
                throw new java.lang.NullPointerException("wheres cannot be null");
            }
            if (this.wheres == null) this.wheres = new java.util.ArrayList();
            this.wheres.addAll(wheres);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder clearWheres() {
            if (this.wheres != null) this.wheres.clear();
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder whereNot(final Query whereNot) {
            if (this.whereNots == null) this.whereNots = new java.util.ArrayList();
            this.whereNots.add(whereNot);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder whereNots(final java.util.Collection whereNots) {
            if (whereNots == null) {
                throw new java.lang.NullPointerException("whereNots cannot be null");
            }
            if (this.whereNots == null) this.whereNots = new java.util.ArrayList();
            this.whereNots.addAll(whereNots);
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions.SuggestionsBuilder clearWhereNots() {
            if (this.whereNots != null) this.whereNots.clear();
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Suggestions build() {
            java.util.Collection includes;
            switch (this.includes == null ? 0 : this.includes.size()) {
            case 0: 
                includes = java.util.Collections.emptyList();
                break;
            case 1: 
                includes = java.util.Collections.singletonList(this.includes.get(0));
                break;
            default: 
                includes = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.includes));
            }
            java.util.Collection withOtherTypes;
            switch (this.withOtherTypes == null ? 0 : this.withOtherTypes.size()) {
            case 0: 
                withOtherTypes = java.util.Collections.emptyList();
                break;
            case 1: 
                withOtherTypes = java.util.Collections.singletonList(this.withOtherTypes.get(0));
                break;
            default: 
                withOtherTypes = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.withOtherTypes));
            }
            java.util.Collection wheres;
            switch (this.wheres == null ? 0 : this.wheres.size()) {
            case 0: 
                wheres = java.util.Collections.emptyList();
                break;
            case 1: 
                wheres = java.util.Collections.singletonList(this.wheres.get(0));
                break;
            default: 
                wheres = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.wheres));
            }
            java.util.Collection whereNots;
            switch (this.whereNots == null ? 0 : this.whereNots.size()) {
            case 0: 
                whereNots = java.util.Collections.emptyList();
                break;
            case 1: 
                whereNots = java.util.Collections.singletonList(this.whereNots.get(0));
                break;
            default: 
                whereNots = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.whereNots));
            }
            return new Suggestions(this.client, this.asset, this.includeArchived, includes, this.maxSuggestions, withOtherTypes, wheres, whereNots);
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public java.lang.String toString() {
            return "Suggestions.SuggestionsBuilder(client=" + this.client + ", asset=" + this.asset + ", includeArchived=" + this.includeArchived + ", includes=" + this.includes + ", maxSuggestions=" + this.maxSuggestions + ", withOtherTypes=" + this.withOtherTypes + ", wheres=" + this.wheres + ", whereNots=" + this.whereNots + ")";
        }
    }

    @Nullable
    private static String getDescriptionToApply(SuggestionResponse response) {
        long maxDescriptionCount = 0;
        String descriptionToApply = null;
        if (response.getUserDescriptions() != null && !response.getUserDescriptions().isEmpty()) {
            maxDescriptionCount = response.getUserDescriptions().get(0).getCount();
            descriptionToApply = response.getUserDescriptions().get(0).getValue();
        }
        if (response.getSystemDescriptions() != null && !response.getSystemDescriptions().isEmpty()) {
            if (response.getSystemDescriptions().get(0).getCount() > maxDescriptionCount) {
                descriptionToApply = response.getSystemDescriptions().get(0).getValue();
            }
        }
        return descriptionToApply;
    }


    private static class Apply {
        boolean includesTags;
        Asset asset;

        private Apply(Asset asset, boolean includesTags) {
            this.asset = asset;
            this.includesTags = includesTags;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public boolean getIncludesTags() {
            return this.includesTags;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public Asset getAsset() {
            return this.asset;
        }
    }

    @java.lang.SuppressWarnings("all")
    @lombok.Generated
    Suggestions(final AtlanClient client, final Asset asset, final Boolean includeArchived, final Collection includes, final Integer maxSuggestions, final Collection withOtherTypes, final Collection wheres, final Collection whereNots) {
        this.client = client;
        this.asset = asset;
        this.includeArchived = includeArchived;
        this.includes = includes;
        this.maxSuggestions = maxSuggestions;
        this.withOtherTypes = withOtherTypes;
        this.wheres = wheres;
        this.whereNots = whereNots;
    }

    @java.lang.SuppressWarnings("all")
    @lombok.Generated
    public static Suggestions.SuggestionsBuilder _internal() {
        return new Suggestions.SuggestionsBuilder();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy