try {
            final String family = a.getString(0);
            if (family != null) {
                return Typeface.create(family, Typeface.NORMAL);
            }
        } finally {
            a.recycle();
        }

package ticwear.design.widget;

import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v4.text.TextDirectionHeuristicsCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Interpolator;
import ticwear.design.R;

final class CollapsingTextHelper {

    // Pre-JB-MR2 doesn't support HW accelerated canvas scaled text so we will workaround it
    // by using our own texture
    private static final boolean USE_SCALING_TEXTURE = Build.VERSION.SDK_INT < 18;

    private static final boolean DEBUG_DRAW = false;

    private static final Paint DEBUG_DRAW_PAINT;

    static {
        DEBUG_DRAW_PAINT = DEBUG_DRAW ? new Paint() : null;
        if (DEBUG_DRAW_PAINT != null) {
            DEBUG_DRAW_PAINT.setAntiAlias(true);
            DEBUG_DRAW_PAINT.setColor(Color.MAGENTA);
        }
    }

    private final View mView;

    private boolean mDrawTitle;

    private float mExpandedFraction;

    private final Rect mExpandedBounds;

    private final Rect mCollapsedBounds;

    private final RectF mCurrentBounds;

    private int mExpandedTextGravity = Gravity.CENTER_VERTICAL;

    private int mCollapsedTextGravity = Gravity.CENTER_VERTICAL;

    private float mExpandedTextSize = 15;

    private float mCollapsedTextSize = 15;

    private int mExpandedTextColor;

    private int mCollapsedTextColor;

    private float mExpandedDrawY;

    private float mCollapsedDrawY;

    private float mExpandedDrawX;

    private float mCollapsedDrawX;

    private float mCurrentDrawX;

    private float mCurrentDrawY;

    private Typeface mCollapsedTypeface;

    private Typeface mExpandedTypeface;

    private Typeface mCurrentTypeface;

    private CharSequence mText;

    private CharSequence mTextToDraw;

    private boolean mIsRtl;

    private boolean mUseTexture;

    private Bitmap mExpandedTitleTexture;

    private Paint mTexturePaint;

    private float mTextureAscent;

    private float mTextureDescent;

    private float mScale;

    private float mCurrentTextSize;

    private boolean mBoundsChanged;

    private final TextPaint mTextPaint;

    private Interpolator mPositionInterpolator;

    private Interpolator mTextSizeInterpolator;

    private float mCollapsedShadowRadius, mCollapsedShadowDx, mCollapsedShadowDy;

    private int mCollapsedShadowColor;

    private float mExpandedShadowRadius, mExpandedShadowDx, mExpandedShadowDy;

    private int mExpandedShadowColor;

    public CollapsingTextHelper(View view) {
        mView = view;
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mCollapsedBounds = new Rect();
        mExpandedBounds = new Rect();
        mCurrentBounds = new RectF();
    }

    void setTextSizeInterpolator(Interpolator interpolator) {
        mTextSizeInterpolator = interpolator;
        recalculate();
    }

    void setPositionInterpolator(Interpolator interpolator) {
        mPositionInterpolator = interpolator;
        recalculate();
    }

    void setExpandedTextSize(float textSize) {
        if (mExpandedTextSize != textSize) {
            mExpandedTextSize = textSize;
            recalculate();
        }
    }

    void setCollapsedTextSize(float textSize) {
        if (mCollapsedTextSize != textSize) {
            mCollapsedTextSize = textSize;
            recalculate();
        }
    }

    void setCollapsedTextColor(int textColor) {
        if (mCollapsedTextColor != textColor) {
            mCollapsedTextColor = textColor;
            recalculate();
        }
    }

    void setExpandedTextColor(int textColor) {
        if (mExpandedTextColor != textColor) {
            mExpandedTextColor = textColor;
            recalculate();
        }
    }

    void setExpandedBounds(int left, int top, int right, int bottom) {
        if (!rectEquals(mExpandedBounds, left, top, right, bottom)) {
            mExpandedBounds.set(left, top, right, bottom);
            mBoundsChanged = true;
            onBoundsChanged();
        }
    }

    void setCollapsedBounds(int left, int top, int right, int bottom) {
        if (!rectEquals(mCollapsedBounds, left, top, right, bottom)) {
            mCollapsedBounds.set(left, top, right, bottom);
            mBoundsChanged = true;
            onBoundsChanged();
        }
    }

    void onBoundsChanged() {
        mDrawTitle = mCollapsedBounds.width() > 0 && mCollapsedBounds.height() > 0 && mExpandedBounds.width() > 0 && mExpandedBounds.height() > 0;
    }

    void setExpandedTextGravity(int gravity) {
        if (mExpandedTextGravity != gravity) {
            mExpandedTextGravity = gravity;
            recalculate();
        }
    }

    int getExpandedTextGravity() {
        return mExpandedTextGravity;
    }

    void setCollapsedTextGravity(int gravity) {
        if (mCollapsedTextGravity != gravity) {
            mCollapsedTextGravity = gravity;
            recalculate();
        }
    }

    int getCollapsedTextGravity() {
        return mCollapsedTextGravity;
    }

