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