Android Open Source - esr-scanner Scanner I M E






From Project

Back to project page esr-scanner.

License

The source code is released under:

Apache License

If you think the Android project esr-scanner listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 *// w w w .j  a  v  a 2  s .  c  o m
 */
package ch.luklanis.esscan.ime;

import android.app.DialogFragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Color;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.KeyboardView;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.style.CharacterStyle;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.googlecode.tesseract.android.TessBaseAPI;

import java.io.File;
import java.io.IOException;

import ch.luklanis.esscan.BeepManager;
import ch.luklanis.esscan.CaptureActivity;
import ch.luklanis.esscan.CaptureActivityHandler;
import ch.luklanis.esscan.IBase;
import ch.luklanis.esscan.OcrInitAsyncTask;
import ch.luklanis.esscan.OcrResult;
import ch.luklanis.esscan.OcrResultText;
import ch.luklanis.esscan.PreferencesActivity;
import ch.luklanis.esscan.R;
import ch.luklanis.esscan.ViewfinderView;
import ch.luklanis.esscan.camera.CameraManager;
import ch.luklanis.esscan.paymentslip.EsrResult;
import ch.luklanis.esscan.paymentslip.EsrValidation;
import ch.luklanis.esscan.paymentslip.PsResult;
import ch.luklanis.esscan.paymentslip.PsValidation;

/**
 * @author llandis
 */