    void setCollapsedTextAppearance(int resId) {
        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
            mCollapsedTextColor = a.getColor(R.styleable.TextAppearance_android_textColor, mCollapsedTextColor);
        }
        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
            mCollapsedTextSize = a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, (int) mCollapsedTextSize);
        }
        mCollapsedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
        mCollapsedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
        mCollapsedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
        mCollapsedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
        a.recycle();
        if (Build.VERSION.SDK_INT >= 16) {
            mCollapsedTypeface = readFontFamilyTypeface(resId);
        }
        recalculate();
    }

    void setExpandedTextAppearance(int resId) {
        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
            mExpandedTextColor = a.getColor(R.styleable.TextAppearance_android_textColor, mExpandedTextColor);
        }
        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
            mExpandedTextSize = a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, (int) mExpandedTextSize);
        }
        mExpandedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
        mExpandedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
        mExpandedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
        mExpandedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
        a.recycle();
        if (Build.VERSION.SDK_INT >= 16) {
            mExpandedTypeface = readFontFamilyTypeface(resId);
        }
        recalculate();
    }

    private Typeface readFontFamilyTypeface(int resId) {
        final TypedArray a = mView.getContext().obtainStyledAttributes(resId, new int[] { android.R.attr.fontFamily });

        return null;
    }

    void setCollapsedTypeface(Typeface typeface) {
        if (mCollapsedTypeface != typeface) {
            mCollapsedTypeface = typeface;
            recalculate();
        }
    }

    void setExpandedTypeface(Typeface typeface) {
        if (mExpandedTypeface != typeface) {
            mExpandedTypeface = typeface;
            recalculate();
        }
    }

    void setTypefaces(Typeface typeface) {
        mCollapsedTypeface = mExpandedTypeface = typeface;
        recalculate();
    }

    Typeface getCollapsedTypeface() {
        return mCollapsedTypeface != null ? mCollapsedTypeface : Typeface.DEFAULT;
    }

    Typeface getExpandedTypeface() {
        return mExpandedTypeface != null ? mExpandedTypeface : Typeface.DEFAULT;
    }

    /**
     * Set the value indicating the current scroll value. This decides how much of the
     * background will be displayed, as well as the title metrics/positioning.
     *
     * A value of {@code 0.0} indicates that the layout is fully expanded.
     * A value of {@code 1.0} indicates that the layout is fully collapsed.
     */
    void setExpansionFraction(float fraction) {
        fraction = MathUtils.constrain(fraction, 0f, 1f);
        if (fraction != mExpandedFraction) {
            mExpandedFraction = fraction;
            calculateCurrentOffsets();
        }
    }

    float getExpansionFraction() {
        return mExpandedFraction;
    }

    float getCollapsedTextSize() {
        return mCollapsedTextSize;
    }

    float getExpandedTextSize() {
        return mExpandedTextSize;
    }

    private void calculateCurrentOffsets() {
        calculateOffsets(mExpandedFraction);
    }

    private void calculateOffsets(final float fraction) {
        interpolateBounds(fraction);
        mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction, mPositionInterpolator);
        mCurrentDrawY = lerp(mExpandedDrawY, mCollapsedDrawY, fraction, mPositionInterpolator);
        setInterpolatedTextSize(lerp(mExpandedTextSize, mCollapsedTextSize, fraction, mTextSizeInterpolator));
        if (mCollapsedTextColor != mExpandedTextColor) {
            // If the collapsed and expanded text colors are different, blend them based on the
            // fraction
            mTextPaint.setColor(blendColors(mExpandedTextColor, mCollapsedTextColor, fraction));
        } else {
            mTextPaint.setColor(mCollapsedTextColor);
        }
        mTextPaint.setShadowLayer(lerp(mExpandedShadowRadius, mCollapsedShadowRadius, fraction, null), lerp(mExpandedShadowDx, mCollapsedShadowDx, fraction, null), lerp(mExpandedShadowDy, mCollapsedShadowDy, fraction, null), blendColors(mExpandedShadowColor, mCollapsedShadowColor, fraction));
        ViewCompat.postInvalidateOnAnimation(mView);
    }

    private void calculateBaseOffsets() {
        final float currentTextSize = mCurrentTextSize;
        // We then calculate the collapsed text size, using the same logic
        calculateUsingTextSize(mCollapsedTextSize);
        float width = mTextToDraw != null ? mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
        final int collapsedAbsGravity = GravityCompat.getAbsoluteGravity(mCollapsedTextGravity, mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
        switch(collapsedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.BOTTOM:
                mCollapsedDrawY = mCollapsedBounds.bottom;
                break;
            case Gravity.TOP:
                mCollapsedDrawY = mCollapsedBounds.top - mTextPaint.ascent();
                break;
            case Gravity.CENTER_VERTICAL:
            default:
                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
                float textOffset = (textHeight / 2) - mTextPaint.descent();
                mCollapsedDrawY = mCollapsedBounds.centerY() + textOffset;
                break;
        }
        switch(collapsedAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                mCollapsedDrawX = mCollapsedBounds.centerX() - (width / 2);
                break;
            case Gravity.RIGHT:
                mCollapsedDrawX = mCollapsedBounds.right - width;
                break;
            case Gravity.LEFT:
            default:
                mCollapsedDrawX = mCollapsedBounds.left;
                break;
        }
        calculateUsingTextSize(mExpandedTextSize);
        width = mTextToDraw != null ? mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
        final int expandedAbsGravity = GravityCompat.getAbsoluteGravity(mExpandedTextGravity, mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
        switch(expandedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.BOTTOM:
                mExpandedDrawY = mExpandedBounds.bottom;
                break;
            case Gravity.TOP:
                mExpandedDrawY = mExpandedBounds.top - mTextPaint.ascent();
                break;
            case Gravity.CENTER_VERTICAL:
            default:
                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
                float textOffset = (textHeight / 2) - mTextPaint.descent();
                mExpandedDrawY = mExpandedBounds.centerY() + textOffset;
                break;
        }
        switch(expandedAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                mExpandedDrawX = mExpandedBounds.centerX() - (width / 2);
                break;
            case Gravity.RIGHT:
                mExpandedDrawX = mExpandedBounds.right - width;
                break;
            case Gravity.LEFT:
            default:
                mExpandedDrawX = mExpandedBounds.left;
                break;
        }
        // The bounds have changed so we need to clear the texture
        clearTexture();
        // Now reset the text size back to the original
        setInterpolatedTextSize(currentTextSize);
    }

    private void interpolateBounds(float fraction) {
        mCurrentBounds.left = lerp(mExpandedBounds.left, mCollapsedBounds.left, fraction, mPositionInterpolator);
        mCurrentBounds.top = lerp(mExpandedDrawY, mCollapsedDrawY, fraction, mPositionInterpolator);
        mCurrentBounds.right = lerp(mExpandedBounds.right, mCollapsedBounds.right, fraction, mPositionInterpolator);
        mCurrentBounds.bottom = lerp(mExpandedBounds.bottom, mCollapsedBounds.bottom, fraction, mPositionInterpolator);
    }

    public void draw(Canvas canvas) {
        final int saveCount = canvas.save();
        if (mTextToDraw != null && mDrawTitle) {
            float x = mCurrentDrawX;
            float y = mCurrentDrawY;
            final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null;
            final float ascent;
            final float descent;
            // Update the TextPaint to the current text size
            mTextPaint.setTextSize(mCurrentTextSize);
            if (drawTexture) {
                ascent = mTextureAscent * mScale;
                descent = mTextureDescent * mScale;
            } else {
                ascent = mTextPaint.ascent() * mScale;
                descent = mTextPaint.descent() * mScale;
            }
            if (DEBUG_DRAW) {
                // Just a debug tool, which drawn a Magneta rect in the text bounds
                canvas.drawRect(mCurrentBounds.left, y + ascent, mCurrentBounds.right, y + descent, DEBUG_DRAW_PAINT);
            }
            if (drawTexture) {
                y += ascent;
            }
            if (mScale != 1f) {
                canvas.scale(mScale, mScale, x, y);
            }
            if (drawTexture) {
                // If we should use a texture, draw it instead of text
                canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
            } else {
                canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint);
            }
        }
        canvas.restoreToCount(saveCount);
    }

    private boolean calculateIsRtl(CharSequence text) {
        final boolean defaultIsRtl = ViewCompat.getLayoutDirection(mView) == ViewCompat.LAYOUT_DIRECTION_RTL;
        return (defaultIsRtl ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length());
    }

    private void setInterpolatedTextSize(float textSize) {
        calculateUsingTextSize(textSize);
        // Use our texture if the scale isn't 1.0
        mUseTexture = USE_SCALING_TEXTURE && mScale != 1f;
        if (mUseTexture) {
            // Make sure we have an expanded texture if needed
            ensureExpandedTexture();
        }
        ViewCompat.postInvalidateOnAnimation(mView);
    }

    private void calculateUsingTextSize(final float textSize) {
        if (mText == null)
            return;
        final float availableWidth;
        final float newTextSize;
        boolean updateDrawText = false;
        if (isClose(textSize, mCollapsedTextSize)) {
            availableWidth = mCollapsedBounds.width();
            newTextSize = mCollapsedTextSize;
            mScale = 1f;
            if (mCurrentTypeface != mCollapsedTypeface) {
                mCurrentTypeface = mCollapsedTypeface;
                updateDrawText = true;
            }
        } else {
            availableWidth = mExpandedBounds.width();
            newTextSize = mExpandedTextSize;
            if (mCurrentTypeface != mExpandedTypeface) {
                mCurrentTypeface = mExpandedTypeface;
                updateDrawText = true;
            }
            if (isClose(textSize, mExpandedTextSize)) {
                // If we're close to the expanded text size, snap to it and use a scale of 1
                mScale = 1f;
            } else {
                // Else, we'll scale down from the expanded text size
                mScale = textSize / mExpandedTextSize;
            }
        }
        if (availableWidth > 0) {
            updateDrawText = (mCurrentTextSize != newTextSize) || mBoundsChanged || updateDrawText;
            mCurrentTextSize = newTextSize;
            mBoundsChanged = false;
        }
        if (mTextToDraw == null || updateDrawText) {
            mTextPaint.setTextSize(mCurrentTextSize);
            mTextPaint.setTypeface(mCurrentTypeface);
            // If we don't currently have text to draw, or the text size has changed, ellipsize...
            final CharSequence title = TextUtils.ellipsize(mText, mTextPaint, availableWidth, TextUtils.TruncateAt.END);
            if (!TextUtils.equals(title, mTextToDraw)) {
                mTextToDraw = title;
                mIsRtl = calculateIsRtl(mTextToDraw);
            }
        }
    }

    private void ensureExpandedTexture() {
        if (mExpandedTitleTexture != null || mExpandedBounds.isEmpty() || TextUtils.isEmpty(mTextToDraw)) {
            return;
        }
        calculateOffsets(0f);
        mTextureAscent = mTextPaint.ascent();
        mTextureDescent = mTextPaint.descent();
        final int w = Math.round(mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()));
        final int h = Math.round(mTextureDescent - mTextureAscent);
        if (w <= 0 && h <= 0) {
            // If the width or height are 0, return
            return;
        }
        mExpandedTitleTexture = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(mExpandedTitleTexture);
        c.drawText(mTextToDraw, 0, mTextToDraw.length(), 0, h - mTextPaint.descent(), mTextPaint);
        if (mTexturePaint == null) {
            // Make sure we have a paint
            mTexturePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        }
    }

    public void recalculate() {
        if (mView.getHeight() > 0 && mView.getWidth() > 0) {
            // If we've already been laid out, calculate everything now otherwise we'll wait
            // until a layout
            calculateBaseOffsets();
            calculateCurrentOffsets();
        }
    }

    /**
     * Set the title to display
     *
     * @param text
     */
    void setText(CharSequence text) {
        if (text == null || !text.equals(mText)) {
            mText = text;
            mTextToDraw = null;
            clearTexture();
            recalculate();
        }
    }

    CharSequence getText() {
        return mText;
    }

    private void clearTexture() {
        if (mExpandedTitleTexture != null) {
            mExpandedTitleTexture.recycle();
            mExpandedTitleTexture = null;
        }
    }

    /**
     * Returns true if {@code value} is 'close' to it's closest decimal value. Close is currently
     * defined as it's difference being < 0.001.
     */
    private static boolean isClose(float value, float targetValue) {
        return Math.abs(value - targetValue) < 0.001f;
    }

    int getExpandedTextColor() {
        return mExpandedTextColor;
    }

    int getCollapsedTextColor() {
        return mCollapsedTextColor;
    }

    /**
     * Blend {@code color1} and {@code color2} using the given ratio.
     *
     * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
     *              1.0 will return {@code color2}.
     */
    private static int blendColors(int color1, int color2, float ratio) {
        final float inverseRatio = 1f - ratio;
        float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
        float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
        float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
        float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
        return Color.argb((int) a, (int) r, (int) g, (int) b);
    }

    private static float lerp(float startValue, float endValue, float fraction, Interpolator interpolator) {
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        return AnimationUtils.lerp(startValue, endValue, fraction);
    }

    private static boolean rectEquals(Rect r, int left, int top, int right, int bottom) {
        return !(r.left != left || r.top != top || r.right != right || r.bottom != bottom);
    }
}


