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             ((InputMethodAdapter)inputMethod).stopListening();
 790         }
 791     }
 792 
 793 
 794     private synchronized InputMethod getInputMethod() {
 795         if (inputMethod != null) {
 796             return inputMethod;
 797         }
 798 
 799         if (inputMethodCreationFailed) {
 800             return null;
 801         }
 802 
 803         inputMethod = getInputMethodInstance();
 804         return inputMethod;
 805     }
 806 
 807     /**
 808      * Returns an instance of the input method described by
 809      * the current input method locator. This may be an input
 810      * method that was previously used and switched out of,
 811      * or a new instance. The locale, character subsets, and
 812      * input method context of the input method are set.
 813      *
 814      * The inputMethodCreationFailed field is set to true if the
 815      * instantiation failed.
 816      *
 817      * @return an InputMethod instance
 818      * @see java.awt.im.spi.InputMethod#setInputMethodContext
 819      * @see java.awt.im.spi.InputMethod#setLocale
 820      * @see java.awt.im.spi.InputMethod#setCharacterSubsets
 821      */
 822     private InputMethod getInputMethodInstance() {
 823         InputMethodLocator locator = inputMethodLocator;
 824         if (locator == null) {
 825             inputMethodCreationFailed = true;
 826             return null;
 827         }
 828 
 829         Locale locale = locator.getLocale();
 830         InputMethod inputMethodInstance = null;
 831 
 832         // see whether we have a previously used input method
 833         if (usedInputMethods != null) {
 834             inputMethodInstance = usedInputMethods.remove(locator.deriveLocator(null));
 835             if (inputMethodInstance != null) {
 836                 if (locale != null) {
 837                     inputMethodInstance.setLocale(locale);
 838                 }
 839                 inputMethodInstance.setCharacterSubsets(characterSubsets);
 840                 Boolean state = perInputMethodState.remove(inputMethodInstance);
 841                 if (state != null) {
 842                     enableClientWindowNotification(inputMethodInstance, state.booleanValue());
 843                 }
 844                 ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 845                         (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 846                         ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 847                 return inputMethodInstance;
 848             }
 849         }
 850 
 851         // need to create new instance
 852         try {
 853             inputMethodInstance = locator.getDescriptor().createInputMethod();
 854 
 855             if (locale != null) {
 856                 inputMethodInstance.setLocale(locale);
 857             }
 858             inputMethodInstance.setInputMethodContext((InputMethodContext) this);
 859             inputMethodInstance.setCharacterSubsets(characterSubsets);
 860 
 861         } catch (Exception e) {
 862             logCreationFailed(e);
 863 
 864             // there are a number of bad things that can happen while creating
 865             // the input method. In any case, we just continue without an
 866             // input method.
 867             inputMethodCreationFailed = true;
 868 
 869             // if the instance has been created, then it means either
 870             // setLocale() or setInputMethodContext() failed.
 871             if (inputMethodInstance != null) {
 872                 inputMethodInstance = null;
 873             }
 874         } catch (LinkageError e) {
 875             logCreationFailed(e);
 876 
 877             // same as above
 878             inputMethodCreationFailed = true;
 879         }
 880         ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 881                 (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 882                 ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 883         return inputMethodInstance;
 884     }
 885 
 886     private void logCreationFailed(Throwable throwable) {
 887         String errorTextFormat = Toolkit.getProperty("AWT.InputMethodCreationFailed",
 888                                                      "Could not create {0}. Reason: {1}");
 889         Object[] args =
 890             {inputMethodLocator.getDescriptor().getInputMethodDisplayName(null, Locale.getDefault()),
 891              throwable.getLocalizedMessage()};
 892         MessageFormat mf = new MessageFormat(errorTextFormat);
 893         PlatformLogger logger = PlatformLogger.getLogger("sun.awt.im");
 894         logger.config(mf.format(args));
 895     }
 896 
 897     InputMethodLocator getInputMethodLocator() {
 898         if (inputMethod != null) {
 899             return inputMethodLocator.deriveLocator(inputMethod.getLocale());
 900         }
 901         return inputMethodLocator;
 902     }
 903 
 904     /**
 905      * @see java.awt.im.InputContext#endComposition
 906      */
 907     public synchronized void endComposition() {
 908         if (inputMethod != null) {
 909             inputMethod.endComposition();
 910         }
 911     }
 912 
 913     /**
 914      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
 915      */
 916     synchronized void enableClientWindowNotification(InputMethod requester,
 917                                                      boolean enable) {
 918         // in case this request is not from the current input method,
 919         // store the request and handle it when this requesting input
 920         // method becomes the current one.
 921         if (requester != inputMethod) {
 922             if (perInputMethodState == null) {
 923                 perInputMethodState = new HashMap<>(5);
 924             }
 925             perInputMethodState.put(requester, Boolean.valueOf(enable));
 926             return;
 927         }
 928 
 929         if (clientWindowNotificationEnabled != enable) {
 930             clientWindowLocation = null;
 931             clientWindowNotificationEnabled = enable;
 932         }
 933         if (clientWindowNotificationEnabled) {
 934             if (!addedClientWindowListeners()) {
 935                 addClientWindowListeners();
 936             }
 937             if (clientWindowListened != null) {
 938                 clientWindowLocation = null;
 939                 notifyClientWindowChange(clientWindowListened);
 940             }
 941         } else {
 942             if (addedClientWindowListeners()) {
 943                 removeClientWindowListeners();
 944             }
 945         }
 946     }
 947 
 948     private synchronized void notifyClientWindowChange(Window window) {
 949         if (inputMethod == null) {
 950             return;
 951         }
 952 
 953         // if the window is invisible or iconified, send null to the input method.
 954         if (!window.isVisible() ||
 955             ((window instanceof Frame) && ((Frame)window).getState() == Frame.ICONIFIED)) {
 956             clientWindowLocation = null;
 957             inputMethod.notifyClientWindowChange(null);
 958             return;
 959         }
 960         Rectangle location = window.getBounds();
 961         if (clientWindowLocation == null || !clientWindowLocation.equals(location)) {
 962             clientWindowLocation = location;
 963             inputMethod.notifyClientWindowChange(clientWindowLocation);
 964         }
 965     }
 966 
 967     private synchronized void addClientWindowListeners() {
 968         Component client = getClientComponent();
 969         if (client == null) {
 970             return;
 971         }
 972         Window window = getComponentWindow(client);
 973         if (window == null) {
 974             return;
 975         }
 976         window.addComponentListener(this);
 977         window.addWindowListener(this);
 978         clientWindowListened = window;
 979     }
 980 
 981     private synchronized void removeClientWindowListeners() {
 982         clientWindowListened.removeComponentListener(this);
 983         clientWindowListened.removeWindowListener(this);
 984         clientWindowListened = null;
 985     }
 986 
 987     /**
 988      * Returns true if listeners have been set up for client window
 989      * change notification.
 990      */
 991     private boolean addedClientWindowListeners() {
 992         return clientWindowListened != null;
 993     }
 994 
 995     /*
 996      * ComponentListener and WindowListener implementation
 997      */
 998     public void componentResized(ComponentEvent e) {
 999         notifyClientWindowChange((Window)e.getComponent());
1000     }
1001 
1002     public void componentMoved(ComponentEvent e) {
1003         notifyClientWindowChange((Window)e.getComponent());
1004     }
1005 
1006     public void componentShown(ComponentEvent e) {
1007         notifyClientWindowChange((Window)e.getComponent());
1008     }
1009 
1010     public void componentHidden(ComponentEvent e) {
1011         notifyClientWindowChange((Window)e.getComponent());
1012     }
1013 
1014     public void windowOpened(WindowEvent e) {}
1015     public void windowClosing(WindowEvent e) {}
1016     public void windowClosed(WindowEvent e) {}
1017 
1018     public void windowIconified(WindowEvent e) {
1019         notifyClientWindowChange(e.getWindow());
1020     }
1021 
1022     public void windowDeiconified(WindowEvent e) {
1023         notifyClientWindowChange(e.getWindow());
1024     }
1025 
1026     public void windowActivated(WindowEvent e) {}
1027     public void windowDeactivated(WindowEvent e) {}
1028 
1029     /**
1030      * Initializes the input method selection key definition in preference trees
1031      */
1032     private void initializeInputMethodSelectionKey() {
1033         AccessController.doPrivileged(new PrivilegedAction<Object>() {
1034             public Object run() {
1035                 // Look in user's tree
1036                 Preferences root = Preferences.userRoot();
1037                 inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1038 
1039                 if (inputMethodSelectionKey == null) {
1040                     // Look in system's tree
1041                     root = Preferences.systemRoot();
1042                     inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1043                 }
1044                 return null;
1045             }
1046         });
1047     }
1048 
1049     private AWTKeyStroke getInputMethodSelectionKeyStroke(Preferences root) {
1050         try {
1051             if (root.nodeExists(inputMethodSelectionKeyPath)) {
1052                 Preferences node = root.node(inputMethodSelectionKeyPath);
1053                 int keyCode = node.getInt(inputMethodSelectionKeyCodeName, KeyEvent.VK_UNDEFINED);
1054                 if (keyCode != KeyEvent.VK_UNDEFINED) {
1055                     int modifiers = node.getInt(inputMethodSelectionKeyModifiersName, 0);
1056                     return AWTKeyStroke.getAWTKeyStroke(keyCode, modifiers);
1057                 }
1058             }
1059         } catch (BackingStoreException bse) {
1060         }
1061 
1062         return null;
1063     }
1064 }