text:
package com.marshalchen.common.uimodule.showcaseview;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.RelativeLayout;

import com.marshalchen.ultimateandroiduicomponent.R;
import com.marshalchen.common.uimodule.showcaseview.targets.Target;

/**
 * A view which allows you to showcase areas of your app with an explanation.
 */
public class ShowcaseView extends RelativeLayout
        implements View.OnClickListener, View.OnTouchListener, ViewTreeObserver.OnPreDrawListener, ViewTreeObserver.OnGlobalLayoutListener {

    private static final int HOLO_BLUE = Color.parseColor("#33B5E5");

    private final Button mEndButton;
    private final TextDrawer textDrawer;
    private final ShowcaseDrawer showcaseDrawer;
    private final ShowcaseAreaCalculator showcaseAreaCalculator;
    private final AnimationFactory animationFactory;
    private final ShotStateStore shotStateStore;

    // Showcase metrics
    private int showcaseX = -1;
    private int showcaseY = -1;
    private float scaleMultiplier = 1f;

    // Touch items
    private boolean hasCustomClickListener = false;
    private boolean blockTouches = true;
    private boolean hideOnTouch = false;
    private OnShowcaseEventListener mEventListener = OnShowcaseEventListener.NONE;

    private boolean hasAlteredText = false;
    private boolean hasNoTarget = false;
    private boolean shouldCentreText;
    private Bitmap bitmapBuffer;

    // Animation items
    private long fadeInMillis;
    private long fadeOutMillis;

    protected ShowcaseView(Context context, boolean newStyle) {
        this(context, null, R.styleable.CustomTheme_showcaseViewStyle, newStyle);
    }

    protected ShowcaseView(Context context, AttributeSet attrs, int defStyle, boolean newStyle) {
        super(context, attrs, defStyle);

        ApiUtils apiUtils = new ApiUtils();
        animationFactory = new AnimatorAnimationFactory();
        showcaseAreaCalculator = new ShowcaseAreaCalculator();
        shotStateStore = new ShotStateStore(context);

        apiUtils.setFitsSystemWindowsCompat(this);
        getViewTreeObserver().addOnPreDrawListener(this);
        getViewTreeObserver().addOnGlobalLayoutListener(this);

        // Get the attributes for the ShowcaseView
        final TypedArray styled = context.getTheme()
                .obtainStyledAttributes(attrs, R.styleable.ShowcaseView, R.attr.showcaseViewStyle,
                        R.style.ShowcaseView);

        // Set the default animation times
        fadeInMillis = getResources().getInteger(android.R.integer.config_mediumAnimTime);
        fadeOutMillis = getResources().getInteger(android.R.integer.config_mediumAnimTime);

        mEndButton = (Button) LayoutInflater.from(context).inflate(R.layout.showcase_button, null);
        if (newStyle) {
            showcaseDrawer = new NewShowcaseDrawer(getResources());
        } else {
            showcaseDrawer = new StandardShowcaseDrawer(getResources());
        }
        textDrawer = new TextDrawer(getResources(), showcaseAreaCalculator, getContext());

        updateStyle(styled, false);

        init();
    }

    private void init() {

        setOnTouchListener(this);

        if (mEndButton.getParent() == null) {
            int margin = (int) getResources().getDimension(R.dimen.showcase_button_margin);
            LayoutParams lps = (LayoutParams) generateDefaultLayoutParams();
            lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            lps.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            lps.setMargins(margin, margin, margin, margin);
            mEndButton.setLayoutParams(lps);
            mEndButton.setText("");
            if (!hasCustomClickListener) {
                mEndButton.setOnClickListener(this);
            }
            addView(mEndButton);
        }

    }

    private boolean hasShot() {
        return shotStateStore.hasShot();
    }

    void setShowcasePosition(Point point) {
        setShowcasePosition(point.x, point.y);
    }

    void setShowcasePosition(int x, int y) {
        if (shotStateStore.hasShot()) {
            return;
        }
        showcaseX = x;
        showcaseY = y;
        //init();
        invalidate();
    }

    public void setTarget(final Target target) {
        setShowcase(target, false);
    }

    public void setShowcase(final Target target, final boolean animate) {
        postDelayed(new Runnable() {
            @Override
            public void run() {

                if (!shotStateStore.hasShot()) {

                    updateBitmap();
                    Point targetPoint = target.getPoint();
                    if (targetPoint != null) {
                        hasNoTarget = false;
                        if (animate) {
                            animationFactory.animateTargetToPoint(ShowcaseView.this, targetPoint);
                        } else {
                            setShowcasePosition(targetPoint);
                        }
                    } else {
                        hasNoTarget = true;
                        invalidate();
                    }

                }
            }
        }, 100);
    }

    private void updateBitmap() {
        if (bitmapBuffer == null || haveBoundsChanged()) {
            bitmapBuffer = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
        }
    }

    private boolean haveBoundsChanged() {
        return getMeasuredWidth() != bitmapBuffer.getWidth() ||
                getMeasuredHeight() != bitmapBuffer.getHeight();
    }

    public boolean hasShowcaseView() {
        return (showcaseX != 1000000 && showcaseY != 1000000) || !hasNoTarget;
    }

    public void setShowcaseX(int x) {
        setShowcasePosition(x, showcaseY);
    }

    public void setShowcaseY(int y) {
        setShowcasePosition(showcaseX, y);
    }

    public int getShowcaseX() {
        return showcaseX;
    }

    public int getShowcaseY() {
        return showcaseY;
    }

    /**
     * Override the standard button click event
     *
     * @param listener Listener to listen to on click events
     */
    public void overrideButtonClick(OnClickListener listener) {
        if (shotStateStore.hasShot()) {
            return;
        }
        if (mEndButton != null) {
            mEndButton.setOnClickListener(listener != null ? listener : this);
        }
        hasCustomClickListener = true;
    }

    public void setOnShowcaseEventListener(OnShowcaseEventListener listener) {
        if (listener != null) {
            mEventListener = listener;
        } else {
            mEventListener = OnShowcaseEventListener.NONE;
        }
    }

    public void setButtonText(CharSequence text) {
        if (mEndButton != null) {
            mEndButton.setText(text);
        }
    }

    @Override
    public boolean onPreDraw() {
        boolean recalculatedCling = showcaseAreaCalculator.calculateShowcaseRect(showcaseX, showcaseY, showcaseDrawer);
        boolean recalculateText = recalculatedCling || hasAlteredText;
        if (recalculateText) {
            textDrawer.calculateTextPosition(getMeasuredWidth(), getMeasuredHeight(), this, shouldCentreText);
        }
        hasAlteredText = false;
        return true;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (showcaseX < 0 || showcaseY < 0 || shotStateStore.hasShot()) {
            super.dispatchDraw(canvas);
            return;
        }

        //Draw background color
        showcaseDrawer.erase(bitmapBuffer);

        // Draw the showcase drawable
        if (!hasNoTarget) {
            showcaseDrawer.drawShowcase(bitmapBuffer, showcaseX, showcaseY, scaleMultiplier);
            showcaseDrawer.drawToCanvas(canvas, bitmapBuffer);
        }

        // Draw the text on the screen, recalculating its position if necessary
        textDrawer.draw(canvas);

        super.dispatchDraw(canvas);

    }

    @Override
    public void onClick(View view) {
        hide();
    }

    public void hide() {
        clearBitmap();
        // If the type is set to one-shot, store that it has shot
        shotStateStore.storeShot();
        mEventListener.onShowcaseViewHide(this);
        fadeOutShowcase();
    }

    private void clearBitmap() {
        if (bitmapBuffer != null && !bitmapBuffer.isRecycled()) {
            bitmapBuffer.recycle();
            bitmapBuffer = null;
        }
    }

    private void fadeOutShowcase() {
        animationFactory.fadeOutView(this, fadeOutMillis, new AnimationFactory.AnimationEndListener() {
            @Override
            public void onAnimationEnd() {
                setVisibility(View.GONE);
                mEventListener.onShowcaseViewDidHide(ShowcaseView.this);
            }
        });
    }

    public void show() {
        mEventListener.onShowcaseViewShow(this);
        fadeInShowcase();
    }

    private void fadeInShowcase() {
        animationFactory.fadeInView(this, fadeInMillis,
                new AnimationFactory.AnimationStartListener() {
                    @Override
                    public void onAnimationStart() {
                        setVisibility(View.VISIBLE);
                    }
                }
        );
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {

        float xDelta = Math.abs(motionEvent.getRawX() - showcaseX);
        float yDelta = Math.abs(motionEvent.getRawY() - showcaseY);
        double distanceFromFocus = Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2));

        if (MotionEvent.ACTION_UP == motionEvent.getAction() &&
                hideOnTouch && distanceFromFocus > showcaseDrawer.getBlockedRadius()) {
            this.hide();
            return true;
        }

        return blockTouches && distanceFromFocus > showcaseDrawer.getBlockedRadius();
    }

    private static void insertShowcaseView(ShowcaseView showcaseView, Activity activity) {
        ((ViewGroup) activity.getWindow().getDecorView()).addView(showcaseView);
        if (!showcaseView.hasShot()) {
            showcaseView.show();
        } else {
            showcaseView.hideImmediate();
        }
    }

    private void hideImmediate() {
        setVisibility(GONE);
    }

    public void setContentTitle(CharSequence title) {
        textDrawer.setContentTitle(title);
    }

    public void setContentText(CharSequence text) {
        textDrawer.setContentText(text);
    }

    private void setScaleMultiplier(float scaleMultiplier) {
        this.scaleMultiplier = scaleMultiplier;
    }

    @Override
    public void onGlobalLayout() {
        if (!shotStateStore.hasShot()) {
            updateBitmap();
        }
    }

    public void hideButton() {
        mEndButton.setVisibility(GONE);
    }

    public void showButton() {
        mEndButton.setVisibility(VISIBLE);
    }

    /**
     * Builder class which allows easier creation of {@link ShowcaseView}s.
     * It is recommended that you use this Builder class.
     */
    public static class Builder {

        final ShowcaseView showcaseView;
        private final Activity activity;

        public Builder(Activity activity) {
            this(activity, false);
        }

        public Builder(Activity activity, boolean useNewStyle) {
            this.activity = activity;
            this.showcaseView = new ShowcaseView(activity, useNewStyle);
            this.showcaseView.setTarget(Target.NONE);
        }

        /**
         * Create the {@link ShowcaseView} and show it.
         *
         * @return the created ShowcaseView
         */
        public ShowcaseView build() {
            insertShowcaseView(showcaseView, activity);
            return showcaseView;
        }

        /**
         * Set the title text shown on the ShowcaseView.
         */
        public Builder setContentTitle(int resId) {
            return setContentTitle(activity.getString(resId));
        }

        /**
         * Set the title text shown on the ShowcaseView.
         */
        public Builder setContentTitle(CharSequence title) {
            showcaseView.setContentTitle(title);
            return this;
        }

        /**
         * Set the descriptive text shown on the ShowcaseView.
         */
        public Builder setContentText(int resId) {
            return setContentText(activity.getString(resId));
        }

        /**
         * Set the descriptive text shown on the ShowcaseView.
         */
        public Builder setContentText(CharSequence text) {
            showcaseView.setContentText(text);
            return this;
        }

        /**
         * Set the target of the showcase.
         *
         * @param target a {@link com.marshalchen.common.uimodule.showcaseview.targets.Target} representing
         *               the item to showcase (e.g., a button, or action item).
         */
        public Builder setTarget(Target target) {
            showcaseView.setTarget(target);
            return this;
        }

        /**
         * Set the style of the ShowcaseView. See the sample app for example styles.
         */
        public Builder setStyle(int theme) {
            showcaseView.setStyle(theme);
            return this;
        }

        /**
         * Set a listener which will override the button clicks.
         *

         * Note that you will have to manually hide the ShowcaseView
         */
        public Builder setOnClickListener(OnClickListener onClickListener) {
            showcaseView.overrideButtonClick(onClickListener);
            return this;
        }

        /**
         * Don't make the ShowcaseView block touches on itself. This doesn't
         * block touches in the showcased area.
         *

         * By default, the ShowcaseView does block touches
         */
        public Builder doNotBlockTouches() {
            showcaseView.setBlocksTouches(false);
            return this;
        }

        /**
         * Make this ShowcaseView hide when the user touches outside the showcased area.
         * This enables {@link #doNotBlockTouches()} as well.
         *

         * By default, the ShowcaseView doesn't hide on touch.
         */
        public Builder hideOnTouchOutside() {
            showcaseView.setBlocksTouches(true);
            showcaseView.setHideOnTouchOutside(true);
            return this;
        }

        /**
         * Set the ShowcaseView to only ever show once.
         *
         * @param shotId a unique identifier (across the app) to store
         *               whether this ShowcaseView has been shown.
         */
        public Builder singleShot(long shotId) {
            showcaseView.setSingleShot(shotId);
            return this;
        }

        public Builder setShowcaseEventListener(OnShowcaseEventListener showcaseEventListener) {
            showcaseView.setOnShowcaseEventListener(showcaseEventListener);
            return this;
        }
    }

    /**
     * Set whether the text should be centred in the screen, or left-aligned (which is the default).
     */
    public void setShouldCentreText(boolean shouldCentreText) {
        this.shouldCentreText = shouldCentreText;
        hasAlteredText = true;
        invalidate();
    }

    /**
     * @see com.marshalchen.common.uimodule.showcaseview.ShowcaseView.Builder#setSingleShot(long)
     */
    private void setSingleShot(long shotId) {
        shotStateStore.setSingleShot(shotId);
    }

    /**
     * Change the position of the ShowcaseView's button from the default bottom-right position.
     *
     * @param layoutParams a {@link android.widget.RelativeLayout.LayoutParams} representing
     *                     the new position of the button
     */
    public void setButtonPosition(LayoutParams layoutParams) {
        mEndButton.setLayoutParams(layoutParams);
    }

    /**
     * Set the duration of the fading in and fading out of the ShowcaseView
     */
    private void setFadeDurations(long fadeInMillis, long fadeOutMillis) {
        this.fadeInMillis = fadeInMillis;
        this.fadeOutMillis = fadeOutMillis;
    }

    /**
     * @see com.marshalchen.common.uimodule.showcaseview.ShowcaseView.Builder#hideOnTouchOutside()
     */
    public void setHideOnTouchOutside(boolean hideOnTouch) {
        this.hideOnTouch = hideOnTouch;
    }

    /**
     * @see com.marshalchen.common.uimodule.showcaseview.ShowcaseView.Builder#doNotBlockTouches()
     */
    public void setBlocksTouches(boolean blockTouches) {
        this.blockTouches = blockTouches;
    }

    /**
     * @see com.marshalchen.common.uimodule.showcaseview.ShowcaseView.Builder#setStyle(int)
     */
    public void setStyle(int theme) {
        TypedArray array = getContext().obtainStyledAttributes(theme, R.styleable.ShowcaseView);
        updateStyle(array, true);
    }

    private void updateStyle(TypedArray styled, boolean invalidate) {
        int backgroundColor = styled.getColor(R.styleable.ShowcaseView_sv_backgroundColor, Color.argb(128, 80, 80, 80));
        int showcaseColor = styled.getColor(R.styleable.ShowcaseView_sv_showcaseColor, HOLO_BLUE);
        String buttonText = styled.getString(R.styleable.ShowcaseView_sv_buttonText);
        if (TextUtils.isEmpty(buttonText)) {
            buttonText ="ok";
        }
        boolean tintButton = styled.getBoolean(R.styleable.ShowcaseView_sv_tintButtonColor, true);

        int titleTextAppearance = styled.getResourceId(R.styleable.ShowcaseView_sv_titleTextAppearance,
                R.style.TextAppearance_ShowcaseView_Title);
        int detailTextAppearance = styled.getResourceId(R.styleable.ShowcaseView_sv_detailTextAppearance,
                R.style.TextAppearance_ShowcaseView_Detail);

        styled.recycle();

        showcaseDrawer.setShowcaseColour(showcaseColor);
        showcaseDrawer.setBackgroundColour(backgroundColor);
        tintButton(showcaseColor, tintButton);
        mEndButton.setText(buttonText);
        textDrawer.setTitleStyling(titleTextAppearance);
        textDrawer.setDetailStyling(detailTextAppearance);
        hasAlteredText = true;

        if (invalidate) {
            invalidate();
        }
    }

    private void tintButton(int showcaseColor, boolean tintButton) {
        if (tintButton) {
            mEndButton.getBackground().setColorFilter(showcaseColor, PorterDuff.Mode.MULTIPLY);
        } else {
            mEndButton.getBackground().setColorFilter(HOLO_BLUE, PorterDuff.Mode.MULTIPLY);
        }
    }

}