public void compiled(SourceFile unit) {
        String message = Messages.bind(Messages.build_compiling, unit.resource.getFullPath().removeLastSegments(1).makeRelative().toString());
        subTask(message);
        updateProgressDelta(this.progressPerCompilationUnit);
        checkCancelWithinCompiler();
    }

/**
 * ****************************************************************************
 *  Copyright (c) 2000, 2008 IBM Corporation and others.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *      IBM Corporation - initial API and implementation
 * *****************************************************************************
 */
package org.eclipse.jdt.internal.core.builder;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.core.util.Messages;

public class BuildNotifier {

    protected IProgressMonitor monitor;

    protected boolean cancelling;

    protected float percentComplete;

    protected float progressPerCompilationUnit;

    protected int newErrorCount;

    protected int fixedErrorCount;

    protected int newWarningCount;

    protected int fixedWarningCount;

    protected int workDone;

    protected int totalWork;

    protected String previousSubtask;

    public static int NewErrorCount = 0;

    public static int FixedErrorCount = 0;

    public static int NewWarningCount = 0;

    public static int FixedWarningCount = 0;

    public static void resetProblemCounters() {
        NewErrorCount = 0;
        FixedErrorCount = 0;
        NewWarningCount = 0;
        FixedWarningCount = 0;
    }

    public BuildNotifier(IProgressMonitor monitor, IProject project) {
        this.monitor = monitor;
        this.cancelling = false;
        this.newErrorCount = NewErrorCount;
        this.fixedErrorCount = FixedErrorCount;
        this.newWarningCount = NewWarningCount;
        this.fixedWarningCount = FixedWarningCount;
        this.workDone = 0;
        this.totalWork = 1000000;
    }

    /**
     * Notification before a compile that a unit is about to be compiled.
     */
    public void aboutToCompile(SourceFile unit) {
        String message = Messages.bind(Messages.build_compiling, unit.resource.getFullPath().removeLastSegments(1).makeRelative().toString());
        subTask(message);
    }

    public void begin() {
        if (this.monitor != null)
            this.monitor.beginTask("", this.totalWork);
        this.previousSubtask = null;
    }

    /**
     * Check whether the build has been canceled.
     */
    public void checkCancel() {
        if (this.monitor != null && this.monitor.isCanceled())
            throw new OperationCanceledException();
    }

