1 /*
   2  * Copyright (c) 1997, 2018, 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.Level.FINE)) {
 391                 log.fine("Current client component " + currentClientComponent);
 392             }
 393             if (inputMethod instanceof InputMethodAdapter) {
 394                 ((InputMethodAdapter) inputMethod).setClientComponent(currentClientComponent);
 395             }
 396             inputMethod.activate();
 397             isInputMethodActive = true;
 398 
 399             if (perInputMethodState != null) {
 400                 Boolean state = perInputMethodState.remove(inputMethod);
 401                 if (state != null) {
 402                     clientWindowNotificationEnabled = state.booleanValue();
 403                 }
 404             }
 405             if (clientWindowNotificationEnabled) {
 406                 if (!addedClientWindowListeners()) {
 407                     addClientWindowListeners();
 408                 }
 409                 synchronized(this) {
 410                     if (clientWindowListened != null) {
 411                         notifyClientWindowChange(clientWindowListened);
 412                     }
 413                 }
 414             } else {
 415                 if (addedClientWindowListeners()) {
 416                     removeClientWindowListeners();
 417                 }
 418             }
 419         }
 420         InputMethodManager.getInstance().setInputContext(this);
 421 
 422         ((InputMethodContext) this).grabCompositionArea(updateCompositionArea);
 423     }
 424 
 425     static Window getComponentWindow(Component component) {
 426         while (true) {
 427             if (component == null) {
 428                 return null;
 429             } else if (component instanceof Window) {
 430                 return (Window) component;
 431             } else {
 432                 component = component.getParent();
 433             }
 434         }
 435     }
 436 
 437     /**
 438      * Handles focus lost events for any component that's using
 439      * this input context.
 440      * These events are generated by AWT when the keyboard focus
 441      * moves away from a component.
 442      * Besides actual client components, the source components
 443      * may also be the composition area or any component in an
 444      * input method window.
 445      *
 446      * @param source the component losing the focus
 447      * @isTemporary whether the focus change is temporary
 448      */
 449     private void focusLost(Component source, boolean isTemporary) {
 450 
 451         // see the note on synchronization in focusGained
 452         synchronized (source.getTreeLock()) {
 453             synchronized (this) {
 454 
 455                 // We need to suppress deactivation if removeNotify has been called earlier.
 456                 // This is indicated by isInputMethodActive == false.
 457                 if (isInputMethodActive) {
 458                     deactivateInputMethod(isTemporary);
 459                 }
 460 
 461                 awtFocussedComponent = null;
 462                 if (inputMethod instanceof InputMethodAdapter) {
 463                     ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(null);
 464                 }
 465 
 466                 // hides the composition area if currently it is visible
 467                 InputMethodContext inputContext = ((InputMethodContext)this);
 468                 if (inputContext.isCompositionAreaVisible()) {
 469                     inputContext.setCompositionAreaVisible(false);
 470                     compositionAreaHidden = true;
 471                 }
 472             }
 473         }
 474     }
 475 
 476     /**
 477      * Checks the key event is the input method selection key or not.
 478      */
 479     private boolean checkInputMethodSelectionKey(KeyEvent event) {
 480         if (inputMethodSelectionKey != null) {
 481             AWTKeyStroke aKeyStroke = AWTKeyStroke.getAWTKeyStrokeForEvent(event);
 482             return inputMethodSelectionKey.equals(aKeyStroke);
 483         } else {
 484             return false;
 485         }
 486     }
 487 
 488     private void deactivateInputMethod(boolean isTemporary) {
 489         InputMethodManager.getInstance().setInputContext(null);
 490         if (inputMethod != null) {
 491             isInputMethodActive = false;
 492             inputMethod.deactivate(isTemporary);
 493             previousInputMethod = inputMethod;
 494         }
 495     }
 496 
 497     /**
 498      * Switches from the current input method to the one described by newLocator.
 499      * The current input method, if any, is asked to end composition, deactivated,
 500      * and saved for future use. The newLocator is made the current locator. If
 501      * the input context is active, an input method instance for the new locator
 502      * is obtained; otherwise this is deferred until required.
 503      */
 504     synchronized void changeInputMethod(InputMethodLocator newLocator) {
 505         // If we don't have a locator yet, this must be a new input context.
 506         // If we created a new input method here, we might get into an
 507         // infinite loop: create input method -> create some input method window ->
 508         // create new input context -> add input context to input method manager's context list ->
 509         // call changeInputMethod on it.
 510         // So, just record the locator. dispatchEvent will create the input method when needed.
 511         if (inputMethodLocator == null) {
 512             inputMethodLocator = newLocator;
 513             inputMethodCreationFailed = false;
 514             return;
 515         }
 516 
 517         // If the same input method is specified, just keep it.
 518         // Adjust the locale if necessary.
 519         if (inputMethodLocator.sameInputMethod(newLocator)) {
 520             Locale newLocale = newLocator.getLocale();
 521             if (newLocale != null && inputMethodLocator.getLocale() != newLocale) {
 522                 if (inputMethod != null) {
 523                     inputMethod.setLocale(newLocale);
 524                 }
 525                 inputMethodLocator = newLocator;
 526             }
 527             return;
 528         }
 529 
 530         // Switch out the old input method
 531         Locale savedLocale = inputMethodLocator.getLocale();
 532         boolean wasInputMethodActive = isInputMethodActive;
 533         boolean wasCompositionEnabledSupported = false;
 534         boolean wasCompositionEnabled = false;
 535         if (inputMethod != null) {
 536             try {
 537                 wasCompositionEnabled = inputMethod.isCompositionEnabled();
 538                 wasCompositionEnabledSupported = true;
 539             } catch (UnsupportedOperationException e) { }
 540 
 541             if (currentClientComponent != null) {
 542                 if (!isInputMethodActive) {
 543                     activateInputMethod(false);
 544                 }
 545                 endComposition();
 546                 deactivateInputMethod(false);
 547                 if (inputMethod instanceof InputMethodAdapter) {
 548                     ((InputMethodAdapter) inputMethod).setClientComponent(null);
 549                 }
 550                 if (null == currentClientComponent.getInputMethodRequests())
 551                     wasCompositionEnabledSupported = false;
 552             }
 553             savedLocale = inputMethod.getLocale();
 554 
 555             // keep the input method instance around for future use
 556             if (usedInputMethods == null) {
 557                 usedInputMethods = new HashMap<>(5);
 558             }
 559             if (perInputMethodState == null) {
 560                 perInputMethodState = new HashMap<>(5);
 561             }
 562             usedInputMethods.put(inputMethodLocator.deriveLocator(null), inputMethod);
 563             perInputMethodState.put(inputMethod,
 564                                     Boolean.valueOf(clientWindowNotificationEnabled));
 565             enableClientWindowNotification(inputMethod, false);
 566             if (this == inputMethodWindowContext) {
 567                 inputMethod.hideWindows();
 568                 inputMethod.removeNotify();
 569                 inputMethodWindowContext = null;
 570             }
 571             inputMethodLocator = null;
 572             inputMethod = null;
 573             inputMethodCreationFailed = false;
 574         }
 575 
 576         // Switch in the new input method
 577         if (newLocator.getLocale() == null && savedLocale != null &&
 578                 newLocator.isLocaleAvailable(savedLocale)) {
 579             newLocator = newLocator.deriveLocator(savedLocale);
 580         }
 581         inputMethodLocator = newLocator;
 582         inputMethodCreationFailed = false;
 583 
 584         // activate the new input method if the old one was active
 585         if (wasInputMethodActive) {
 586             inputMethod = getInputMethodInstance();
 587             if (inputMethod instanceof InputMethodAdapter) {
 588                 ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(awtFocussedComponent);
 589             }
 590             activateInputMethod(true);
 591         }
 592 
 593         // enable/disable composition if the old one supports querying enable/disable
 594         if (wasCompositionEnabledSupported) {
 595             inputMethod = getInputMethod();
 596             if (inputMethod != null) {
 597                 try {
 598                     inputMethod.setCompositionEnabled(wasCompositionEnabled);
 599                 } catch (UnsupportedOperationException e) { }
 600             }
 601         }
 602     }
 603 
 604     /**
 605      * Returns the client component.
 606      */
 607     Component getClientComponent() {
 608         return currentClientComponent;
 609     }
 610 
 611     /**
 612      * @see java.awt.im.InputContext#removeNotify
 613      * @exception NullPointerException when the component is null.
 614      */
 615     public synchronized void removeNotify(Component component) {
 616         if (component == null) {
 617             throw new NullPointerException();
 618         }
 619 
 620         if (inputMethod == null) {
 621             if (component == currentClientComponent) {
 622                 currentClientComponent = null;
 623             }
 624             return;
 625         }
 626 
 627         // We may or may not get a FOCUS_LOST event for this component,
 628         // so do the deactivation stuff here too.
 629         if (component == awtFocussedComponent) {
 630             focusLost(component, false);
 631         }
 632 
 633         if (component == currentClientComponent) {
 634             if (isInputMethodActive) {
 635                 // component wasn't the one that had the focus
 636                 deactivateInputMethod(false);
 637             }
 638             inputMethod.removeNotify();
 639             if (clientWindowNotificationEnabled && addedClientWindowListeners()) {
 640                 removeClientWindowListeners();
 641             }
 642             currentClientComponent = null;
 643             if (inputMethod instanceof InputMethodAdapter) {
 644                 ((InputMethodAdapter) inputMethod).setClientComponent(null);
 645             }
 646 
 647             // removeNotify() can be issued from a thread other than the event dispatch
 648             // thread.  In that case, avoid possible deadlock between Component.AWTTreeLock
 649             // and InputMethodContext.compositionAreaHandlerLock by releasing the composition
 650             // area on the event dispatch thread.
 651             if (EventQueue.isDispatchThread()) {
 652                 ((InputMethodContext)this).releaseCompositionArea();
 653             } else {
 654                 EventQueue.invokeLater(new Runnable() {
 655                     public void run() {
 656                         ((InputMethodContext)InputContext.this).releaseCompositionArea();
 657                     }
 658                 });
 659             }
 660         }
 661     }
 662 
 663     /**
 664      * @see java.awt.im.InputContext#dispose
 665      * @exception IllegalStateException when the currentClientComponent is not null
 666      */
 667     public synchronized void dispose() {
 668         if (currentClientComponent != null) {
 669             throw new IllegalStateException("Can't dispose InputContext while it's active");
 670         }
 671         if (inputMethod != null) {
 672             if (this == inputMethodWindowContext) {
 673                 inputMethod.hideWindows();
 674                 inputMethodWindowContext = null;
 675             }
 676             if (inputMethod == previousInputMethod) {
 677                 previousInputMethod = null;
 678             }
 679             if (clientWindowNotificationEnabled) {
 680                 if (addedClientWindowListeners()) {
 681                     removeClientWindowListeners();
 682                 }
 683                 clientWindowNotificationEnabled = false;
 684             }
 685             inputMethod.dispose();
 686 
 687             // in case the input method enabled the client window
 688             // notification in dispose(), which shouldn't happen, it
 689             // needs to be cleaned up again.
 690             if (clientWindowNotificationEnabled) {
 691                 enableClientWindowNotification(inputMethod, false);
 692             }
 693 
 694             inputMethod = null;
 695         }
 696         inputMethodLocator = null;
 697         if (usedInputMethods != null && !usedInputMethods.isEmpty()) {
 698             Iterator<InputMethod> iterator = usedInputMethods.values().iterator();
 699             usedInputMethods = null;
 700             while (iterator.hasNext()) {
 701                 iterator.next().dispose();
 702             }
 703         }
 704 
 705         // cleanup client window notification variables
 706         clientWindowNotificationEnabled = false;
 707         clientWindowListened = null;
 708         perInputMethodState = null;
 709     }
 710 
 711     /**
 712      * @see java.awt.im.InputContext#getInputMethodControlObject
 713      */
 714     public synchronized Object getInputMethodControlObject() {
 715         InputMethod inputMethod = getInputMethod();
 716 
 717         if (inputMethod != null) {
 718             return inputMethod.getControlObject();
 719         } else {
 720             return null;
 721         }
 722     }
 723 
 724     /**
 725      * @see java.awt.im.InputContext#setCompositionEnabled(boolean)
 726      * @exception UnsupportedOperationException when input method is null
 727      */
 728     public void setCompositionEnabled(boolean enable) {
 729         InputMethod inputMethod = getInputMethod();
 730 
 731         if (inputMethod == null) {
 732             throw new UnsupportedOperationException();
 733         }
 734         inputMethod.setCompositionEnabled(enable);
 735     }
 736 
 737     /**
 738      * @see java.awt.im.InputContext#isCompositionEnabled
 739      * @exception UnsupportedOperationException when input method is null
 740      */
 741     public boolean isCompositionEnabled() {
 742         InputMethod inputMethod = getInputMethod();
 743 
 744         if (inputMethod == null) {
 745             throw new UnsupportedOperationException();
 746         }
 747         return inputMethod.isCompositionEnabled();
 748     }
 749 
 750     /**
 751      * @return a string with information about the current input method.
 752      * @exception UnsupportedOperationException when input method is null
 753      */
 754     public String getInputMethodInfo() {
 755         InputMethod inputMethod = getInputMethod();
 756 
 757         if (inputMethod == null) {
 758             throw new UnsupportedOperationException("Null input method");
 759         }
 760 
 761         String inputMethodInfo = null;
 762         if (inputMethod instanceof InputMethodAdapter) {
 763             // returns the information about the host native input method.
 764             inputMethodInfo = ((InputMethodAdapter)inputMethod).
 765                 getNativeInputMethodInfo();
 766         }
 767 
 768         // extracts the information from the InputMethodDescriptor
 769         // associated with the current java input method.
 770         if (inputMethodInfo == null && inputMethodLocator != null) {
 771             inputMethodInfo = inputMethodLocator.getDescriptor().
 772                 getInputMethodDisplayName(getLocale(), SunToolkit.
 773                                           getStartupLocale());
 774         }
 775 
 776         if (inputMethodInfo != null && !inputMethodInfo.equals("")) {
 777             return inputMethodInfo;
 778         }
 779 
 780         // do our best to return something useful.
 781         return inputMethod.toString() + "-" + inputMethod.getLocale().toString();
 782     }
 783 
 784     /**
 785      * Turns off the native IM. The native IM is diabled when
 786      * the deactive method of InputMethod is called. It is
 787      * delayed until the active method is called on a different
 788      * peer component. This method is provided to explicitly disable
 789      * the native IM.
 790      */
 791     public void disableNativeIM() {
 792         InputMethod inputMethod = getInputMethod();
 793         if (inputMethod != null && inputMethod instanceof InputMethodAdapter) {
 794             ((InputMethodAdapter)inputMethod).stopListening();
 795         }
 796     }
 797 
 798 
 799     private synchronized InputMethod getInputMethod() {
 800         if (inputMethod != null) {
 801             return inputMethod;
 802         }
 803 
 804         if (inputMethodCreationFailed) {
 805             return null;
 806         }
 807 
 808         inputMethod = getInputMethodInstance();
 809         return inputMethod;
 810     }
 811 
 812     /**
 813      * Returns an instance of the input method described by
 814      * the current input method locator. This may be an input
 815      * method that was previously used and switched out of,
 816      * or a new instance. The locale, character subsets, and
 817      * input method context of the input method are set.
 818      *
 819      * The inputMethodCreationFailed field is set to true if the
 820      * instantiation failed.
 821      *
 822      * @return an InputMethod instance
 823      * @see java.awt.im.spi.InputMethod#setInputMethodContext
 824      * @see java.awt.im.spi.InputMethod#setLocale
 825      * @see java.awt.im.spi.InputMethod#setCharacterSubsets
 826      */
 827     private InputMethod getInputMethodInstance() {
 828         InputMethodLocator locator = inputMethodLocator;
 829         if (locator == null) {
 830             inputMethodCreationFailed = true;
 831             return null;
 832         }
 833 
 834         Locale locale = locator.getLocale();
 835         InputMethod inputMethodInstance = null;
 836 
 837         // see whether we have a previously used input method
 838         if (usedInputMethods != null) {
 839             inputMethodInstance = usedInputMethods.remove(locator.deriveLocator(null));
 840             if (inputMethodInstance != null) {
 841                 if (locale != null) {
 842                     inputMethodInstance.setLocale(locale);
 843                 }
 844                 inputMethodInstance.setCharacterSubsets(characterSubsets);
 845                 Boolean state = perInputMethodState.remove(inputMethodInstance);
 846                 if (state != null) {
 847                     enableClientWindowNotification(inputMethodInstance, state.booleanValue());
 848                 }
 849                 ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 850                         (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 851                         ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 852                 return inputMethodInstance;
 853             }
 854         }
 855 
 856         // need to create new instance
 857         try {
 858             inputMethodInstance = locator.getDescriptor().createInputMethod();
 859 
 860             if (locale != null) {
 861                 inputMethodInstance.setLocale(locale);
 862             }
 863             inputMethodInstance.setInputMethodContext((InputMethodContext) this);
 864             inputMethodInstance.setCharacterSubsets(characterSubsets);
 865 
 866         } catch (Exception e) {
 867             logCreationFailed(e);
 868 
 869             // there are a number of bad things that can happen while creating
 870             // the input method. In any case, we just continue without an
 871             // input method.
 872             inputMethodCreationFailed = true;
 873 
 874             // if the instance has been created, then it means either
 875             // setLocale() or setInputMethodContext() failed.
 876             if (inputMethodInstance != null) {
 877                 inputMethodInstance = null;
 878             }
 879         } catch (LinkageError e) {
 880             logCreationFailed(e);
 881 
 882             // same as above
 883             inputMethodCreationFailed = true;
 884         }
 885         ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 886                 (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 887                 ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 888         return inputMethodInstance;
 889     }
 890 
 891     private void logCreationFailed(Throwable throwable) {
 892         PlatformLogger logger = PlatformLogger.getLogger("sun.awt.im");
 893         if (logger.isLoggable(PlatformLogger.Level.CONFIG)) {
 894             String errorTextFormat = Toolkit.getProperty("AWT.InputMethodCreationFailed",
 895                                                          "Could not create {0}. Reason: {1}");
 896             Object[] args =
 897                 {inputMethodLocator.getDescriptor().getInputMethodDisplayName(null, Locale.getDefault()),
 898                  throwable.getLocalizedMessage()};
 899             MessageFormat mf = new MessageFormat(errorTextFormat);
 900             logger.config(mf.format(args));
 901         }
 902     }
 903 
 904     InputMethodLocator getInputMethodLocator() {
 905         if (inputMethod != null) {
 906             return inputMethodLocator.deriveLocator(inputMethod.getLocale());
 907         }
 908         return inputMethodLocator;
 909     }
 910 
 911     /**
 912      * @see java.awt.im.InputContext#endComposition
 913      */
 914     public synchronized void endComposition() {
 915         if (inputMethod != null) {
 916             inputMethod.endComposition();
 917         }
 918     }
 919 
 920     /**
 921      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
 922      */
 923     synchronized void enableClientWindowNotification(InputMethod requester,
 924                                                      boolean enable) {
 925         // in case this request is not from the current input method,
 926         // store the request and handle it when this requesting input
 927         // method becomes the current one.
 928         if (requester != inputMethod) {
 929             if (perInputMethodState == null) {
 930                 perInputMethodState = new HashMap<>(5);
 931             }
 932             perInputMethodState.put(requester, Boolean.valueOf(enable));
 933             return;
 934         }
 935 
 936         if (clientWindowNotificationEnabled != enable) {
 937             clientWindowLocation = null;
 938             clientWindowNotificationEnabled = enable;
 939         }
 940         if (clientWindowNotificationEnabled) {
 941             if (!addedClientWindowListeners()) {
 942                 addClientWindowListeners();
 943             }
 944             if (clientWindowListened != null) {
 945                 clientWindowLocation = null;
 946                 notifyClientWindowChange(clientWindowListened);
 947             }
 948         } else {
 949             if (addedClientWindowListeners()) {
 950                 removeClientWindowListeners();
 951             }
 952         }
 953     }
 954 
 955     private synchronized void notifyClientWindowChange(Window window) {
 956         if (inputMethod == null) {
 957             return;
 958         }
 959 
 960         // if the window is invisible or iconified, send null to the input method.
 961         if (!window.isVisible() ||
 962             ((window instanceof Frame) && ((Frame)window).getState() == Frame.ICONIFIED)) {
 963             clientWindowLocation = null;
 964             inputMethod.notifyClientWindowChange(null);
 965             return;
 966         }
 967         Rectangle location = window.getBounds();
 968         if (clientWindowLocation == null || !clientWindowLocation.equals(location)) {
 969             clientWindowLocation = location;
 970             inputMethod.notifyClientWindowChange(clientWindowLocation);
 971         }
 972     }
 973 
 974     private synchronized void addClientWindowListeners() {
 975         Component client = getClientComponent();
 976         if (client == null) {
 977             return;
 978         }
 979         Window window = getComponentWindow(client);
 980         if (window == null) {
 981             return;
 982         }
 983         window.addComponentListener(this);
 984         window.addWindowListener(this);
 985         clientWindowListened = window;
 986     }
 987 
 988     private synchronized void removeClientWindowListeners() {
 989         clientWindowListened.removeComponentListener(this);
 990         clientWindowListened.removeWindowListener(this);
 991         clientWindowListened = null;
 992     }
 993 
 994     /**
 995      * Returns true if listeners have been set up for client window
 996      * change notification.
 997      */
 998     private boolean addedClientWindowListeners() {
 999         return clientWindowListened != null;
1000     }
1001 
1002     /*
1003      * ComponentListener and WindowListener implementation
1004      */
1005     public void componentResized(ComponentEvent e) {
1006         notifyClientWindowChange((Window)e.getComponent());
1007     }
1008 
1009     public void componentMoved(ComponentEvent e) {
1010         notifyClientWindowChange((Window)e.getComponent());
1011     }
1012 
1013     public void componentShown(ComponentEvent e) {
1014         notifyClientWindowChange((Window)e.getComponent());
1015     }
1016 
1017     public void componentHidden(ComponentEvent e) {
1018         notifyClientWindowChange((Window)e.getComponent());
1019     }
1020 
1021     public void windowOpened(WindowEvent e) {}
1022     public void windowClosing(WindowEvent e) {}
1023     public void windowClosed(WindowEvent e) {}
1024 
1025     public void windowIconified(WindowEvent e) {
1026         notifyClientWindowChange(e.getWindow());
1027     }
1028 
1029     public void windowDeiconified(WindowEvent e) {
1030         notifyClientWindowChange(e.getWindow());
1031     }
1032 
1033     public void windowActivated(WindowEvent e) {}
1034     public void windowDeactivated(WindowEvent e) {}
1035 
1036     /**
1037      * Initializes the input method selection key definition in preference trees
1038      */
1039     private void initializeInputMethodSelectionKey() {
1040         AccessController.doPrivileged(new PrivilegedAction<Object>() {
1041             public Object run() {
1042                 // Look in user's tree
1043                 Preferences root = Preferences.userRoot();
1044                 inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1045 
1046                 if (inputMethodSelectionKey == null) {
1047                     // Look in system's tree
1048                     root = Preferences.systemRoot();
1049                     inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1050                 }
1051                 return null;
1052             }
1053         });
1054     }
1055 
1056     private AWTKeyStroke getInputMethodSelectionKeyStroke(Preferences root) {
1057         try {
1058             if (root.nodeExists(inputMethodSelectionKeyPath)) {
1059                 Preferences node = root.node(inputMethodSelectionKeyPath);
1060                 int keyCode = node.getInt(inputMethodSelectionKeyCodeName, KeyEvent.VK_UNDEFINED);
1061                 if (keyCode != KeyEvent.VK_UNDEFINED) {
1062                     int modifiers = node.getInt(inputMethodSelectionKeyModifiersName, 0);
1063                     return AWTKeyStroke.getAWTKeyStroke(keyCode, modifiers);
1064                 }
1065             }
1066         } catch (BackingStoreException bse) {
1067         }
1068 
1069         return null;
1070     }
1071 }