text:
using System;
using System.Threading.Tasks;
using Journalist.EventStore.Events;
using Journalist.EventStore.UnitTests.Infrastructure.Stubs;
using Ploeh.AutoFixture;

namespace Journalist.EventStore.UnitTests.Infrastructure.Customizations
{
    public class CommitStreamVersionFMockCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Customize<Func<StreamVersion, Task>>(composer => composer
                .FromFactory((CommitStreamVersionFMock mock) => mock.Invoke));
        }
    }
}

redpajama_set_name: RedPajamaGithub


text:
@interface FAQTableViewController : UITableViewController

@end


text:
<?php

App::uses('AppModel', 'Model');

class LogTag extends AppModel {

	public $useTable = 'log_tag';

}


text:
The Azure Cognitive Service for Language is a cloud-based service that provides Natural Language Processing (NLP)
features for understanding and analyzing text, and includes the following main features:

- Sentiment Analysis
- Entity Recognition (Named, Linked, and Personally Identifiable Information (PII) entities)
- Language Detection
- Key Phrase Extraction
- Multiple Actions Analysis Per Document
- Healthcare Entities Analysis
- Abstractive Text Summarization
- Extractive Text Summarization
- Custom Named Entity Recognition
- Custom Text Classification
- Dynamic Text Classification

[Source code][source_code] | [Package (Maven)][package] | [API reference documentation][api_reference_doc] | [Product Documentation][product_documentation] | [Samples][samples_readme]

## Getting started

### Prerequisites
- A [Java Development Kit (JDK)][jdk_link], version 8 or later.
- [Azure Subscription][azure_subscription]
- [Cognitive Services or Language service account][language_service_account] to use this package.

### Include the Package

#### Include the BOM file

Please include the azure-sdk-bom to your project to take dependency on GA version of the library. In the following snippet, replace the {bom_version_to_target} placeholder with the version number.
To learn more about the BOM, see the [AZURE SDK BOM README](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/boms/azure-sdk-bom/README.md).

```xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-sdk-bom</artifactId>
            <version>{bom_version_to_target}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
```
and then include the direct dependency in the dependencies section without the version tag.

```xml
<dependencies>
  <dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-ai-textanalytics</artifactId>
  </dependency>
</dependencies>
```

#### Include direct dependency
If you want to take dependency on a particular version of the library that is not present in the BOM,
add the direct dependency to your project as follows.

[//]: # ({x-version-update-start;com.azure:azure-ai-textanalytics;current})
```xml
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-ai-textanalytics</artifactId>
    <version>5.3.0-beta.1</version>
</dependency>
```
[//]: # ({x-version-update-end})
**Note:** This version of the client library defaults to the `2022-10-01-preview` version of the service.
It is a newer version than `3_0`, `3_1` and `2022-05-01`.

This table shows the relationship between SDK services and supported API versions of the service:

|SDK version|Supported API version of service
|-|-
|5.3.x | 3.0, 3.1, 2022-05-01, 2022-10-01-preview (default)
|5.2.x | 3.0, 3.1, 2022-05-01
|5.1.x | 3.0, 3.1
|5.0.x | 3.0

#### Create a Cognitive Services or Language Service resource
The Language service supports both [multi-service and single-service access][service_access]. Create a Cognitive Services
resource if you plan to access multiple cognitive services under a single endpoint/key. For Language service access only,
create a Language service resource.

You can create either resource using the

**Option 1:** [Azure Portal][create_new_resource]

**Option 2:** [Azure CLI][azure_cli]

Below is an example of how you can create a Language service resource using the CLI:

```bash
# Create a new resource group to hold the Language service resource -
# if using an existing resource group, skip this step
az group create --name <your-resource-group> --location <location>
```

```bash
# Create language service
az cognitiveservices account create \
    --name <your-resource-name> \
    --resource-group <your-resource-group-name> \
    --kind TextAnalytics \
    --sku <sku> \
    --location <location> \
    --yes
```
For more information about creating the resource or how to get the location and sku information see [here][azure_cli]

### Authenticate the client
In order to interact with the Language service, you will need to create an instance of the Text Analytics client,
both the asynchronous and synchronous clients can be created by using `TextAnalyticsClientBuilder` invoking `buildClient()`
creates a synchronous client while `buildAsyncClient()` creates its asynchronous counterpart.

You will need an **endpoint** and either a **key** or **AAD TokenCredential** to instantiate a client object.

#### Looking up the endpoint
You can find the **endpoint** for your Language service resource in the [Azure Portal][azure_portal] under the
"Keys and Endpoint", or [Azure CLI][azure_cli_endpoint].
```bash
# Get the endpoint for the Language service resource
az cognitiveservices account show --name "resource-name" --resource-group "resource-group-name" --query "endpoint"
```

#### Create a Text Analytics client with key credential
Once you have the value for the [key][key], provide it as a string to the [AzureKeyCredential][azure_key_credential].
This can be found in the [Azure Portal][azure_portal] under the "Keys and Endpoint" section in your created Language
service resource or by running the following Azure CLI command:

```bash
az cognitiveservices account keys list --resource-group <your-resource-group-name> --name <your-resource-name>
```

Use the key as the credential parameter to authenticate the client:
```java readme-sample-createTextAnalyticsClientWithKeyCredential
TextAnalyticsClient textAnalyticsClient = new TextAnalyticsClientBuilder()
    .credential(new AzureKeyCredential("{key}"))
    .endpoint("{endpoint}")
    .buildClient();
```

The Azure Text Analytics client library provides a way to **rotate the existing key**.
```java readme-sample-rotatingAzureKeyCredential
AzureKeyCredential credential = new AzureKeyCredential("{key}");
TextAnalyticsClient textAnalyticsClient = new TextAnalyticsClientBuilder()
    .credential(credential)
    .endpoint("{endpoint}")
    .buildClient();

credential.update("{new_key}");
```

#### Create a Text Analytics client with Azure Active Directory credential
Azure SDK for Java supports an Azure Identity package, making it easy to get credentials from Microsoft identity
platform.

Authentication with AAD requires some initial setup:
* Add the Azure Identity package

[//]: # ({x-version-update-start;com.azure:azure-identity;dependency})
```xml
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-identity</artifactId>
    <version>1.7.1</version>
</dependency>
```
[//]: # ({x-version-update-end})
* [Register a new Azure Active Directory application][register_AAD_application]
* [Grant access][grant_access] to Language service by assigning the `"Cognitive Services User"` role to your service principal.

After setup, you can choose which type of [credential][azure_identity_credential_type] from azure.identity to use.
As an example, [DefaultAzureCredential][wiki_identity] can be used to authenticate the client:
Set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables:
AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET.

Authorization is easiest using [DefaultAzureCredential][wiki_identity]. It finds the best credential to use in its
running environment. For more information about using Azure Active Directory authorization with Language service, please
refer to [the associated documentation][aad_authorization].

```java readme-sample-createTextAnalyticsAsyncClientWithAAD
TokenCredential defaultCredential = new DefaultAzureCredentialBuilder().build();
TextAnalyticsAsyncClient textAnalyticsAsyncClient = new TextAnalyticsClientBuilder()
    .endpoint("{endpoint}")
    .credential(defaultCredential)
    .buildAsyncClient();
```

## Key concepts
### Text Analytics client
The Text Analytics client library provides a [TextAnalyticsClient][text_analytics_sync_client] and
[TextAnalyticsAsyncClient][text_analytics_async_client] to do analysis on batches of documents. It provides both synchronous and
asynchronous operations to access a specific use of Language service, such as language detection or key phrase extraction.

### Input
A **text input**, also called a **document**, is a single unit of document to be analyzed by the predictive models
in the Language service. Operations on a Text Analytics client may take a single document or a collection
of documents to be analyzed as a batch.
See [service limitations][service_input_limitation] for the document, including document length limits, maximum batch size,
and supported text encoding.

### Operation on multiple documents
For each supported operation, the Text Analytics client provides method overloads to take a single document, a batch
of documents as strings, or a batch of either `TextDocumentInput` or `DetectLanguageInput` objects. The overload
taking the `TextDocumentInput` or `DetectLanguageInput` batch allows callers to give each document a unique ID,
indicate that the documents in the batch are written in different languages, or provide a country hint about the
language of the document.

### Return value
An operation result, such as `AnalyzeSentimentResult`, is the result of a Language service operation, containing a
prediction or predictions about a single document and a list of warnings inside of it. An operation's result type also
may optionally include information about the input document and how it was processed. An operation result contains a
`isError` property that allows to identify if an operation executed was successful or unsuccessful for the given
document. When the operation results an error, you can simply call `getError()` to get `TextAnalyticsError` which
contains the reason why it is unsuccessful. If you are interested in how many characters are in your document,
or the number of operation transactions that have gone through, simply call `getStatistics()` to get the
`TextDocumentStatistics` which contains both information.

### Return value collection
An operation result collection, such as `AnalyzeSentimentResultCollection`, which is the collection of
the result of analyzing sentiment operation. It also includes the model version of the operation and statistics of
the batch documents.

**Note**: It is recommended to use the batch methods when working on production environments as they allow you to send one
request with multiple documents. This is more performant than sending a request per each document.

## Examples
The following sections provide several code snippets covering some of the most common Language service tasks, including:

* [Analyze Sentiment](#analyze-sentiment "Analyze sentiment")
* [Detect Language](#detect-language "Detect language")
* [Extract Key Phrases](#extract-key-phrases "Extract key phrases")
* [Recognize Named Entities](#recognize-named-entities "Recognize named entities")
* [Recognize Personally Identifiable Information Entities](#recognize-personally-identifiable-information-entities "Recognize Personally Identifiable Information entities")
* [Recognize Linked Entities](#recognize-linked-entities "Recognize linked entities")
* [Analyze Healthcare Entities](#analyze-healthcare-entities "Analyze healthcare entities")
* [Analyze Multiple Actions](#analyze-multiple-actions "Analyze multiple actions")
* [Custom Entities Recognition](#custom-entities-recognition "Custom entities recognition")
* [Custom Text Classification](#custom-text-classification "Custom text classification")
* [Dynamic Text Classification][dynamic_classification_sample]
* [Abstractive Text Summarization][abstractive_summary_action_sample]
* [Extractive Text Summarization][extractive_summary_action_sample]

### Text Analytics Client
Language service supports both synchronous and asynchronous client creation by using
`TextAnalyticsClientBuilder`,

``` java readme-sample-createTextAnalyticsClientWithKeyCredential
TextAnalyticsClient textAnalyticsClient = new TextAnalyticsClientBuilder()
    .credential(new AzureKeyCredential("{key}"))
    .endpoint("{endpoint}")
    .buildClient();
```

or

``` java readme-sample-createTextAnalyticsAsyncClientWithKeyCredential
TextAnalyticsAsyncClient textAnalyticsAsyncClient = new TextAnalyticsClientBuilder()
    .credential(new AzureKeyCredential("{key}"))
    .endpoint("{endpoint}")
    .buildAsyncClient();
```

### Analyze sentiment
Run a predictive model to identify the positive, negative, neutral or mixed sentiment contained in the
provided document or batch of documents.
``` java readme-sample-analyzeSentiment
String document = "The hotel was dark and unclean. I like microsoft.";
DocumentSentiment documentSentiment = textAnalyticsClient.analyzeSentiment(document);
System.out.printf("Analyzed document sentiment: %s.%n", documentSentiment.getSentiment());
documentSentiment.getSentences().forEach(sentenceSentiment ->
    System.out.printf("Analyzed sentence sentiment: %s.%n", sentenceSentiment.getSentiment()));
```

For samples on using the production recommended option `AnalyzeSentimentBatch` see [here][analyze_sentiment_sample].

To get more granular information about the opinions related to aspects of a product/service, also knows as Aspect-based
Sentiment Analysis in Natural Language Processing (NLP), see sample on sentiment analysis with opinion mining see
[here][analyze_sentiment_with_opinion_mining_sample].

Please refer to the service documentation for a conceptual discussion of [sentiment analysis][sentiment_analysis].

### Detect language
Run a predictive model to determine the language that the provided document or batch of documents are written in.

```java readme-sample-detectLanguages
String document = "Bonjour tout le monde";
DetectedLanguage detectedLanguage = textAnalyticsClient.detectLanguage(document);
System.out.printf("Detected language name: %s, ISO 6391 name: %s, confidence score: %f.%n",
    detectedLanguage.getName(), detectedLanguage.getIso6391Name(), detectedLanguage.getConfidenceScore());
```
For samples on using the production recommended option `DetectLanguageBatch` see [here][detect_language_sample].
Please refer to the service documentation for a conceptual discussion of [language detection][language_detection].

### Extract key phrases
Run a model to identify a collection of significant phrases found in the provided document or batch of documents.

```java readme-sample-extractKeyPhrases
String document = "My cat might need to see a veterinarian.";
System.out.println("Extracted phrases:");
textAnalyticsClient.extractKeyPhrases(document).forEach(keyPhrase -> System.out.printf("%s.%n", keyPhrase));
```
For samples on using the production recommended option `ExtractKeyPhrasesBatch` see [here][extract_key_phrases_sample].
Please refer to the service documentation for a conceptual discussion of [key phrase extraction][key_phrase_extraction].

### Recognize named entities
Run a predictive model to identify a collection of named entities in the provided document or batch of documents and
categorize those entities into categories such as person, location, or organization.  For more information on available
categories, see [Named Entity Categories][named_entities_categories].

```java readme-sample-recognizeEntity
String document = "Satya Nadella is the CEO of Microsoft";
textAnalyticsClient.recognizeEntities(document).forEach(entity ->
    System.out.printf("Recognized entity: %s, category: %s, subcategory: %s, confidence score: %f.%n",
        entity.getText(), entity.getCategory(), entity.getSubcategory(), entity.getConfidenceScore()));
```
For samples on using the production recommended option `RecognizeEntitiesBatch` see [here][recognize_entities_sample].
Please refer to the service documentation for a conceptual discussion of [named entity recognition][named_entity_recognition].

### Recognize Personally Identifiable Information entities
Run a predictive model to identify a collection of Personally Identifiable Information(PII) entities in the provided
document. It recognizes and categorizes PII entities in its input text, such as
Social Security Numbers, bank account information, credit card numbers, and more. This endpoint is only supported for
API versions v3.1-preview.1 and above.

```java readme-sample-recognizePiiEntity
String document = "My SSN is 859-98-0987";
PiiEntityCollection piiEntityCollection = textAnalyticsClient.recognizePiiEntities(document);
System.out.printf("Redacted Text: %s%n", piiEntityCollection.getRedactedText());
piiEntityCollection.forEach(entity -> System.out.printf(
    "Recognized Personally Identifiable Information entity: %s, entity category: %s, entity subcategory: %s,"
        + " confidence score: %f.%n",
    entity.getText(), entity.getCategory(), entity.getSubcategory(), entity.getConfidenceScore()));
```

For samples on using the production recommended option `RecognizePiiEntitiesBatch` see [here][recognize_pii_entities_sample].
Please refer to the service documentation for [supported PII entity types][pii_entity_recognition].

### Recognize linked entities
Run a predictive model to identify a collection of entities found in the provided document or batch of documents,
and include information linking the entities to their corresponding entries in a well-known knowledge base.

```java readme-sample-recognizeLinkedEntity
String document = "Old Faithful is a geyser at Yellowstone Park.";
textAnalyticsClient.recognizeLinkedEntities(document).forEach(linkedEntity -> {
    System.out.println("Linked Entities:");
    System.out.printf("Name: %s, entity ID in data source: %s, URL: %s, data source: %s.%n",
        linkedEntity.getName(), linkedEntity.getDataSourceEntityId(), linkedEntity.getUrl(), linkedEntity.getDataSource());
    linkedEntity.getMatches().forEach(match ->
        System.out.printf("Text: %s, confidence score: %f.%n", match.getText(), match.getConfidenceScore()));
});
```
For samples on using the production recommended option `RecognizeLinkedEntitiesBatch` see [here][recognize_linked_entities_sample].
Please refer to the service documentation for a conceptual discussion of [entity linking][entity_linking].

### Analyze healthcare entities
Text Analytics for health is a containerized service that extracts and labels relevant medical information from
unstructured texts such as doctor's notes, discharge summaries, clinical documents, and electronic health records.
For more information see [How to: Use Text Analytics for health][healthcare].
```java readme-sample-recognizeHealthcareEntities
List<TextDocumentInput> documents = Arrays.asList(new TextDocumentInput("0",
    "RECORD #333582770390100 | MH | 85986313 | | 054351 | 2/14/2001 12:00:00 AM | "
        + "CORONARY ARTERY DISEASE | Signed | DIS | Admission Date: 5/22/2001 "
        + "Report Status: Signed Discharge Date: 4/24/2001 ADMISSION DIAGNOSIS: "
        + "CORONARY ARTERY DISEASE. HISTORY OF PRESENT ILLNESS: "
        + "The patient is a 54-year-old gentleman with a history of progressive angina over the past"
        + " several months. The patient had a cardiac catheterization in July of this year revealing total"
        + " occlusion of the RCA and 50% left main disease , with a strong family history of coronary"
        + " artery disease with a brother dying at the age of 52 from a myocardial infarction and another"
        + " brother who is status post coronary artery bypass grafting. The patient had a stress"
        + " echocardiogram done on July , 2001 , which showed no wall motion abnormalities,"
        + " but this was a difficult study due to body habitus. The patient went for six minutes with"
        + " minimal ST depressions in the anterior lateral leads , thought due to fatigue and wrist pain,"
        + " his anginal equivalent. Due to the patient's increased symptoms and family history and"
        + " history left main disease with total occasional of his RCA was referred"
        + " for revascularization with open heart surgery."
));
AnalyzeHealthcareEntitiesOptions options = new AnalyzeHealthcareEntitiesOptions().setIncludeStatistics(true);
SyncPoller<AnalyzeHealthcareEntitiesOperationDetail, AnalyzeHealthcareEntitiesPagedIterable>
    syncPoller = textAnalyticsClient.beginAnalyzeHealthcareEntities(documents, options, Context.NONE);
syncPoller.waitForCompletion();
syncPoller.getFinalResult().forEach(
    analyzeHealthcareEntitiesResultCollection -> analyzeHealthcareEntitiesResultCollection.forEach(
        healthcareEntitiesResult -> {
            System.out.println("Document entities: ");
            AtomicInteger ct = new AtomicInteger();
            healthcareEntitiesResult.getEntities().forEach(healthcareEntity -> {
                System.out.printf("\ti = %d, Text: %s, category: %s, subcategory: %s, confidence score: %f.%n",
                    ct.getAndIncrement(), healthcareEntity.getText(), healthcareEntity.getCategory(),
                    healthcareEntity.getSubcategory(), healthcareEntity.getConfidenceScore());
                IterableStream<EntityDataSource> healthcareEntityDataSources =
                    healthcareEntity.getDataSources();
                if (healthcareEntityDataSources != null) {
                    healthcareEntityDataSources.forEach(healthcareEntityLink -> System.out.printf(
                        "\t\tEntity ID in data source: %s, data source: %s.%n",
                        healthcareEntityLink.getEntityId(), healthcareEntityLink.getName()));
                }
            });
            // Healthcare entity relation groups
            healthcareEntitiesResult.getEntityRelations().forEach(entityRelation -> {
                System.out.printf("\tRelation type: %s.%n", entityRelation.getRelationType());
                entityRelation.getRoles().forEach(role -> {
                    final HealthcareEntity entity = role.getEntity();
                    System.out.printf("\t\tEntity text: %s, category: %s, role: %s.%n",
                        entity.getText(), entity.getCategory(), role.getName());
                });
                System.out.printf("\tRelation confidence score: %f.%n", entityRelation.getConfidenceScore());
            });
        }));
```

### Custom entities recognition
Custom NER is one of the custom features offered by Azure Cognitive Service for Language. It is a cloud-based API
service that applies machine-learning intelligence to enable you to build custom models for custom named entity
recognition tasks.

```java readme-sample-custom-entities-recognition
List<String> documents = new ArrayList<>();
documents.add(
    "A recent report by the Government Accountability Office (GAO) found that the dramatic increase "
        + "in oil and natural gas development on federal lands over the past six years has stretched the"
        + " staff of the BLM to a point that it has been unable to meet its environmental protection "
        + "responsibilities.");
documents.add(
    "David Schmidt, senior vice president--Food Safety, International Food"
        + " Information Council (IFIC), Washington, D.C., discussed the physical activity component."
);

// See the service documentation for regional support and how to train a model to recognize the custom entities,
// see https://aka.ms/azsdk/textanalytics/customentityrecognition
SyncPoller<RecognizeCustomEntitiesOperationDetail, RecognizeCustomEntitiesPagedIterable> syncPoller =
    textAnalyticsClient.beginRecognizeCustomEntities(documents, "{project_name}", "{deployment_name}");
syncPoller.waitForCompletion();
syncPoller.getFinalResult().forEach(documentsResults -> {
    System.out.printf("Project name: %s, deployment name: %s.%n",
        documentsResults.getProjectName(), documentsResults.getDeploymentName());
    for (RecognizeEntitiesResult documentResult : documentsResults) {
        System.out.println("Document ID: " + documentResult.getId());
        if (!documentResult.isError()) {
            for (CategorizedEntity entity : documentResult.getEntities()) {
                System.out.printf(
                    "\tText: %s, category: %s, confidence score: %f.%n",
                    entity.getText(), entity.getCategory(), entity.getConfidenceScore());
            }
        } else {
            System.out.printf("\tCannot recognize custom entities. Error: %s%n",
                documentResult.getError().getMessage());
        }
    }
});
```

For more information see [How to use: Custom Entities Recognition][custom_entities_recognition_overview].

### Custom text classification
Custom text classification is one of the custom features offered by Azure Cognitive Service for Language. It is a
cloud-based API service that applies machine-learning intelligence to enable you to build custom models for text
classification tasks.

- Single label classification
```java readme-sample-single-label-classification
List<String> documents = new ArrayList<>();
documents.add(
    "A recent report by the Government Accountability Office (GAO) found that the dramatic increase "
        + "in oil and natural gas development on federal lands over the past six years has stretched the"
        + " staff of the BLM to a point that it has been unable to meet its environmental protection "
        + "responsibilities.");
documents.add(
    "David Schmidt, senior vice president--Food Safety, International Food"
        + " Information Council (IFIC), Washington, D.C., discussed the physical activity component."
);
documents.add(
    "I need a reservation for an indoor restaurant in China. Please don't stop the music. Play music "
        + "and add it to my playlist"
);

// See the service documentation for regional support and how to train a model to classify your documents,
// see https://aka.ms/azsdk/textanalytics/customfunctionalities
SyncPoller<ClassifyDocumentOperationDetail, ClassifyDocumentPagedIterable> syncPoller =
    textAnalyticsClient.beginSingleLabelClassify(documents, "{project_name}", "{deployment_name}");
syncPoller.waitForCompletion();
syncPoller.getFinalResult().forEach(documentsResults -> {
    System.out.printf("Project name: %s, deployment name: %s.%n",
        documentsResults.getProjectName(), documentsResults.getDeploymentName());
    for (ClassifyDocumentResult documentResult : documentsResults) {
        System.out.println("Document ID: " + documentResult.getId());
        if (!documentResult.isError()) {
            for (ClassificationCategory classification : documentResult.getClassifications()) {
                System.out.printf("\tCategory: %s, confidence score: %f.%n",
                    classification.getCategory(), classification.getConfidenceScore());
            }
        } else {
            System.out.printf("\tCannot classify category of document. Error: %s%n",
                documentResult.getError().getMessage());
        }
    }
});
```

- Multi label classification
```java readme-sample-multi-label-classification
List<String> documents = new ArrayList<>();
documents.add(
    "I need a reservation for an indoor restaurant in China. Please don't stop the music."
        + " Play music and add it to my playlist"
);

// See the service documentation for regional support and how to train a model to classify your documents,
// see https://aka.ms/azsdk/textanalytics/customfunctionalities
SyncPoller<ClassifyDocumentOperationDetail, ClassifyDocumentPagedIterable> syncPoller =
    textAnalyticsClient.beginMultiLabelClassify(documents, "{project_name}", "{deployment_name}");
syncPoller.waitForCompletion();
syncPoller.getFinalResult().forEach(documentsResults -> {
    System.out.printf("Project name: %s, deployment name: %s.%n",
        documentsResults.getProjectName(), documentsResults.getDeploymentName());
    for (ClassifyDocumentResult documentResult : documentsResults) {
        System.out.println("Document ID: " + documentResult.getId());
        if (!documentResult.isError()) {
            for (ClassificationCategory classification : documentResult.getClassifications()) {
                System.out.printf("\tCategory: %s, confidence score: %f.%n",
                    classification.getCategory(), classification.getConfidenceScore());
            }
        } else {
            System.out.printf("\tCannot classify category of document. Error: %s%n",
                documentResult.getError().getMessage());
        }
    }
});
```

For more information see [How to use: Custom Text Classification][custom_text_classification_overview].

### Analyze multiple actions
The `Analyze` functionality allows choosing which of the supported Language service features to execute in the same
set of documents. Currently, the supported features are:

- Named Entities Recognition
- PII Entities Recognition
- Linked Entity Recognition
- Key Phrase Extraction
- Sentiment Analysis
- Healthcare Analysis
- Custom Entity Recognition (API version 2022-05-01 and newer)
- Custom Single-Label Classification (API version 2022-05-01 and newer)
- Custom Multi-Label Classification (API version 2022-05-01 and newer)
- Abstractive Text Summarization (API version 2022-10-01-preview and newer)
- Extractive Text Summarization (API version 2022-10-01-preview and newer)

```java readme-sample-analyzeActions
    List<TextDocumentInput> documents = Arrays.asList(
        new TextDocumentInput("0",
            "We went to Contoso Steakhouse located at midtown NYC last week for a dinner party, and we adore"
                + " the spot! They provide marvelous food and they have a great menu. The chief cook happens to be"
                + " the owner (I think his name is John Doe) and he is super nice, coming out of the kitchen and "
                + "greeted us all. We enjoyed very much dining in the place! The Sirloin steak I ordered was tender"
                + " and juicy, and the place was impeccably clean. You can even pre-order from their online menu at"
                + " www.contososteakhouse.com, call 312-555-0176 or send email to order@contososteakhouse.com! The"
                + " only complaint I have is the food didn't come fast enough. Overall I highly recommend it!")
    );

    SyncPoller<AnalyzeActionsOperationDetail, AnalyzeActionsResultPagedIterable> syncPoller =
        textAnalyticsClient.beginAnalyzeActions(documents,
            new TextAnalyticsActions().setDisplayName("{tasks_display_name}")
                .setExtractKeyPhrasesActions(new ExtractKeyPhrasesAction())
                .setRecognizePiiEntitiesActions(new RecognizePiiEntitiesAction()),
            new AnalyzeActionsOptions().setIncludeStatistics(false),
            Context.NONE);
    syncPoller.waitForCompletion();
    syncPoller.getFinalResult().forEach(analyzeActionsResult -> {
        System.out.println("Key phrases extraction action results:");
        analyzeActionsResult.getExtractKeyPhrasesResults().forEach(actionResult -> {
            AtomicInteger counter = new AtomicInteger();
            if (!actionResult.isError()) {
                for (ExtractKeyPhraseResult extractKeyPhraseResult : actionResult.getDocumentsResults()) {
                    System.out.printf("%n%s%n", documents.get(counter.getAndIncrement()));
                    System.out.println("Extracted phrases:");
                    extractKeyPhraseResult.getKeyPhrases()
                        .forEach(keyPhrases -> System.out.printf("\t%s.%n", keyPhrases));
                }
            }
        });
        System.out.println("PII entities recognition action results:");
        analyzeActionsResult.getRecognizePiiEntitiesResults().forEach(actionResult -> {
            AtomicInteger counter = new AtomicInteger();
            if (!actionResult.isError()) {
                for (RecognizePiiEntitiesResult entitiesResult : actionResult.getDocumentsResults()) {
                    System.out.printf("%n%s%n", documents.get(counter.getAndIncrement()));
                    PiiEntityCollection piiEntityCollection = entitiesResult.getEntities();
                    System.out.printf("Redacted Text: %s%n", piiEntityCollection.getRedactedText());
                    piiEntityCollection.forEach(entity -> System.out.printf(
                        "Recognized Personally Identifiable Information entity: %s, entity category: %s, "
                            + "entity subcategory: %s, offset: %s, confidence score: %f.%n",
                        entity.getText(), entity.getCategory(), entity.getSubcategory(), entity.getOffset(),
                        entity.getConfidenceScore()));
                }
            }
        });
    });
}
```
For more examples, such as asynchronous samples, refer to [here][samples_readme].

## Troubleshooting
### General
Text Analytics clients raise exceptions. For example, if you try to detect the languages of a batch of text with same
document IDs, `400` error is return that indicating bad request. In the following code snippet, the error is handled
gracefully by catching the exception and display the additional information about the error.

```java readme-sample-handlingException
List<DetectLanguageInput> documents = Arrays.asList(
    new DetectLanguageInput("1", "This is written in English.", "us"),
    new DetectLanguageInput("1", "Este es un documento  escrito en Español.", "es")
);

try {
    textAnalyticsClient.detectLanguageBatchWithResponse(documents, null, Context.NONE);
} catch (HttpResponseException e) {
    System.out.println(e.getMessage());
}
```

### Enable client logging
You can set the `AZURE_LOG_LEVEL` environment variable to view logging statements made in the client library. For
example, setting `AZURE_LOG_LEVEL=2` would show all informational, warning, and error log messages. The log levels can
be found here: [log levels][LogLevels].

### Default HTTP Client
All client libraries by default use the Netty HTTP client. Adding the above dependency will automatically configure
the client library to use the Netty HTTP client. Configuring or changing the HTTP client is detailed in the
[HTTP clients wiki](https://github.com/Azure/azure-sdk-for-java/wiki/HTTP-clients).

### Default SSL library
All client libraries, by default, use the Tomcat-native Boring SSL library to enable native-level performance for SSL
operations. The Boring SSL library is an uber jar containing native libraries for Linux / macOS / Windows, and provides
better performance compared to the default SSL implementation within the JDK. For more information, including how to
reduce the dependency size, refer to the [performance tuning][performance_tuning] section of the wiki.

## Next steps
- Samples are explained in detail [here][samples_readme].

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a [Contributor License Agreement (CLA)][cla] declaring that you have the right to, and actually do, grant us the rights to use your contribution.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct][coc]. For more information see the [Code of Conduct FAQ][coc_faq] or contact [opencode@microsoft.com][coc_contact] with any additional questions or comments.

<!-- LINKS -->
[aad_authorization]: https://docs.microsoft.com/azure/cognitive-services/authentication#authenticate-with-azure-active-directory
[aad_credential]: https://docs.microsoft.com/azure/cognitive-services/authentication#authenticate-with-azure-active-directory
[api_reference_doc]: https://aka.ms/azsdk-java-textanalytics-ref-docs
[authentication]: https://docs.microsoft.com/azure/cognitive-services/authentication
[azure_cli]: https://docs.microsoft.com/azure/cognitive-services/cognitive-services-apis-create-account-cli?tabs=windows
[azure_cli_endpoint]: https://docs.microsoft.com/cli/azure/cognitiveservices/account?view=azure-cli-latest#az-cognitiveservices-account-show
[azure_identity]: https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/identity/azure-identity
[azure_identity_credential_type]: https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/identity/azure-identity#credentials
[azure_key_credential]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/core/azure-core/src/main/java/com/azure/core/credential/AzureKeyCredential.java
[azure_portal]: https://ms.portal.azure.com
[azure_subscription]: https://azure.microsoft.com/free
[cla]: https://cla.microsoft.com
[coc]: https://opensource.microsoft.com/codeofconduct/
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
[coc_contact]: mailto:opencode@microsoft.com
[create_new_resource]: https://docs.microsoft.com/azure/cognitive-services/cognitive-services-apis-create-account?tabs=multiservice%2Cwindows#create-a-new-azure-cognitive-services-resource
[custom_entities_recognition_overview]: https://docs.microsoft.com/azure/cognitive-services/language-service/custom-named-entity-recognition/overview
[custom_subdomain]: https://docs.microsoft.com/azure/cognitive-services/authentication#create-a-resource-with-a-custom-subdomain
[custom_text_classification_overview]: https://docs.microsoft.com/azure/cognitive-services/language-service/custom-text-classification/overview
[grant_access]: https://docs.microsoft.com/azure/cognitive-services/authentication#assign-a-role-to-a-service-principal
[healthcare]: https://docs.microsoft.com/azure/cognitive-services/language-service/text-analytics-for-health/overview?tabs=ner
[jdk_link]: https://docs.microsoft.com/java/azure/jdk/?view=azure-java-stable
[key]: https://docs.microsoft.com/azure/cognitive-services/cognitive-services-apis-create-account?tabs=multiservice%2Cwindows#get-the-keys-for-your-resource
[key_phrase_extraction]: https://docs.microsoft.com/azure/cognitive-services/language-service/key-phrase-extraction/overview
[language_detection]: https://docs.microsoft.com/azure/cognitive-services/language-service/language-detection/overview
[language_regional_support]: https://docs.microsoft.com/azure/cognitive-services/language-service/language-detection/language-support
[named_entity_recognition]: https://docs.microsoft.com/azure/cognitive-services/language-service/named-entity-recognition/overview
[named_entities_categories]: https://docs.microsoft.com/azure/cognitive-services/language-service/named-entity-recognition/concepts/named-entity-categories
[entity_linking]: https://docs.microsoft.com/azure/cognitive-services/language-service/entity-linking/overview
[pii_entity_recognition]: https://docs.microsoft.com/azure/cognitive-services/language-service/personally-identifiable-information/overview
[package]: https://mvnrepository.com/artifact/com.azure/azure-ai-textanalytics
[performance_tuning]: https://github.com/Azure/azure-sdk-for-java/wiki/Performance-Tuning
[product_documentation]: https://docs.microsoft.com/azure/cognitive-services/language-service/overview
[register_AAD_application]: https://docs.microsoft.com/azure/cognitive-services/authentication#assign-a-role-to-a-service-principal
[service_access]: https://docs.microsoft.com/azure/cognitive-services/cognitive-services-apis-create-account?tabs=multiservice%2Cwindows
[service_input_limitation]: https://docs.microsoft.com/azure/cognitive-services/language-service/overview#data-limits
[sentiment_analysis]: https://docs.microsoft.com/azure/cognitive-services/language-service/sentiment-opinion-mining/overview
[source_code]: https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/textanalytics/azure-ai-textanalytics/src
[language_service_account]: https://docs.microsoft.com/azure/cognitive-services/cognitive-services-apis-create-account?tabs=multiservice%2Cwindows
[text_analytics_async_client]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/main/java/com/azure/ai/textanalytics/TextAnalyticsAsyncClient.java
[text_analytics_sync_client]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/main/java/com/azure/ai/textanalytics/TextAnalyticsClient.java
[wiki_identity]: https://github.com/Azure/azure-sdk-for-java/wiki/Identity-and-Authentication
[LogLevels]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/ClientLogger.java

[samples_readme]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/README.md
[detect_language_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/DetectLanguageBatchDocuments.java
[analyze_sentiment_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/AnalyzeSentimentBatchDocuments.java
[analyze_sentiment_with_opinion_mining_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/AnalyzeSentimentWithOpinionMining.java
[extract_key_phrases_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/ExtractKeyPhrasesBatchDocuments.java
[recognize_entities_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/RecognizeEntitiesBatchDocuments.java
[recognize_pii_entities_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/RecognizePiiEntitiesBatchDocuments.java
[recognize_linked_entities_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/RecognizeLinkedEntitiesBatchDocuments.java
[abstractive_summary_action_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/lro/AbstractiveSummarization.java
[extractive_summary_action_sample]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/lro/ExtractiveSummarization.java
[dynamic_classification_sample]:  https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/textanalytics/azure-ai-textanalytics/src/samples/java/com/azure/ai/textanalytics/batch/DynamicClassificationBatchDocuments.java

![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-java%2Fsdk%2Ftextanalytics%2Fazure-ai-textanalytics%2FREADME.png)


text:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.lorislab.tower</groupId>
		<artifactId>tower-agent</artifactId>
		<version>0.0.1-SNAPSHOT</version>
		<relativePath>../</relativePath>
	</parent>

	<name>tower-agent-impl</name>
	<artifactId>tower-agent-impl</artifactId>
	<packaging>jar</packaging>

	<dependencies>
		<dependency>
			<groupId>org.lorislab.tower</groupId>
			<artifactId>tower-agent-api</artifactId>
			<version>${project.version}</version>
		</dependency>
		<!-- BASE -->
		<dependency>
			<groupId>org.lorislab.tower</groupId>
			<artifactId>tower-base-api</artifactId>
		</dependency>
		<!-- META-INF SERVICES -->
		<dependency>
			<groupId>org.kohsuke.metainf-services</groupId>
			<artifactId>metainf-services</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>

</project>


text:
//
//  ILSwapService.h
//  SwapKit
//
//  Created by ∞ on 21/12/09.

#import <UIKit/UIKit.h>
@class ILSwapRequest;
@class ILSwapItem;
@class ILSwapPasteboardSender;

/**
\addtogroup ILSwapKitRegistrationKeys App Registration Keys

 These constants are the keys in an app registration -- a dictionary that says what an app can do. You can -- indeed, if you use SwapKit, you're expected to -- add a registration for your app, and you can query the services for all registrations on the system.
 These constants are used:

 - in Info.plist, in a dictionary under the ILSwapRegistration key, if you're using ILSwapService#didFinishLaunchingWithOptions: (much easier), or
 - as an argument to ILSwapService#registerWithAttributes:, if you are using it, less commonly, or
 - in the dictionaries returned by ILSwapService#applicationRegistrations and related methods (and in dictionaries passed to any ...Registration: argument).

 Note that if a key is marked "optional" below, and it has a default value, then you can expect to find it in dictionaries returned by ILSwapService#applicationRegistrations and ILSwapService#registrationForApplicationWithIdentifier: with the default value set. Keys marked as optional without a default may be missing from those dictionaries if unspecified.

 @{
*/
/// String. A unique identifier for your app. Automatically taken from CFBundleIdentifier if not given.
#define kILAppIdentifier @"ILAppIdentifier"

/// String. Localized, user-visible name for your app. Automatically taken from CFBundleDisplayName or CFBundleName or the bundle's name on the filesystem if not present.
#define kILAppVisibleName @"ILAppVisibleName"

/// String. The URL scheme that can be used to send items to this application.
#define kILAppReceiveItemURLScheme @"ILAppReceiveItemURLScheme"

/**
Array of strings, 'actions'. An action marks the intended use of the received data for the receiving application. For example, an application may receive text to be made into a message, or a mood message change, so it can define two actions for that. The default action is "ILReceive" (that is, "Do your thing, whatever it is").

 The default for this key is '()' (the empty array) if you specify no types in the @ref kILAppSupportedReceivedItemsUTIs key (or if that key is missing), or '(ILReceive)' (just the default action) otherwise.

PLEASE NOTE: If you specify custom actions AND you also want to receive stuff for the default action, you have to mention it explicitly. Example: '(ILReceive, new-tweet, new-direct-message)'.
TODO: A better architecture to specify which actions apply to which types.
*/
#define kILAppSupportedActions @"ILAppSupportedActions"

/**
Array of strings. UTIs advertised as accepted for receiving. Default is '()' (ie. nothing).
*/
#define kILAppSupportedReceivedItemsUTIs @"ILAppSupportedReceivedItemsUTIs"

/// Boolean (NSNumber). If YES, sending multiple items in the same request is meaningful. If NO (default), sending multiple items will only cause the first item to be received.
#define kILAppSupportsReceivingMultipleItems @"ILAppSupportsReceivingMultipleItems"

/// Boolean (NSNumber). If YES, sending multiple items of multiple types in the same request is meaningful. If NO (default), sending multiple items will only cause the first item to be received. If an app sets this flag, it also must set kILAppSupportsReceivingMultipleItems to YES. (SwapKit 1.1 or later is required to send or receive requests with multiple items of multiple types.)
#define kILAppSupportsReceivingMultipleTypes @"ILAppSupportsReceivingMultipleTypes"

/// String. Used to avoid registering multiple times. Will be ignored and overwritten by the internal registration machinery if given during registration, but will be returned by ILSwapRegistration#applicationRegistrations and related methods.
#define kILAppRegistrationUUID @"ILAppRegistrationUUID"

/// Property list object. The bundle version (CFBundleVersion in Info.plist). Used to update the registration after an app update. Will be ignored and overwritten by the internal registration machinery if given during registration, but will be returned by ILSwapRegistration#applicationRegistrations and related methods.
#define kILAppVersion @"ILAppVersion"

/** @} */

// -- - --

/**
\addtogroup ILSwapKitConstants Other Constants
*/

/**
\ingroup ILSwapKitConstants

This is the key in Info.plist the swap service will look for when autoregistering in didFinishLaunchingWithOptions:. It must contain a registration dictionary (see @ref ILSwapKitRegistrationKeys for more information on the contents of a registration dictionary).
*/
#define kILSwapServiceRegistrationInfoDictionaryKey @"ILSwapRegistration"

/**
\ingroup ILSwapKitConstants

This is the default action, that is, a generic "I can receive items of this type" action without strings attached. It's the action used if you specify nil in ILSwapService#sendItems:forAction:toApplicationWithIdentifier: and other methods that take an action.
*/
#define kILSwapDefaultAction @"ILReceive"

// -- - --

/**
\addtogroup ILSwapKitRequestAttributes Request Attributes

Request attributes are keys attached to a particular request, usually accessed through ILSwapRequest#attributes. Usually, the only key you will care about is @ref kILSwapServiceActionKey, which is the key that contains the action that was used to produce this request, but you can produce your own custom requests by specifying a dictionary containing these keys to the ILSwapService#sendRequestWithAttributes:toApplicationWithRegistration: method.

@{
*/

/** String: the action that was specified by the application that produced this request. */
#define kILSwapServiceActionKey @"swap.action"

/** String: the name for the pasteboard that contains the data for this request. You shouldn't use this key directly; instead, implement the ILSwapServiceDelegate#swapServiceDidReceiveRequest: method in your delegate. This allows SwapKit to dispose of the pasteboard correctly once it's no longer useful (and, in the future, use means other than pasteboards to receive and send data). */
#define kILSwapServicePasteboardNameKey @"swap.pasteboard"

/** String: if equal to 'YES', this application was opened using the #openApplicationWithIdentifier: method from another application. */
#define kILSwapServiceJustOpenKey @"swap.open"

/** @} */

// -- - --

@protocol ILSwapServiceDelegate;

/**
 \addtogroup ILSwapKit SwapKit Classes and Protocols
 */

/**
 \ingroup ILSwapKit
ILSwapService is a singleton class whose instance (referred to as simply the 'swap service') manages interactions between applications, including the registration of metadata in the shared application catalog and sending and receiving requests based on that metadata.

TODO: More detailed information.
*/
@interface ILSwapService : NSObject {
@private
	UIPasteboard* appCatalog;
	id <ILSwapServiceDelegate> delegate;
	NSDictionary* registrationAttributes;
	NSDictionary* appRegistrations;
	NSMutableSet* thisSessionOnlyPasteboards;
	ILSwapPasteboardSender* asyncSender;

	NSInteger changeCount;

	BOOL isInternallyDeletingAllAppRegistrations;
}

/// Returns the shared instance of this class.
+ (ILSwapService*) sharedService;

/// Sets or retrieves the delegate. The object set through this property receives callbacks when SwapKit finds that certain events have happened (for example, that an array of items was received).
@property(assign) id <ILSwapServiceDelegate> delegate;

/**
Registers this app with the swap service. Most methods of this class DO NOT WORK unless this method is called first with valid attributes. Methods that only work after registration are noted in their documentation.

Usually, you don't call this method directly. Instead, you use the #didFinishLaunchingWithOptions: method from the application:didFinishLaunchingWithOptions: method of your app delegate, which automatically registers your app using registration attributes found in your Info.plist file at the @ref kILSwapServiceRegistrationInfoDictionaryKey.

@param a A dictionary containing app registration keys and values. If values are not supplied for some app registration keys, defaults may be used during registration instead.
@param update If YES, any current application's registration will be removed and replaced with this one. If NO, then the current registration will not be replaced, if it exists. Note that this method may try to perform maintenance operations upon the application catalog anyway (for instance, clearing unwanted multiple registrations for this app) even if this argument is set to NO.

@see #didFinishLaunchingWithOptions:
*/
- (void) registerWithAttributes:(NSDictionary*) a update:(BOOL) update;

/**
Called to perform appropriate delegate method calls based on the given URL. Returns YES if it has performed any action based on the URL (such as calling a delegate method), NO otherwise. Calling delegate methods requires having called #registerWithAttributes:update: since the app launched; otherwise, this method will always return NO.

Usually, you don't call this method directly. Instead, you use the #didFinishLaunchingWithOptions: method from the application:didFinishLaunchingWithOptions: method of your app delegate, and the #handleOpenURL: method from the application:handleOpenURL: method of your app delegate, which automatically call this method if needed.

@see #didFinishLaunchingWithOptions:
@see #handleOpenURL:
*/
- (BOOL) performActionsForURL:(NSURL*) u;

/**
 Convenience method for performing startup actions. It will perform the following upon the swap service instance:
 - sets the #delegate to the UIApplication delegate, and
 - calls #registerWithAttributes:update: with the dictionary at the @ref kILSwapServiceRegistrationInfoDictionaryKey key of Info.plist if present, and
 - if the passed-in options indicate a URL being opened, calls #performActionsForURL: (which may call appropriate delegate methods).
 Returns YES if #performActionsForURL: acted upon the URL, NO otherwise. (This allows you to ignore SwapKit-handled URLs.)

@param options The same dictionary that was passed to the application:didFinishLaunchingWithOptions: call on the application delegate.
*/
+ (BOOL) didFinishLaunchingWithOptions:(NSDictionary*) options;

/// Convenience method that calls #performActionsForURL: on the swap service instance.
/// Returns YES if performActionsForURL: acted upon the URL, NO otherwise. (This allows you to ignore SwapKit-handled URLs.)
+ (BOOL) handleOpenURL:(NSURL*) u;

// -- - --

// INTERACTING WITH OTHER APPLICATIONS

/**
Returns all application registrations. The returned dictionary uses application identifiers as keys, and registration dictionaries as their associated values. These contain any number of registration keys as specified in @ref ILSwapKitRegistrationKeys, and may contain the registration for this app if #registerWithAttributes:update: was called at least once from it.
*/
@property(readonly) NSDictionary* applicationRegistrations;

/**
 \internal
 Returns all application registration records in the application catalog. Items in this array are guaranteed to be registration dictionaries, but they are offered as-they-are, which means they may be missing required app registration keys, and there may be obsoleted or duplicate registrations in the catalog; this may not match what applicationRegistrations returns for the same applications.

 This property may be useful as a debugging feature or for writing apps that inspect the contents of the catalog, but should never be used by a normal SwapKit client. Do not rely on this being here in the future (that is, it's NOT part of the stable API).
 */
@property(readonly) NSArray* internalApplicationRegistrationRecords;

/**
 \internal
 Removes all application registration records and resets the application catalog to a clean state.

 Note that the use of this method also removes the current app's registration, as though you never called #registerWithAttributes:update: this session. If you want to use any service that requires registration, you might need to call this again.

 This method may be useful as a debugging feature or for writing apps that inspect the contents of the catalog, but should never be used by a normal SwapKit client. Do not rely on this being here in the future (that is, it's NOT part of the stable API).

 SwapKit availability doesn't change for this application as the method is invoked, but does change for all concurrently running applications.
 */
- (void) deleteAllApplicationRegistrations;

/**
 \internal
 Performs a test on the app catalog's backing store to determine if another app has changed it. If so, it will update its state and call appropriate delegate methods.

 This method may be useful as a debugging feature or for writing apps that inspect the contents of the catalog, but should never be used by a normal SwapKit client. Do not rely on this being here in the future (that is, it's NOT part of the stable API).
 */
- (void) internalCheckWhetherAppCatalogChanged;

/// Returns the registration info for the given application identifier, or nil if it's unavailable.
- (NSDictionary*) registrationForApplicationWithIdentifier:(NSString*) appID;

/**
 Sends one item to the application with the given identifier. This is a convenience method for #sendItems:forAction:toApplicationWithIdentifier: for sending a single item; see the docs for that method for more information.
*/
- (BOOL) sendItem:(ILSwapItem*) item forAction:(NSString*) action toApplicationWithIdentifier:(NSString*) appID;

/**
 Sends the given items to the application with the given identifier.
 This method behaves as follows:
 - if an app ID is specified (non-nil), its registration will be used to send the items to it.
 - if no app ID is specified, the first app that supports receiving their item type for the given action will receive the items. If there's more than one item, and there is an app that has the @ref kILAppSupportsReceivingMultipleItems set to YES, it's preferred; otherwise, only the first item will be sent to the first app that supports that type, if any. Note that apps that do not support the given action are not considered.

Passing nil for the action is the same as passing @ref kILSwapDefaultAction.

 The items array must contain ILSwapItem instances only. This is a change from 1.0, which could contain ILSwapItems or raw values.

 SwapKit might elect to send your request asynchronously. In this case, this method will return YES, and the #sendingAsynchronously property will be YES for the duration of the send. See also ILSwapServiceDelegate for delegate methods that are called in this case.

@return YES if the item was (or is going to be) dispatched to an app, NO otherwise.

@param items An array of items, as specified above.
@param action The action to be performed upon the items by the target application. Can be nil; @ref kILSwapDefaultAction will be used in that case.
@param ident The application identifier for the target application, or nil to send to the first app that can handle the specified items, type and action.
*/
- (BOOL) sendItems:(NSArray*) items forAction:(NSString*) action toApplicationWithIdentifier:(NSString*) ident;

/**
 This property is YES if the last call to #sendItem:forAction:toApplicationWithIdentifier: or #sendItems:forAction:toApplicationWithIdentifier: is sending your request asynchronously, NO otherwise.
 */
@property(readonly) BOOL sendingAsynchronously;

/**
 Searches for a registered application that can receive the given items and perform the given action. This is the same algorithm described for the #sendItems:forAction:toApplicationWithIdentifier: method when the application identifier is nil.

 If you specify nil for the action, @ref kILSwapDefaultAction will be used.

 The returned registration is guaranteed to never refer to the currently running application.

 @return The registration dictionary for the right application, if found, or nil if no such application is registered with SwapKit.
 @see #sendItems:forAction:toApplicationWithIdentifier:
 */
- (NSDictionary*) applicationRegistrationForSendingItems:(NSArray*) items forAction:(NSString*) action;

/**
 Returns YES if there is at least one app, other than the current one, that can receive the given items and action, NO otherwise. Using this method is more efficient than inspecting the return value of #applicationRegistrationForSendingItems:forAction: and #allApplicationRegistrationsForSendingItems:forAction:.

 If you specify nil for the action, @ref kILSwapDefaultAction will be used.

 @return The registration dictionary for the right application, if found, or nil if no such application is registered with SwapKit.
 @see #sendItems:forAction:toApplicationWithIdentifier:
 */
- (BOOL) canSendItems:(NSArray*) items forAction:(NSString*) action;

/**
 Searches for a set of registered applications that can receive the given items and perform the given action. This is similar to #applicationRegistrationForSendingItems:forAction:, but returns all possible matches rather than a single one. The order of matches returned is currently arbitrary, but this may change in a future release of SwapKit.

 If you specify nil for the action, @ref kILSwapDefaultAction will be used.

 The returned array never contains the currently running application. If you want to get the currently running application's registration, use #registrationForApplicationWithIdentifier: with the current app's identifier instead.

 @return An array of registration dictionaries for all found applications. If none is found, an empty array will be returned.
 @see #applicationRegistrationForSendingItems:forAction:
 @see #sendItems:forAction:toApplicationWithIdentifier:
*/
- (NSArray*) allApplicationRegistrationsForSendingItems:(NSArray*) items forAction:(NSString*) action;

/**
 This is the primitive request-sending method. It will send a request with the given attributes to the application whose registration dictionary is passed in.

 This method does no checking -- it is assumed that this method is called with an attributes dictionary that will be successfully parsed by another app, either via its delegate method or via SwapKit's built-in checks. #sendItems:forAction:toApplicationWithIdentifier: uses this method to send appropriately-formatted requests; it is preferred that you use that method, as it also takes care of placing the items in external storage (such as a pasteboard) and manages that storage's lifetime.

 @return YES if the request was dispatched, NO otherwise. (This only happens in cases of force majeoure -- eg the app does not have a URL scheme for receiving because the registration contains incorrect data.)
 */
- (BOOL) sendRequestWithAttributes:(NSDictionary*) attributes toApplicationWithRegistration:(NSDictionary*) reg;

/**
 This is our application regiatration, that is, the dictionary that was passed to the last #registerWithAttributes:update: call since the app started.
 */
@property(readonly) NSDictionary* applicationRegistration;

/**
 Starts the application with the given identifier.

 The app will open as though opened by an URL, and a request with custom attributes will be dispatched to it. This will not cause the ILSwapServiceDelegate#swapServiceDidReceiveRequest: to be called in the opened application.

 This kind of request is always dispatched synchronously; it will never trigger an asynchronous send. (See #sendingAsynchronously for more information on asynchronous requests.)

 @return YES if the request for starting an app has been dispatched, NO otherwise.
 */
- (BOOL) openApplicationWithIdentifier:(NSString*) ident;

/**
 If NO, the SwapKit service is unavailable (usually temporarily). The delegate will receive appropriate calls when this property changes.
 */
@property(readonly) BOOL available;

@end

/**
 This is the delegate protocol that SwapKit expects its delegate to have. The delegate can be set by changing the ILSwapService#delegate property, and ILSwapService#didFinishLaunchingWithOptions: will set it by default to be the same as UIApplication's delegate.
 */
@protocol ILSwapServiceDelegate <NSObject>

@optional

/**
 Called whenever SwapKit detects that another application sent us a request through ILSwapService#sendItems:forAction:toApplicationWithIdentifier: or an equivalent method.

 @param request The request that was received.
 */
- (void) swapServiceDidReceiveRequest:(ILSwapRequest*) request;

/**
 Called whenever SwapKit detects a request not matching any of the usual patterns (that is, not associated with any other methods in this protocol). This is usually called when ILSwapService#sendRequestWithAttributes:toApplicationWithRegistration: is used by another application for sending custom requests.

 @param attricutres Contains the attributes for this request, the same ones that were passed to ILSwapService#sendRequestWithAttributes:.
 */
- (void) swapServiceDidReceiveRequestWithAttributes:(NSDictionary*) a;

/**
 Sent when the service begins sending a request asynchronously.
 */
- (void) swapServiceWillBeginSendingAsynchronousRequest;

/**
 Sent when the service ends sending a request asynchronously.

 @param succeeded If YES, the request succeeded. If NO, an error prevented the request from being delivered.
 */
- (void) swapServiceDidEndSendingAsynchronousRequest:(BOOL) succeeded;

/**
 Sent when the service detects external changes in the application catalog. If you have cached any registrations, for example by holding onto the results of ILSwapService#applicationRegistrationForSendingItems:forAction:, it's wise to invalidate that cache and repeat.
 */
- (void) swapServiceApplicationRegistrationsDidChange;

/**
 Sent when SwapKit availability changes. If the system purges SwapKit data, it might become temporarily unavailable as the data is rebuilt. While SwapKit is unavailable, requests will always fail.

 @param available YES if SwapKit returned available from a period of unavailability. NO if SwapKit has become unavailable after a period of unavailability.
 */
- (void) swapServiceDidChangeAvailability:(BOOL) available;

@end


text:
package org.skycrawl.wsdemo.remoteservice;

import org.skycrawl.wsdemo.util.cache.MyCache;
import org.skycrawl.wsdemo.util.cache.MyCacheManager;

import br.com.caelum.restfulie.Response;
import br.com.caelum.restfulie.Restfulie;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.XppDriver;

public class IpToGeo
{
    private static final MyCache<String,GeoInfo> cache = MyCacheManager.create("remoteGeoServiceCache");
    private static final XStream xstream = new XStream(new XppDriver());
    static
    {
        xstream.processAnnotations(GeoInfo.class);
    }

    public static GeoInfo ipToGeo(String ip)
    {
    	// preliminary processing and checks
        ip = ip.trim();
        if (ip.equals("127.0.0.1"))
        {
            return new GeoInfo(null, null, null, null, null, "The comfortable home of myself", null, null, null, null, null, null, null, ip);
        }

        // if the given ip is suitable to be resolved:
        if (cache.containsKey(ip))
        {
        	// first look into cache
            return cache.get(ip);
        }
        else
        {
        	// otherwise resolve and cache
        	Response resp = Restfulie.at("http://ip-api.com/xml/".concat(ip)).get();
            GeoInfo info = (GeoInfo) xstream.fromXML(resp.getContent());
            cache.put(ip, info);
            return info;
        }
    }
}