    /**
     * Check whether the build has been canceled.
     * Must use this call instead of checkCancel() when within the compiler.
     */
    public void checkCancelWithinCompiler() {
        if (this.monitor != null && this.monitor.isCanceled() && !this.cancelling) {
            // Once the compiler has been canceled, don't check again.
            setCancelling(true);
            // Only AbortCompilation can stop the compiler cleanly.
            // We check cancelation again following the call to compile.
            throw new AbortCompilation(true, null);
        }
    }

    /**
     * Notification while within a compile that a unit has finished being compiled.
     */

    public void done() {
        NewErrorCount = this.newErrorCount;
        FixedErrorCount = this.fixedErrorCount;
        NewWarningCount = this.newWarningCount;
        FixedWarningCount = this.fixedWarningCount;
        updateProgress(1.0f);
        subTask(Messages.build_done);
        if (this.monitor != null)
            this.monitor.done();
        this.previousSubtask = null;
    }

    /**
     * Returns a string describing the problems.
     */
    protected String problemsMessage() {
        int numNew = this.newErrorCount + this.newWarningCount;
        int numFixed = this.fixedErrorCount + this.fixedWarningCount;
        if (numNew == 0 && numFixed == 0)
            return "";
        boolean displayBoth = numNew > 0 && numFixed > 0;
        StringBuffer buffer = new StringBuffer();
        buffer.append('(');
        if (numNew > 0) {
            // (Found x errors + y warnings)
            buffer.append(Messages.build_foundHeader);
            buffer.append(' ');
            if (displayBoth || this.newErrorCount > 0) {
                if (this.newErrorCount == 1)
                    buffer.append(Messages.build_oneError);
                else
                    buffer.append(Messages.bind(Messages.build_multipleErrors, String.valueOf(this.newErrorCount)));
                if (displayBoth || this.newWarningCount > 0)
                    buffer.append(" + ");
            }
            if (displayBoth || this.newWarningCount > 0) {
                if (this.newWarningCount == 1)
                    buffer.append(Messages.build_oneWarning);
                else
                    buffer.append(Messages.bind(Messages.build_multipleWarnings, String.valueOf(this.newWarningCount)));
            }
            if (numFixed > 0)
                buffer.append(", ");
        }
        if (numFixed > 0) {
            // (Fixed x errors + y warnings) or (Found x errors + y warnings, Fixed x + y)
            buffer.append(Messages.build_fixedHeader);
            buffer.append(' ');
            if (displayBoth) {
                buffer.append(String.valueOf(this.fixedErrorCount));
                buffer.append(" + ");
                buffer.append(String.valueOf(this.fixedWarningCount));
            } else {
                if (this.fixedErrorCount > 0) {
                    if (this.fixedErrorCount == 1)
                        buffer.append(Messages.build_oneError);
                    else
                        buffer.append(Messages.bind(Messages.build_multipleErrors, String.valueOf(this.fixedErrorCount)));
                    if (this.fixedWarningCount > 0)
                        buffer.append(" + ");
                }
                if (this.fixedWarningCount > 0) {
                    if (this.fixedWarningCount == 1)
                        buffer.append(Messages.build_oneWarning);
                    else
                        buffer.append(Messages.bind(Messages.build_multipleWarnings, String.valueOf(this.fixedWarningCount)));
                }
            }
        }
        buffer.append(')');
        return buffer.toString();
    }

    /**
     * Sets the cancelling flag, which indicates we are in the middle
     * of being cancelled.  Certain places (those callable indirectly from the compiler)
     * should not check cancel again while this is true, to avoid OperationCanceledException
     * being thrown at an inopportune time.
     */
    public void setCancelling(boolean cancelling) {
        this.cancelling = cancelling;
    }

    /**
     * Sets the amount of progress to report for compiling each compilation unit.
     */
    public void setProgressPerCompilationUnit(float progress) {
        this.progressPerCompilationUnit = progress;
    }

    public void subTask(String message) {
        String pm = problemsMessage();
        String msg = pm.length() == 0 ? message : pm + " " + message;
        // avoid refreshing with same one
        if (msg.equals(this.previousSubtask))
            return;
        if (this.monitor != null)
            this.monitor.subTask(msg);
        this.previousSubtask = msg;
    }

    protected void updateProblemCounts(CategorizedProblem[] newProblems) {
        for (int i = 0, l = newProblems.length; i < l; i++) if (newProblems[i].isError())
            this.newErrorCount++;
        else
            this.newWarningCount++;
    }

    /**
     * Update the problem counts from one compilation result given the old and new problems,
     * either of which may be null.
     */
    protected void updateProblemCounts(IMarker[] oldProblems, CategorizedProblem[] newProblems) {
        if (newProblems != null) {
            next: for (int i = 0, l = newProblems.length; i < l; i++) {
                CategorizedProblem newProblem = newProblems[i];
                // skip task
                if (newProblem.getID() == IProblem.Task)
                    continue;
                boolean isError = newProblem.isError();
                String message = newProblem.getMessage();
                if (oldProblems != null) {
                    for (int j = 0, m = oldProblems.length; j < m; j++) {
                        IMarker pb = oldProblems[j];
                        // already matched up with a new problem
                        if (pb == null)
                            continue;
                        boolean wasError = IMarker.SEVERITY_ERROR == pb.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
                        if (isError == wasError && message.equals(pb.getAttribute(IMarker.MESSAGE, ""))) {
                            oldProblems[j] = null;
                            continue next;
                        }
                    }
                }
                if (isError)
                    this.newErrorCount++;
                else
                    this.newWarningCount++;
            }
        }
        if (oldProblems != null) {
            next: for (int i = 0, l = oldProblems.length; i < l; i++) {
                IMarker oldProblem = oldProblems[i];
                // already matched up with a new problem
                if (oldProblem == null)
                    continue next;
                boolean wasError = IMarker.SEVERITY_ERROR == oldProblem.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
                String message = oldProblem.getAttribute(IMarker.MESSAGE, "");
                if (newProblems != null) {
                    for (int j = 0, m = newProblems.length; j < m; j++) {
                        CategorizedProblem pb = newProblems[j];
                        // skip task
                        if (pb.getID() == IProblem.Task)
                            continue;
                        if (wasError == pb.isError() && message.equals(pb.getMessage()))
                            continue next;
                    }
                }
                if (wasError)
                    this.fixedErrorCount++;
                else
                    this.fixedWarningCount++;
            }
        }
    }

    public void updateProgress(float newPercentComplete) {
        if (newPercentComplete > this.percentComplete) {
            this.percentComplete = Math.min(newPercentComplete, 1.0f);
            int work = Math.round(this.percentComplete * this.totalWork);
            if (work > this.workDone) {
                if (this.monitor != null)
                    this.monitor.worked(work - this.workDone);
                this.workDone = work;
            }
        }
    }

    public void updateProgressDelta(float percentWorked) {
        updateProgress(this.percentComplete + percentWorked);
    }
}


