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