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