for (int j = 0, m = newProblems.length; j < m; j++) {
                        CategorizedProblem pb = newProblems[j];
                        // skip task
                        if (pb.getID() == IProblem.Task)
                            continue;
                        if (wasError == pb.isError() && message.equals(pb.getMessage()))
                            continue next;
                    }

/**
 * ****************************************************************************
 *  Copyright (c) 2000, 2008 IBM Corporation and others.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *      IBM Corporation - initial API and implementation
 * *****************************************************************************
 */
package org.eclipse.jdt.internal.core.builder;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.core.util.Messages;

public class BuildNotifier {

    protected IProgressMonitor monitor;

    protected boolean cancelling;

    protected float percentComplete;

    protected float progressPerCompilationUnit;

    protected int newErrorCount;

    protected int fixedErrorCount;

    protected int newWarningCount;

    protected int fixedWarningCount;

    protected int workDone;

    protected int totalWork;

    protected String previousSubtask;

    public static int NewErrorCount = 0;

    public static int FixedErrorCount = 0;

    public static int NewWarningCount = 0;

    public static int FixedWarningCount = 0;

    public static void resetProblemCounters() {
        NewErrorCount = 0;
        FixedErrorCount = 0;
        NewWarningCount = 0;
        FixedWarningCount = 0;
    }

    public BuildNotifier(IProgressMonitor monitor, IProject project) {
        this.monitor = monitor;
        this.cancelling = false;
        this.newErrorCount = NewErrorCount;
        this.fixedErrorCount = FixedErrorCount;
        this.newWarningCount = NewWarningCount;
        this.fixedWarningCount = FixedWarningCount;
        this.workDone = 0;
        this.totalWork = 1000000;
    }

    /**
     * Notification before a compile that a unit is about to be compiled.
     */
    public void aboutToCompile(SourceFile unit) {
        String message = Messages.bind(Messages.build_compiling, unit.resource.getFullPath().removeLastSegments(1).makeRelative().toString());
        subTask(message);
    }

    public void begin() {
        if (this.monitor != null)
            this.monitor.beginTask("", this.totalWork);
        this.previousSubtask = null;
    }

    /**
     * Check whether the build has been canceled.
     */
    public void checkCancel() {
        if (this.monitor != null && this.monitor.isCanceled())
            throw new OperationCanceledException();
    }

    /**
     * Check whether the build has been canceled.
     * Must use this call instead of checkCancel() when within the compiler.
     */
    public void checkCancelWithinCompiler() {
        if (this.monitor != null && this.monitor.isCanceled() && !this.cancelling) {
            // Once the compiler has been canceled, don't check again.
            setCancelling(true);
            // Only AbortCompilation can stop the compiler cleanly.
            // We check cancelation again following the call to compile.
            throw new AbortCompilation(true, null);
        }
    }

    /**
     * Notification while within a compile that a unit has finished being compiled.
     */
    public void compiled(SourceFile unit) {
        String message = Messages.bind(Messages.build_compiling, unit.resource.getFullPath().removeLastSegments(1).makeRelative().toString());
        subTask(message);
        updateProgressDelta(this.progressPerCompilationUnit);
        checkCancelWithinCompiler();
    }

    public void done() {
        NewErrorCount = this.newErrorCount;
        FixedErrorCount = this.fixedErrorCount;
        NewWarningCount = this.newWarningCount;
        FixedWarningCount = this.fixedWarningCount;
        updateProgress(1.0f);
        subTask(Messages.build_done);
        if (this.monitor != null)
            this.monitor.done();
        this.previousSubtask = null;
    }

    /**
     * Returns a string describing the problems.
     */
    protected String problemsMessage() {
        int numNew = this.newErrorCount + this.newWarningCount;
        int numFixed = this.fixedErrorCount + this.fixedWarningCount;
        if (numNew == 0 && numFixed == 0)
            return "";
        boolean displayBoth = numNew > 0 && numFixed > 0;
        StringBuffer buffer = new StringBuffer();
        buffer.append('(');
        if (numNew > 0) {
            // (Found x errors + y warnings)
            buffer.append(Messages.build_foundHeader);
            buffer.append(' ');
            if (displayBoth || this.newErrorCount > 0) {
                if (this.newErrorCount == 1)
                    buffer.append(Messages.build_oneError);
                else
                    buffer.append(Messages.bind(Messages.build_multipleErrors, String.valueOf(this.newErrorCount)));
                if (displayBoth || this.newWarningCount > 0)
                    buffer.append(" + ");
            }
            if (displayBoth || this.newWarningCount > 0) {
                if (this.newWarningCount == 1)
                    buffer.append(Messages.build_oneWarning);
                else
                    buffer.append(Messages.bind(Messages.build_multipleWarnings, String.valueOf(this.newWarningCount)));
            }
            if (numFixed > 0)
                buffer.append(", ");
        }
        if (numFixed > 0) {
            // (Fixed x errors + y warnings) or (Found x errors + y warnings, Fixed x + y)
            buffer.append(Messages.build_fixedHeader);
            buffer.append(' ');
            if (displayBoth) {
                buffer.append(String.valueOf(this.fixedErrorCount));
                buffer.append(" + ");
                buffer.append(String.valueOf(this.fixedWarningCount));
            } else {
                if (this.fixedErrorCount > 0) {
                    if (this.fixedErrorCount == 1)
                        buffer.append(Messages.build_oneError);
                    else
                        buffer.append(Messages.bind(Messages.build_multipleErrors, String.valueOf(this.fixedErrorCount)));
                    if (this.fixedWarningCount > 0)
                        buffer.append(" + ");
                }
                if (this.fixedWarningCount > 0) {
                    if (this.fixedWarningCount == 1)
                        buffer.append(Messages.build_oneWarning);
                    else
                        buffer.append(Messages.bind(Messages.build_multipleWarnings, String.valueOf(this.fixedWarningCount)));
                }
            }
        }
        buffer.append(')');
        return buffer.toString();
    }

    /**
     * Sets the cancelling flag, which indicates we are in the middle
     * of being cancelled.  Certain places (those callable indirectly from the compiler)
     * should not check cancel again while this is true, to avoid OperationCanceledException
     * being thrown at an inopportune time.
     */
    public void setCancelling(boolean cancelling) {
        this.cancelling = cancelling;
    }

    /**
     * Sets the amount of progress to report for compiling each compilation unit.
     */
    public void setProgressPerCompilationUnit(float progress) {
        this.progressPerCompilationUnit = progress;
    }

    public void subTask(String message) {
        String pm = problemsMessage();
        String msg = pm.length() == 0 ? message : pm + " " + message;
        // avoid refreshing with same one
        if (msg.equals(this.previousSubtask))
            return;
        if (this.monitor != null)
            this.monitor.subTask(msg);
        this.previousSubtask = msg;
    }