public class ScannerIME extends InputMethodService
        implements KeyboardView.OnKeyboardActionListener, SurfaceHolder.Callback, IBase {

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

    /**
     * The default OCR engine to use.
     */
    public static final String DEFAULT_OCR_ENGINE_MODE = "Tesseract";

    public static final String EXTERNAL_STORAGE_DIRECTORY = "ESRScan";

    private static final int OCR_ENGINE_MODE = TessBaseAPI.OEM_TESSERACT_ONLY;
    private static final String SOURCE_LANGUAGE_CODE_OCR = "psl";
    private static final String SOURCE_LANGUAGE_READABLE = "payment slip";

    private static final int PAGE_SEGMENTATION_MODE = TessBaseAPI.PageSegMode.PSM_SINGLE_LINE;
    private static final String CHARACTER_WHITELIST = "0123456789>+";

    private CameraManager mCameraManager;
    private CaptureActivityHandler mCaptureActivityHandler;
    private ViewfinderView mViewfinderView;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private TextView mStatusViewBottomLeft;
    private boolean mHasSurface;
    private TessBaseAPI mBaseApi; // Java interface for the Tesseract OCR engine
    // "English"

    private SharedPreferences mSharedPreferences;
    private OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener;

    private PsValidation mPsValidation;

    private int mLastValidationStep;

    private boolean mShowOcrResult;

    private TextView mStatusViewBottomRight;

    private RelativeLayout mInputView;

    private BeepManager mBeepManager;

    @Override
    public void onStartInputView(EditorInfo info, boolean restarting) {
        super.onStartInputView(info, restarting);

        if (mInputView == null) {
            return;
        }

        updateFullscreenMode();

        mSurfaceView = (SurfaceView) mInputView.findViewById(R.id.preview_view);

        mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                mCameraManager.focus();
                return true;
            }
        });

        mCameraManager = new CameraManager(mSurfaceView);

        mViewfinderView = (ViewfinderView) mInputView.findViewById(R.id.viewfinder_view);

        mViewfinderView.setCameraManager(mCameraManager);

        mStatusViewBottomLeft = (TextView) mInputView.findViewById(R.id.status_view_bottom_left);

        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

        mBeepManager = new BeepManager(this);

        mStatusViewBottomRight = (TextView) mInputView.findViewById(R.id.status_view_bottom_right);
        mStatusViewBottomRight.setVisibility(View.GONE);

        if ((info.inputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
            mStatusViewBottomRight.setText(getResources().getString(R.string.msg_unsupported_field));
            mStatusViewBottomRight.setVisibility(View.VISIBLE);
        }

        mPsValidation = new EsrValidation();
        this.mLastValidationStep = mPsValidation.getCurrentStep();

        mCaptureActivityHandler = null;

        retrievePreferences();

        resetStatusView();
        mPsValidation.gotoBeginning(true);
        this.mLastValidationStep = mPsValidation.getCurrentStep();

        // Do OCR engine initialization, if necessary
        if (mBaseApi == null) {
            // Initialize the OCR engine
            File storageDirectory = getFilesDir();
            if (storageDirectory != null) {
                initOcrEngine(storageDirectory, SOURCE_LANGUAGE_CODE_OCR, SOURCE_LANGUAGE_READABLE);
            }
        } else {
            resumeOcrEngine();
        }

        // Set up the camera preview surface.
        mSurfaceHolder = mSurfaceView.getHolder();

        if (mHasSurface) {
            // The activity was paused but not stopped, so the surface still
            // exists. Therefore
            // surfaceCreated() won't be called, so init the camera here.
            initCamera(mSurfaceHolder);
        } else {
            // Install the callback and wait for surfaceCreated() to init the
            // camera.
            mSurfaceHolder.addCallback(this);
            mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(TAG, "surfaceCreated()");

        if (holder == null) {
            Log.e(TAG, "surfaceCreated gave us a null surface");
        }

        // Only initialize the camera if the OCR engine is ready to go.
        if (!mHasSurface) {
            Log.d(TAG, "surfaceCreated(): calling initCamera()...");
            initCamera(holder);
        }

        mHasSurface = true;
    }

    @Override
    public void onFinishInputView(boolean finishingInput) {
        shutdownOcr();

        super.onFinishInputView(finishingInput);
    }

    private void shutdownOcr() {
        if (mCaptureActivityHandler != null) {
            mCaptureActivityHandler.quitSynchronously();
            mCaptureActivityHandler = null;
        }

        // Stop using the camera, to avoid conflicting with other camera-based
        // apps
        if (mCameraManager != null) {
            mCameraManager.closeDriver();
            mCameraManager = null;
        }

        if (!mHasSurface && mInputView != null) {
            SurfaceView surfaceView = (SurfaceView) mInputView.findViewById(R.id.preview_view);
            SurfaceHolder surfaceHolder = surfaceView.getHolder();
            surfaceHolder.removeCallback(this);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mBaseApi != null) {
            mBaseApi.end();
            mBaseApi = null;
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if (this.mPsValidation == null) {
            return super.onKeyDown(keyCode, event);
        }

        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                if (this.mPsValidation.getCurrentStep() > 1) {
                    mPsValidation.gotoBeginning(true);
                    this.mLastValidationStep = mPsValidation.getCurrentStep();
                    resetStatusView();
                    return true;
                }

                break;
            // Use volume up/down to turn on light
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                mCameraManager.setTorch(false);
                return true;
            case KeyEvent.KEYCODE_VOLUME_UP:
                mCameraManager.setTorch(true);
                return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    public Handler getHandler() {
        return mCaptureActivityHandler;
    }

    public CameraManager getCameraManager() {
        return mCameraManager;
    }

    public PsValidation getValidation() {
        return mPsValidation;
    }

    /**
     * Method to start or restart recognition after the OCR engine has been
     * initialized, or after the app regains focus. Sets state related settings
     * and OCR engine parameters, and requests camera initialization.
     */
    public void resumeOcrEngine() {
        Log.d(TAG, "resumeOcrEngine()");

        // This method is called when Tesseract has already been successfully
        // initialized, so set
        // isEngineReady = true here.

        if (mBaseApi != null) {
            if (mCaptureActivityHandler != null) {
                mCaptureActivityHandler.startDecode(mBaseApi);
            }

            mBaseApi.setPageSegMode(PAGE_SEGMENTATION_MODE);
            mBaseApi.setVariable(TessBaseAPI.VAR_CHAR_BLACKLIST, "");
            mBaseApi.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, CHARACTER_WHITELIST);
        }
    }

    /**
     * Called to resume recognition after translation in continuous mode.
     */
    void restartPreviewAfterDelay(long delayMS) {
        if (mCaptureActivityHandler != null) {
            mCaptureActivityHandler.sendEmptyMessageDelayed(R.id.restart_decode, delayMS);
        }

        resumeOcrEngine();
        resetStatusView();
    }

    /**
     * Initializes the camera and starts the handler to begin previewing.
     */
    private void initCamera(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "initCamera()");
        try {
            // Open and initialize the camera
            mCameraManager.openDriver(surfaceHolder);

            if (mCaptureActivityHandler == null) {
                // Creating the handler starts the preview, which can also throw
                // a RuntimeException.
                mCaptureActivityHandler = new CaptureActivityHandler(this, mCameraManager);

                if (mBaseApi != null) {
                    mCaptureActivityHandler.startDecode(mBaseApi);
                }
            }
        } catch (IOException ioe) {
            showErrorMessage("Error", "Could not initialize camera. Please try restarting device.");
        } catch (RuntimeException e) {
            // Barcode Scanner has seen crashes in the wild of this variety:
            // java.?lang.?RuntimeException: Fail to connect to camera service
            showErrorMessage("Error", "Could not initialize camera. Please try restarting device.");
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        mHasSurface = false;
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    /**
     * Requests initialization of the OCR engine with the given parameters.
     *
     * @param storageRoot  Path to location of the tessdata directory to use
     * @param languageCode Three-letter ISO 639-3 language code for OCR
     * @param languageName Name of the language for OCR, for example, "English"
     */
    private void initOcrEngine(File storageRoot, String languageCode, String languageName) {
        // Start AsyncTask to install language data and init OCR
        new OcrInitAsyncTask(this,
                new TessBaseAPI(),
                null,
                languageCode,
                languageName,
                OCR_ENGINE_MODE).execute(storageRoot.toString());
    }

    public void showResult(PsResult psResult) {
        mBeepManager.playBeepSoundAndVibrate();

        InputConnection inputConnection = getCurrentInputConnection();

        String completeCode = psResult.getCompleteCode();
        int indexOfNewline = completeCode.indexOf('\n');
        if (indexOfNewline < 0) {
            inputConnection.commitText(completeCode, 1);
        } else {
            inputConnection.commitText(completeCode.substring(0, indexOfNewline), 1);
        }

        this.hideWindow();
    }

    /**
     * Displays information relating to the results of a successful real-time
     * OCR request.
     *
     * @param ocrResult Object representing successful OCR results
     */
    public void presentOcrDecodeResult(OcrResult ocrResult) {

        // Send an OcrResultText object to the ViewfinderView for text rendering
        mViewfinderView.addResultText(new OcrResultText(ocrResult.getText(),
                ocrResult.getWordConfidences(),
                ocrResult.getMeanConfidence(),
                ocrResult.getBitmapDimensions(),
                ocrResult.getWordBoundingBoxes(),
                ocrResult.getTextlineBoundingBoxes(),
                ocrResult.getRegionBoundingBoxes()));

        mStatusViewBottomLeft.setText(ocrResult.getText());

        if (this.mPsValidation.getCurrentStep() != this.mLastValidationStep) {
            this.mLastValidationStep = this.mPsValidation.getCurrentStep();
            mBeepManager.playBeepSoundAndVibrate();
            refreshStatusView();
        }
    }

    /**
     * Given either a Spannable String or a regular String and a token, apply
     * the given CharacterStyle to the span between the tokens.
     * <p/>
     * NOTE: This method was adapted from:
     * http://www.androidengineer.com/2010/08
     * /easy-method-for-formatting-android.html
     * <p/>
     * <p/>
     * For example, {@code setSpanBetweenTokens("Hello ##world##!", "##", new
     *ForegroundColorSpan(0xFFFF0000));} will return a CharSequence
     * {@code "Hello world!"} with {@code world} in red.
     */
    @SuppressWarnings("unused")
    private CharSequence setSpanBetweenTokens(CharSequence text, String token,
                                              CharacterStyle... cs) {
        // Start and end refer to the points where the span will apply
        int tokenLen = token.length();
        int start = text.toString().indexOf(token) + tokenLen;
        int end = text.toString().indexOf(token, start);

        if (start > -1 && end > -1) {
            // Copy the spannable string to a mutable spannable string
            SpannableStringBuilder ssb = new SpannableStringBuilder(text);
            for (CharacterStyle c : cs)
                ssb.setSpan(c, start, end, 0);
            text = ssb;
        }
        return text;
    }

    /**
     * Resets view elements.
     */
    private void resetStatusView() {

        View orangeStatusView = mInputView.findViewById(R.id.status_view_top_orange);
        View redStatusView = mInputView.findViewById(R.id.status_view_top_red);

        if (this.mPsValidation.getSpokenType().equals(EsrResult.PS_TYPE_NAME)) {
            redStatusView.setVisibility(View.GONE);
            orangeStatusView.setVisibility(View.VISIBLE);
        } else {
            orangeStatusView.setVisibility(View.GONE);
            redStatusView.setVisibility(View.VISIBLE);
        }

        refreshStatusView();

        mStatusViewBottomLeft.setText("");

        if (mShowOcrResult) {
            mStatusViewBottomLeft.setVisibility(View.VISIBLE);
        }

        mViewfinderView.removeResultText();
        mViewfinderView.setVisibility(View.VISIBLE);

        Log.i(TAG, "resetStatusView: set lastItem to null");
        mViewfinderView.removeResultText();
    }

    private void refreshStatusView() {
        TextView statusView1;
        TextView statusView2;
        TextView statusView3;

        if (this.mPsValidation.getSpokenType().equals(EsrResult.PS_TYPE_NAME)) {
            statusView1 = (TextView) mInputView.findViewById(R.id.status_view_1_orange);
            statusView2 = (TextView) mInputView.findViewById(R.id.status_view_2_orange);
            statusView3 = (TextView) mInputView.findViewById(R.id.status_view_3_orange);
        } else {
            statusView1 = (TextView) mInputView.findViewById(R.id.status_view_1_red);
            statusView2 = (TextView) mInputView.findViewById(R.id.status_view_2_red);
            statusView3 = (TextView) mInputView.findViewById(R.id.status_view_3_red);
        }

        statusView1.setBackgroundResource(0);
        statusView2.setBackgroundResource(0);
        statusView3.setBackgroundResource(0);

        switch (this.mPsValidation.getCurrentStep()) {
            case 1:
                statusView1.setBackgroundResource(R.drawable.status_view_background);
                break;
            case 2:
                statusView2.setBackgroundResource(R.drawable.status_view_background);
                break;
            case 3:
                statusView3.setBackgroundResource(R.drawable.status_view_background);
                break;

            default:
                break;
        }
    }

    /**
     * Request the viewfinder to be invalidated.
     */
    public void drawViewfinder() {
        mViewfinderView.drawViewfinder();
    }

    /**
     * Returns a string that represents which OCR engine(s) are currently set to
     * be run.
     *
     * @return OCR engine mode
     */
    String getOcrEngineModeName() {
        return DEFAULT_OCR_ENGINE_MODE;
    }

    /**
     * Gets values from shared preferences and sets the corresponding data
     * members in this activity.
     */
    private void retrievePreferences() {
        // Retrieve from preferences, and set in this Activity, the language
        // preferences
        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);

        mSharedPreferences.registerOnSharedPreferenceChangeListener(
                mOnSharedPreferenceChangeListener);

        mShowOcrResult = mSharedPreferences.getBoolean(PreferencesActivity.KEY_SHOW_DEBUG_OUTPUT_PREFERENCE,
                false);

        mBeepManager.updatePrefs();
    }

    /**
     * Displays an error message dialog box to the user on the UI thread.
     *
     * @param title   The title for the dialog box
     * @param message The error message to be displayed
     */
    public DialogFragment showErrorMessage(String title, String message) {
        mStatusViewBottomRight.setText(message);
        return null;
    }

    public void setBaseApi(TessBaseAPI baseApi) {
        this.mBaseApi = baseApi;
    }

    /**
     * Called by the framework when your view for creating input needs to be
     * generated. This will be called the first time your input method is
     * displayed, and every time it needs to be re-created such as due to a
     * configuration change.
     */
    @Override
    public View onCreateInputView() {

        shutdownOcr();

        Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

        int screenWidth = display.getWidth();
        int screenHeight = display.getHeight();

        if (screenWidth < screenHeight) {
            mInputView = null;
            LinearLayout layout = new LinearLayout(this);
            layout.setBackgroundColor(Color.WHITE);
            layout.setOrientation(LinearLayout.VERTICAL);
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT));
            layout.setGravity(Gravity.BOTTOM);

            TextView textView = new TextView(this);
            textView.setText(getResources().getString(R.string.msg_unsupported_orientation));
            textView.setTextColor(Color.RED);
            textView.setPadding(6, 0, 6, 0);
            textView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT));
            layout.addView(textView, 0);

            Button button = new Button(this);
            button.setText(R.string.button_switch_ime);
            button.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    InputMethodManager im = (InputMethodManager) getContext().getSystemService(
                            Context.INPUT_METHOD_SERVICE);
                    im.showInputMethodPicker();
                }
            });
            button.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT));
            layout.addView(button, 1);

            return layout;
        }

        mInputView = (RelativeLayout) getLayoutInflater().inflate(R.layout.input, null);

        return mInputView;
    }

    @Override
    public void onKey(int primaryCode, int[] keyCodes) {

    }

    @Override
    public void onPress(int primaryCode) {

    }

    @Override
    public void onRelease(int primaryCode) {

    }

    @Override
    public void onText(CharSequence text) {

    }

    @Override
    public void swipeDown() {

    }

    @Override
    public void swipeLeft() {

    }

    @Override
    public void swipeRight() {
    }

    @Override
    public void swipeUp() {

    }

    @Override
    public Context getContext() {
        return this;
    }

    @Override
    public boolean onEvaluateFullscreenMode() {
        return false;
    }

    @Override
    public void showDialogAndRestartScan(int resourceId) {
    }

    @Override
    public void setValidation(PsValidation validation) {
        this.mPsValidation = validation;
        resetStatusView();
    }

}




