1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt;
  27 
  28 import java.util.Collections;
  29 import java.util.Locale;
  30 import java.util.Map;
  31 import java.util.HashMap;
  32 import java.awt.AWTEvent;
  33 import java.awt.AWTException;
  34 import java.awt.Component;
  35 import java.awt.Container;
  36 import java.awt.EventQueue;
  37 import java.awt.Window;
  38 import java.awt.im.InputMethodHighlight;
  39 import java.awt.im.spi.InputMethodContext;
  40 import sun.awt.im.InputMethodAdapter;
  41 import java.awt.event.InputMethodEvent;
  42 import java.awt.font.TextAttribute;
  43 import java.awt.font.TextHitInfo;
  44 import java.awt.peer.ComponentPeer;
  45 import java.lang.Character.Subset;
  46 import java.text.AttributedString;
  47 import java.text.AttributedCharacterIterator;
  48 
  49 import java.io.File;
  50 import java.io.FileReader;
  51 import java.io.BufferedReader;
  52 import java.io.IOException;
  53 import java.lang.ref.WeakReference;
  54 import sun.util.logging.PlatformLogger;
  55 import java.util.StringTokenizer;
  56 import java.util.regex.Pattern;
  57 
  58 
  59 /**
  60  * Input Method Adapter for XIM
  61  *
  62  * @author JavaSoft International
  63  */
  64 public abstract class X11InputMethod extends InputMethodAdapter {
  65     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11InputMethod");
  66     /*
  67      * The following XIM* values must be the same as those defined in
  68      * Xlib.h
  69      */
  70     private static final int XIMReverse = (1<<0);
  71     private static final int XIMUnderline = (1<<1);
  72     private static final int XIMHighlight = (1<<2);
  73     private static final int XIMPrimary = (1<<5);
  74     private static final int XIMSecondary = (1<<6);
  75     private static final int XIMTertiary = (1<<7);
  76 
  77     /*
  78      * visible position values
  79      */
  80     private static final int XIMVisibleToForward = (1<<8);
  81     private static final int XIMVisibleToBackward = (1<<9);
  82     private static final int XIMVisibleCenter = (1<<10);
  83     private static final int XIMVisibleMask = (XIMVisibleToForward|
  84                                                XIMVisibleToBackward|
  85                                                XIMVisibleCenter);
  86 
  87     private Locale locale;
  88     private static boolean isXIMOpened = false;
  89     protected Container clientComponentWindow = null;
  90     private Component awtFocussedComponent = null;
  91     private Component lastXICFocussedComponent = null;
  92     private boolean   isLastXICActive = false;
  93     private boolean   isLastTemporary = false;
  94     private boolean   isActive = false;
  95     private boolean   isActiveClient = false;
  96     private static Map<TextAttribute, ?>[] highlightStyles;
  97     private boolean disposed = false;
  98 
  99     //reset the XIC if necessary
 100     private boolean   needResetXIC = false;
 101     private WeakReference<Component> needResetXICClient = new WeakReference<>(null);
 102 
 103     // The use of compositionEnableSupported is to reduce unnecessary
 104     // native calls if set/isCompositionEnabled
 105     // throws UnsupportedOperationException.
 106     // It is set to false if that exception is thrown first time
 107     // either of the two methods are called.
 108     private boolean compositionEnableSupported = true;
 109     // The savedCompositionState indicates the composition mode when
 110     // endComposition or setCompositionEnabled is called. It doesn't always
 111     // reflect the actual composition state because it doesn't get updated
 112     // when the user changes the composition state through direct interaction
 113     // with the input method. It is used to save the composition mode when
 114     // focus is traversed across different client components sharing the
 115     // same java input context. Also if set/isCompositionEnabled are not
 116     // supported, it remains false.
 117     private boolean savedCompositionState = false;
 118 
 119     // variables to keep track of preedit context.
 120     // these variables need to be accessed within AWT_LOCK/UNLOCK
 121     private String committedText = null;
 122     private StringBuffer composedText = null;
 123     private IntBuffer rawFeedbacks;
 124 
 125     // private data (X11InputMethodData structure defined in
 126     // awt_InputMethod.c) for native methods
 127     // this structure needs to be accessed within AWT_LOCK/UNLOCK
 128     private transient long pData = 0; // accessed by native
 129 
 130     // Initialize highlight mapping table
 131     static {
 132         @SuppressWarnings({"unchecked", "rawtypes"})
 133         Map<TextAttribute, ?> styles[] = new Map[4];
 134         HashMap<TextAttribute, Object> map;
 135 
 136         // UNSELECTED_RAW_TEXT_HIGHLIGHT
 137         map = new HashMap<>(1);
 138         map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
 139         styles[0] = Collections.unmodifiableMap(map);
 140 
 141         // SELECTED_RAW_TEXT_HIGHLIGHT
 142         map = new HashMap<>(1);
 143         map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);
 144         styles[1] = Collections.unmodifiableMap(map);
 145 
 146         // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT
 147         map = new HashMap<>(1);
 148         map.put(TextAttribute.INPUT_METHOD_UNDERLINE,
 149                 TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
 150         styles[2] = Collections.unmodifiableMap(map);
 151 
 152         // SELECTED_CONVERTED_TEXT_HIGHLIGHT
 153         map = new HashMap<>(1);
 154         map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);
 155         styles[3] = Collections.unmodifiableMap(map);
 156 
 157         highlightStyles = styles;
 158     }
 159 
 160     static {
 161         initIDs();
 162     }
 163 
 164     /**
 165      * Initialize JNI field and method IDs for fields that may be
 166        accessed from C.
 167      */
 168     private static native void initIDs();
 169 
 170     /**
 171      * Constructs an X11InputMethod instance. It initializes the XIM
 172      * environment if it's not done yet.
 173      *
 174      * @exception AWTException if XOpenIM() failed.
 175      */
 176     public X11InputMethod() throws AWTException {
 177         // supports only the locale in which the VM is started
 178         locale = X11InputMethodDescriptor.getSupportedLocale();
 179         if (initXIM() == false) {
 180             throw new AWTException("Cannot open X Input Method");
 181         }
 182     }
 183 
 184     protected void finalize() throws Throwable {
 185         dispose();
 186         super.finalize();
 187     }
 188 
 189     /**
 190      * Invokes openIM() that invokes XOpenIM() if it's not opened yet.
 191      * @return  true if openXIM() is successful or it's already been opened.
 192      */
 193     private synchronized boolean initXIM() {
 194         if (isXIMOpened == false)
 195             isXIMOpened = openXIM();
 196         return isXIMOpened;
 197     }
 198 
 199     protected abstract boolean openXIM();
 200 
 201     protected boolean isDisposed() {
 202         return disposed;
 203     }
 204 
 205     protected abstract void setXICFocus(ComponentPeer peer,
 206                                     boolean value, boolean active);
 207 
 208     /**
 209      * Does nothing - this adapter doesn't use the input method context.
 210      *
 211      * @see java.awt.im.spi.InputMethod#setInputMethodContext
 212      */
 213     public void setInputMethodContext(InputMethodContext context) {
 214     }
 215 
 216     /**
 217      * Set locale to input. If input method doesn't support specified locale,
 218      * false will be returned and its behavior is not changed.
 219      *
 220      * @param lang locale to input
 221      * @return the true is returned when specified locale is supported.
 222      */
 223     public boolean setLocale(Locale lang) {
 224         if (lang.equals(locale)) {
 225             return true;
 226         }
 227         // special compatibility rule for Japanese and Korean
 228         if (locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) ||
 229                 locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) {
 230             return true;
 231         }
 232         return false;
 233     }
 234 
 235     /**
 236      * Returns current input locale.
 237      */
 238     public Locale getLocale() {
 239         return locale;
 240     }
 241 
 242     /**
 243      * Does nothing - XIM doesn't let you specify which characters you expect.
 244      *
 245      * @see java.awt.im.spi.InputMethod#setCharacterSubsets
 246      */
 247     public void setCharacterSubsets(Subset[] subsets) {
 248     }
 249 
 250     /**
 251      * Dispatch event to input method. InputContext dispatch event with this
 252      * method. Input method set consume flag if event is consumed in
 253      * input method.
 254      *
 255      * @param e event
 256      */
 257     public void dispatchEvent(AWTEvent e) {
 258     }
 259 
 260 
 261     protected final void resetXICifneeded(){
 262         /* needResetXIC is used to indicate whether to call
 263            resetXIC on the active client. resetXIC will always be
 264            called on the passive client when endComposition is called.
 265         */
 266         if (needResetXIC && haveActiveClient() &&
 267             getClientComponent() != needResetXICClient.get()){
 268             resetXIC();
 269 
 270             // needs to reset the last xic focussed component.
 271             lastXICFocussedComponent = null;
 272             isLastXICActive = false;
 273 
 274             needResetXICClient.clear();
 275             needResetXIC = false;
 276         }
 277     }
 278 
 279     /**
 280      * Reset the composition state to the current composition state.
 281      */
 282     private void resetCompositionState() {
 283         if (compositionEnableSupported) {
 284             try {
 285                 /* Restore the composition mode to the last saved composition
 286                    mode. */
 287                 setCompositionEnabled(savedCompositionState);
 288             } catch (UnsupportedOperationException e) {
 289                 compositionEnableSupported = false;
 290             }
 291         }
 292     }
 293 
 294     /**
 295      * Query and then return the current composition state.
 296      * @return the composition state if isCompositionEnabled call
 297      * is successful. Otherwise, it returns false.
 298      */
 299     private boolean getCompositionState() {
 300         boolean compositionState = false;
 301         if (compositionEnableSupported) {
 302             try {
 303                 compositionState = isCompositionEnabled();
 304             } catch (UnsupportedOperationException e) {
 305                 compositionEnableSupported = false;
 306             }
 307         }
 308         return compositionState;
 309     }
 310 
 311     /**
 312      * Activate input method.
 313      */
 314     public synchronized void activate() {
 315         clientComponentWindow = getClientComponentWindow();
 316         if (clientComponentWindow == null)
 317             return;
 318 
 319         if (lastXICFocussedComponent != null){
 320             if (log.isLoggable(PlatformLogger.Level.FINE)) {
 321                 log.fine("XICFocused {0}, AWTFocused {1}",
 322                          lastXICFocussedComponent, awtFocussedComponent);
 323             }
 324         }
 325 
 326         if (pData == 0) {
 327             if (!createXIC()) {
 328                 return;
 329             }
 330             disposed = false;
 331         }
 332 
 333         /*  reset input context if necessary and set the XIC focus
 334         */
 335         resetXICifneeded();
 336         ComponentPeer lastXICFocussedComponentPeer = null;
 337         ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
 338 
 339         if (lastXICFocussedComponent != null) {
 340            lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
 341         }
 342 
 343         /* If the last XIC focussed component has a different peer as the
 344            current focussed component, change the XIC focus to the newly
 345            focussed component.
 346         */
 347         if (isLastTemporary || lastXICFocussedComponentPeer != awtFocussedComponentPeer ||
 348             isLastXICActive != haveActiveClient()) {
 349             if (lastXICFocussedComponentPeer != null) {
 350                 setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
 351             }
 352             if (awtFocussedComponentPeer != null) {
 353                 setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
 354             }
 355             lastXICFocussedComponent = awtFocussedComponent;
 356             isLastXICActive = haveActiveClient();
 357         }
 358         resetCompositionState();
 359         isActive = true;
 360     }
 361 
 362     protected abstract boolean createXIC();
 363 
 364     /**
 365      * Deactivate input method.
 366      */
 367     public synchronized void deactivate(boolean isTemporary) {
 368         boolean   isAc =  haveActiveClient();
 369         /* Usually as the client component, let's call it component A,
 370            loses the focus, this method is called. Then when another client
 371            component, let's call it component B,  gets the focus, activate is first called on
 372            the previous focused compoent which is A, then endComposition is called on A,
 373            deactivate is called on A again. And finally activate is called on the newly
 374            focused component B. Here is the call sequence.
 375 
 376            A loses focus               B gains focus
 377            -------------> deactivate A -------------> activate A -> endComposition A ->
 378            deactivate A -> activate B ----....
 379 
 380            So in order to carry the composition mode across the components sharing the same
 381            input context, we save it when deactivate is called so that when activate is
 382            called, it can be restored correctly till activate is called on the newly focused
 383            component. (See also sun/awt/im/InputContext and bug 6184471).
 384            Last note, getCompositionState should be called before setXICFocus since
 385            setXICFocus here sets the XIC to 0.
 386         */
 387         savedCompositionState = getCompositionState();
 388 
 389         if (isTemporary){
 390             //turn the status window off...
 391             turnoffStatusWindow();
 392         }
 393 
 394         /* Delay resetting the XIC focus until activate is called and the newly
 395            focussed component has a different peer as the last focussed component.
 396         */
 397         lastXICFocussedComponent = awtFocussedComponent;
 398         isLastXICActive = isAc;
 399         isLastTemporary = isTemporary;
 400         isActive = false;
 401     }
 402 
 403     /**
 404      * Explicitly disable the native IME. Native IME is not disabled when
 405      * deactivate is called.
 406      */
 407     public void disableInputMethod() {
 408         if (lastXICFocussedComponent != null) {
 409             setXICFocus(getPeer(lastXICFocussedComponent), false, isLastXICActive);
 410             lastXICFocussedComponent = null;
 411             isLastXICActive = false;
 412 
 413             resetXIC();
 414             needResetXICClient.clear();
 415             needResetXIC = false;
 416         }
 417     }
 418 
 419     // implements java.awt.im.spi.InputMethod.hideWindows
 420     public void hideWindows() {
 421         // ??? need real implementation
 422     }
 423 
 424     /**
 425      * @see java.awt.Toolkit#mapInputMethodHighlight
 426      */
 427     public static Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) {
 428         int index;
 429         int state = highlight.getState();
 430         if (state == InputMethodHighlight.RAW_TEXT) {
 431             index = 0;
 432         } else if (state == InputMethodHighlight.CONVERTED_TEXT) {
 433             index = 2;
 434         } else {
 435             return null;
 436         }
 437         if (highlight.isSelected()) {
 438             index += 1;
 439         }
 440         return highlightStyles[index];
 441     }
 442 
 443     /**
 444      * @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent
 445      */
 446     protected void setAWTFocussedComponent(Component component) {
 447         if (component == null) {
 448             return;
 449         }
 450         if (isActive) {
 451             // deactivate/activate are being suppressed during a focus change -
 452             // this may happen when an input method window is made visible
 453             boolean ac = haveActiveClient();
 454             setXICFocus(getPeer(awtFocussedComponent), false, ac);
 455             setXICFocus(getPeer(component), true, ac);
 456         }
 457         awtFocussedComponent = component;
 458     }
 459 
 460     /**
 461      * @see sun.awt.im.InputMethodAdapter#stopListening
 462      */
 463     protected void stopListening() {
 464         // It is desirable to disable XIM by calling XSetICValues with
 465         // XNPreeditState == XIMPreeditDisable.  But Solaris 2.6 and
 466         // Solaris 7 do not implement this correctly without a patch,
 467         // so just call resetXIC here.  Prior endComposition call commits
 468         // the existing composed text.
 469         endComposition();
 470         // disable the native input method so that the other input
 471         // method could get the input focus.
 472         disableInputMethod();
 473         if (needResetXIC) {
 474             resetXIC();
 475             needResetXICClient.clear();
 476             needResetXIC = false;
 477         }
 478     }
 479 
 480     /**
 481      * Returns the Window instance in which the client component is
 482      * contained. If not found, null is returned. (IS THIS POSSIBLE?)
 483      */
 484     // NOTE: This method may be called by privileged threads.
 485     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 486     private Window getClientComponentWindow() {
 487         Component client = getClientComponent();
 488         Container container;
 489 
 490         if (client instanceof Container) {
 491             container = (Container) client;
 492         } else {
 493             container = getParent(client);
 494         }
 495 
 496         while (container != null && !(container instanceof java.awt.Window)) {
 497             container = getParent(container);
 498         }
 499         return (Window) container;
 500     }
 501 
 502     protected abstract Container getParent(Component client);
 503 
 504     /**
 505      * Returns peer of the given client component. If the given client component
 506      * doesn't have peer, peer of the native container of the client is returned.
 507      */
 508     protected abstract ComponentPeer getPeer(Component client);
 509 
 510     /**
 511      * Used to protect preedit data
 512      */
 513     protected abstract void awtLock();
 514     protected abstract void awtUnlock();
 515 
 516     /**
 517      * Creates an input method event from the arguments given
 518      * and posts it on the AWT event queue. For arguments,
 519      * see InputMethodEvent. Called by input method.
 520      *
 521      * @see java.awt.event.InputMethodEvent#InputMethodEvent
 522      */
 523     private void postInputMethodEvent(int id,
 524                                       AttributedCharacterIterator text,
 525                                       int committedCharacterCount,
 526                                       TextHitInfo caret,
 527                                       TextHitInfo visiblePosition,
 528                                       long when) {
 529         Component source = getClientComponent();
 530         if (source != null) {
 531             InputMethodEvent event = new InputMethodEvent(source,
 532                 id, when, text, committedCharacterCount, caret, visiblePosition);
 533             SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event);
 534         }
 535     }
 536 
 537     private void postInputMethodEvent(int id,
 538                                       AttributedCharacterIterator text,
 539                                       int committedCharacterCount,
 540                                       TextHitInfo caret,
 541                                       TextHitInfo visiblePosition) {
 542         postInputMethodEvent(id, text, committedCharacterCount,
 543                              caret, visiblePosition, EventQueue.getMostRecentEventTime());
 544     }
 545 
 546     /**
 547      * Dispatches committed text from XIM to the awt event queue. This
 548      * method is invoked from the event handler in canvas.c in the
 549      * AWT Toolkit thread context and thus inside the AWT Lock.
 550      * @param   str     committed text
 551      * @param   when    when
 552      */
 553     // NOTE: This method may be called by privileged threads.
 554     //       This functionality is implemented in a package-private method
 555     //       to insure that it cannot be overridden by client subclasses.
 556     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 557     void dispatchCommittedText(String str, long when) {
 558         if (str == null)
 559             return;
 560 
 561         if (composedText == null) {
 562             AttributedString attrstr = new AttributedString(str);
 563             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 564                                  attrstr.getIterator(),
 565                                  str.length(),
 566                                  null,
 567                                  null,
 568                                  when);
 569         } else {
 570             // if there is composed text, wait until the preedit
 571             // callback is invoked.
 572             committedText = str;
 573         }
 574     }
 575 
 576     private void dispatchCommittedText(String str) {
 577         dispatchCommittedText(str, EventQueue.getMostRecentEventTime());
 578     }
 579 
 580     /**
 581      * Updates composed text with XIM preedit information and
 582      * posts composed text to the awt event queue. The args of
 583      * this method correspond to the XIM preedit callback
 584      * information. The XIM highlight attributes are translated via
 585      * fixed mapping (i.e., independent from any underlying input
 586      * method engine). This method is invoked in the AWT Toolkit
 587      * (X event loop) thread context and thus inside the AWT Lock.
 588      */
 589     // NOTE: This method may be called by privileged threads.
 590     //       This functionality is implemented in a package-private method
 591     //       to insure that it cannot be overridden by client subclasses.
 592     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 593     void dispatchComposedText(String chgText,
 594                                            int chgStyles[],
 595                                            int chgOffset,
 596                                            int chgLength,
 597                                            int caretPosition,
 598                                            long when) {
 599         if (disposed) {
 600             return;
 601         }
 602 
 603         //Workaround for deadlock bug on solaris2.6_zh bug#4170760
 604         if (chgText == null
 605             && chgStyles == null
 606             && chgOffset == 0
 607             && chgLength == 0
 608             && caretPosition == 0
 609             && composedText == null
 610             && committedText == null)
 611             return;
 612 
 613         if (composedText == null) {
 614             // TODO: avoid reallocation of those buffers
 615             composedText = new StringBuffer(INITIAL_SIZE);
 616             rawFeedbacks = new IntBuffer(INITIAL_SIZE);
 617         }
 618         if (chgLength > 0) {
 619             if (chgText == null && chgStyles != null) {
 620                 rawFeedbacks.replace(chgOffset, chgStyles);
 621             } else {
 622                 if (chgLength == composedText.length()) {
 623                     // optimization for the special case to replace the
 624                     // entire previous text
 625                     composedText = new StringBuffer(INITIAL_SIZE);
 626                     rawFeedbacks = new IntBuffer(INITIAL_SIZE);
 627                 } else {
 628                     if (composedText.length() > 0) {
 629                         if (chgOffset+chgLength < composedText.length()) {
 630                             String text;
 631                             text = composedText.toString().substring(chgOffset+chgLength,
 632                                                                      composedText.length());
 633                             composedText.setLength(chgOffset);
 634                             composedText.append(text);
 635                         } else {
 636                             // in case to remove substring from chgOffset
 637                             // to the end
 638                             composedText.setLength(chgOffset);
 639                         }
 640                         rawFeedbacks.remove(chgOffset, chgLength);
 641                     }
 642                 }
 643             }
 644         }
 645         if (chgText != null) {
 646             composedText.insert(chgOffset, chgText);
 647             if (chgStyles != null)
 648                 rawFeedbacks.insert(chgOffset, chgStyles);
 649         }
 650 
 651         if (composedText.length() == 0) {
 652             composedText = null;
 653             rawFeedbacks = null;
 654 
 655             // if there is any outstanding committed text stored by
 656             // dispatchCommittedText(), it has to be sent to the
 657             // client component.
 658             if (committedText != null) {
 659                 dispatchCommittedText(committedText, when);
 660                 committedText = null;
 661                 return;
 662             }
 663 
 664             // otherwise, send null text to delete client's composed
 665             // text.
 666             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 667                                  null,
 668                                  0,
 669                                  null,
 670                                  null,
 671                                  when);
 672 
 673             return;
 674         }
 675 
 676         // Now sending the composed text to the client
 677         int composedOffset;
 678         AttributedString inputText;
 679 
 680         // if there is any partially committed text, concatenate it to
 681         // the composed text.
 682         if (committedText != null) {
 683             composedOffset = committedText.length();
 684             inputText = new AttributedString(committedText + composedText);
 685             committedText = null;
 686         } else {
 687             composedOffset = 0;
 688             inputText = new AttributedString(composedText.toString());
 689         }
 690 
 691         int currentFeedback;
 692         int nextFeedback;
 693         int startOffset = 0;
 694         int currentOffset;
 695         int visiblePosition = 0;
 696         TextHitInfo visiblePositionInfo = null;
 697 
 698         rawFeedbacks.rewind();
 699         currentFeedback = rawFeedbacks.getNext();
 700         rawFeedbacks.unget();
 701         while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
 702             if (visiblePosition == 0) {
 703                 visiblePosition = nextFeedback & XIMVisibleMask;
 704                 if (visiblePosition != 0) {
 705                     int index = rawFeedbacks.getOffset() - 1;
 706 
 707                     if (visiblePosition == XIMVisibleToBackward)
 708                         visiblePositionInfo = TextHitInfo.leading(index);
 709                     else
 710                         visiblePositionInfo = TextHitInfo.trailing(index);
 711                 }
 712             }
 713             nextFeedback &= ~XIMVisibleMask;
 714             if (currentFeedback != nextFeedback) {
 715                 rawFeedbacks.unget();
 716                 currentOffset = rawFeedbacks.getOffset();
 717                 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
 718                                        convertVisualFeedbackToHighlight(currentFeedback),
 719                                        composedOffset + startOffset,
 720                                        composedOffset + currentOffset);
 721                 startOffset = currentOffset;
 722                 currentFeedback = nextFeedback;
 723             }
 724         }
 725         currentOffset = rawFeedbacks.getOffset();
 726         if (currentOffset >= 0) {
 727             inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
 728                                    convertVisualFeedbackToHighlight(currentFeedback),
 729                                    composedOffset + startOffset,
 730                                    composedOffset + currentOffset);
 731         }
 732 
 733         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 734                              inputText.getIterator(),
 735                              composedOffset,
 736                              TextHitInfo.leading(caretPosition),
 737                              visiblePositionInfo,
 738                              when);
 739     }
 740 
 741     /**
 742      * Flushes composed and committed text held in this context.
 743      * This method is invoked in the AWT Toolkit (X event loop) thread context
 744      * and thus inside the AWT Lock.
 745      */
 746     // NOTE: This method may be called by privileged threads.
 747     //       This functionality is implemented in a package-private method
 748     //       to insure that it cannot be overridden by client subclasses.
 749     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 750     void flushText() {
 751         String flush = (committedText != null ? committedText : "");
 752         if (composedText != null) {
 753             flush += composedText.toString();
 754         }
 755 
 756         if (!flush.equals("")) {
 757             AttributedString attrstr = new AttributedString(flush);
 758             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 759                                  attrstr.getIterator(),
 760                                  flush.length(),
 761                                  null,
 762                                  null,
 763                                  EventQueue.getMostRecentEventTime());
 764             composedText = null;
 765             committedText = null;
 766         }
 767     }
 768 
 769     /*
 770      * Subclasses should override disposeImpl() instead of dispose(). Client
 771      * code should always invoke dispose(), never disposeImpl().
 772      */
 773     protected synchronized void disposeImpl() {
 774         disposeXIC();
 775         awtLock();
 776         composedText = null;
 777         committedText = null;
 778         rawFeedbacks = null;
 779         awtUnlock();
 780         awtFocussedComponent = null;
 781         lastXICFocussedComponent = null;
 782     }
 783 
 784     /**
 785      * Frees all X Window resources associated with this object.
 786      *
 787      * @see java.awt.im.spi.InputMethod#dispose
 788      */
 789     public final void dispose() {
 790         boolean call_disposeImpl = false;
 791 
 792         if (!disposed) {
 793             synchronized (this) {
 794                 if (!disposed) {
 795                     disposed = call_disposeImpl = true;
 796                 }
 797             }
 798         }
 799 
 800         if (call_disposeImpl) {
 801             disposeImpl();
 802         }
 803     }
 804 
 805     /**
 806      * Returns null.
 807      *
 808      * @see java.awt.im.spi.InputMethod#getControlObject
 809      */
 810     public Object getControlObject() {
 811         return null;
 812     }
 813 
 814     /**
 815      * @see java.awt.im.spi.InputMethod#removeNotify
 816      */
 817     public synchronized void removeNotify() {
 818         dispose();
 819     }
 820 
 821     /**
 822      * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
 823      */
 824     public void setCompositionEnabled(boolean enable) {
 825         /* If the composition state is successfully changed, set
 826            the savedCompositionState to 'enable'. Otherwise, simply
 827            return.
 828            setCompositionEnabledNative may throw UnsupportedOperationException.
 829            Don't try to catch it since the method may be called by clients.
 830            Use package private mthod 'resetCompositionState' if you want the
 831            exception to be caught.
 832         */
 833         if (setCompositionEnabledNative(enable)) {
 834             savedCompositionState = enable;
 835         }
 836     }
 837 
 838     /**
 839      * @see java.awt.im.spi.InputMethod#isCompositionEnabled
 840      */
 841     public boolean isCompositionEnabled() {
 842         /* isCompositionEnabledNative may throw UnsupportedOperationException.
 843            Don't try to catch it since this method may be called by clients.
 844            Use package private method 'getCompositionState' if you want the
 845            exception to be caught.
 846         */
 847         return isCompositionEnabledNative();
 848     }
 849 
 850     /**
 851      * Ends any input composition that may currently be going on in this
 852      * context. Depending on the platform and possibly user preferences,
 853      * this may commit or delete uncommitted text. Any changes to the text
 854      * are communicated to the active component using an input method event.
 855      *
 856      * <p>
 857      * A text editing component may call this in a variety of situations,
 858      * for example, when the user moves the insertion point within the text
 859      * (but outside the composed text), or when the component's text is
 860      * saved to a file or copied to the clipboard.
 861      *
 862      */
 863     public void endComposition() {
 864         if (disposed) {
 865             return;
 866         }
 867 
 868         /* Before calling resetXIC, record the current composition mode
 869            so that it can be restored later. */
 870         savedCompositionState = getCompositionState();
 871         boolean active = haveActiveClient();
 872         if (active && composedText == null && committedText == null){
 873             needResetXIC = true;
 874             needResetXICClient = new WeakReference<>(getClientComponent());
 875             return;
 876         }
 877 
 878         String text = resetXIC();
 879         /* needResetXIC is only set to true for active client. So passive
 880            client should not reset the flag to false. */
 881         if (active) {
 882             needResetXIC = false;
 883         }
 884 
 885         // Remove any existing composed text by posting an InputMethodEvent
 886         // with null composed text.  It would be desirable to wait for a
 887         // dispatchComposedText call from X input method engine, but some
 888         // input method does not conform to the XIM specification and does
 889         // not call the preedit callback to erase preedit text on calling
 890         // XmbResetIC.  To work around this problem, do it here by ourselves.
 891         awtLock();
 892         composedText = null;
 893         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 894                              null,
 895                              0,
 896                              null,
 897                              null);
 898 
 899         if (text != null && text.length() > 0) {
 900             dispatchCommittedText(text);
 901         }
 902         awtUnlock();
 903 
 904         // Restore the preedit state if it was enabled
 905         if (savedCompositionState) {
 906             resetCompositionState();
 907         }
 908     }
 909 
 910     /**
 911      * Returns a string with information about the current input method server, or null.
 912      * On both Linux & SunOS, the value of environment variable XMODIFIERS is
 913      * returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed
 914      * to find out the language service engine (atok or wnn) since there is
 915      * no API in Xlib which returns the information of native
 916      * IM server or language service and we want to try our best to return as much
 917      * information as possible.
 918      *
 919      * Note: This method could return null on Linux if XMODIFIERS is not set properly or
 920      * if any IOException is thrown.
 921      * See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS,
 922      * atok12setup(1) and wnn6setup(1) for the information written to
 923      * $HOME/.dtprofile when you run these two commands.
 924      *
 925      */
 926     public String getNativeInputMethodInfo() {
 927         String xmodifiers = System.getenv("XMODIFIERS");
 928         String imInfo = null;
 929 
 930         // If XMODIFIERS is set, return the value
 931         if (xmodifiers != null) {
 932             int imIndex = xmodifiers.indexOf("@im=");
 933             if (imIndex != -1) {
 934                 imInfo = xmodifiers.substring(imIndex + 4);
 935             }
 936         } else if (System.getProperty("os.name").startsWith("SunOS")) {
 937             File dtprofile = new File(System.getProperty("user.home") +
 938                                       "/.dtprofile");
 939             String languageEngineInfo = null;
 940             try {
 941                 BufferedReader br = new BufferedReader(new FileReader(dtprofile));
 942                 String line = null;
 943 
 944                 while ( languageEngineInfo == null && (line = br.readLine()) != null) {
 945                     if (line.contains("atok") || line.contains("wnn")) {
 946                         StringTokenizer tokens =  new StringTokenizer(line);
 947                         while (tokens.hasMoreTokens()) {
 948                             String token = tokens.nextToken();
 949                             if (Pattern.matches("atok.*setup", token) ||
 950                                 Pattern.matches("wnn.*setup", token)){
 951                                 languageEngineInfo = token.substring(0, token.indexOf("setup"));
 952                                 break;
 953                             }
 954                         }
 955                     }
 956                 }
 957 
 958                 br.close();
 959             } catch(IOException ioex) {
 960                 // Since this method is provided for internal testing only,
 961                 // we dump the stack trace for the ease of debugging.
 962                 ioex.printStackTrace();
 963             }
 964 
 965             imInfo = "htt " + languageEngineInfo;
 966         }
 967 
 968         return imInfo;
 969     }
 970 
 971 
 972     /**
 973      * Performs mapping from an XIM visible feedback value to Java IM highlight.
 974      * @return Java input method highlight
 975      */
 976     private InputMethodHighlight convertVisualFeedbackToHighlight(int feedback) {
 977         InputMethodHighlight highlight;
 978 
 979         switch (feedback) {
 980         case XIMUnderline:
 981             highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
 982             break;
 983         case XIMReverse:
 984             highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
 985             break;
 986         case XIMHighlight:
 987             highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
 988             break;
 989         case XIMPrimary:
 990             highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
 991             break;
 992         case XIMSecondary:
 993             highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
 994             break;
 995         case XIMTertiary:
 996             highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
 997             break;
 998         default:
 999             highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
1000             break;
1001         }
1002         return highlight;
1003     }
1004 
1005     // initial capacity size for string buffer, etc.
1006     private static final int INITIAL_SIZE = 64;
1007 
1008     /**
1009      * IntBuffer is an inner class that manipulates an int array and
1010      * provides UNIX file io stream-like programming interfaces to
1011      * access it. (An alternative would be to use ArrayList which may
1012      * be too expensive for the work.)
1013      */
1014     private final class IntBuffer {
1015         private int[] intArray;
1016         private int size;
1017         private int index;
1018 
1019         IntBuffer(int initialCapacity) {
1020             intArray = new int[initialCapacity];
1021             size = 0;
1022             index = 0;
1023         }
1024 
1025         void insert(int offset, int[] values) {
1026             int newSize = size + values.length;
1027             if (intArray.length < newSize) {
1028                 int[] newIntArray = new int[newSize * 2];
1029                 System.arraycopy(intArray, 0, newIntArray, 0, size);
1030                 intArray = newIntArray;
1031             }
1032             System.arraycopy(intArray, offset, intArray, offset+values.length,
1033                              size - offset);
1034             System.arraycopy(values, 0, intArray, offset, values.length);
1035             size += values.length;
1036             if (index > offset)
1037                 index = offset;
1038         }
1039 
1040         void remove(int offset, int length) {
1041             if (offset + length != size)
1042                 System.arraycopy(intArray, offset+length, intArray, offset,
1043                                  size - offset - length);
1044             size -= length;
1045             if (index > offset)
1046                 index = offset;
1047         }
1048 
1049         void replace(int offset, int[] values) {
1050             System.arraycopy(values, 0, intArray, offset, values.length);
1051         }
1052 
1053         void removeAll() {
1054             size = 0;
1055             index = 0;
1056         }
1057 
1058         void rewind() {
1059             index = 0;
1060         }
1061 
1062         int getNext() {
1063             if (index == size)
1064                 return -1;
1065             return intArray[index++];
1066         }
1067 
1068         void unget() {
1069             if (index != 0)
1070                 index--;
1071         }
1072 
1073         int getOffset() {
1074             return index;
1075         }
1076 
1077         public String toString() {
1078             StringBuffer s = new StringBuffer();
1079             for (int i = 0; i < size;) {
1080                 s.append(intArray[i++]);
1081                 if (i < size)
1082                     s.append(",");
1083             }
1084             return s.toString();
1085         }
1086     }
1087 
1088     /*
1089      * Native methods
1090      */
1091     private native String resetXIC();
1092     private native void disposeXIC();
1093     private native boolean setCompositionEnabledNative(boolean enable);
1094     private native boolean isCompositionEnabledNative();
1095     private native void turnoffStatusWindow();
1096 }