    protected void updateProblemCounts(CategorizedProblem[] newProblems) {
        for (int i = 0, l = newProblems.length; i < l; i++) if (newProblems[i].isError())
            this.newErrorCount++;
        else
            this.newWarningCount++;
    }

    /**
     * Update the problem counts from one compilation result given the old and new problems,
     * either of which may be null.
     */
    protected void updateProblemCounts(IMarker[] oldProblems, CategorizedProblem[] newProblems) {
        if (newProblems != null) {
            next: for (int i = 0, l = newProblems.length; i < l; i++) {
                CategorizedProblem newProblem = newProblems[i];
                // skip task
                if (newProblem.getID() == IProblem.Task)
                    continue;
                boolean isError = newProblem.isError();
                String message = newProblem.getMessage();
                if (oldProblems != null) {
                    for (int j = 0, m = oldProblems.length; j < m; j++) {
                        IMarker pb = oldProblems[j];
                        // already matched up with a new problem
                        if (pb == null)
                            continue;
                        boolean wasError = IMarker.SEVERITY_ERROR == pb.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
                        if (isError == wasError && message.equals(pb.getAttribute(IMarker.MESSAGE, ""))) {
                            oldProblems[j] = null;
                            continue next;
                        }
                    }
                }
                if (isError)
                    this.newErrorCount++;
                else
                    this.newWarningCount++;
            }
        }
        if (oldProblems != null) {
            next: for (int i = 0, l = oldProblems.length; i < l; i++) {
                IMarker oldProblem = oldProblems[i];
                // already matched up with a new problem
                if (oldProblem == null)
                    continue next;
                boolean wasError = IMarker.SEVERITY_ERROR == oldProblem.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
                String message = oldProblem.getAttribute(IMarker.MESSAGE, "");
                if (newProblems != null) {

                }
                if (wasError)
                    this.fixedErrorCount++;
                else
                    this.fixedWarningCount++;
            }
        }
    }

    public void updateProgress(float newPercentComplete) {
        if (newPercentComplete > this.percentComplete) {
            this.percentComplete = Math.min(newPercentComplete, 1.0f);
            int work = Math.round(this.percentComplete * this.totalWork);
            if (work > this.workDone) {
                if (this.monitor != null)
                    this.monitor.worked(work - this.workDone);
                this.workDone = work;
            }
        }
    }

    public void updateProgressDelta(float percentWorked) {
        updateProgress(this.percentComplete + percentWorked);
    }
}


public String getRevision() {
        return RevisionUtils.extract("$Revision$");
    }

package weka.core.matrix;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

/**
 * Class for the format of floating point numbers
 *
 * @author Yong Wang
 * @version $Revision$
 */
public class FloatingPointFormat extends DecimalFormat implements RevisionHandler {

    private static final long serialVersionUID = 4500373755333429499L;

    protected DecimalFormat nf;

    protected int width;

    protected int decimal;

    protected boolean trailing = true;

    public FloatingPointFormat() {
        this(8, 5);
    }

    public FloatingPointFormat(int digits) {
        this(8, 2);
    }

    public FloatingPointFormat(int w, int d) {
        width = w;
        decimal = d;
        nf = new DecimalFormat(pattern(w, d));
        nf.setPositivePrefix(" ");
        nf.setNegativePrefix("-");
    }

    public FloatingPointFormat(int w, int d, boolean trailingZeros) {
        this(w, d);
        this.trailing = trailingZeros;
    }

    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
        StringBuffer s = new StringBuffer(nf.format(number));
        if (s.length() > width) {
            if (s.charAt(0) == ' ' && s.length() == width + 1) {
                s.deleteCharAt(0);
            } else {
                s.setLength(width);
                for (int i = 0; i < width; i++) s.setCharAt(i, '*');
            }
        } else {
            for (// padding
            int i = 0; // padding
            i < width - s.length(); // padding
            i++) s.insert(0, ' ');
        }
        if (!trailing && decimal > 0) {
            // delete trailing zeros
            while (s.charAt(s.length() - 1) == '0') s.deleteCharAt(s.length() - 1);
            if (s.charAt(s.length() - 1) == '.')
                s.deleteCharAt(s.length() - 1);
        }
        return toAppendTo.append(s);
    }

    public static String pattern(int w, int d) {
        StringBuffer s = new StringBuffer();
        s.append(padding(w - d - 3, '#'));
        if (d == 0)
            s.append('0');
        else {
            s.append("0.");
            s.append(padding(d, '0'));
        }
        return s.toString();
    }

    private static StringBuffer padding(int n, char c) {
        StringBuffer text = new StringBuffer();
        for (int i = 0; i < n; i++) {
            text.append(c);
        }
        return text;
    }

    public int width() {
        if (!trailing)
            throw new RuntimeException("flexible width");
        return width;
    }

    /**
     * Returns the revision string.
     *
     * @return		the revision
     */


for (// padding
            int i = 0; // padding
            i < width - s.length(); // padding
            i++) s.insert(0, ' ');

package weka.core.matrix;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

/**
 * Class for the format of floating point numbers
 *
 * @author Yong Wang
 * @version $Revision$
 */
public class FloatingPointFormat extends DecimalFormat implements RevisionHandler {

    private static final long serialVersionUID = 4500373755333429499L;

    protected DecimalFormat nf;

    protected int width;

    protected int decimal;

    protected boolean trailing = true;

    public FloatingPointFormat() {
        this(8, 5);
    }

    public FloatingPointFormat(int digits) {
        this(8, 2);
    }

    public FloatingPointFormat(int w, int d) {
        width = w;
        decimal = d;
        nf = new DecimalFormat(pattern(w, d));
        nf.setPositivePrefix(" ");
        nf.setNegativePrefix("-");
    }

    public FloatingPointFormat(int w, int d, boolean trailingZeros) {
        this(w, d);
        this.trailing = trailingZeros;
    }

    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
        StringBuffer s = new StringBuffer(nf.format(number));
        if (s.length() > width) {
            if (s.charAt(0) == ' ' && s.length() == width + 1) {
                s.deleteCharAt(0);
            } else {
                s.setLength(width);
                for (int i = 0; i < width; i++) s.setCharAt(i, '*');
            }
        } else {

        }
        if (!trailing && decimal > 0) {
            // delete trailing zeros
            while (s.charAt(s.length() - 1) == '0') s.deleteCharAt(s.length() - 1);
            if (s.charAt(s.length() - 1) == '.')
                s.deleteCharAt(s.length() - 1);
        }
        return toAppendTo.append(s);
    }

    public static String pattern(int w, int d) {
        StringBuffer s = new StringBuffer();
        s.append(padding(w - d - 3, '#'));
        if (d == 0)
            s.append('0');
        else {
            s.append("0.");
            s.append(padding(d, '0'));
        }
        return s.toString();
    }

    private static StringBuffer padding(int n, char c) {
        StringBuffer text = new StringBuffer();
        for (int i = 0; i < n; i++) {
            text.append(c);
        }
        return text;
    }

    public int width() {
        if (!trailing)
            throw new RuntimeException("flexible width");
        return width;
    }

    /**
     * Returns the revision string.
     *
     * @return		the revision
     */
    public String getRevision() {
        return RevisionUtils.extract("$Revision$");
    }
}


