1 /*
   2  * Copyright (c) 1997, 2013, 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             }
 551             savedLocale = inputMethod.getLocale();
 552 
 553             // keep the input method instance around for future use
 554             if (usedInputMethods == null) {
 555                 usedInputMethods = new HashMap<>(5);
 556             }
 557             if (perInputMethodState == null) {
 558                 perInputMethodState = new HashMap<>(5);
 559             }
 560             usedInputMethods.put(inputMethodLocator.deriveLocator(null), inputMethod);
 561             perInputMethodState.put(inputMethod,
 562                                     Boolean.valueOf(clientWindowNotificationEnabled));
 563             enableClientWindowNotification(inputMethod, false);
 564             if (this == inputMethodWindowContext) {
 565                 inputMethod.hideWindows();
 566                 inputMethodWindowContext = null;
 567             }
 568             inputMethodLocator = null;
 569             inputMethod = null;
 570             inputMethodCreationFailed = false;
 571         }
 572 
 573         // Switch in the new input method
 574         if (newLocator.getLocale() == null && savedLocale != null &&
 575                 newLocator.isLocaleAvailable(savedLocale)) {
 576             newLocator = newLocator.deriveLocator(savedLocale);
 577         }
 578         inputMethodLocator = newLocator;
 579         inputMethodCreationFailed = false;
 580 
 581         // activate the new input method if the old one was active
 582         if (wasInputMethodActive) {
 583             inputMethod = getInputMethodInstance();
 584             if (inputMethod instanceof InputMethodAdapter) {
 585                 ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(awtFocussedComponent);
 586             }
 587             activateInputMethod(true);
 588         }
 589 
 590         // enable/disable composition if the old one supports querying enable/disable
 591         if (wasCompositionEnabledSupported) {
 592             inputMethod = getInputMethod();
 593             if (inputMethod != null) {
 594                 try {
 595                     inputMethod.setCompositionEnabled(wasCompositionEnabled);
 596                 } catch (UnsupportedOperationException e) { }
 597             }
 598         }
 599     }
 600 
 601     /**
 602      * Returns the client component.
 603      */
 604     Component getClientComponent() {
 605         return currentClientComponent;
 606     }
 607 
 608     /**
 609      * @see java.awt.im.InputContext#removeNotify
 610      * @exception NullPointerException when the component is null.
 611      */
 612     public synchronized void removeNotify(Component component) {
 613         if (component == null) {
 614             throw new NullPointerException();
 615         }
 616 
 617         if (inputMethod == null) {
 618             if (component == currentClientComponent) {
 619                 currentClientComponent = null;
 620             }
 621             return;
 622         }
 623 
 624         // We may or may not get a FOCUS_LOST event for this component,
 625         // so do the deactivation stuff here too.
 626         if (component == awtFocussedComponent) {
 627             focusLost(component, false);
 628         }
 629 
 630         if (component == currentClientComponent) {
 631             if (isInputMethodActive) {
 632                 // component wasn't the one that had the focus
 633                 deactivateInputMethod(false);
 634             }
 635             inputMethod.removeNotify();
 636             if (clientWindowNotificationEnabled && addedClientWindowListeners()) {
 637                 removeClientWindowListeners();
 638             }
 639             currentClientComponent = null;
 640             if (inputMethod instanceof InputMethodAdapter) {
 641                 ((InputMethodAdapter) inputMethod).setClientComponent(null);
 642             }
 643 
 644             // removeNotify() can be issued from a thread other than the event dispatch
 645             // thread.  In that case, avoid possible deadlock between Component.AWTTreeLock
 646             // and InputMethodContext.compositionAreaHandlerLock by releasing the composition
 647             // area on the event dispatch thread.
 648             if (EventQueue.isDispatchThread()) {
 649                 ((InputMethodContext)this).releaseCompositionArea();
 650             } else {
 651                 EventQueue.invokeLater(new Runnable() {
 652                     public void run() {
 653                         ((InputMethodContext)InputContext.this).releaseCompositionArea();
 654                     }
 655                 });
 656             }
 657         }
 658     }
 659 
 660     /**
 661      * @see java.awt.im.InputContext#dispose
 662      * @exception IllegalStateException when the currentClientComponent is not null
 663      */
 664     public synchronized void dispose() {
 665         if (currentClientComponent != null) {
 666             throw new IllegalStateException("Can't dispose InputContext while it's active");
 667         }
 668         if (inputMethod != null) {
 669             if (this == inputMethodWindowContext) {
 670                 inputMethod.hideWindows();
 671                 inputMethodWindowContext = null;
 672             }
 673             if (inputMethod == previousInputMethod) {
 674                 previousInputMethod = null;
 675             }
 676             if (clientWindowNotificationEnabled) {
 677                 if (addedClientWindowListeners()) {
 678                     removeClientWindowListeners();
 679                 }
 680                 clientWindowNotificationEnabled = false;
 681             }
 682             inputMethod.dispose();
 683 
 684             // in case the input method enabled the client window
 685             // notification in dispose(), which shouldn't happen, it
 686             // needs to be cleaned up again.
 687             if (clientWindowNotificationEnabled) {
 688                 enableClientWindowNotification(inputMethod, false);
 689             }
 690 
 691             inputMethod = null;
 692         }
 693         inputMethodLocator = null;
 694         if (usedInputMethods != null && !usedInputMethods.isEmpty()) {
 695             Iterator<InputMethod> iterator = usedInputMethods.values().iterator();
 696             usedInputMethods = null;
 697             while (iterator.hasNext()) {
 698                 iterator.next().dispose();
 699             }
 700         }
 701 
 702         // cleanup client window notification variables
 703         clientWindowNotificationEnabled = false;
 704         clientWindowListened = null;
 705         perInputMethodState = null;
 706     }
 707 
 708     /**
 709      * @see java.awt.im.InputContext#getInputMethodControlObject
 710      */
 711     public synchronized Object getInputMethodControlObject() {
 712         InputMethod inputMethod = getInputMethod();
 713 
 714         if (inputMethod != null) {
 715             return inputMethod.getControlObject();
 716         } else {
 717             return null;
 718         }
 719     }
 720 
 721     /**
 722      * @see java.awt.im.InputContext#setCompositionEnabled(boolean)
 723      * @exception UnsupportedOperationException when input method is null
 724      */
 725     public void setCompositionEnabled(boolean enable) {
 726         InputMethod inputMethod = getInputMethod();
 727 
 728         if (inputMethod == null) {
 729             throw new UnsupportedOperationException();
 730         }
 731         inputMethod.setCompositionEnabled(enable);
 732     }
 733 
 734     /**
 735      * @see java.awt.im.InputContext#isCompositionEnabled
 736      * @exception UnsupportedOperationException when input method is null
 737      */
 738     public boolean isCompositionEnabled() {
 739         InputMethod inputMethod = getInputMethod();
 740 
 741         if (inputMethod == null) {
 742             throw new UnsupportedOperationException();
 743         }
 744         return inputMethod.isCompositionEnabled();
 745     }
 746 
 747     /**
 748      * @return a string with information about the current input method.
 749      * @exception UnsupportedOperationException when input method is null
 750      */
 751     public String getInputMethodInfo() {
 752         InputMethod inputMethod = getInputMethod();
 753 
 754         if (inputMethod == null) {
 755             throw new UnsupportedOperationException("Null input method");
 756         }
 757 
 758         String inputMethodInfo = null;
 759         if (inputMethod instanceof InputMethodAdapter) {
 760             // returns the information about the host native input method.
 761             inputMethodInfo = ((InputMethodAdapter)inputMethod).
 762                 getNativeInputMethodInfo();
 763         }
 764 
 765         // extracts the information from the InputMethodDescriptor
 766         // associated with the current java input method.
 767         if (inputMethodInfo == null && inputMethodLocator != null) {
 768             inputMethodInfo = inputMethodLocator.getDescriptor().
 769                 getInputMethodDisplayName(getLocale(), SunToolkit.
 770                                           getStartupLocale());
 771         }
 772 
 773         if (inputMethodInfo != null && !inputMethodInfo.equals("")) {
 774             return inputMethodInfo;
 775         }
 776 
 777         // do our best to return something useful.
 778         return inputMethod.toString() + "-" + inputMethod.getLocale().toString();
 779     }
 780 
 781     /**
 782      * Turns off the native IM. The native IM is diabled when
 783      * the deactive method of InputMethod is called. It is
 784      * delayed until the active method is called on a different
 785      * peer component. This method is provided to explicitly disable
 786      * the native IM.
 787      */
 788     public void disableNativeIM() {
 789         InputMethod inputMethod = getInputMethod();
 790         if (inputMethod != null && inputMethod instanceof InputMethodAdapter) {
 791             ((InputMethodAdapter)inputMethod).stopListening();
 792         }
 793     }
 794 
 795 
 796     private synchronized InputMethod getInputMethod() {
 797         if (inputMethod != null) {
 798             return inputMethod;
 799         }
 800 
 801         if (inputMethodCreationFailed) {
 802             return null;
 803         }
 804 
 805         inputMethod = getInputMethodInstance();
 806         return inputMethod;
 807     }
 808 
 809     /**
 810      * Returns an instance of the input method described by
 811      * the current input method locator. This may be an input
 812      * method that was previously used and switched out of,
 813      * or a new instance. The locale, character subsets, and
 814      * input method context of the input method are set.
 815      *
 816      * The inputMethodCreationFailed field is set to true if the
 817      * instantiation failed.
 818      *
 819      * @return an InputMethod instance
 820      * @see java.awt.im.spi.InputMethod#setInputMethodContext
 821      * @see java.awt.im.spi.InputMethod#setLocale
 822      * @see java.awt.im.spi.InputMethod#setCharacterSubsets
 823      */
 824     private InputMethod getInputMethodInstance() {
 825         InputMethodLocator locator = inputMethodLocator;
 826         if (locator == null) {
 827             inputMethodCreationFailed = true;
 828             return null;
 829         }
 830 
 831         Locale locale = locator.getLocale();
 832         InputMethod inputMethodInstance = null;
 833 
 834         // see whether we have a previously used input method
 835         if (usedInputMethods != null) {
 836             inputMethodInstance = usedInputMethods.remove(locator.deriveLocator(null));
 837             if (inputMethodInstance != null) {
 838                 if (locale != null) {
 839                     inputMethodInstance.setLocale(locale);
 840                 }
 841                 inputMethodInstance.setCharacterSubsets(characterSubsets);
 842                 Boolean state = perInputMethodState.remove(inputMethodInstance);
 843                 if (state != null) {
 844                     enableClientWindowNotification(inputMethodInstance, state.booleanValue());
 845                 }
 846                 ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 847                         (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 848                         ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 849                 return inputMethodInstance;
 850             }
 851         }
 852 
 853         // need to create new instance
 854         try {
 855             inputMethodInstance = locator.getDescriptor().createInputMethod();
 856 
 857             if (locale != null) {
 858                 inputMethodInstance.setLocale(locale);
 859             }
 860             inputMethodInstance.setInputMethodContext((InputMethodContext) this);
 861             inputMethodInstance.setCharacterSubsets(characterSubsets);
 862 
 863         } catch (Exception e) {
 864             logCreationFailed(e);
 865 
 866             // there are a number of bad things that can happen while creating
 867             // the input method. In any case, we just continue without an
 868             // input method.
 869             inputMethodCreationFailed = true;
 870 
 871             // if the instance has been created, then it means either
 872             // setLocale() or setInputMethodContext() failed.
 873             if (inputMethodInstance != null) {
 874                 inputMethodInstance = null;
 875             }
 876         } catch (LinkageError e) {
 877             logCreationFailed(e);
 878 
 879             // same as above
 880             inputMethodCreationFailed = true;
 881         }
 882         ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot(
 883                 (!(inputMethodInstance instanceof InputMethodAdapter)) ||
 884                 ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot());
 885         return inputMethodInstance;
 886     }
 887 
 888     private void logCreationFailed(Throwable throwable) {
 889         PlatformLogger logger = PlatformLogger.getLogger("sun.awt.im");
 890         if (logger.isLoggable(PlatformLogger.Level.CONFIG)) {
 891             String errorTextFormat = Toolkit.getProperty("AWT.InputMethodCreationFailed",
 892                                                          "Could not create {0}. Reason: {1}");
 893             Object[] args =
 894                 {inputMethodLocator.getDescriptor().getInputMethodDisplayName(null, Locale.getDefault()),
 895                  throwable.getLocalizedMessage()};
 896             MessageFormat mf = new MessageFormat(errorTextFormat);
 897             logger.config(mf.format(args));
 898         }
 899     }
 900 
 901     InputMethodLocator getInputMethodLocator() {
 902         if (inputMethod != null) {
 903             return inputMethodLocator.deriveLocator(inputMethod.getLocale());
 904         }
 905         return inputMethodLocator;
 906     }
 907 
 908     /**
 909      * @see java.awt.im.InputContext#endComposition
 910      */
 911     public synchronized void endComposition() {
 912         if (inputMethod != null) {
 913             inputMethod.endComposition();
 914         }
 915     }
 916 
 917     /**
 918      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
 919      */
 920     synchronized void enableClientWindowNotification(InputMethod requester,
 921                                                      boolean enable) {
 922         // in case this request is not from the current input method,
 923         // store the request and handle it when this requesting input
 924         // method becomes the current one.
 925         if (requester != inputMethod) {
 926             if (perInputMethodState == null) {
 927                 perInputMethodState = new HashMap<>(5);
 928             }
 929             perInputMethodState.put(requester, Boolean.valueOf(enable));
 930             return;
 931         }
 932 
 933         if (clientWindowNotificationEnabled != enable) {
 934             clientWindowLocation = null;
 935             clientWindowNotificationEnabled = enable;
 936         }
 937         if (clientWindowNotificationEnabled) {
 938             if (!addedClientWindowListeners()) {
 939                 addClientWindowListeners();
 940             }
 941             if (clientWindowListened != null) {
 942                 clientWindowLocation = null;
 943                 notifyClientWindowChange(clientWindowListened);
 944             }
 945         } else {
 946             if (addedClientWindowListeners()) {
 947                 removeClientWindowListeners();
 948             }
 949         }
 950     }
 951 
 952     private synchronized void notifyClientWindowChange(Window window) {
 953         if (inputMethod == null) {
 954             return;
 955         }
 956 
 957         // if the window is invisible or iconified, send null to the input method.
 958         if (!window.isVisible() ||
 959             ((window instanceof Frame) && ((Frame)window).getState() == Frame.ICONIFIED)) {
 960             clientWindowLocation = null;
 961             inputMethod.notifyClientWindowChange(null);
 962             return;
 963         }
 964         Rectangle location = window.getBounds();
 965         if (clientWindowLocation == null || !clientWindowLocation.equals(location)) {
 966             clientWindowLocation = location;
 967             inputMethod.notifyClientWindowChange(clientWindowLocation);
 968         }
 969     }
 970 
 971     private synchronized void addClientWindowListeners() {
 972         Component client = getClientComponent();
 973         if (client == null) {
 974             return;
 975         }
 976         Window window = getComponentWindow(client);
 977         if (window == null) {
 978             return;
 979         }
 980         window.addComponentListener(this);
 981         window.addWindowListener(this);
 982         clientWindowListened = window;
 983     }
 984 
 985     private synchronized void removeClientWindowListeners() {
 986         clientWindowListened.removeComponentListener(this);
 987         clientWindowListened.removeWindowListener(this);
 988         clientWindowListened = null;
 989     }
 990 
 991     /**
 992      * Returns true if listeners have been set up for client window
 993      * change notification.
 994      */
 995     private boolean addedClientWindowListeners() {
 996         return clientWindowListened != null;
 997     }
 998 
 999     /*
1000      * ComponentListener and WindowListener implementation
1001      */
1002     public void componentResized(ComponentEvent e) {
1003         notifyClientWindowChange((Window)e.getComponent());
1004     }
1005 
1006     public void componentMoved(ComponentEvent e) {
1007         notifyClientWindowChange((Window)e.getComponent());
1008     }
1009 
1010     public void componentShown(ComponentEvent e) {
1011         notifyClientWindowChange((Window)e.getComponent());
1012     }
1013 
1014     public void componentHidden(ComponentEvent e) {
1015         notifyClientWindowChange((Window)e.getComponent());
1016     }
1017 
1018     public void windowOpened(WindowEvent e) {}
1019     public void windowClosing(WindowEvent e) {}
1020     public void windowClosed(WindowEvent e) {}
1021 
1022     public void windowIconified(WindowEvent e) {
1023         notifyClientWindowChange(e.getWindow());
1024     }
1025 
1026     public void windowDeiconified(WindowEvent e) {
1027         notifyClientWindowChange(e.getWindow());
1028     }
1029 
1030     public void windowActivated(WindowEvent e) {}
1031     public void windowDeactivated(WindowEvent e) {}
1032 
1033     /**
1034      * Initializes the input method selection key definition in preference trees
1035      */
1036     private void initializeInputMethodSelectionKey() {
1037         AccessController.doPrivileged(new PrivilegedAction<Object>() {
1038             public Object run() {
1039                 // Look in user's tree
1040                 Preferences root = Preferences.userRoot();
1041                 inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1042 
1043                 if (inputMethodSelectionKey == null) {
1044                     // Look in system's tree
1045                     root = Preferences.systemRoot();
1046                     inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root);
1047                 }
1048                 return null;
1049             }
1050         });
1051     }
1052 
1053     private AWTKeyStroke getInputMethodSelectionKeyStroke(Preferences root) {
1054         try {
1055             if (root.nodeExists(inputMethodSelectionKeyPath)) {
1056                 Preferences node = root.node(inputMethodSelectionKeyPath);
1057                 int keyCode = node.getInt(inputMethodSelectionKeyCodeName, KeyEvent.VK_UNDEFINED);
1058                 if (keyCode != KeyEvent.VK_UNDEFINED) {
1059                     int modifiers = node.getInt(inputMethodSelectionKeyModifiersName, 0);
1060                     return AWTKeyStroke.getAWTKeyStroke(keyCode, modifiers);
1061                 }
1062             }
1063         } catch (BackingStoreException bse) {
1064         }
1065 
1066         return null;
1067     }
1068 }