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