while (s.charAt(s.length() - 1) == '0') s.deleteCharAt(s.length() - 1);

package weka.core.matrix;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

/**
 * Class for the format of floating point numbers
 *
 * @author Yong Wang
 * @version $Revision$
 */
public class FloatingPointFormat extends DecimalFormat implements RevisionHandler {

    private static final long serialVersionUID = 4500373755333429499L;

    protected DecimalFormat nf;

    protected int width;

    protected int decimal;

    protected boolean trailing = true;

    public FloatingPointFormat() {
        this(8, 5);
    }

    public FloatingPointFormat(int digits) {
        this(8, 2);
    }

    public FloatingPointFormat(int w, int d) {
        width = w;
        decimal = d;
        nf = new DecimalFormat(pattern(w, d));
        nf.setPositivePrefix(" ");
        nf.setNegativePrefix("-");
    }

    public FloatingPointFormat(int w, int d, boolean trailingZeros) {
        this(w, d);
        this.trailing = trailingZeros;
    }

    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
        StringBuffer s = new StringBuffer(nf.format(number));
        if (s.length() > width) {
            if (s.charAt(0) == ' ' && s.length() == width + 1) {
                s.deleteCharAt(0);
            } else {
                s.setLength(width);
                for (int i = 0; i < width; i++) s.setCharAt(i, '*');
            }
        } else {
            for (// padding
            int i = 0; // padding
            i < width - s.length(); // padding
            i++) s.insert(0, ' ');
        }
        if (!trailing && decimal > 0) {
            // delete trailing zeros

            if (s.charAt(s.length() - 1) == '.')
                s.deleteCharAt(s.length() - 1);
        }
        return toAppendTo.append(s);
    }

    public static String pattern(int w, int d) {
        StringBuffer s = new StringBuffer();
        s.append(padding(w - d - 3, '#'));
        if (d == 0)
            s.append('0');
        else {
            s.append("0.");
            s.append(padding(d, '0'));
        }
        return s.toString();
    }

    private static StringBuffer padding(int n, char c) {
        StringBuffer text = new StringBuffer();
        for (int i = 0; i < n; i++) {
            text.append(c);
        }
        return text;
    }

    public int width() {
        if (!trailing)
            throw new RuntimeException("flexible width");
        return width;
    }

    /**
     * Returns the revision string.
     *
     * @return		the revision
     */
    public String getRevision() {
        return RevisionUtils.extract("$Revision$");
    }
}


try {
                if (enabled) {
                    capturer.startCapture(1280, 720, 30);
                } else {
                    capturer.stopCapture();
                }
            } catch (InterruptedException e) {
                ALog.w(TAG, "Get interrupted while trying to stop video capture. " + e.getMessage());
            }

package com.bcm.messenger.chats.privatechat.webrtc;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bcm.messenger.utility.concurrent.SettableFuture;
import com.bcm.messenger.utility.logger.ALog;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.DataChannel;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoSink;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static com.bcm.messenger.chats.privatechat.webrtc.CameraState.Direction.BACK;
import static com.bcm.messenger.chats.privatechat.webrtc.CameraState.Direction.FRONT;
import static com.bcm.messenger.chats.privatechat.webrtc.CameraState.Direction.NONE;
import static com.bcm.messenger.chats.privatechat.webrtc.CameraState.Direction.PENDING;

public class PeerConnectionWrapper {

    private static final String TAG = PeerConnectionWrapper.class.getSimpleName();

    private static final PeerConnection.IceServer STUN_SERVER = new PeerConnection.IceServer("stun:stun1.l.google.com:19302");

    @NonNull
    private final PeerConnection peerConnection;

    @NonNull
    private AudioTrack audioTrack;

    @NonNull
    private AudioSource audioSource;

    @NonNull
    private final Camera camera;

    @Nullable
    private final VideoSource videoSource;

    @Nullable
    private final VideoTrack videoTrack;

    @Nullable
    private MediaStream mediaStream;

    @NonNull
    private List<PeerConnection.IceServer> iceServers;

