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.im;
  27 
  28 import java.awt.AWTEvent;
  29 import java.awt.AWTKeyStroke;
  30 import java.awt.Component;
  31 import java.awt.EventQueue;
  32 import java.awt.Frame;
  33 import java.awt.Rectangle;
  34 import java.awt.Toolkit;
  35 import java.awt.Window;
  36 import java.awt.event.ComponentEvent;
  37 import java.awt.event.ComponentListener;
  38 import java.awt.event.FocusEvent;
  39 import java.awt.event.InputEvent;
  40 import java.awt.event.InputMethodEvent;
  41 import java.awt.event.KeyEvent;
  42 import java.awt.event.WindowEvent;
  43 import java.awt.event.WindowListener;
  44 import java.awt.im.InputMethodRequests;
  45 import java.awt.im.spi.InputMethod;
  46 import java.lang.Character.Subset;
  47 import java.security.AccessController;
  48 import java.security.PrivilegedAction;
  49 import java.text.MessageFormat;
  50 import java.util.HashMap;
  51 import java.util.Iterator;
  52 import java.util.Locale;
  53 import java.util.prefs.BackingStoreException;
  54 import java.util.prefs.Preferences;
  55 import sun.util.logging.PlatformLogger;
  56 import sun.awt.SunToolkit;
  57 
  58 /**
  59  * This InputContext class contains parts of the implementation of
  60  * java.text.im.InputContext. These parts have been moved
  61  * here to avoid exposing protected members that are needed by the
  62  * subclass InputMethodContext.
  63  *
  64  * @see java.awt.im.InputContext
  65  * @author JavaSoft Asia/Pacific
  66  */
  67 
  68 public class InputContext extends java.awt.im.InputContext
  69                           implements ComponentListener, WindowListener {
  70     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.im.InputContext");
  71     // The current input method is represented by two objects:
  72     // a locator is used to keep information about the selected
  73     // input method and locale until we actually need a real input
  74     // method; only then the input method itself is created.
  75     // Once there is an input method, the input method's locale
  76     // takes precedence over locale information in the locator.
  77     private InputMethodLocator inputMethodLocator;
  78     private InputMethod inputMethod;
  79     private boolean inputMethodCreationFailed;
  80 
  81     // holding bin for previously used input method instances, but not the current one
  82     private HashMap<InputMethodLocator, InputMethod> usedInputMethods;
  83 
  84     // the current client component is kept until the user focusses on a different
  85     // client component served by the same input context. When that happens, we call
  86     // endComposition so that text doesn't jump from one component to another.
  87     private Component currentClientComponent;
  88     private Component awtFocussedComponent;
  89     private boolean   isInputMethodActive;
  90     private Subset[]  characterSubsets = null;
  91 
  92     // true if composition area has been set to invisible when focus was lost
  93     private boolean compositionAreaHidden = false;
  94 
  95     // The input context for whose input method we may have to call hideWindows
  96     private static InputContext inputMethodWindowContext;
  97 
  98     // Previously active input method to decide whether we need to call
  99     // InputMethodAdapter.stopListening() on activateInputMethod()
 100     private static InputMethod previousInputMethod = null;
 101 
 102     // true if the current input method requires client window change notification
 103     private boolean clientWindowNotificationEnabled = false;
 104     // client window to which this input context is listening
 105     private Window clientWindowListened;
 106     // cache location notification
 107     private Rectangle clientWindowLocation = null;
 108     // holding the state of clientWindowNotificationEnabled of only non-current input methods
 109     private HashMap<InputMethod, Boolean> perInputMethodState;
 110 
 111     // Input Method selection hot key stuff
 112     private static AWTKeyStroke inputMethodSelectionKey;
 113     private static boolean inputMethodSelectionKeyInitialized = false;
 114     private static final String inputMethodSelectionKeyPath = "/java/awt/im/selectionKey";
 115     private static final String inputMethodSelectionKeyCodeName = "keyCode";
 116     private static final String inputMethodSelectionKeyModifiersName = "modifiers";
 117 
 118     /**
 119      * Constructs an InputContext.
 120      */
 121     protected InputContext() {
 122         InputMethodManager imm = InputMethodManager.getInstance();
 123         synchronized (InputContext.class) {
 124             if (!inputMethodSelectionKeyInitialized) {
 125                 inputMethodSelectionKeyInitialized = true;
 126                 if (imm.hasMultipleInputMethods()) {
 127                     initializeInputMethodSelectionKey();
 128                 }
 129             }
 130         }
 131         selectInputMethod(imm.getDefaultKeyboardLocale());
 132     }
 133 
 134     /**
 135      * @see java.awt.im.InputContext#selectInputMethod
 136      * @exception NullPointerException when the locale is null.
 137      */
 138     public synchronized boolean selectInputMethod(Locale locale) {
 139         if (locale == null) {
 140             throw new NullPointerException();
 141         }
 142 
 143         // see whether the current input method supports the locale
 144         if (inputMethod != null) {
 145             if (inputMethod.setLocale(locale)) {
 146                 return true;
 147             }
 148         } else if (inputMethodLocator != null) {
 149             // This is not 100% correct, since the input method
 150             // may support the locale without advertising it.
 151             // But before we try instantiations and setLocale,
 152             // we look for an input method that's more confident.
 153             if (inputMethodLocator.isLocaleAvailable(locale)) {
 154                 inputMethodLocator = inputMethodLocator.deriveLocator(locale);
 155                 return true;
 156             }
 157         }
 158 
 159         // see whether there's some other input method that supports the locale
 160         InputMethodLocator newLocator = InputMethodManager.getInstance().findInputMethod(locale);
 161         if (newLocator != null) {
 162             changeInputMethod(newLocator);
 163             return true;
 164         }
 165 
 166         // make one last desperate effort with the current input method
 167         // ??? is this good? This is pretty high cost for something that's likely to fail.
 168         if (inputMethod == null && inputMethodLocator != null) {
 169             inputMethod = getInputMethod();
 170             if (inputMethod != null) {
 171                 return inputMethod.setLocale(locale);
 172             }
 173         }
 174         return false;
 175     }
 176 
 177     /**
 178      * @see java.awt.im.InputContext#getLocale
 179      */
 180     public Locale getLocale() {
 181         if (inputMethod != null) {
 182             return inputMethod.getLocale();
 183         } else if (inputMethodLocator != null) {
 184             return inputMethodLocator.getLocale();
 185         } else {
 186             return null;
 187         }
 188     }
 189 
 190     /**
 191      * @see java.awt.im.InputContext#setCharacterSubsets
 192      */
 193     public void setCharacterSubsets(Subset[] subsets) {
 194         if (subsets == null) {
 195             characterSubsets = null;
 196         } else {
 197             characterSubsets = new Subset[subsets.length];
 198             System.arraycopy(subsets, 0,
 199                              characterSubsets, 0, characterSubsets.length);
 200         }
 201         if (inputMethod != null) {
 202             inputMethod.setCharacterSubsets(subsets);
 203         }
 204     }
 205 
 206     /**
 207      * @see java.awt.im.InputContext#reconvert
 208      * @since 1.3
 209      * @exception UnsupportedOperationException when input method is null
 210      */
 211     public synchronized void reconvert() {
 212         InputMethod inputMethod = getInputMethod();
 213         if (inputMethod == null) {
 214             throw new UnsupportedOperationException();
 215         }
 216         inputMethod.reconvert();
 217     }
 218 
 219     /**
 220      * @see java.awt.im.InputContext#dispatchEvent
 221      */
 222     @SuppressWarnings("fallthrough")
 223     public void dispatchEvent(AWTEvent event) {
 224 
 225         if (event instanceof InputMethodEvent) {
 226             return;
 227         }
 228 
 229         // Ignore focus events that relate to the InputMethodWindow of this context.
 230         // This is a workaround.  Should be removed after 4452384 is fixed.
 231         if (event instanceof FocusEvent) {
 232             Component opposite = ((FocusEvent)event).getOppositeComponent();
 233             if ((opposite != null) &&
 234                 (getComponentWindow(opposite) instanceof InputMethodWindow) &&
 235                 (opposite.getInputContext() == this)) {
 236                 return;
 237             }
 238         }
 239 
 240         InputMethod inputMethod = getInputMethod();
 241         int id = event.getID();
 242 
 243         switch (id) {
 244         case FocusEvent.FOCUS_GAINED:
 245             focusGained((Component) event.getSource());
 246             break;
 247 
 248         case FocusEvent.FOCUS_LOST:
 249             focusLost((Component) event.getSource(), ((FocusEvent) event).isTemporary());
 250             break;
 251 
 252         case KeyEvent.KEY_PRESSED:
 253             if (checkInputMethodSelectionKey((KeyEvent)event)) {
 254                 // pop up the input method selection menu
 255                 InputMethodManager.getInstance().notifyChangeRequestByHotKey((Component)event.getSource());
 256                 break;
 257             }
 258 
 259             // fall through
 260 
 261         default:
 262             if ((inputMethod != null) && (event instanceof InputEvent)) {
 263                 inputMethod.dispatchEvent(event);
 264             }
 265         }
 266     }
 267 
 268     /**
 269      * Handles focus gained events for any component that's using
 270      * this input context.
 271      * These events are generated by AWT when the keyboard focus
 272      * moves to a component.
 273      * Besides actual client components, the source components
 274      * may also be the composition area or any component in an
 275      * input method window.
 276      * <p>
 277      * When handling the focus event for a client component, this
 278      * method checks whether the input context was previously
 279      * active for a different client component, and if so, calls
 280      * endComposition for the previous client component.
 281      *
 282      * @param source the component gaining the focus
 283      */
 284     private void focusGained(Component source) {
 285 
 286         /*
 287          * NOTE: When a Container is removing its Component which
 288          * invokes this.removeNotify(), the Container has the global
 289          * Component lock. It is possible to happen that an
 290          * application thread is calling this.removeNotify() while an
 291          * AWT event queue thread is dispatching a focus event via
 292          * this.dispatchEvent(). If an input method uses AWT
 293          * components (e.g., IIIMP status window), it causes deadlock,
 294          * for example, Component.show()/hide() in this situation
 295          * because hide/show tried to obtain the lock.  Therefore,
 296          * it's necessary to obtain the global Component lock before
 297          * activating or deactivating an input method.
 298          */
 299         synchronized (source.getTreeLock()) {
 300             synchronized (this) {
 301                 if ("sun.awt.im.CompositionArea".equals(source.getClass().getName())) {
 302                     // no special handling for this one
 303                 } else if (getComponentWindow(source) instanceof InputMethodWindow) {
 304                     // no special handling for this one either
 305                 } else {
 306                     if (!source.isDisplayable()) {
 307                         // Component is being disposed
 308                         return;
 309                     }
 310 
 311                     // Focus went to a real client component.
 312                     // Check whether we're switching between client components
 313                     // that share an input context. We can't do that earlier
 314                     // than here because we don't want to end composition
 315                     // until we really know we're switching to a different component
 316                     if (inputMethod != null) {
 317                         if (currentClientComponent != null && currentClientComponent != source) {
 318                             if (!isInputMethodActive) {
 319                                 activateInputMethod(false);
 320                             }
 321                             endComposition();
 322                             deactivateInputMethod(false);
 323                         }
 324                     }
 325 
 326                     currentClientComponent = source;
 327                 }
 328 
 329                 awtFocussedComponent = source;
 330                 if (inputMethod instanceof InputMethodAdapter) {
 331                     ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(source);
 332                 }
 333 
 334                 // it's possible that the input method is still active because
 335                 // we suppressed a deactivate cause by an input method window
 336                 // coming up
 337                 if (!isInputMethodActive) {
 338                     activateInputMethod(true);
 339                 }
 340 
 341 
 342                 // If the client component is an active client with the below-the-spot
 343                 // input style, then make the composition window undecorated without a title bar.
 344                 InputMethodContext inputContext = ((InputMethodContext)this);
 345                 if (!inputContext.isCompositionAreaVisible()) {
 346                       InputMethodRequests req = source.getInputMethodRequests();
 347                       if (req != null && inputContext.useBelowTheSpotInput()) {
 348                           inputContext.setCompositionAreaUndecorated(true);
 349                       } else {
 350                           inputContext.setCompositionAreaUndecorated(false);
 351                       }
 352                 }
 353                 // restores the composition area if it was set to invisible
 354                 // when focus got lost
 355                 if (compositionAreaHidden == true) {
 356                     ((InputMethodContext)this).setCompositionAreaVisible(true);
 357                     compositionAreaHidden = false;
 358                 }
 359             }
 360         }
 361     }
 362 
 363     /**
 364      * Activates the current input method of this input context, and grabs
 365      * the composition area for use by this input context.
 366      * If updateCompositionArea is true, the text in the composition area
 367      * is updated (set to false if the text is going to change immediately
 368      * to avoid screen flicker).
 369      */
 370     private void activateInputMethod(boolean updateCompositionArea) {
 371         // call hideWindows() if this input context uses a different
 372         // input method than the previously activated one
 373         if (inputMethodWindowContext != null && inputMethodWindowContext != this &&
 374                 inputMethodWindowContext.inputMethodLocator != null &&
 375                 !inputMethodWindowContext.inputMethodLocator.sameInputMethod(inputMethodLocator) &&
 376                 inputMethodWindowContext.inputMethod != null) {
 377             inputMethodWindowContext.inputMethod.hideWindows();
 378         }
 379         inputMethodWindowContext = this;
 380 
 381         if (inputMethod != null) {
 382             if (previousInputMethod != inputMethod &&
 383                     previousInputMethod instanceof InputMethodAdapter) {
 384                 // let the host adapter pass through the input events for the
 385                 // new input method
 386                 ((InputMethodAdapter) previousInputMethod).stopListening();
 387             }
 388             previousInputMethod = null;
 389 
 390             if (log.isLoggable(PlatformLogger.FINE)) log.fine("Current client component " + currentClientComponent);
 391             if (inputMethod instanceof InputMethodAdapter) {
 392                 ((InputMethodAdapter) inputMethod).setClientComponent(currentClientComponent);
 393             }
 394             inputMethod.activate();
 395             isInputMethodActive = true;
 396 
 397             if (perInputMethodState != null) {
 398                 Boolean state = perInputMethodState.remove(inputMethod);
 399                 if (state != null) {
 400                     clientWindowNotificationEnabled = state.booleanValue();
 401                 }
 402             }
 403             if (clientWindowNotificationEnabled) {
 404                 if (!addedClientWindowListeners()) {
 405                     addClientWindowListeners();
 406                 }
 407                 synchronized(this) {
 408                     if (clientWindowListened != null) {
 409                         notifyClientWindowChange(clientWindowListened);
 410                     }
 411                 }
 412             } else {
 413                 if (addedClientWindowListeners()) {
 414                     removeClientWindowListeners();
 415                 }
 416             }
 417         }
 418         InputMethodManager.getInstance().setInputContext(this);
 419 
 420         ((InputMethodContext) this).grabCompositionArea(updateCompositionArea);
 421     }
 422 
 423     static Window getComponentWindow(Component component) {
 424         while (true) {
 425             if (component == null) {
 426                 return null;
 427             } else if (component instanceof Window) {
 428                 return (Window) component;
 429             } else {
 430                 component = component.getParent();
 431             }
 432         }
 433     }
 434 
 435     /**
 436      * Handles focus lost events for any component that's using
 437      * this input context.
 438      * These events are generated by AWT when the keyboard focus
 439      * moves away from a component.
 440      * Besides actual client components, the source components
 441      * may also be the composition area or any component in an
 442      * input method window.
 443      *
 444      * @param source the component losing the focus
 445      * @isTemporary whether the focus change is temporary
 446      */
 447     private void focusLost(Component source, boolean isTemporary) {
 448 
 449         // see the note on synchronization in focusGained
 450         synchronized (source.getTreeLock()) {
 451             synchronized (this) {
 452 
 453                 // We need to suppress deactivation if removeNotify has been called earlier.
 454                 // This is indicated by isInputMethodActive == false.
 455                 if (isInputMethodActive) {
 456                     deactivateInputMethod(isTemporary);
 457                 }
 458 
 459                 awtFocussedComponent = null;
 460                 if (inputMethod instanceof InputMethodAdapter) {
 461                     ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(null);
 462                 }
 463 
 464                 // hides the composition area if currently it is visible
 465                 InputMethodContext inputContext = ((InputMethodContext)this);
 466                 if (inputContext.isCompositionAreaVisible()) {
 467                     inputContext.setCompositionAreaVisible(false);
 468                     compositionAreaHidden = true;
 469                 }
 470             }
 471         }
 472     }
 473 
 474     /**
 475      * Checks the key event is the input method selection key or not.
 476      */
 477     private boolean checkInputMethodSelectionKey(KeyEvent event) {
 478         if (inputMethodSelectionKey != null) {
 479             AWTKeyStroke aKeyStroke = AWTKeyStroke.getAWTKeyStrokeForEvent(event);
 480             return inputMethodSelectionKey.equals(aKeyStroke);
 481         } else {
 482             return false;
 483         }
 484     }
 485 
 486     private void deactivateInputMethod(boolean isTemporary) {
 487         InputMethodManager.getInstance().setInputContext(null);
 488         if (inputMethod != null) {
 489             isInputMethodActive = false;
 490             inputMethod.deactivate(isTemporary);
 491             previousInputMethod = inputMethod;
 492         }
 493     }
 494 
 495     /**
 496      * Switches from the current input method to the one described by newLocator.
 497      * The current input method, if any, is asked to end composition, deactivated,
 498      * and saved for future use. The newLocator is made the current locator. If
 499      * the input context is active, an input method instance for the new locator
 500      * is obtained; otherwise this is deferred until required.
 501      */
 502     synchronized void changeInputMethod(InputMethodLocator newLocator) {
 503         // If we don't have a locator yet, this must be a new input context.
 504         // If we created a new input method here, we might get into an
 505         // infinite loop: create input method -> create some input method window ->
 506         // create new input context -> add input context to input method manager's context list ->
 507         // call changeInputMethod on it.
 508         // So, just record the locator. dispatchEvent will create the input method when needed.
 509         if (inputMethodLocator == null) {
 510             inputMethodLocator = newLocator;
 511             inputMethodCreationFailed = false;
 512             return;
 513         }
 514 
 515         // If the same input method is specified, just keep it.
 516         // Adjust the locale if necessary.
 517         if (inputMethodLocator.sameInputMethod(newLocator)) {
 518             Locale newLocale = newLocator.getLocale();
 519             if (newLocale != null && inputMethodLocator.getLocale() != newLocale) {
 520                 if (inputMethod != null) {
 521                     inputMethod.setLocale(newLocale);
 522                 }
 523                 inputMethodLocator = newLocator;
 524             }
 525             return;
 526         }
 527 
 528         // Switch out the old input method
 529         Locale savedLocale = inputMethodLocator.getLocale();
 530         boolean wasInputMethodActive = isInputMethodActive;
 531         boolean wasCompositionEnabledSupported = false;
 532         boolean wasCompositionEnabled = false;
 533         if (inputMethod != null) {
 534             try {
 535                 wasCompositionEnabled = inputMethod.isCompositionEnabled();
 536                 wasCompositionEnabledSupported = true;
 537             } catch (UnsupportedOperationException e) { }
 538 
 539             if (currentClientComponent != null) {
 540                 if (!isInputMethodActive) {
 541                     activateInputMethod(false);
 542                 }
 543                 endComposition();
 544                 deactivateInputMethod(false);
 545                 if (inputMethod instanceof InputMethodAdapter) {
 546                     ((InputMethodAdapter) inputMethod).setClientComponent(null);
 547                 }
 548             }
 549             savedLocale = inputMethod.getLocale();
 550 
 551             // keep the input method instance around for future use
 552             if (usedInputMethods == null) {
 553                 usedInputMethods = new HashMap<>(5);
 554             }
 555             if (perInputMethodState == null) {
 556                 perInputMethodState = new HashMap<>(5);
 557             }
 558             usedInputMethods.put(inputMethodLocator.deriveLocator(null), inputMethod);
 559             perInputMethodState.put(inputMethod,
 560                                     Boolean.valueOf(clientWindowNotificationEnabled));
 561             enableClientWindowNotification(inputMethod, false);
 562             if (this == inputMethodWindowContext) {
 563                 inputMethod.hideWindows();
 564                 inputMethodWindowContext = null;
 565             }
 566             inputMethodLocator = null;
 567             inputMethod = null;
 568             inputMethodCreationFailed = false;
 569         }
 570 
 571         // Switch in the new input method
 572         if (newLocator.getLocale() == null && savedLocale != null &&
 573                 newLocator.isLocaleAvailable(savedLocale)) {
 574             newLocator = newLocator.deriveLocator(savedLocale);
 575         }
 576         inputMethodLocator = newLocator;
 577         inputMethodCreationFailed = false;
 578 
 579         // activate the new input method if the old one was active
 580         if (wasInputMethodActive) {
 581             inputMethod = getInputMethodInstance();
 582             if (inputMethod instanceof InputMethodAdapter) {
 583                 ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(awtFocussedComponent);
 584             }
 585             activateInputMethod(true);
 586         }
 587 
 588         // enable/disable composition if the old one supports querying enable/disable
 589         if (wasCompositionEnabledSupported) {
 590             inputMethod = getInputMethod();
 591             if (inputMethod != null) {
 592                 try {
 593                     inputMethod.setCompositionEnabled(wasCompositionEnabled);
 594                 } catch (UnsupportedOperationException e) { }
 595             }
 596         }
 597     }
 598 
 599     /**
 600      * Returns the client component.
 601      */
 602     Component getClientComponent() {
 603         return currentClientComponent;
 604     }
 605 
 606     /**
 607      * @see java.awt.im.InputContext#removeNotify
 608      * @exception NullPointerException when the component is null.
 609      */
 610     public synchronized void removeNotify(Component component) {
 611         if (component == null) {
 612             throw new NullPointerException();
 613         }
 614 
 615         if (inputMethod == null) {
 616             if (component == currentClientComponent) {
 617                 currentClientComponent = null;
 618             }
 619             return;
 620         }
 621 
 622         // We may or may not get a FOCUS_LOST event for this component,
 623         // so do the deactivation stuff here too.
 624         if (component == awtFocussedComponent) {
 625             focusLost(component, false);
 626         }
 627 
 628         if (component == currentClientComponent) {
 629             if (isInputMethodActive) {
 630                 // component wasn't the one that had the focus
 631                 deactivateInputMethod(false);
 632             }
 633             inputMethod.removeNotify();
 634             if (clientWindowNotificationEnabled && addedClientWindowListeners()) {
 635                 removeClientWindowListeners();
 636             }
 637             currentClientComponent = null;
 638             if (inputMethod instanceof InputMethodAdapter) {
 639                 ((InputMethodAdapter) inputMethod).setClientComponent(null);
 640             }
 641 
 642             // removeNotify() can be issued from a thread other than the event dispatch
 643             // thread.  In that case, avoid possible deadlock between Component.AWTTreeLock
 644             // and InputMethodContext.compositionAreaHandlerLock by releasing the composition
 645             // area on the event dispatch thread.
 646             if (EventQueue.isDispatchThread()) {
 647                 ((InputMethodContext)this).releaseCompositionArea();
 648             } else {
 649                 EventQueue.invokeLater(new Runnable() {
 650                     public void run() {
 651                         ((InputMethodContext)InputContext.this).releaseCompositionArea();
 652                     }
 653                 });
 654             }
 655         }
 656     }
 657 
 658     /**
 659      * @see java.awt.im.InputContext#dispose
 660      * @exception IllegalStateException when the currentClientComponent is not null
 661      */
 662     public synchronized void dispose() {
 663         if (currentClientComponent != null) {
 664             throw new IllegalStateException("Can't dispose InputContext while it's active");
 665         }
 666         if (inputMethod != null) {
 667             if (this == inputMethodWindowContext) {
 668                 inputMethod.hideWindows();
 669                 inputMethodWindowContext = null;
 670             }
 671             if (inputMethod == previousInputMethod) {
 672                 previousInputMethod = null;
 673             }
 674             if (clientWindowNotificationEnabled) {
 675                 if (addedClientWindowListeners()) {
 676                     removeClientWindowListeners();
 677                 }
 678                 clientWindowNotificationEnabled = false;
 679             }
 680             inputMethod.dispose();
 681 
 682             // in case the input method enabled the client window
 683             // notification in dispose(), which shouldn't happen, it
 684             // needs to be cleaned up again.
 685             if (clientWindowNotificationEnabled) {
 686                 enableClientWindowNotification(inputMethod, false);
 687             }
 688 
 689             inputMethod = null;
 690         }
 691         inputMethodLocator = null;
 692         if (usedInputMethods != null && !usedInputMethods.isEmpty()) {
 693             Iterator<InputMethod> iterator = usedInputMethods.values().iterator();
 694             usedInputMethods = null;
 695             while (iterator.hasNext()) {
 696                 iterator.next().dispose();
 697             }
 698         }
 699 
 700         // cleanup client window notification variables
 701         clientWindowNotificationEnabled = false;
 702         clientWindowListened = null;
 703         perInputMethodState = null;
 704     }
 705 
 706     /**
 707      * @see java.awt.im.InputContext#getInputMethodControlObject
 708      */
 709     public synchronized Object getInputMethodControlObject() {
 710         InputMethod inputMethod = getInputMethod();
 711 
 712         if (inputMethod != null) {
 713             return inputMethod.getControlObject();
 714         } else {
 715             return null;
 716         }
 717     }
 718 
 719     /**
 720      * @see java.awt.im.InputContext#setCompositionEnabled(boolean)
 721      * @exception UnsupportedOperationException when input method is null
 722      */
 723     public void setCompositionEnabled(boolean enable) {
 724         InputMethod inputMethod = getInputMethod();
 725 
 726         if (inputMethod == null) {
 727             throw new UnsupportedOperationException();
 728         }
 729         inputMethod.setCompositionEnabled(enable);
 730     }
 731 
 732     /**
 733      * @see java.awt.im.InputContext#isCompositionEnabled
 734      * @exception UnsupportedOperationException when input method is null
 735      */
 736     public boolean isCompositionEnabled() {
 737         InputMethod inputMethod = getInputMethod();
 738 
 739         if (inputMethod == null) {
 740             throw new UnsupportedOperationException();
 741         }
 742         return inputMethod.isCompositionEnabled();
 743     }
 744 
 745     /**
 746      * @return a string with information about the current input method.
 747      * @exception UnsupportedOperationException when input method is null
 748      */
 749     public String getInputMethodInfo() {
 750         InputMethod inputMethod = getInputMethod();
 751 
 752         if (inputMethod == null) {
 753             throw new UnsupportedOperationException("Null input method");
 754         }
 755 
 756         String inputMethodInfo = null;
 757         if (inputMethod instanceof InputMethodAdapter) {
 758             // returns the information about the host native input method.
 759             inputMethodInfo = ((InputMethodAdapter)inputMethod).
 760                 getNativeInputMethodInfo();
 761         }
 762 
 763         // extracts the information from the InputMethodDescriptor
 764         // associated with the current java input method.
 765         if (inputMethodInfo == null && inputMethodLocator != null) {
 766             inputMethodInfo = inputMethodLocator.getDescriptor().
 767                 getInputMethodDisplayName(getLocale(), SunToolkit.
 768                                           getStartupLocale());
 769         }
 770 
 771         if (inputMethodInfo != null && !inputMethodInfo.equals("")) {
 772             return inputMethodInfo;
 773         }
 774 
 775         // do our best to return something useful.
 776         return inputMethod.toString() + "-" + inputMethod.getLocale().toString();
 777     }
 778 
 779     /**
 780      * Turns off the native IM. The native IM is diabled when
 781      * the deactive method of InputMethod is called. It is
 782      * delayed until the active method is called on a different
 783      * peer component. This method is provided to explicitly disable
 784      * the native IM.
 785      */
 786     public void disableNativeIM() {
 787         InputMethod inputMethod = getInputMethod();
 788         if (inputMethod != null && inputMethod instanceof InputMethodAdapter) {
 789             inputMethod.endComposition();
 790             ((InputMethodAdapter)inputMethod).disableInputMethod();
 791         }
 792     }
 793 
 794 
 795     private synchronized InputMethod getInputMethod() {
 796         if (inputMethod != null) {
 797             return inputMethod;
 798         }
 799 
 800         if (inputMethodCreationFailed) {
 801             return null;
 802         }
 803 
 804         inputMethod = getInputMethodInstance();
 805         return inputMethod;
 806     }
 807 
 808     /**
 809      * Returns an instance of the input method described by
 810      * the current input method locator. This may be an input
 811      * method that was previously used and switched out of,
 812      * or a new instance. The locale, character subsets, and
 813      * input method context of the input method are set.
 814      *
 815      * The inputMethodCreationFailed field is set to true if the
 816      * instantiation failed.
 817      *
 818      * @return an InputMethod instance
 819      * @see java.awt.im.spi.InputMethod#setInputMethodContext
 820      * @see java.awt.im.spi.InputMethod#setLocale
 821      * @see java.awt.im.spi.InputMethod#setCharacterSubsets
 822      */
 823     private InputMethod getInputMethodInstance() {
 824         InputMethodLocator locator = inputMethodLocator;
 825         if (locator == null) {
 826             inputMethodCreationFailed = true;
 827             return null;
 828         }
 829 
 830         Locale locale = locator.getLocale();
 831         InputMethod inputMethodInstance = null;
 832 
 833         // see whether we have a previously used input method
 834         if (usedInputMethods != null) {
 835             inputMethodInstance = usedInputMethods.remove(locator.deriveLocator(null));
 836             if (inputMethodInstance != null) {
 837                 if (locale != null) {
 838                     inputMethodInstance.setLocale(locale);
 839                 }
 840                 inputMethodInstance.setCharacterSubsets(characterSubsets);
 841                 Boolean state = perInputMethodState.remove(inputMethodInstance);
 842                 if (state != null) {
 843                     enableClientWindowNotification(inputMethodInstance, state.booleanValue());
 844                 }
 845                 ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 846                         (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 847                         ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 848                 return inputMethodInstance;
 849             }
 850         }
 851 
 852         // need to create new instance
 853         try {
 854             inputMethodInstance = locator.getDescriptor().createInputMethod();
 855 
 856             if (locale != null) {
 857                 inputMethodInstance.setLocale(locale);
 858             }
 859             inputMethodInstance.setInputMethodContext((InputMethodContext) this);
 860             inputMethodInstance.setCharacterSubsets(characterSubsets);
 861 
 862         } catch (Exception e) {
 863             logCreationFailed(e);
 864 
 865             // there are a number of bad things that can happen while creating
 866             // the input method. In any case, we just continue without an
 867             // input method.
 868             inputMethodCreationFailed = true;
 869 
 870             // if the instance has been created, then it means either
 871             // setLocale() or setInputMethodContext() failed.
 872             if (inputMethodInstance != null) {
 873                 inputMethodInstance = null;
 874             }
 875         } catch (LinkageError e) {
 876             logCreationFailed(e);
 877 
 878             // same as above
 879             inputMethodCreationFailed = true;
 880         }
 881         ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 882                 (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 883                 ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 884         return inputMethodInstance;
 885     }
 886 
 887     private void logCreationFailed(Throwable throwable) {
 888         String errorTextFormat = Toolkit.getProperty("AWT.InputMethodCreationFailed",
 889                                                      "Could not create {0}. Reason: {1}");
 890         Object[] args =
 891             {inputMethodLocator.getDescriptor().getInputMethodDisplayName(null, Locale.getDefault()),
 892              throwable.getLocalizedMessage()};
 893         MessageFormat mf = new MessageFormat(errorTextFormat);
 894         PlatformLogger logger = PlatformLogger.getLogger("sun.awt.im");
 895         logger.config(mf.format(args));
 896     }
 897 
 898     InputMethodLocator getInputMethodLocator() {
 899         if (inputMethod != null) {
 900             return inputMethodLocator.deriveLocator(inputMethod.getLocale());
 901         }
 902         return inputMethodLocator;
 903     }
 904 
 905     /**
 906      * @see java.awt.im.InputContext#endComposition
 907      */
 908     public synchronized void endComposition() {
 909         if (inputMethod != null) {
 910             inputMethod.endComposition();
 911         }
 912     }
 913 
 914     /**
 915      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
 916      */
 917     synchronized void enableClientWindowNotification(InputMethod requester,
 918                                                      boolean enable) {
 919         // in case this request is not from the current input method,
 920         // store the request and handle it when this requesting input
 921         // method becomes the current one.
 922         if (requester != inputMethod) {
 923             if (perInputMethodState == null) {
 924                 perInputMethodState = new HashMap<>(5);
 925             }
 926             perInputMethodState.put(requester, Boolean.valueOf(enable));
 927             return;
 928         }
 929 
 930         if (clientWindowNotificationEnabled != enable) {
 931             clientWindowLocation = null;
 932             clientWindowNotificationEnabled = enable;
 933         }
 934         if (clientWindowNotificationEnabled) {
 935             if (!addedClientWindowListeners()) {
 936                 addClientWindowListeners();
 937             }
 938             if (clientWindowListened != null) {
 939                 clientWindowLocation = null;
 940                 notifyClientWindowChange(clientWindowListened);
 941             }
 942         } else {
 943             if (addedClientWindowListeners()) {
 944                 removeClientWindowListeners();
 945             }
 946         }
 947     }
 948 
 949     private synchronized void notifyClientWindowChange(Window window) {
 950         if (inputMethod == null) {
 951             return;
 952         }
 953 
 954         // if the window is invisible or iconified, send null to the input method.
 955         if (!window.isVisible() ||
 956             ((window instanceof Frame) && ((Frame)window).getState() == Frame.ICONIFIED)) {
 957             clientWindowLocation = null;
 958             inputMethod.notifyClientWindowChange(null);
 959             return;
 960         }
 961         Rectangle location = window.getBounds();
 962         if (clientWindowLocation == null || !clientWindowLocation.equals(location)) {
 963             clientWindowLocation = location;
 964             inputMethod.notifyClientWindowChange(clientWindowLocation);
 965         }
 966     }
 967 
 968     private synchronized void addClientWindowListeners() {
 969         Component client = getClientComponent();
 970         if (client == null) {
 971             return;
 972         }
 973         Window window = getComponentWindow(client);
 974         if (window == null) {
 975             return;
 976         }
 977         window.addComponentListener(this);
 978         window.addWindowListener(this);
 979         clientWindowListened = window;
 980     }
 981 
 982     private synchronized void removeClientWindowListeners() {
 983         clientWindowListened.removeComponentListener(this);
 984         clientWindowListened.removeWindowListener(this);
 985         clientWindowListened = null;
 986     }
 987 
 988     /**
 989      * Returns true if listeners have been set up for client window
 990      * change notification.
 991      */
 992     private boolean addedClientWindowListeners() {
 993         return clientWindowListened != null;
 994     }
 995 
 996     /*
 997      * ComponentListener and WindowListener implementation
 998      */
 999     public void componentResized(ComponentEvent e) {
1000         notifyClientWindowChange((Window)e.getComponent());
1001     }
1002 
1003     public void componentMoved(ComponentEvent e) {
1004         notifyClientWindowChange((Window)e.getComponent());
1005     }
1006 
1007     public void componentShown(ComponentEvent e) {
1008         notifyClientWindowChange((Window)e.getComponent());
1009     }
1010 
1011     public void componentHidden(ComponentEvent e) {
1012         notifyClientWindowChange((Window)e.getComponent());
1013     }
1014 
1015     public void windowOpened(WindowEvent e) {}
1016     public void windowClosing(WindowEvent e) {}
1017     public void windowClosed(WindowEvent e) {}
1018 
1019     public void windowIconified(WindowEvent e) {
1020         notifyClientWindowChange(e.getWindow());
1021     }
1022 
1023     public void windowDeiconified(WindowEvent e) {
1024         notifyClientWindowChange(e.getWindow());
1025     }
1026 
1027     public void windowActivated(WindowEvent e) {}
1028     public void windowDeactivated(WindowEvent e) {}
1029 
1030     /**
1031      * Initializes the input method selection key definition in preference trees
1032      */
1033     private void initializeInputMethodSelectionKey() {
1034         AccessController.doPrivileged(new PrivilegedAction<Object>() {
1035             public Object run() {
1036                 // Look in user's tree
1037                 Preferences root = Preferences.userRoot();
1038                 inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1039 
1040                 if (inputMethodSelectionKey == null) {
1041                     // Look in system's tree
1042                     root = Preferences.systemRoot();
1043                     inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1044                 }
1045                 return null;
1046             }
1047         });
1048     }
1049 
1050     private AWTKeyStroke getInputMethodSelectionKeyStroke(Preferences root) {
1051         try {
1052             if (root.nodeExists(inputMethodSelectionKeyPath)) {
1053                 Preferences node = root.node(inputMethodSelectionKeyPath);
1054                 int keyCode = node.getInt(inputMethodSelectionKeyCodeName, KeyEvent.VK_UNDEFINED);
1055                 if (keyCode != KeyEvent.VK_UNDEFINED) {
1056                     int modifiers = node.getInt(inputMethodSelectionKeyModifiersName, 0);
1057                     return AWTKeyStroke.getAWTKeyStroke(keyCode, modifiers);
1058                 }
1059             }
1060         } catch (BackingStoreException bse) {
1061         }
1062 
1063         return null;
1064     }
1065 }