1/*
2 * Copyright (c) 1997, 2017, 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
26package sun.awt;
27
28import java.util.Collections;
29import java.util.Locale;
30import java.util.Map;
31import java.util.HashMap;
32import java.awt.AWTEvent;
33import java.awt.AWTException;
34import java.awt.Component;
35import java.awt.Container;
36import java.awt.EventQueue;
37import java.awt.Window;
38import java.awt.im.InputMethodHighlight;
39import java.awt.im.spi.InputMethodContext;
40import sun.awt.im.InputMethodAdapter;
41import java.awt.event.InputMethodEvent;
42import java.awt.font.TextAttribute;
43import java.awt.font.TextHitInfo;
44import java.awt.peer.ComponentPeer;
45import java.lang.Character.Subset;
46import java.text.AttributedString;
47import java.text.AttributedCharacterIterator;
48
49import java.io.File;
50import java.io.FileReader;
51import java.io.BufferedReader;
52import java.io.IOException;
53import java.lang.ref.WeakReference;
54import sun.util.logging.PlatformLogger;
55import java.util.StringTokenizer;
56import java.util.regex.Pattern;
57
58
59/**
60 * Input Method Adapter for XIM
61 *
62 * @author JavaSoft International
63 */
64public abstract class X11InputMethod extends InputMethodAdapter {
65    private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11InputMethod");
66    /*
67     * The following XIM* values must be the same as those defined in
68     * Xlib.h
69     */
70    private static final int XIMReverse = (1<<0);
71    private static final int XIMUnderline = (1<<1);
72    private static final int XIMHighlight = (1<<2);
73    private static final int XIMPrimary = (1<<5);
74    private static final int XIMSecondary = (1<<6);
75    private static final int XIMTertiary = (1<<7);
76
77    /*
78     * visible position values
79     */
80    private static final int XIMVisibleToForward = (1<<8);
81    private static final int XIMVisibleToBackward = (1<<9);
82    private static final int XIMVisibleCenter = (1<<10);
83    private static final int XIMVisibleMask = (XIMVisibleToForward|
84                                               XIMVisibleToBackward|
85                                               XIMVisibleCenter);
86
87    private Locale locale;
88    private static boolean isXIMOpened = false;
89    protected Container clientComponentWindow = null;
90    private Component awtFocussedComponent = null;
91    private Component lastXICFocussedComponent = null;
92    private boolean   isLastXICActive = false;
93    private boolean   isLastTemporary = false;
94    private boolean   isActive = false;
95    private boolean   isActiveClient = false;
96    private static Map<TextAttribute, ?>[] highlightStyles;
97    private boolean disposed = false;
98
99    //reset the XIC if necessary
100    private boolean   needResetXIC = false;
101    private WeakReference<Component> needResetXICClient = new WeakReference<>(null);
102
103    // The use of compositionEnableSupported is to reduce unnecessary
104    // native calls if set/isCompositionEnabled
105    // throws UnsupportedOperationException.
106    // It is set to false if that exception is thrown first time
107    // either of the two methods are called.
108    private boolean compositionEnableSupported = true;
109    // The savedCompositionState indicates the composition mode when
110    // endComposition or setCompositionEnabled is called. It doesn't always
111    // reflect the actual composition state because it doesn't get updated
112    // when the user changes the composition state through direct interaction
113    // with the input method. It is used to save the composition mode when
114    // focus is traversed across different client components sharing the
115    // same java input context. Also if set/isCompositionEnabled are not
116    // supported, it remains false.
117    private boolean savedCompositionState = false;
118
119    // variables to keep track of preedit context.
120    // these variables need to be accessed within AWT_LOCK/UNLOCK
121    private String committedText = null;
122    private StringBuffer composedText = null;
123    private IntBuffer rawFeedbacks;
124
125    // private data (X11InputMethodData structure defined in
126    // awt_InputMethod.c) for native methods
127    // this structure needs to be accessed within AWT_LOCK/UNLOCK
128    private transient long pData = 0; // accessed by native
129
130    // Initialize highlight mapping table
131    static {
132        @SuppressWarnings({"unchecked", "rawtypes"})
133        Map<TextAttribute, ?> styles[] = new Map[4];
134        HashMap<TextAttribute, Object> map;
135
136        // UNSELECTED_RAW_TEXT_HIGHLIGHT
137        map = new HashMap<>(1);
138        map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
139        styles[0] = Collections.unmodifiableMap(map);
140
141        // SELECTED_RAW_TEXT_HIGHLIGHT
142        map = new HashMap<>(1);
143        map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);
144        styles[1] = Collections.unmodifiableMap(map);
145
146        // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT
147        map = new HashMap<>(1);
148        map.put(TextAttribute.INPUT_METHOD_UNDERLINE,
149                TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
150        styles[2] = Collections.unmodifiableMap(map);
151
152        // SELECTED_CONVERTED_TEXT_HIGHLIGHT
153        map = new HashMap<>(1);
154        map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);
155        styles[3] = Collections.unmodifiableMap(map);
156
157        highlightStyles = styles;
158    }
159
160    static {
161        initIDs();
162    }
163
164    /**
165     * Initialize JNI field and method IDs for fields that may be
166       accessed from C.
167     */
168    private static native void initIDs();
169
170    /**
171     * Constructs an X11InputMethod instance. It initializes the XIM
172     * environment if it's not done yet.
173     *
174     * @exception AWTException if XOpenIM() failed.
175     */
176    public X11InputMethod() throws AWTException {
177        // supports only the locale in which the VM is started
178        locale = X11InputMethodDescriptor.getSupportedLocale();
179        if (initXIM() == false) {
180            throw new AWTException("Cannot open X Input Method");
181        }
182    }
183
184    @SuppressWarnings("deprecation")
185    protected void finalize() throws Throwable {
186        dispose();
187        super.finalize();
188    }
189
190    /**
191     * Invokes openIM() that invokes XOpenIM() if it's not opened yet.
192     * @return  true if openXIM() is successful or it's already been opened.
193     */
194    private synchronized boolean initXIM() {
195        if (isXIMOpened == false)
196            isXIMOpened = openXIM();
197        return isXIMOpened;
198    }
199
200    protected abstract boolean openXIM();
201
202    protected boolean isDisposed() {
203        return disposed;
204    }
205
206    protected abstract void setXICFocus(ComponentPeer peer,
207                                    boolean value, boolean active);
208
209    /**
210     * Does nothing - this adapter doesn't use the input method context.
211     *
212     * @see java.awt.im.spi.InputMethod#setInputMethodContext
213     */
214    public void setInputMethodContext(InputMethodContext context) {
215    }
216
217    /**
218     * Set locale to input. If input method doesn't support specified locale,
219     * false will be returned and its behavior is not changed.
220     *
221     * @param lang locale to input
222     * @return the true is returned when specified locale is supported.
223     */
224    public boolean setLocale(Locale lang) {
225        if (lang.equals(locale)) {
226            return true;
227        }
228        // special compatibility rule for Japanese and Korean
229        if (locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) ||
230                locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) {
231            return true;
232        }
233        return false;
234    }
235
236    /**
237     * Returns current input locale.
238     */
239    public Locale getLocale() {
240        return locale;
241    }
242
243    /**
244     * Does nothing - XIM doesn't let you specify which characters you expect.
245     *
246     * @see java.awt.im.spi.InputMethod#setCharacterSubsets
247     */
248    public void setCharacterSubsets(Subset[] subsets) {
249    }
250
251    /**
252     * Dispatch event to input method. InputContext dispatch event with this
253     * method. Input method set consume flag if event is consumed in
254     * input method.
255     *
256     * @param e event
257     */
258    public void dispatchEvent(AWTEvent e) {
259    }
260
261
262    protected final void resetXICifneeded(){
263        /* needResetXIC is used to indicate whether to call
264           resetXIC on the active client. resetXIC will always be
265           called on the passive client when endComposition is called.
266        */
267        if (needResetXIC && haveActiveClient() &&
268            getClientComponent() != needResetXICClient.get()){
269            resetXIC();
270
271            // needs to reset the last xic focussed component.
272            lastXICFocussedComponent = null;
273            isLastXICActive = false;
274
275            needResetXICClient.clear();
276            needResetXIC = false;
277        }
278    }
279
280    /**
281     * Reset the composition state to the current composition state.
282     */
283    private void resetCompositionState() {
284        if (compositionEnableSupported) {
285            try {
286                /* Restore the composition mode to the last saved composition
287                   mode. */
288                setCompositionEnabled(savedCompositionState);
289            } catch (UnsupportedOperationException e) {
290                compositionEnableSupported = false;
291            }
292        }
293    }
294
295    /**
296     * Query and then return the current composition state.
297     * @return the composition state if isCompositionEnabled call
298     * is successful. Otherwise, it returns false.
299     */
300    private boolean getCompositionState() {
301        boolean compositionState = false;
302        if (compositionEnableSupported) {
303            try {
304                compositionState = isCompositionEnabled();
305            } catch (UnsupportedOperationException e) {
306                compositionEnableSupported = false;
307            }
308        }
309        return compositionState;
310    }
311
312    /**
313     * Activate input method.
314     */
315    public synchronized void activate() {
316        clientComponentWindow = getClientComponentWindow();
317        if (clientComponentWindow == null)
318            return;
319
320        if (lastXICFocussedComponent != null){
321            if (log.isLoggable(PlatformLogger.Level.FINE)) {
322                log.fine("XICFocused {0}, AWTFocused {1}",
323                         lastXICFocussedComponent, awtFocussedComponent);
324            }
325        }
326
327        if (pData == 0) {
328            if (!createXIC()) {
329                return;
330            }
331            disposed = false;
332        }
333
334        /*  reset input context if necessary and set the XIC focus
335        */
336        resetXICifneeded();
337        ComponentPeer lastXICFocussedComponentPeer = null;
338        ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
339
340        if (lastXICFocussedComponent != null) {
341           lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
342        }
343
344        /* If the last XIC focussed component has a different peer as the
345           current focussed component, change the XIC focus to the newly
346           focussed component.
347        */
348        if (isLastTemporary || lastXICFocussedComponentPeer != awtFocussedComponentPeer ||
349            isLastXICActive != haveActiveClient()) {
350            if (lastXICFocussedComponentPeer != null) {
351                setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
352            }
353            if (awtFocussedComponentPeer != null) {
354                setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
355            }
356            lastXICFocussedComponent = awtFocussedComponent;
357            isLastXICActive = haveActiveClient();
358        }
359        resetCompositionState();
360        isActive = true;
361    }
362
363    protected abstract boolean createXIC();
364
365    /**
366     * Deactivate input method.
367     */
368    public synchronized void deactivate(boolean isTemporary) {
369        boolean   isAc =  haveActiveClient();
370        /* Usually as the client component, let's call it component A,
371           loses the focus, this method is called. Then when another client
372           component, let's call it component B,  gets the focus, activate is first called on
373           the previous focused compoent which is A, then endComposition is called on A,
374           deactivate is called on A again. And finally activate is called on the newly
375           focused component B. Here is the call sequence.
376
377           A loses focus               B gains focus
378           -------------> deactivate A -------------> activate A -> endComposition A ->
379           deactivate A -> activate B ----....
380
381           So in order to carry the composition mode across the components sharing the same
382           input context, we save it when deactivate is called so that when activate is
383           called, it can be restored correctly till activate is called on the newly focused
384           component. (See also sun/awt/im/InputContext and bug 6184471).
385           Last note, getCompositionState should be called before setXICFocus since
386           setXICFocus here sets the XIC to 0.
387        */
388        savedCompositionState = getCompositionState();
389
390        if (isTemporary){
391            //turn the status window off...
392            turnoffStatusWindow();
393        }
394
395        /* Delay resetting the XIC focus until activate is called and the newly
396           focussed component has a different peer as the last focussed component.
397        */
398        lastXICFocussedComponent = awtFocussedComponent;
399        isLastXICActive = isAc;
400        isLastTemporary = isTemporary;
401        isActive = false;
402    }
403
404    /**
405     * Explicitly disable the native IME. Native IME is not disabled when
406     * deactivate is called.
407     */
408    public void disableInputMethod() {
409        if (lastXICFocussedComponent != null) {
410            setXICFocus(getPeer(lastXICFocussedComponent), false, isLastXICActive);
411            lastXICFocussedComponent = null;
412            isLastXICActive = false;
413
414            resetXIC();
415            needResetXICClient.clear();
416            needResetXIC = false;
417        }
418    }
419
420    // implements java.awt.im.spi.InputMethod.hideWindows
421    public void hideWindows() {
422        // ??? need real implementation
423    }
424
425    /**
426     * @see java.awt.Toolkit#mapInputMethodHighlight
427     */
428    public static Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) {
429        int index;
430        int state = highlight.getState();
431        if (state == InputMethodHighlight.RAW_TEXT) {
432            index = 0;
433        } else if (state == InputMethodHighlight.CONVERTED_TEXT) {
434            index = 2;
435        } else {
436            return null;
437        }
438        if (highlight.isSelected()) {
439            index += 1;
440        }
441        return highlightStyles[index];
442    }
443
444    /**
445     * @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent
446     */
447    protected void setAWTFocussedComponent(Component component) {
448        if (component == null) {
449            return;
450        }
451        if (isActive) {
452            // deactivate/activate are being suppressed during a focus change -
453            // this may happen when an input method window is made visible
454            boolean ac = haveActiveClient();
455            setXICFocus(getPeer(awtFocussedComponent), false, ac);
456            setXICFocus(getPeer(component), true, ac);
457        }
458        awtFocussedComponent = component;
459    }
460
461    /**
462     * @see sun.awt.im.InputMethodAdapter#stopListening
463     */
464    protected void stopListening() {
465        // It is desirable to disable XIM by calling XSetICValues with
466        // XNPreeditState == XIMPreeditDisable.  But Solaris 2.6 and
467        // Solaris 7 do not implement this correctly without a patch,
468        // so just call resetXIC here.  Prior endComposition call commits
469        // the existing composed text.
470        endComposition();
471        // disable the native input method so that the other input
472        // method could get the input focus.
473        disableInputMethod();
474        if (needResetXIC) {
475            resetXIC();
476            needResetXICClient.clear();
477            needResetXIC = false;
478        }
479    }
480
481    /**
482     * Returns the Window instance in which the client component is
483     * contained. If not found, null is returned. (IS THIS POSSIBLE?)
484     */
485    // NOTE: This method may be called by privileged threads.
486    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
487    private Window getClientComponentWindow() {
488        Component client = getClientComponent();
489        Container container;
490
491        if (client instanceof Container) {
492            container = (Container) client;
493        } else {
494            container = getParent(client);
495        }
496
497        while (container != null && !(container instanceof java.awt.Window)) {
498            container = getParent(container);
499        }
500        return (Window) container;
501    }
502
503    protected abstract Container getParent(Component client);
504
505    /**
506     * Returns peer of the given client component. If the given client component
507     * doesn't have peer, peer of the native container of the client is returned.
508     */
509    protected abstract ComponentPeer getPeer(Component client);
510
511    /**
512     * Used to protect preedit data
513     */
514    protected abstract void awtLock();
515    protected abstract void awtUnlock();
516
517    /**
518     * Creates an input method event from the arguments given
519     * and posts it on the AWT event queue. For arguments,
520     * see InputMethodEvent. Called by input method.
521     *
522     * @see java.awt.event.InputMethodEvent#InputMethodEvent
523     */
524    private void postInputMethodEvent(int id,
525                                      AttributedCharacterIterator text,
526                                      int committedCharacterCount,
527                                      TextHitInfo caret,
528                                      TextHitInfo visiblePosition,
529                                      long when) {
530        Component source = getClientComponent();
531        if (source != null) {
532            InputMethodEvent event = new InputMethodEvent(source,
533                id, when, text, committedCharacterCount, caret, visiblePosition);
534            SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event);
535        }
536    }
537
538    private void postInputMethodEvent(int id,
539                                      AttributedCharacterIterator text,
540                                      int committedCharacterCount,
541                                      TextHitInfo caret,
542                                      TextHitInfo visiblePosition) {
543        postInputMethodEvent(id, text, committedCharacterCount,
544                             caret, visiblePosition, EventQueue.getMostRecentEventTime());
545    }
546
547    /**
548     * Dispatches committed text from XIM to the awt event queue. This
549     * method is invoked from the event handler in canvas.c in the
550     * AWT Toolkit thread context and thus inside the AWT Lock.
551     * @param   str     committed text
552     * @param   when    when
553     */
554    // NOTE: This method may be called by privileged threads.
555    //       This functionality is implemented in a package-private method
556    //       to insure that it cannot be overridden by client subclasses.
557    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
558    void dispatchCommittedText(String str, long when) {
559        if (str == null)
560            return;
561
562        if (composedText == null) {
563            AttributedString attrstr = new AttributedString(str);
564            postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
565                                 attrstr.getIterator(),
566                                 str.length(),
567                                 null,
568                                 null,
569                                 when);
570        } else {
571            // if there is composed text, wait until the preedit
572            // callback is invoked.
573            committedText = str;
574        }
575    }
576
577    private void dispatchCommittedText(String str) {
578        dispatchCommittedText(str, EventQueue.getMostRecentEventTime());
579    }
580
581    /**
582     * Updates composed text with XIM preedit information and
583     * posts composed text to the awt event queue. The args of
584     * this method correspond to the XIM preedit callback
585     * information. The XIM highlight attributes are translated via
586     * fixed mapping (i.e., independent from any underlying input
587     * method engine). This method is invoked in the AWT Toolkit
588     * (X event loop) thread context and thus inside the AWT Lock.
589     */
590    // NOTE: This method may be called by privileged threads.
591    //       This functionality is implemented in a package-private method
592    //       to insure that it cannot be overridden by client subclasses.
593    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
594    void dispatchComposedText(String chgText,
595                                           int chgStyles[],
596                                           int chgOffset,
597                                           int chgLength,
598                                           int caretPosition,
599                                           long when) {
600        if (disposed) {
601            return;
602        }
603
604        //Workaround for deadlock bug on solaris2.6_zh bug#4170760
605        if (chgText == null
606            && chgStyles == null
607            && chgOffset == 0
608            && chgLength == 0
609            && caretPosition == 0
610            && composedText == null
611            && committedText == null)
612            return;
613
614        if (composedText == null) {
615            // TODO: avoid reallocation of those buffers
616            composedText = new StringBuffer(INITIAL_SIZE);
617            rawFeedbacks = new IntBuffer(INITIAL_SIZE);
618        }
619        if (chgLength > 0) {
620            if (chgText == null && chgStyles != null) {
621                rawFeedbacks.replace(chgOffset, chgStyles);
622            } else {
623                if (chgLength == composedText.length()) {
624                    // optimization for the special case to replace the
625                    // entire previous text
626                    composedText = new StringBuffer(INITIAL_SIZE);
627                    rawFeedbacks = new IntBuffer(INITIAL_SIZE);
628                } else {
629                    if (composedText.length() > 0) {
630                        if (chgOffset+chgLength < composedText.length()) {
631                            String text;
632                            text = composedText.toString().substring(chgOffset+chgLength,
633                                                                     composedText.length());
634                            composedText.setLength(chgOffset);
635                            composedText.append(text);
636                        } else {
637                            // in case to remove substring from chgOffset
638                            // to the end
639                            composedText.setLength(chgOffset);
640                        }
641                        rawFeedbacks.remove(chgOffset, chgLength);
642                    }
643                }
644            }
645        }
646        if (chgText != null) {
647            composedText.insert(chgOffset, chgText);
648            if (chgStyles != null)
649                rawFeedbacks.insert(chgOffset, chgStyles);
650        }
651
652        if (composedText.length() == 0) {
653            composedText = null;
654            rawFeedbacks = null;
655
656            // if there is any outstanding committed text stored by
657            // dispatchCommittedText(), it has to be sent to the
658            // client component.
659            if (committedText != null) {
660                dispatchCommittedText(committedText, when);
661                committedText = null;
662                return;
663            }
664
665            // otherwise, send null text to delete client's composed
666            // text.
667            postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
668                                 null,
669                                 0,
670                                 null,
671                                 null,
672                                 when);
673
674            return;
675        }
676
677        // Now sending the composed text to the client
678        int composedOffset;
679        AttributedString inputText;
680
681        // if there is any partially committed text, concatenate it to
682        // the composed text.
683        if (committedText != null) {
684            composedOffset = committedText.length();
685            inputText = new AttributedString(committedText + composedText);
686            committedText = null;
687        } else {
688            composedOffset = 0;
689            inputText = new AttributedString(composedText.toString());
690        }
691
692        int currentFeedback;
693        int nextFeedback;
694        int startOffset = 0;
695        int currentOffset;
696        int visiblePosition = 0;
697        TextHitInfo visiblePositionInfo = null;
698
699        rawFeedbacks.rewind();
700        currentFeedback = rawFeedbacks.getNext();
701        rawFeedbacks.unget();
702        while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
703            if (visiblePosition == 0) {
704                visiblePosition = nextFeedback & XIMVisibleMask;
705                if (visiblePosition != 0) {
706                    int index = rawFeedbacks.getOffset() - 1;
707
708                    if (visiblePosition == XIMVisibleToBackward)
709                        visiblePositionInfo = TextHitInfo.leading(index);
710                    else
711                        visiblePositionInfo = TextHitInfo.trailing(index);
712                }
713            }
714            nextFeedback &= ~XIMVisibleMask;
715            if (currentFeedback != nextFeedback) {
716                rawFeedbacks.unget();
717                currentOffset = rawFeedbacks.getOffset();
718                inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
719                                       convertVisualFeedbackToHighlight(currentFeedback),
720                                       composedOffset + startOffset,
721                                       composedOffset + currentOffset);
722                startOffset = currentOffset;
723                currentFeedback = nextFeedback;
724            }
725        }
726        currentOffset = rawFeedbacks.getOffset();
727        if (currentOffset >= 0) {
728            inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
729                                   convertVisualFeedbackToHighlight(currentFeedback),
730                                   composedOffset + startOffset,
731                                   composedOffset + currentOffset);
732        }
733
734        postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
735                             inputText.getIterator(),
736                             composedOffset,
737                             TextHitInfo.leading(caretPosition),
738                             visiblePositionInfo,
739                             when);
740    }
741
742    /**
743     * Flushes composed and committed text held in this context.
744     * This method is invoked in the AWT Toolkit (X event loop) thread context
745     * and thus inside the AWT Lock.
746     */
747    // NOTE: This method may be called by privileged threads.
748    //       This functionality is implemented in a package-private method
749    //       to insure that it cannot be overridden by client subclasses.
750    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
751    void flushText() {
752        String flush = (committedText != null ? committedText : "");
753        if (composedText != null) {
754            flush += composedText.toString();
755        }
756
757        if (!flush.equals("")) {
758            AttributedString attrstr = new AttributedString(flush);
759            postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
760                                 attrstr.getIterator(),
761                                 flush.length(),
762                                 null,
763                                 null,
764                                 EventQueue.getMostRecentEventTime());
765            composedText = null;
766            committedText = null;
767        }
768    }
769
770    /*
771     * Subclasses should override disposeImpl() instead of dispose(). Client
772     * code should always invoke dispose(), never disposeImpl().
773     */
774    protected synchronized void disposeImpl() {
775        disposeXIC();
776        awtLock();
777        composedText = null;
778        committedText = null;
779        rawFeedbacks = null;
780        awtUnlock();
781        awtFocussedComponent = null;
782        lastXICFocussedComponent = null;
783    }
784
785    /**
786     * Frees all X Window resources associated with this object.
787     *
788     * @see java.awt.im.spi.InputMethod#dispose
789     */
790    public final void dispose() {
791        boolean call_disposeImpl = false;
792
793        if (!disposed) {
794            synchronized (this) {
795                if (!disposed) {
796                    disposed = call_disposeImpl = true;
797                }
798            }
799        }
800
801        if (call_disposeImpl) {
802            disposeImpl();
803        }
804    }
805
806    /**
807     * Returns null.
808     *
809     * @see java.awt.im.spi.InputMethod#getControlObject
810     */
811    public Object getControlObject() {
812        return null;
813    }
814
815    /**
816     * @see java.awt.im.spi.InputMethod#removeNotify
817     */
818    public synchronized void removeNotify() {
819        dispose();
820    }
821
822    /**
823     * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
824     */
825    public void setCompositionEnabled(boolean enable) {
826        /* If the composition state is successfully changed, set
827           the savedCompositionState to 'enable'. Otherwise, simply
828           return.
829           setCompositionEnabledNative may throw UnsupportedOperationException.
830           Don't try to catch it since the method may be called by clients.
831           Use package private mthod 'resetCompositionState' if you want the
832           exception to be caught.
833        */
834        if (setCompositionEnabledNative(enable)) {
835            savedCompositionState = enable;
836        }
837    }
838
839    /**
840     * @see java.awt.im.spi.InputMethod#isCompositionEnabled
841     */
842    public boolean isCompositionEnabled() {
843        /* isCompositionEnabledNative may throw UnsupportedOperationException.
844           Don't try to catch it since this method may be called by clients.
845           Use package private method 'getCompositionState' if you want the
846           exception to be caught.
847        */
848        return isCompositionEnabledNative();
849    }
850
851    /**
852     * Ends any input composition that may currently be going on in this
853     * context. Depending on the platform and possibly user preferences,
854     * this may commit or delete uncommitted text. Any changes to the text
855     * are communicated to the active component using an input method event.
856     *
857     * <p>
858     * A text editing component may call this in a variety of situations,
859     * for example, when the user moves the insertion point within the text
860     * (but outside the composed text), or when the component's text is
861     * saved to a file or copied to the clipboard.
862     *
863     */
864    public void endComposition() {
865        if (disposed) {
866            return;
867        }
868
869        /* Before calling resetXIC, record the current composition mode
870           so that it can be restored later. */
871        savedCompositionState = getCompositionState();
872        boolean active = haveActiveClient();
873        if (active && composedText == null && committedText == null){
874            needResetXIC = true;
875            needResetXICClient = new WeakReference<>(getClientComponent());
876            return;
877        }
878
879        String text = resetXIC();
880        /* needResetXIC is only set to true for active client. So passive
881           client should not reset the flag to false. */
882        if (active) {
883            needResetXIC = false;
884        }
885
886        // Remove any existing composed text by posting an InputMethodEvent
887        // with null composed text.  It would be desirable to wait for a
888        // dispatchComposedText call from X input method engine, but some
889        // input method does not conform to the XIM specification and does
890        // not call the preedit callback to erase preedit text on calling
891        // XmbResetIC.  To work around this problem, do it here by ourselves.
892        awtLock();
893        composedText = null;
894        postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
895                             null,
896                             0,
897                             null,
898                             null);
899
900        if (text != null && text.length() > 0) {
901            dispatchCommittedText(text);
902        }
903        awtUnlock();
904
905        // Restore the preedit state if it was enabled
906        if (savedCompositionState) {
907            resetCompositionState();
908        }
909    }
910
911    /**
912     * Returns a string with information about the current input method server, or null.
913     * On both Linux & SunOS, the value of environment variable XMODIFIERS is
914     * returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed
915     * to find out the language service engine (atok or wnn) since there is
916     * no API in Xlib which returns the information of native
917     * IM server or language service and we want to try our best to return as much
918     * information as possible.
919     *
920     * Note: This method could return null on Linux if XMODIFIERS is not set properly or
921     * if any IOException is thrown.
922     * See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS,
923     * atok12setup(1) and wnn6setup(1) for the information written to
924     * $HOME/.dtprofile when you run these two commands.
925     *
926     */
927    public String getNativeInputMethodInfo() {
928        String xmodifiers = System.getenv("XMODIFIERS");
929        String imInfo = null;
930
931        // If XMODIFIERS is set, return the value
932        if (xmodifiers != null) {
933            int imIndex = xmodifiers.indexOf("@im=");
934            if (imIndex != -1) {
935                imInfo = xmodifiers.substring(imIndex + 4);
936            }
937        } else if (System.getProperty("os.name").startsWith("SunOS")) {
938            File dtprofile = new File(System.getProperty("user.home") +
939                                      "/.dtprofile");
940            String languageEngineInfo = null;
941            try {
942                BufferedReader br = new BufferedReader(new FileReader(dtprofile));
943                String line = null;
944
945                while ( languageEngineInfo == null && (line = br.readLine()) != null) {
946                    if (line.contains("atok") || line.contains("wnn")) {
947                        StringTokenizer tokens =  new StringTokenizer(line);
948                        while (tokens.hasMoreTokens()) {
949                            String token = tokens.nextToken();
950                            if (Pattern.matches("atok.*setup", token) ||
951                                Pattern.matches("wnn.*setup", token)){
952                                languageEngineInfo = token.substring(0, token.indexOf("setup"));
953                                break;
954                            }
955                        }
956                    }
957                }
958
959                br.close();
960            } catch(IOException ioex) {
961                // Since this method is provided for internal testing only,
962                // we dump the stack trace for the ease of debugging.
963                ioex.printStackTrace();
964            }
965
966            imInfo = "htt " + languageEngineInfo;
967        }
968
969        return imInfo;
970    }
971
972
973    /**
974     * Performs mapping from an XIM visible feedback value to Java IM highlight.
975     * @return Java input method highlight
976     */
977    private InputMethodHighlight convertVisualFeedbackToHighlight(int feedback) {
978        InputMethodHighlight highlight;
979
980        switch (feedback) {
981        case XIMUnderline:
982            highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
983            break;
984        case XIMReverse:
985            highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
986            break;
987        case XIMHighlight:
988            highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
989            break;
990        case XIMPrimary:
991            highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
992            break;
993        case XIMSecondary:
994            highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
995            break;
996        case XIMTertiary:
997            highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
998            break;
999        default:
1000            highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
1001            break;
1002        }
1003        return highlight;
1004    }
1005
1006    // initial capacity size for string buffer, etc.
1007    private static final int INITIAL_SIZE = 64;
1008
1009    /**
1010     * IntBuffer is an inner class that manipulates an int array and
1011     * provides UNIX file io stream-like programming interfaces to
1012     * access it. (An alternative would be to use ArrayList which may
1013     * be too expensive for the work.)
1014     */
1015    private final class IntBuffer {
1016        private int[] intArray;
1017        private int size;
1018        private int index;
1019
1020        IntBuffer(int initialCapacity) {
1021            intArray = new int[initialCapacity];
1022            size = 0;
1023            index = 0;
1024        }
1025
1026        void insert(int offset, int[] values) {
1027            int newSize = size + values.length;
1028            if (intArray.length < newSize) {
1029                int[] newIntArray = new int[newSize * 2];
1030                System.arraycopy(intArray, 0, newIntArray, 0, size);
1031                intArray = newIntArray;
1032            }
1033            System.arraycopy(intArray, offset, intArray, offset+values.length,
1034                             size - offset);
1035            System.arraycopy(values, 0, intArray, offset, values.length);
1036            size += values.length;
1037            if (index > offset)
1038                index = offset;
1039        }
1040
1041        void remove(int offset, int length) {
1042            if (offset + length != size)
1043                System.arraycopy(intArray, offset+length, intArray, offset,
1044                                 size - offset - length);
1045            size -= length;
1046            if (index > offset)
1047                index = offset;
1048        }
1049
1050        void replace(int offset, int[] values) {
1051            System.arraycopy(values, 0, intArray, offset, values.length);
1052        }
1053
1054        void removeAll() {
1055            size = 0;
1056            index = 0;
1057        }
1058
1059        void rewind() {
1060            index = 0;
1061        }
1062
1063        int getNext() {
1064            if (index == size)
1065                return -1;
1066            return intArray[index++];
1067        }
1068
1069        void unget() {
1070            if (index != 0)
1071                index--;
1072        }
1073
1074        int getOffset() {
1075            return index;
1076        }
1077
1078        public String toString() {
1079            StringBuffer s = new StringBuffer();
1080            for (int i = 0; i < size;) {
1081                s.append(intArray[i++]);
1082                if (i < size)
1083                    s.append(",");
1084            }
1085            return s.toString();
1086        }
1087    }
1088
1089    /*
1090     * Native methods
1091     */
1092    private native String resetXIC();
1093    private native void disposeXIC();
1094    private native boolean setCompositionEnabledNative(boolean enable);
1095    private native boolean isCompositionEnabledNative();
1096    private native void turnoffStatusWindow();
1097}
1098