    public PeerConnectionWrapper(@NonNull Context context, @NonNull PeerConnectionFactory factory, @NonNull PeerConnection.Observer observer, @NonNull VideoSink localRenderer, @NonNull List<PeerConnection.IceServer> turnServers, @NonNull CameraEventListener cameraEventListener, @NonNull EglBase eglBase, boolean hideIp) {
        List<PeerConnection.IceServer> iceServers = new LinkedList<>();
        iceServers.add(STUN_SERVER);
        iceServers.addAll(turnServers);
        this.iceServers = iceServers;
        MediaConstraints constraints = new MediaConstraints();
        MediaConstraints audioConstraints = new MediaConstraints();
        PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
        configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
        configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
        if (hideIp) {
            configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
        }
        constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        this.peerConnection = factory.createPeerConnection(configuration, constraints, observer);
        this.peerConnection.setAudioPlayout(false);
        this.peerConnection.setAudioRecording(false);
        this.mediaStream = factory.createLocalMediaStream("ARDAMS");
        this.audioSource = factory.createAudioSource(audioConstraints);
        this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
        this.audioTrack.setEnabled(false);
        mediaStream.addTrack(audioTrack);
        this.camera = new Camera(context, cameraEventListener);
        if (camera.capturer != null) {
            this.videoSource = factory.createVideoSource(false);
            this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
            camera.capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, videoSource.getCapturerObserver());
            this.videoTrack.addSink(localRenderer);
            this.videoTrack.setEnabled(false);
            mediaStream.addTrack(videoTrack);
        } else {
            this.videoSource = null;
            this.videoTrack = null;
        }
        this.peerConnection.addStream(mediaStream);
    }

    public void reInitAudioTrack(@NonNull PeerConnectionFactory factory) {
        this.peerConnection.removeStream(this.mediaStream);
        this.mediaStream.removeTrack(audioTrack);
        MediaConstraints audioConstraints = new MediaConstraints();
        audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        this.audioSource = factory.createAudioSource(audioConstraints);
        this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
        this.audioTrack.setEnabled(false);
        this.mediaStream.addTrack(audioTrack);
        this.peerConnection.addStream(this.mediaStream);
    }

    public void setVideoEnabled(boolean enabled) {
        if (this.videoTrack != null) {
            this.videoTrack.setEnabled(enabled);
        }
        camera.setEnabled(enabled);
    }

    public void flipCamera() {
        camera.flip();
    }

    public CameraState getCameraState() {
        return new CameraState(camera.getActiveDirection(), camera.getCount());
    }

    public void setCommunicationMode() {
        this.peerConnection.setAudioPlayout(true);
        this.peerConnection.setAudioRecording(true);
    }

    public void setAudioEnabled(boolean enabled) {
        this.audioTrack.setEnabled(enabled);
    }

    public DataChannel createDataChannel(String name) {
        DataChannel.Init dataChannelConfiguration = new DataChannel.Init();
        dataChannelConfiguration.ordered = true;
        return this.peerConnection.createDataChannel(name, dataChannelConfiguration);
    }

    public List<PeerConnection.IceServer> getServerList() {
        return iceServers;
    }

    public SessionDescription createOffer(MediaConstraints mediaConstraints) throws PeerConnectionException {
        final SettableFuture<SessionDescription> future = new SettableFuture<>();
        peerConnection.createOffer(new SdpObserver() {

            @Override
            public void onCreateSuccess(SessionDescription sdp) {
                future.set(sdp);
            }

            @Override
            public void onCreateFailure(String error) {
                future.setException(new PeerConnectionException(error));
            }

            @Override
            public void onSetSuccess() {
                throw new AssertionError();
            }

            @Override
            public void onSetFailure(String error) {
                throw new AssertionError();
            }
        }, mediaConstraints);
        try {
            return correctSessionDescription(future.get());
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        } catch (ExecutionException e) {
            throw new PeerConnectionException(e);
        }
    }

    public SessionDescription createAnswer(MediaConstraints mediaConstraints) throws PeerConnectionException {
        final SettableFuture<SessionDescription> future = new SettableFuture<>();
        peerConnection.createAnswer(new SdpObserver() {

            @Override
            public void onCreateSuccess(SessionDescription sdp) {
                future.set(sdp);
            }

            @Override
            public void onCreateFailure(String error) {
                future.setException(new PeerConnectionException(error));
            }

            @Override
            public void onSetSuccess() {
                throw new AssertionError();
            }

            @Override
            public void onSetFailure(String error) {
                throw new AssertionError();
            }
        }, mediaConstraints);
        try {
            return correctSessionDescription(future.get());
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        } catch (ExecutionException e) {
            throw new PeerConnectionException(e);
        }
    }

    public void setRemoteDescription(SessionDescription sdp) throws PeerConnectionException {
        final SettableFuture<Boolean> future = new SettableFuture<>();
        peerConnection.setRemoteDescription(new SdpObserver() {

            @Override
            public void onCreateSuccess(SessionDescription sdp) {
            }

            @Override
            public void onCreateFailure(String error) {
            }

            @Override
            public void onSetSuccess() {
                future.set(true);
            }

            @Override
            public void onSetFailure(String error) {
                future.setException(new PeerConnectionException(error));
            }
        }, sdp);
        try {
            future.get();
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        } catch (ExecutionException e) {
            throw new PeerConnectionException(e);
        }
    }

    public void setLocalDescription(SessionDescription sdp) throws PeerConnectionException {
        final SettableFuture<Boolean> future = new SettableFuture<>();
        peerConnection.setLocalDescription(new SdpObserver() {

            @Override
            public void onCreateSuccess(SessionDescription sdp) {
                throw new AssertionError();
            }

            @Override
            public void onCreateFailure(String error) {
                throw new AssertionError();
            }

            @Override
            public void onSetSuccess() {
                future.set(true);
            }

            @Override
            public void onSetFailure(String error) {
                future.setException(new PeerConnectionException(error));
            }
        }, sdp);
        try {
            future.get();
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        } catch (ExecutionException e) {
            throw new PeerConnectionException(e);
        }
    }

    public void dispose() {
        this.camera.dispose();
        if (this.videoSource != null) {
            this.videoSource.dispose();
        }
        this.audioSource.dispose();
        this.peerConnection.close();
        this.peerConnection.dispose();
    }

    public boolean addIceCandidate(IceCandidate candidate) {
        return this.peerConnection.addIceCandidate(candidate);
    }

    private SessionDescription correctSessionDescription(SessionDescription sessionDescription) {
        String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n");
        updatedSdp = updatedSdp.replaceAll(".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", "");
        return new SessionDescription(sessionDescription.type, updatedSdp);
    }

    public static class PeerConnectionException extends Exception {

        public PeerConnectionException(String error) {
            super(error);
        }

        public PeerConnectionException(Throwable throwable) {
            super(throwable);
        }
    }

    private static class Camera implements CameraVideoCapturer.CameraSwitchHandler {

        @Nullable
        private final CameraVideoCapturer capturer;

        private final CameraEventListener cameraEventListener;

        private final int cameraCount;

        private CameraState.Direction activeDirection;

        private boolean enabled;

        Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener) {
            this.cameraEventListener = cameraEventListener;
            CameraEnumerator enumerator = getCameraEnumerator(context);
            cameraCount = enumerator.getDeviceNames().length;
            CameraVideoCapturer cameraVideoCapturer = createVideoCapturer(enumerator, FRONT);
            if (cameraVideoCapturer != null) {
                activeDirection = FRONT;
            } else {
                cameraVideoCapturer = createVideoCapturer(enumerator, BACK);
                if (cameraVideoCapturer != null) {
                    activeDirection = BACK;
                } else {
                    activeDirection = NONE;
                }
            }
            capturer = cameraVideoCapturer;
        }

        void flip() {
            if (capturer == null || cameraCount < 2) {
                ALog.w(TAG, "Only have " + cameraCount + " cameras in this device.");
                return;
            }
            activeDirection = PENDING;
            capturer.switchCamera(this);
        }

        void setEnabled(boolean enabled) {
            this.enabled = enabled;
            if (capturer == null) {
                return;
            }

        }

        void dispose() {
            if (capturer != null) {
                capturer.dispose();
            }
        }

        int getCount() {
            return cameraCount;
        }

        @NonNull
        CameraState.Direction getActiveDirection() {
            return enabled ? activeDirection : NONE;
        }

        @Nullable
        CameraVideoCapturer getCapturer() {
            return capturer;
        }

        @Nullable
        private CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator, @NonNull CameraState.Direction direction) {
            String[] deviceNames = enumerator.getDeviceNames();
            for (String deviceName : deviceNames) {
                if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) || (direction == BACK && enumerator.isBackFacing(deviceName))) {
                    return enumerator.createCapturer(deviceName, null);
                }
            }
            return null;
        }

        @NonNull
        private CameraEnumerator getCameraEnumerator(@NonNull Context context) {
            boolean camera2EnumeratorIsSupported = false;
            try {
                camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
            } catch (final Throwable throwable) {
                ALog.w(TAG, "Camera2Enumator.isSupport() threw. " + throwable.getMessage());
            }
            ALog.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
            return camera2EnumeratorIsSupported ? new Camera2Enumerator(context) : new Camera1Enumerator(true);
        }

        @Override
        public void onCameraSwitchDone(boolean isFrontFacing) {
            activeDirection = isFrontFacing ? FRONT : BACK;
            cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
        }

        @Override
        public void onCameraSwitchError(String errorMessage) {
            ALog.e(TAG, "onCameraSwitchError: " + errorMessage);
            cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
        }
    }

    public interface CameraEventListener {

        void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
    }
}