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