Java Source Code List

ch.luklanis.android.common.PlatformSupportManager.java
ch.luklanis.android.common.executor.AsyncTaskExecInterface.java
ch.luklanis.android.common.executor.AsyncTaskExecManager.java
ch.luklanis.android.common.executor.DefaultAsyncTaskExecInterface.java
ch.luklanis.android.common.executor.HoneycombAsyncTaskExecInterface.java
ch.luklanis.esscan.BeepManager.java
ch.luklanis.esscan.CaptureActivityHandler.java
ch.luklanis.esscan.CaptureActivity.java
ch.luklanis.esscan.DecodeHandler.java
ch.luklanis.esscan.DecodeThread.java
ch.luklanis.esscan.EsrBaseActivity.java
ch.luklanis.esscan.FinishListener.java
ch.luklanis.esscan.HelpActivity.java
ch.luklanis.esscan.IBase.java
ch.luklanis.esscan.Intents.java
ch.luklanis.esscan.LuminanceSource.java
ch.luklanis.esscan.OcrCharacterHelper.java
ch.luklanis.esscan.OcrInitAsyncTask.java
ch.luklanis.esscan.OcrRecognizeAsyncTask.java
ch.luklanis.esscan.OcrResultFailure.java
ch.luklanis.esscan.OcrResultText.java
ch.luklanis.esscan.OcrResult.java
ch.luklanis.esscan.PlanarYUVLuminanceSource.java
ch.luklanis.esscan.PreferencesActivity.java
ch.luklanis.esscan.ViewfinderView.java
ch.luklanis.esscan.camera.AutoFocusManager.java
ch.luklanis.esscan.camera.CameraConfigurationManager.java
ch.luklanis.esscan.camera.CameraManager.java
ch.luklanis.esscan.camera.PreviewCallback.java
ch.luklanis.esscan.camera.exposure.DefaultExposureInterface.java
ch.luklanis.esscan.camera.exposure.ExposureInterface.java
ch.luklanis.esscan.camera.exposure.ExposureManager.java
ch.luklanis.esscan.camera.exposure.FroyoExposureInterface.java
ch.luklanis.esscan.camera.open.DefaultOpenCameraInterface.java
ch.luklanis.esscan.camera.open.GingerbreadOpenCameraInterface.java
ch.luklanis.esscan.camera.open.OpenCameraInterface.java
ch.luklanis.esscan.camera.open.OpenCameraManager.java
ch.luklanis.esscan.codesend.Crypto.java
ch.luklanis.esscan.codesend.ESRSenderHttp.java
ch.luklanis.esscan.codesend.IEsrSender.java
ch.luklanis.esscan.dialogs.BankProfileDialogFragment.java
ch.luklanis.esscan.dialogs.BankProfileListDialog.java
ch.luklanis.esscan.dialogs.CancelOkDialog.java
ch.luklanis.esscan.dialogs.OkDialog.java
ch.luklanis.esscan.dialogs.OptionalOkDialog.java
ch.luklanis.esscan.history.BankProfile.java
ch.luklanis.esscan.history.DBHelper.java
ch.luklanis.esscan.history.GetHistoryAsyncTask.java
ch.luklanis.esscan.history.HistoryActivity.java
ch.luklanis.esscan.history.HistoryExportUpdateAsyncTask.java
ch.luklanis.esscan.history.HistoryFragment.java
ch.luklanis.esscan.history.HistoryItemAdapter.java
ch.luklanis.esscan.history.HistoryItem.java
ch.luklanis.esscan.history.HistoryManager.java
ch.luklanis.esscan.history.PsDetailActivity.java
ch.luklanis.esscan.history.PsDetailFragment.java
ch.luklanis.esscan.ime.ScannerIME.java
ch.luklanis.esscan.paymentslip.DTAFileCreator.java
ch.luklanis.esscan.paymentslip.EsIbanResult.java
ch.luklanis.esscan.paymentslip.EsIbanValidation.java
ch.luklanis.esscan.paymentslip.EsrResult.java
ch.luklanis.esscan.paymentslip.EsrValidation.java
ch.luklanis.esscan.paymentslip.PsResult.java
ch.luklanis.esscan.paymentslip.PsValidation.java