Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
// Generated by delombok at Sat Oct 07 17:30:34 CEST 2023
package de.captaingoldfish.scim.sdk.client.builder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import de.captaingoldfish.scim.sdk.common.utils.EncodingUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import de.captaingoldfish.scim.sdk.client.ScimClientConfig;
import de.captaingoldfish.scim.sdk.client.http.HttpResponse;
import de.captaingoldfish.scim.sdk.client.http.ScimHttpClient;
import de.captaingoldfish.scim.sdk.client.response.ServerResponse;
import de.captaingoldfish.scim.sdk.common.constants.AttributeNames;
import de.captaingoldfish.scim.sdk.common.constants.HttpStatus;
import de.captaingoldfish.scim.sdk.common.constants.SchemaUris;
import de.captaingoldfish.scim.sdk.common.constants.enums.Comparator;
import de.captaingoldfish.scim.sdk.common.constants.enums.SortOrder;
import de.captaingoldfish.scim.sdk.common.request.SearchRequest;
import de.captaingoldfish.scim.sdk.common.resources.ResourceNode;
import de.captaingoldfish.scim.sdk.common.resources.base.ScimObjectNode;
import de.captaingoldfish.scim.sdk.common.response.ListResponse;
import de.captaingoldfish.scim.sdk.common.utils.JsonHelper;
/**
* author Pascal Knueppel
* created at: 16.12.2019 - 13:00
*
* a builder that can be used to build a list request
*/
public class ListBuilder
{
/**
* the base url of the scim provider
*/
private final String baseUrl;
/**
* the endpoint path of the resource e.g. /Users or /Groups
*/
private final String endpoint;
/**
* the entity type that should be returned. Has actually no usage but is only here to setup the generic type
* of this instance
*/
private final Class responseEntityType;
/**
* the parameters that will be used for the list request
*/
private final Map requestParameters = new HashMap<>();
/**
* an apache http client wrapper that offers some convenience methods
*/
private final ScimHttpClient scimHttpClient;
/**
* the fully qualified url to the required resource
*/
private final String fullUrl;
/**
* if the resource should be retrieved by using the fully qualified url
*
* @param fullUrl the fully qualified url to the required resource
* @param responseEntityType the type of the resource that should be returned
* @param scimHttpClient the http client instance
*/
public ListBuilder(String fullUrl, Class responseEntityType, ScimHttpClient scimHttpClient)
{
this.baseUrl = null;
this.endpoint = null;
this.responseEntityType = responseEntityType;
this.scimHttpClient = scimHttpClient;
this.fullUrl = fullUrl;
}
public ListBuilder(String baseUrl, String endpoint, Class responseEntityType, ScimHttpClient scimHttpClient)
{
this.baseUrl = baseUrl;
this.endpoint = endpoint;
this.responseEntityType = responseEntityType;
this.scimHttpClient = scimHttpClient;
this.fullUrl = null;
}
/**
* sets the count parameter for the maximum number of entries that should be returned
*
* @param count the maximum number of entries that should be returned
*/
public ListBuilder count(int count)
{
requestParameters.put(AttributeNames.RFC7643.COUNT, String.valueOf(count));
return this;
}
/**
* sets the startIndex parameter for the first entry that should be returned
*
* @param startIndex the start index from which the entries should be returned
*/
public ListBuilder startIndex(long startIndex)
{
requestParameters.put(AttributeNames.RFC7643.START_INDEX, String.valueOf(startIndex));
return this;
}
/**
* sets the attribute name that should be used for sorting the entries
*
* @param sortBy the attribute name that should be used for sorting the entries
*/
public ListBuilder sortBy(String sortBy)
{
requestParameters.put(AttributeNames.RFC7643.SORT_BY, sortBy);
return this;
}
/**
* sets the sorting order of the resources
*
* @param sortOrder the sorting order of the resources
*/
public ListBuilder sortOrder(SortOrder sortOrder)
{
requestParameters.put(AttributeNames.RFC7643.SORT_ORDER, sortOrder.name().toLowerCase());
return this;
}
/**
* adds the attributes that should be returned by the service provider
*
* @param attributeNames the names of the attributes that should be returned by the service provider
*/
public ListBuilder attributes(String... attributeNames)
{
if (attributeNames != null)
{
requestParameters.put(AttributeNames.RFC7643.ATTRIBUTES, String.join(",", attributeNames));
}
return this;
}
/**
* adds the excluded attributes that should not be returned by the service provider
*
* @param attributeNames the names of the excluded attributes that should not be returned by the service
* provider
*/
public ListBuilder excludedAttributes(String... attributeNames)
{
if (attributeNames != null)
{
requestParameters.put(AttributeNames.RFC7643.EXCLUDED_ATTRIBUTES, String.join(",", attributeNames));
}
return this;
}
/**
* creates a new filter-builder that can be used to create filter expressions
*/
public FilterBuilder filter(String attributeName, Comparator comparator, String value)
{
return filter(false, attributeName, comparator, value);
}
/**
* creates a new filter-builder that can be used to create filter expressions
*/
public FilterBuilder filter(boolean openParanthesis, String attributeName, Comparator comparator, String value)
{
return new FilterBuilder<>(this, attributeName, comparator, value, openParanthesis);
}
/**
* sets the given filter as attribute
*/
public ListBuilder filter(String filter)
{
requestParameters.put(AttributeNames.RFC7643.FILTER, filter);
return this;
}
/**
* adds additional custom parameters to the request that are unknown by the SCIM specification
*
* @param attributeName the name of the attribute to add
* @param attribute the value of the attribute to add
*/
public ListBuilder custom(String attributeName, String attribute)
{
requestParameters.put(attributeName, attribute);
return this;
}
/**
* list requests can be either send with a get-request or a post request
*
* @return a get-request builder
*/
public GetRequestBuilder get()
{
return new GetRequestBuilder<>(this);
}
/**
* list requests can be either send with a get-request or a post request
*
* @return a post-request builder
*/
public PostRequestBuilder post()
{
return new PostRequestBuilder<>(this);
}
/**
* a request builder that builds the list-request as a http-get request
*/
public static class GetRequestBuilder extends RequestBuilder>
{
/**
* the original list builder instance
*/
private ListBuilder listBuilder;
public GetRequestBuilder(ListBuilder listBuilder)
{
super(listBuilder.baseUrl, listBuilder.endpoint, (Class>)new ListResponse().getClass(),
listBuilder.scimHttpClient);
this.listBuilder = listBuilder;
}
/**
* {@inheritDoc}
*/
@Override
public GetRequestBuilder setExpectedResponseHeaders(Map requiredResponseHeaders)
{
return (GetRequestBuilder)super.setExpectedResponseHeaders(requiredResponseHeaders);
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isExpectedResponseCode(int httpStatus)
{
return HttpStatus.OK == httpStatus;
}
/**
* {@inheritDoc}
*/
@Override
protected HttpUriRequest getHttpUriRequest()
{
StringBuilder queryBuilder = new StringBuilder();
if (!listBuilder.requestParameters.isEmpty())
{
if (StringUtils.contains(listBuilder.fullUrl, "?"))
{
queryBuilder.append("&");
}
else
{
queryBuilder.append("?");
}
List pairs = new ArrayList<>();
listBuilder.requestParameters.forEach((key, value) -> {
pairs.add(key + "=" + EncodingUtils.urlEncode(value));
});
queryBuilder.append(String.join("&", pairs));
}
HttpGet httpGet;
if (StringUtils.isBlank(listBuilder.fullUrl))
{
httpGet = new HttpGet(getBaseUrl() + getEndpoint() + queryBuilder.toString());
}
else
{
httpGet = new HttpGet(listBuilder.fullUrl + queryBuilder.toString());
}
return httpGet;
}
/**
* checks if the response contains a schema-uri that matches the value of
* {@link de.captaingoldfish.scim.sdk.common.constants.SchemaUris#LIST_RESPONSE_URI}
*/
@Override
protected Function isResponseParseable()
{
return httpResponse -> {
String responseBody = httpResponse.getResponseBody();
if (StringUtils.isNotBlank(responseBody) && responseBody.contains(SchemaUris.LIST_RESPONSE_URI))
{
return true;
}
return false;
};
}
/**
* uses a custom response type that overrides the translation of the returned resource
*/
@Override
protected ServerResponse> toResponse(HttpResponse response)
{
return new ListServerResponse<>(response, isExpectedResponseCode(response.getHttpStatusCode()),
getResponseEntityType(), listBuilder.responseEntityType, isResponseParseable(),
getRequiredResponseHeaders());
}
}
/**
* a request builder that builds the list-request as a http-post request
*/
public static class PostRequestBuilder extends RequestBuilder>
{
/**
* the original list builder instance
*/
private ListBuilder listBuilder;
public PostRequestBuilder(ListBuilder listBuilder)
{
super(listBuilder.baseUrl, listBuilder.endpoint, (Class>)new ListResponse().getClass(),
listBuilder.scimHttpClient);
this.listBuilder = listBuilder;
}
/**
* {@inheritDoc}
*/
@Override
public PostRequestBuilder setExpectedResponseHeaders(Map requiredResponseHeaders)
{
return (PostRequestBuilder)super.setExpectedResponseHeaders(requiredResponseHeaders);
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isExpectedResponseCode(int httpStatus)
{
return HttpStatus.OK == httpStatus;
}
/**
* {@inheritDoc}
*/
@Override
protected HttpUriRequest getHttpUriRequest()
{
HttpPost httpPost;
if (StringUtils.isBlank(listBuilder.fullUrl))
{
httpPost = new HttpPost(getBaseUrl() + getEndpoint() + "/.search");
}
else
{
String url = listBuilder.fullUrl;
if (url.endsWith("/.search"))
{
httpPost = new HttpPost(listBuilder.fullUrl);
}
else
{
httpPost = new HttpPost(listBuilder.fullUrl + "/.search");
}
}
if (!listBuilder.requestParameters.isEmpty())
{
SearchRequest searchRequest = SearchRequest.builder().build();
listBuilder.requestParameters.forEach(searchRequest::put);
super.setResource(searchRequest);
StringEntity stringEntity = new StringEntity(getResource(), StandardCharsets.UTF_8);
httpPost.setEntity(stringEntity);
}
return httpPost;
}
/**
* checks if the response contains a schema-uri that matches the value of
* {@link de.captaingoldfish.scim.sdk.common.constants.SchemaUris#LIST_RESPONSE_URI}
*/
@Override
protected Function isResponseParseable()
{
return httpResponse -> {
String responseBody = httpResponse.getResponseBody();
if (StringUtils.isNotBlank(responseBody) && responseBody.contains(SchemaUris.LIST_RESPONSE_URI))
{
return true;
}
return false;
};
}
/**
* uses a custom response type that overrides the translation of the returned resource
*/
@Override
protected ServerResponse> toResponse(HttpResponse response)
{
return new ListServerResponse<>(response, isExpectedResponseCode(response.getHttpStatusCode()),
getResponseEntityType(), listBuilder.responseEntityType, isResponseParseable(),
getRequiredResponseHeaders());
}
}
/**
* overrides the translation of the returned resource from the server
*/
public static class ListServerResponse extends ServerResponse>
{
/**
* the generic type of the resources within the list response
*/
private Class responseEntityType;
public ListServerResponse(HttpResponse httpResponse,
boolean expectedResponseCode,
Class> type,
Class responseEntityType,
Function isResponseParseable,
Map requiredResponseHeaders)
{
super(httpResponse, expectedResponseCode, type, isResponseParseable, requiredResponseHeaders);
this.responseEntityType = responseEntityType;
}
/**
* translates the response body into a list response and parses then all json nodes within the resource into
* objects of the given resource node type
*
* @param responseType the type of the node which might be of type
* {@link de.captaingoldfish.scim.sdk.common.resources.User},
* {@link de.captaingoldfish.scim.sdk.common.resources.Group}
* @return a list response with resources of type R
*/
@Override
public R getResource(Class responseType)
{
ListResponse listResponse = JsonHelper.readJsonDocument(getResponseBody(), ListResponse.class);
List typedResources = listResponse.getListedResources().parallelStream().map(scimObjectNode -> {
return JsonHelper.readJsonDocument(scimObjectNode.toString(), responseEntityType);
}).collect(Collectors.toList());
ListResponse typedListResponse = new ListResponse<>(responseEntityType);
typedListResponse.setItemsPerPage(listResponse.getItemsPerPage());
typedListResponse.setStartIndex(listResponse.getStartIndex());
typedListResponse.setTotalResults(listResponse.getTotalResults());
typedListResponse.setListedResources(typedResources);
return (R)typedListResponse;
}
}
/**
* used to build a filter expression
*/
public class FilterBuilder
{
/**
* the original builder to which this builder will return if building the filter is finished
*/
private final ListBuilder listBuilder;
/**
* this builder will represent the built filter expression
*/
private final StringBuilder filterString = new StringBuilder();
/**
* tells us how many parenthesis' have been opened so far
*/
private int openedParenthesis = 0;
/**
* tells us how many parenthesis' have been closed so far
*/
private int closedParenthesis = 0;
public FilterBuilder(ListBuilder listBuilder,
String attributeName,
Comparator comparator,
String value,
boolean openParenthesis)
{
this.listBuilder = listBuilder;
this.openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
}
/**
* opens a parenthesis for the current filter expression
*/
private FilterBuilder openParenthesis(boolean openParenthesis)
{
if (openParenthesis)
{
openedParenthesis++;
filterString.append("(");
}
return this;
}
/**
* closes a parenthesis for the current filter expression
*/
public FilterBuilder closeParenthesis()
{
closedParenthesis++;
filterString.append(")");
return this;
}
/**
* closes a parenthesis for the current filter expression
*/
private FilterBuilder closeParenthesis(boolean closeParenthesis)
{
if (closeParenthesis)
{
return closeParenthesis();
}
return this;
}
public FilterBuilder and(String attributeName, Comparator comparator, String value)
{
return and(false, attributeName, comparator, value);
}
public FilterBuilder or(String attributeName, Comparator comparator, String value)
{
return or(false, attributeName, comparator, value);
}
public FilterBuilder and(String attributeName, Comparator comparator, Boolean value)
{
return and(false, attributeName, comparator, value);
}
public FilterBuilder or(String attributeName, Comparator comparator, Boolean value)
{
return or(false, attributeName, comparator, value);
}
public FilterBuilder and(String attributeName, Comparator comparator, Integer value)
{
return and(false, attributeName, comparator, value);
}
public FilterBuilder or(String attributeName, Comparator comparator, Integer value)
{
return or(false, attributeName, comparator, value);
}
public FilterBuilder and(String attributeName, Comparator comparator, Long value)
{
return and(false, attributeName, comparator, value);
}
public FilterBuilder or(String attributeName, Comparator comparator, Long value)
{
return or(false, attributeName, comparator, value);
}
public FilterBuilder and(String attributeName, Comparator comparator, Double value)
{
return and(false, attributeName, comparator, value);
}
public FilterBuilder or(String attributeName, Comparator comparator, Double value)
{
return or(false, attributeName, comparator, value);
}
public FilterBuilder and(String attributeName, Comparator comparator, Instant value)
{
return and(false, attributeName, comparator, value);
}
public FilterBuilder or(String attributeName, Comparator comparator, Instant value)
{
return or(false, attributeName, comparator, value);
}
public FilterBuilder and(boolean openParenthesis, String attributeName, Comparator comparator, String value)
{
return and(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder or(boolean openParenthesis, String attributeName, Comparator comparator, String value)
{
return or(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder and(boolean openParenthesis, String attributeName, Comparator comparator, Boolean value)
{
return and(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder or(boolean openParenthesis, String attributeName, Comparator comparator, Boolean value)
{
return or(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder and(boolean openParenthesis, String attributeName, Comparator comparator, Integer value)
{
return and(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder or(boolean openParenthesis, String attributeName, Comparator comparator, Integer value)
{
return or(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder and(boolean openParenthesis, String attributeName, Comparator comparator, Long value)
{
return and(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder or(boolean openParenthesis, String attributeName, Comparator comparator, Long value)
{
return or(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder and(boolean openParenthesis, String attributeName, Comparator comparator, Double value)
{
return and(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder or(boolean openParenthesis, String attributeName, Comparator comparator, Double value)
{
return or(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder and(boolean openParenthesis, String attributeName, Comparator comparator, Instant value)
{
return and(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder or(boolean openParenthesis, String attributeName, Comparator comparator, Instant value)
{
return or(openParenthesis, attributeName, comparator, value, false);
}
public FilterBuilder and(String attributeName, Comparator comparator, String value, boolean closeParenthesis)
{
return and(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder or(String attributeName, Comparator comparator, String value, boolean closeParenthesis)
{
return or(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder and(String attributeName, Comparator comparator, Boolean value, boolean closeParenthesis)
{
return and(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder or(String attributeName, Comparator comparator, Boolean value, boolean closeParenthesis)
{
return or(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder and(String attributeName, Comparator comparator, Integer value, boolean closeParenthesis)
{
return and(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder or(String attributeName, Comparator comparator, Integer value, boolean closeParenthesis)
{
return or(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder and(String attributeName, Comparator comparator, Long value, boolean closeParenthesis)
{
return and(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder or(String attributeName, Comparator comparator, Long value, boolean closeParenthesis)
{
return or(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder and(String attributeName, Comparator comparator, Double value, boolean closeParenthesis)
{
return and(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder or(String attributeName, Comparator comparator, Double value, boolean closeParenthesis)
{
return or(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder and(String attributeName, Comparator comparator, Instant value, boolean closeParenthesis)
{
return and(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder or(String attributeName, Comparator comparator, Instant value, boolean closeParenthesis)
{
return or(false, attributeName, comparator, value, closeParenthesis);
}
public FilterBuilder and(boolean openParenthesis,
String attributeName,
Comparator comparator,
String value,
boolean closeParenthesis)
{
filterString.append(" and ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder or(boolean openParenthesis,
String attributeName,
Comparator comparator,
String value,
boolean closeParenthesis)
{
filterString.append(" or ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder and(boolean openParenthesis,
String attributeName,
Comparator comparator,
Boolean value,
boolean closeParenthesis)
{
filterString.append(" and ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder or(boolean openParenthesis,
String attributeName,
Comparator comparator,
Boolean value,
boolean closeParenthesis)
{
filterString.append(" or ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder and(boolean openParenthesis,
String attributeName,
Comparator comparator,
Integer value,
boolean closeParenthesis)
{
filterString.append(" and ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder or(boolean openParenthesis,
String attributeName,
Comparator comparator,
Integer value,
boolean closeParenthesis)
{
filterString.append(" or ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder and(boolean openParenthesis,
String attributeName,
Comparator comparator,
Long value,
boolean closeParenthesis)
{
filterString.append(" and ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder or(boolean openParenthesis,
String attributeName,
Comparator comparator,
Long value,
boolean closeParenthesis)
{
filterString.append(" or ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder and(boolean openParenthesis,
String attributeName,
Comparator comparator,
Double value,
boolean closeParenthesis)
{
filterString.append(" and ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder or(boolean openParenthesis,
String attributeName,
Comparator comparator,
Double value,
boolean closeParenthesis)
{
filterString.append(" or ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value);
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder and(boolean openParenthesis,
String attributeName,
Comparator comparator,
Instant value,
boolean closeParenthesis)
{
filterString.append(" and ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value.toString());
closeParenthesis(closeParenthesis);
return this;
}
public FilterBuilder or(boolean openParenthesis,
String attributeName,
Comparator comparator,
Instant value,
boolean closeParenthesis)
{
filterString.append(" or ");
openParenthesis(openParenthesis);
setExpression(attributeName, comparator, value.toString());
closeParenthesis(closeParenthesis);
return this;
}
/**
* adds an expression into the filter
*
* @param attributeName the attribute name of the expression
* @param comparator the comparator to use
* @param value the value of the expression
*/
private void setExpression(String attributeName, Comparator comparator, Object value)
{
ScimClientConfig scimClientConfig = scimHttpClient.getScimClientConfig();
String comparatorString = scimClientConfig.isUseLowerCaseInFilterComparators() ? comparator.name().toLowerCase()
: comparator.name();
filterString.append(attributeName).append(" ").append(comparatorString);
if (value instanceof String)
{
filterString.append(value == null ? "" : " ").append("\"").append(value == null ? "" : value).append("\"");
}
else
{
filterString.append(value == null ? "" : " " + value);
}
}
/**
* builds the filter string and puts it into the parameter map of the list builder instance
*/
public ListBuilder build()
{
if (openedParenthesis != closedParenthesis)
{
throw new IllegalStateException("error within filter expression\n\topened parentheses: " + openedParenthesis
+ "\n\tclosed parentheses: " + closedParenthesis + "\n\tfilter: "
+ filterString);
}
listBuilder.requestParameters.put(AttributeNames.RFC7643.FILTER, filterString.toString());
return listBuilder;
}
}
/**
* the parameters that will be used for the list request
*/
@java.lang.SuppressWarnings("all")
protected Map getRequestParameters()
{
return this.requestParameters;
}
}