001/*
002 * $Id: Utilities.java 4084 2011-11-15 18:53:49Z kschaefe $
003 *
004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.util;
022
023
024import java.awt.Component;
025import java.awt.GraphicsConfiguration;
026import java.awt.GraphicsEnvironment;
027import java.awt.Insets;
028import java.awt.KeyboardFocusManager;
029import java.awt.Rectangle;
030import java.awt.Toolkit;
031import java.awt.Window;
032import java.awt.event.KeyEvent;
033import java.lang.ref.Reference;
034import java.lang.ref.SoftReference;
035import java.lang.reflect.Field;
036import java.lang.reflect.Modifier;
037import java.text.BreakIterator;
038import java.util.ArrayList;
039import java.util.HashMap;
040import java.util.Locale;
041import java.util.NoSuchElementException;
042import java.util.StringTokenizer;
043import java.util.logging.Level;
044import java.util.logging.Logger;
045
046import javax.swing.KeyStroke;
047import javax.swing.SwingUtilities;
048
049/**
050 * Contribution from NetBeans: Issue #319-swingx. <p>
051 * 
052 * PENDING: need to reconcile with OS, JVM... added as-is
053 * because needed the shortcut handling to fix #
054 *
055 * @author apple
056 */
057public class Utilities {
058    private Utilities() {
059    }
060    
061    private static final int CTRL_WILDCARD_MASK = 32768;
062    private static final int ALT_WILDCARD_MASK = CTRL_WILDCARD_MASK * 2;
063    
064    /** Operating system is Windows NT. */
065    public static final int OS_WINNT = 1 << 0;
066
067    /** Operating system is Windows 95. */
068    public static final int OS_WIN95 = OS_WINNT << 1;
069
070    /** Operating system is Windows 98. */
071    public static final int OS_WIN98 = OS_WIN95 << 1;
072
073    /** Operating system is Solaris. */
074    public static final int OS_SOLARIS = OS_WIN98 << 1;
075
076    /** Operating system is Linux. */
077    public static final int OS_LINUX = OS_SOLARIS << 1;
078
079    /** Operating system is HP-UX. */
080    public static final int OS_HP = OS_LINUX << 1;
081
082    /** Operating system is IBM AIX. */
083    public static final int OS_AIX = OS_HP << 1;
084
085    /** Operating system is SGI IRIX. */
086    public static final int OS_IRIX = OS_AIX << 1;
087
088    /** Operating system is Sun OS. */
089    public static final int OS_SUNOS = OS_IRIX << 1;
090
091    /** Operating system is Compaq TRU64 Unix */
092    public static final int OS_TRU64 = OS_SUNOS << 1;
093
094    /** Operating system is OS/2. */
095    public static final int OS_OS2 = OS_TRU64 << 2;
096
097    /** Operating system is Mac. */
098    public static final int OS_MAC = OS_OS2 << 1;
099
100    /** Operating system is Windows 2000. */
101    public static final int OS_WIN2000 = OS_MAC << 1;
102
103    /** Operating system is Compaq OpenVMS */
104    public static final int OS_VMS = OS_WIN2000 << 1;
105
106    /**
107     *Operating system is one of the Windows variants but we don't know which
108     *one it is
109     */
110    public static final int OS_WIN_OTHER = OS_VMS << 1;
111
112    /** Operating system is unknown. */
113    public static final int OS_OTHER = OS_WIN_OTHER << 1;
114
115    /** Operating system is FreeBSD
116     * @since 4.50
117     */
118    public static final int OS_FREEBSD = OS_OTHER << 1;
119
120    /** A mask for Windows platforms. */
121    public static final int OS_WINDOWS_MASK = OS_WINNT | OS_WIN95 | OS_WIN98 | OS_WIN2000 | OS_WIN_OTHER;
122
123    /** A mask for Unix platforms. */
124    public static final int OS_UNIX_MASK = OS_SOLARIS | OS_LINUX | OS_HP | OS_AIX | OS_IRIX | OS_SUNOS | OS_TRU64 |
125        OS_MAC | OS_FREEBSD;
126
127    /** A height of the windows's taskbar */
128    public static final int TYPICAL_WINDOWS_TASKBAR_HEIGHT = 27;
129
130    /** A height of the Mac OS X's menu */
131    private static final int TYPICAL_MACOSX_MENU_HEIGHT = 24;
132    
133    private static int operatingSystem = -1;
134    
135    /** reference to map that maps allowed key names to their values (String, Integer)
136    and reference to map for mapping of values to their names */
137    private static Reference<Object> namesAndValues;
138
139    /** Get the operating system on which NetBeans is running.
140    * @return one of the <code>OS_*</code> constants (such as {@link #OS_WINNT})
141    */
142    public static int getOperatingSystem() {
143        if (operatingSystem == -1) {
144            String osName = System.getProperty("os.name");
145
146            if ("Windows NT".equals(osName)) { // NOI18N
147                operatingSystem = OS_WINNT;
148            } else if ("Windows 95".equals(osName)) { // NOI18N
149                operatingSystem = OS_WIN95;
150            } else if ("Windows 98".equals(osName)) { // NOI18N
151                operatingSystem = OS_WIN98;
152            } else if ("Windows 2000".equals(osName)) { // NOI18N
153                operatingSystem = OS_WIN2000;
154            } else if (osName.startsWith("Windows ")) { // NOI18N
155                operatingSystem = OS_WIN_OTHER;
156            } else if ("Solaris".equals(osName)) { // NOI18N
157                operatingSystem = OS_SOLARIS;
158            } else if (osName.startsWith("SunOS")) { // NOI18N
159                operatingSystem = OS_SOLARIS;
160            }
161            // JDK 1.4 b2 defines os.name for me as "Redhat Linux" -jglick
162            else if (osName.endsWith("Linux")) { // NOI18N
163                operatingSystem = OS_LINUX;
164            } else if ("HP-UX".equals(osName)) { // NOI18N
165                operatingSystem = OS_HP;
166            } else if ("AIX".equals(osName)) { // NOI18N
167                operatingSystem = OS_AIX;
168            } else if ("Irix".equals(osName)) { // NOI18N
169                operatingSystem = OS_IRIX;
170            } else if ("SunOS".equals(osName)) { // NOI18N
171                operatingSystem = OS_SUNOS;
172            } else if ("Digital UNIX".equals(osName)) { // NOI18N
173                operatingSystem = OS_TRU64;
174            } else if ("OS/2".equals(osName)) { // NOI18N
175                operatingSystem = OS_OS2;
176            } else if ("OpenVMS".equals(osName)) { // NOI18N
177                operatingSystem = OS_VMS;
178            } else if (osName.equals("Mac OS X")) { // NOI18N
179                operatingSystem = OS_MAC;
180            } else if (osName.startsWith("Darwin")) { // NOI18N
181                operatingSystem = OS_MAC;
182            } else if (osName.toLowerCase(Locale.US).startsWith("freebsd")) { // NOI18N 
183                operatingSystem = OS_FREEBSD;
184            } else {
185                operatingSystem = OS_OTHER;
186            }
187        }
188        return operatingSystem;
189    }
190    
191    /** Test whether NetBeans is running on some variant of Windows.
192    * @return <code>true</code> if Windows, <code>false</code> if some other manner of operating system
193    */
194    public static boolean isWindows() {
195        return (getOperatingSystem() & OS_WINDOWS_MASK) != 0;
196    }
197
198    /** Test whether NetBeans is running on some variant of Unix.
199    * Linux is included as well as the commercial vendors, and Mac OS X.
200    * @return <code>true</code> some sort of Unix, <code>false</code> if some other manner of operating system
201    */
202    public static boolean isUnix() {
203        return (getOperatingSystem() & OS_UNIX_MASK) != 0;
204    }
205    
206    /** Test whether the operating system supports icons on frames (windows).
207    * @return <code>true</code> if it does <em>not</em>
208    *
209    */
210    public static boolean isLargeFrameIcons() {
211        return (getOperatingSystem() == OS_SOLARIS) || (getOperatingSystem() == OS_HP);
212    }
213
214    /**
215     * Finds out the monitor where the user currently has the input focus.
216     * This method is usually used to help the client code to figure out on
217     * which monitor it should place newly created windows/frames/dialogs.
218     *
219     * @return the GraphicsConfiguration of the monitor which currently has the
220     * input focus
221     */
222    private static GraphicsConfiguration getCurrentGraphicsConfiguration() {
223        Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
224        if (focusOwner != null) {
225            Window w = SwingUtilities.getWindowAncestor(focusOwner);
226            if (w != null) {
227                return w.getGraphicsConfiguration();
228            }
229        }
230
231        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
232    }
233
234    /**
235     * Returns the usable area of the screen where applications can place its
236     * windows.  The method subtracts from the screen the area of taskbars,
237     * system menus and the like.  The screen this method applies to is the one
238     * which is considered current, ussually the one where the current input
239     * focus is.
240     *
241     * @return the rectangle of the screen where one can place windows
242     *
243     * @since 2.5
244     */
245    public static Rectangle getUsableScreenBounds() {
246        return getUsableScreenBounds(getCurrentGraphicsConfiguration());
247    }
248
249    /**
250     * Returns the usable area of the screen where applications can place its
251     * windows.  The method subtracts from the screen the area of taskbars,
252     * system menus and the like.
253     *
254     * @param gconf the GraphicsConfiguration of the monitor
255     * @return the rectangle of the screen where one can place windows
256     *
257     * @since 2.5
258     */
259    public static Rectangle getUsableScreenBounds(GraphicsConfiguration gconf) {
260        if (gconf == null) {
261            gconf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
262        }
263
264        Rectangle bounds = new Rectangle(gconf.getBounds());
265
266        String str;
267
268        str = System.getProperty("netbeans.screen.insets"); // NOI18N
269
270        if (str != null) {
271            StringTokenizer st = new StringTokenizer(str, ", "); // NOI18N
272
273            if (st.countTokens() == 4) {
274                try {
275                    bounds.y = Integer.parseInt(st.nextToken());
276                    bounds.x = Integer.parseInt(st.nextToken());
277                    bounds.height -= (bounds.y + Integer.parseInt(st.nextToken()));
278                    bounds.width -= (bounds.x + Integer.parseInt(st.nextToken()));
279                } catch (NumberFormatException ex) {
280                    Logger.getAnonymousLogger().log(Level.WARNING, null, ex);
281                }
282            }
283
284            return bounds;
285        }
286
287        str = System.getProperty("netbeans.taskbar.height"); // NOI18N
288
289        if (str != null) {
290            bounds.height -= Integer.getInteger(str, 0).intValue();
291
292            return bounds;
293        }
294
295        try {
296            Toolkit toolkit = Toolkit.getDefaultToolkit();
297            Insets insets = toolkit.getScreenInsets(gconf);
298            bounds.y += insets.top;
299            bounds.x += insets.left;
300            bounds.height -= (insets.top + insets.bottom);
301            bounds.width -= (insets.left + insets.right);
302        } catch (Exception ex) {
303            Logger.getAnonymousLogger().log(Level.WARNING, null, ex);
304        }
305
306        return bounds;
307    }
308    
309
310    /** Initialization of the names and values
311    * @return array of two hashmaps first maps
312    *   allowed key names to their values (String, Integer)
313    *  and second
314    * hashtable for mapping of values to their names (Integer, String)
315    */
316    private static synchronized HashMap[] initNameAndValues() {
317        if (namesAndValues != null) {
318            HashMap[] arr = (HashMap[]) namesAndValues.get();
319
320            if (arr != null) {
321                return arr;
322            }
323        }
324
325        Field[] fields;
326        // JW - fix Issue #353-swingx: play nicer inside sandbox.
327        try {
328            fields = KeyEvent.class.getDeclaredFields();
329//           fields = KeyEvent.class.getFields();
330        } catch (SecurityException e) { 
331            // JW: need to do better? What are the use-cases where we don't have
332            // any access to the fields?
333            fields = new Field[0];
334        }
335
336        HashMap<String,Integer> names = new HashMap<String,Integer>(((fields.length * 4) / 3) + 5, 0.75f);
337        HashMap<Integer,String> values = new HashMap<Integer,String>(((fields.length * 4) / 3) + 5, 0.75f);
338
339        for (int i = 0; i < fields.length; i++) {
340            if (Modifier.isStatic(fields[i].getModifiers())) {
341                String name = fields[i].getName();
342
343                if (name.startsWith("VK_")) { // NOI18N
344
345                    // exclude VK
346                    name = name.substring(3);
347
348                    try {
349                        int numb = fields[i].getInt(null);
350                        Integer value = new Integer(numb);
351                        names.put(name, value);
352                        values.put(value, name);
353                    } catch (IllegalArgumentException ex) {
354                    } catch (IllegalAccessException ex) {
355                    }
356                }
357            }
358        }
359
360        if (names.get("CONTEXT_MENU") == null) { // NOI18N
361
362            Integer n = new Integer(0x20C);
363            names.put("CONTEXT_MENU", n); // NOI18N
364            values.put(n, "CONTEXT_MENU"); // NOI18N
365
366            n = new Integer(0x20D);
367            names.put("WINDOWS", n); // NOI18N
368            values.put(n, "WINDOWS"); // NOI18N
369        }
370
371        HashMap[] arr = { names, values };
372
373        namesAndValues = new SoftReference<Object>(arr);
374
375        return arr;
376    }
377
378    /** Converts a Swing key stroke descriptor to a familiar Emacs-like name.
379    * @param stroke key description
380    * @return name of the key (e.g. <code>CS-F1</code> for control-shift-function key one)
381    * @see #stringToKey
382    */
383    public static String keyToString(KeyStroke stroke) {
384        StringBuffer sb = new StringBuffer();
385
386        // add modifiers that must be pressed
387        if (addModifiers(sb, stroke.getModifiers())) {
388            sb.append('-');
389        }
390
391        HashMap[] namesAndValues = initNameAndValues();
392
393        String c = (String) namesAndValues[1].get(new Integer(stroke.getKeyCode()));
394
395        if (c == null) {
396            sb.append(stroke.getKeyChar());
397        } else {
398            sb.append(c);
399        }
400
401        return sb.toString();
402    }
403
404    /** Construct a new key description from a given universal string
405    * description.
406    * Provides mapping between Emacs-like textual key descriptions and the
407    * <code>KeyStroke</code> object used in Swing.
408    * <P>
409    * This format has following form:
410    * <P><code>[C][A][S][M]-<em>identifier</em></code>
411    * <p>Where:
412    * <UL>
413    * <LI> <code>C</code> stands for the Control key
414    * <LI> <code>A</code> stands for the Alt key
415    * <LI> <code>S</code> stands for the Shift key
416    * <LI> <code>M</code> stands for the Meta key
417    * </UL>
418    * The format also supports two wildcard codes, to support differences in
419    * platforms.  These are the preferred choices for registering keystrokes,
420    * since platform conflicts will automatically be handled:
421    * <UL>
422    * <LI> <code>D</code> stands for the default menu accelerator - the Control
423    *  key on most platforms, the Command (meta) key on Macintosh</LI>
424    * <LI> <code>O</code> stands for the alternate accelerator - the Alt key on
425    *  most platforms, the Ctrl key on Macintosh (Macintosh uses Alt as a
426    *  secondary shift key for composing international characters - if you bind
427    *  Alt-8 to an action, a mac user with a French keyboard will not be able
428    *  to type the <code>[</code> character, which is a significant handicap</LI>
429    * </UL>
430    * If you use the wildcard characters, and specify a key which will conflict
431    * with keys the operating system consumes, it will be mapped to whichever
432    * choice can work - for example, on Macintosh, Command-Q is always consumed
433    * by the operating system, so <code>D-Q</code> will always map to Control-Q.
434    * <p>
435    * Every modifier before the hyphen must be pressed.
436    * <em>identifier</EM> can be any text constant from {@link KeyEvent} but
437    * without the leading <code>VK_</code> characters. So {@link KeyEvent#VK_ENTER} is described as
438    * <code>ENTER</code>.
439    *
440    * @param s the string with the description of the key
441    * @return key description object, or <code>null</code> if the string does not represent any valid key
442    */
443    public static KeyStroke stringToKey(String s) {
444        StringTokenizer st = new StringTokenizer(s.toUpperCase(Locale.ENGLISH), "-", true); // NOI18N
445
446        int needed = 0;
447
448        HashMap names = initNameAndValues()[0];
449
450        int lastModif = -1;
451
452        try {
453            for (;;) {
454                String el = st.nextToken();
455
456                // required key
457                if (el.equals("-")) { // NOI18N
458
459                    if (lastModif != -1) {
460                        needed |= lastModif;
461                        lastModif = -1;
462                    }
463
464                    continue;
465                }
466
467                // if there is more elements
468                if (st.hasMoreElements()) {
469                    // the text should describe modifiers
470                    lastModif = readModifiers(el);
471                } else {
472                    // last text must be the key code
473                    Integer i = (Integer) names.get(el);
474                    boolean wildcard = (needed & CTRL_WILDCARD_MASK) != 0;
475
476                    //Strip out the explicit mask - KeyStroke won't know
477                    //what to do with it
478                    needed = needed & ~CTRL_WILDCARD_MASK;
479
480                    boolean macAlt = (needed & ALT_WILDCARD_MASK) != 0;
481                    needed = needed & ~ALT_WILDCARD_MASK;
482
483                    if (i != null) {
484                        //#26854 - Default accelerator should be Command on mac
485                        if (wildcard) {
486                            needed |= getMenuShortCutKeyMask();
487
488                            if ((getOperatingSystem() & OS_MAC) != 0) {
489                                if (!usableKeyOnMac(i.intValue(), needed)) {
490                                    needed &= ~getMenuShortCutKeyMask();
491                                    needed |= KeyEvent.CTRL_MASK;
492                                }
493                            }
494                        }
495
496                        if (macAlt) {
497                            if (getOperatingSystem() == OS_MAC) {
498                                needed |= KeyEvent.CTRL_MASK;
499                            } else {
500                                needed |= KeyEvent.ALT_MASK;
501                            }
502                        }
503
504                        return KeyStroke.getKeyStroke(i.intValue(), needed);
505                    } else {
506                        return null;
507                    }
508                }
509            }
510        } catch (NoSuchElementException ex) {
511            return null;
512        }
513    }
514    /**
515     * need to guard against headlessExceptions when testing.
516     * @return the acceletor mask for shortcuts.
517     */
518    private static int getMenuShortCutKeyMask() {
519        if (GraphicsEnvironment.isHeadless()) {
520            return ((getOperatingSystem() & OS_MAC) != 0) ? 
521                    KeyEvent.META_MASK : KeyEvent.CTRL_MASK;
522        }
523 
524        return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
525    }
526
527    private static boolean usableKeyOnMac(int key, int mask) {
528        //All permutations fail for Q except ctrl
529        if (key == KeyEvent.VK_Q) {
530            return false;
531        }
532
533        boolean isMeta = ((mask & KeyEvent.META_MASK) != 0) || ((mask & KeyEvent.CTRL_DOWN_MASK) != 0);
534
535        boolean isAlt = ((mask & KeyEvent.ALT_MASK) != 0) || ((mask & KeyEvent.ALT_DOWN_MASK) != 0);
536
537        boolean isOnlyMeta = isMeta && ((mask & ~(KeyEvent.META_DOWN_MASK | KeyEvent.META_MASK)) == 0);
538
539        //Mac OS consumes keys Command+ these keys - the app will never see
540        //them, so CTRL should not be remapped for these
541        if (isOnlyMeta) {
542            return (key != KeyEvent.VK_H) && (key != KeyEvent.VK_SPACE) && (key != KeyEvent.VK_TAB);
543        } else return !((key == KeyEvent.VK_D) && isMeta && isAlt);
544    }
545
546    /** Convert a space-separated list of Emacs-like key binding names to a list of Swing key strokes.
547    * @param s the string with keys
548    * @return array of key strokes, or <code>null</code> if the string description is not valid
549    * @see #stringToKey
550    */
551    public static KeyStroke[] stringToKeys(String s) {
552        StringTokenizer st = new StringTokenizer(s.toUpperCase(Locale.ENGLISH), " "); // NOI18N
553        ArrayList<KeyStroke> arr = new ArrayList<KeyStroke>();
554
555        while (st.hasMoreElements()) {
556            s = st.nextToken();
557
558            KeyStroke k = stringToKey(s);
559
560            if (k == null) {
561                return null;
562            }
563
564            arr.add(k);
565        }
566
567        return arr.toArray(new KeyStroke[arr.size()]);
568    }
569
570    /** Adds characters for modifiers to the buffer.
571    * @param buf buffer to add to
572    * @param modif modifiers to add (KeyEvent.XXX_MASK)
573    * @return true if something has been added
574    */
575    private static boolean addModifiers(StringBuffer buf, int modif) {
576        boolean b = false;
577
578        if ((modif & KeyEvent.CTRL_MASK) != 0) {
579            buf.append("C"); // NOI18N
580            b = true;
581        }
582
583        if ((modif & KeyEvent.ALT_MASK) != 0) {
584            buf.append("A"); // NOI18N
585            b = true;
586        }
587
588        if ((modif & KeyEvent.SHIFT_MASK) != 0) {
589            buf.append("S"); // NOI18N
590            b = true;
591        }
592
593        if ((modif & KeyEvent.META_MASK) != 0) {
594            buf.append("M"); // NOI18N
595            b = true;
596        }
597
598        if ((modif & CTRL_WILDCARD_MASK) != 0) {
599            buf.append("D");
600            b = true;
601        }
602
603        if ((modif & ALT_WILDCARD_MASK) != 0) {
604            buf.append("O");
605            b = true;
606        }
607
608        return b;
609    }
610
611    /** Reads for modifiers and creates integer with required mask.
612    * @param s string with modifiers
613    * @return integer with mask
614    * @exception NoSuchElementException if some letter is not modifier
615    */
616    private static int readModifiers(String s) throws NoSuchElementException {
617        int m = 0;
618
619        for (int i = 0; i < s.length(); i++) {
620            switch (s.charAt(i)) {
621            case 'C':
622                m |= KeyEvent.CTRL_MASK;
623                break;
624
625            case 'A':
626                m |= KeyEvent.ALT_MASK;
627                break;
628
629            case 'M':
630                m |= KeyEvent.META_MASK;
631                break;
632
633            case 'S':
634                m |= KeyEvent.SHIFT_MASK;
635                break;
636
637            case 'D':
638                m |= CTRL_WILDCARD_MASK;
639                break;
640
641            case 'O':
642                m |= ALT_WILDCARD_MASK;
643                break;
644
645            default:
646                throw new NoSuchElementException(s);
647            }
648        }
649
650        return m;
651    }
652    
653    /**
654    * Convert an array of objects to an array of primitive types.
655    * E.g. an <code>Integer[]</code> would be changed to an <code>int[]</code>.
656    * @param array the wrapper array
657    * @return a primitive array
658    * @throws IllegalArgumentException if the array element type is not a primitive wrapper
659    */
660    public static Object toPrimitiveArray(Object[] array) {
661        if (array instanceof Integer[]) {
662            int[] r = new int[array.length];
663            int i;
664            int k = array.length;
665
666            for (i = 0; i < k; i++)
667                r[i] = (array[i] == null) ? 0 : ((Integer) array[i]).intValue();
668
669            return r;
670        }
671
672        if (array instanceof Boolean[]) {
673            boolean[] r = new boolean[array.length];
674            int i;
675            int k = array.length;
676
677            for (i = 0; i < k; i++)
678                r[i] = (array[i] != null) && ((Boolean) array[i]).booleanValue();
679
680            return r;
681        }
682
683        if (array instanceof Byte[]) {
684            byte[] r = new byte[array.length];
685            int i;
686            int k = array.length;
687
688            for (i = 0; i < k; i++)
689                r[i] = (array[i] == null) ? 0 : ((Byte) array[i]).byteValue();
690
691            return r;
692        }
693
694        if (array instanceof Character[]) {
695            char[] r = new char[array.length];
696            int i;
697            int k = array.length;
698
699            for (i = 0; i < k; i++)
700                r[i] = (array[i] == null) ? 0 : ((Character) array[i]).charValue();
701
702            return r;
703        }
704
705        if (array instanceof Double[]) {
706            double[] r = new double[array.length];
707            int i;
708            int k = array.length;
709
710            for (i = 0; i < k; i++)
711                r[i] = (array[i] == null) ? 0 : ((Double) array[i]).doubleValue();
712
713            return r;
714        }
715
716        if (array instanceof Float[]) {
717            float[] r = new float[array.length];
718            int i;
719            int k = array.length;
720
721            for (i = 0; i < k; i++)
722                r[i] = (array[i] == null) ? 0 : ((Float) array[i]).floatValue();
723
724            return r;
725        }
726
727        if (array instanceof Long[]) {
728            long[] r = new long[array.length];
729            int i;
730            int k = array.length;
731
732            for (i = 0; i < k; i++)
733                r[i] = (array[i] == null) ? 0 : ((Long) array[i]).longValue();
734
735            return r;
736        }
737
738        if (array instanceof Short[]) {
739            short[] r = new short[array.length];
740            int i;
741            int k = array.length;
742
743            for (i = 0; i < k; i++)
744                r[i] = (array[i] == null) ? 0 : ((Short) array[i]).shortValue();
745
746            return r;
747        }
748
749        throw new IllegalArgumentException();
750    }
751    
752    /**
753    * Convert an array of primitive types to an array of objects.
754    * E.g. an <code>int[]</code> would be turned into an <code>Integer[]</code>.
755    * @param array the primitive array
756    * @return a wrapper array
757    * @throws IllegalArgumentException if the array element type is not primitive
758    */
759    public static Object[] toObjectArray(Object array) {
760        if (array instanceof Object[]) {
761            return (Object[]) array;
762        }
763
764        if (array instanceof int[]) {
765            int i;
766            int k = ((int[]) array).length;
767            Integer[] r = new Integer[k];
768
769            for (i = 0; i < k; i++)
770                r[i] = new Integer(((int[]) array)[i]);
771
772            return r;
773        }
774
775        if (array instanceof boolean[]) {
776            int i;
777            int k = ((boolean[]) array).length;
778            Boolean[] r = new Boolean[k];
779
780            for (i = 0; i < k; i++)
781                r[i] = ((boolean[]) array)[i] ? Boolean.TRUE : Boolean.FALSE;
782
783            return r;
784        }
785
786        if (array instanceof byte[]) {
787            int i;
788            int k = ((byte[]) array).length;
789            Byte[] r = new Byte[k];
790
791            for (i = 0; i < k; i++)
792                r[i] = new Byte(((byte[]) array)[i]);
793
794            return r;
795        }
796
797        if (array instanceof char[]) {
798            int i;
799            int k = ((char[]) array).length;
800            Character[] r = new Character[k];
801
802            for (i = 0; i < k; i++)
803                r[i] = new Character(((char[]) array)[i]);
804
805            return r;
806        }
807
808        if (array instanceof double[]) {
809            int i;
810            int k = ((double[]) array).length;
811            Double[] r = new Double[k];
812
813            for (i = 0; i < k; i++)
814                r[i] = new Double(((double[]) array)[i]);
815
816            return r;
817        }
818
819        if (array instanceof float[]) {
820            int i;
821            int k = ((float[]) array).length;
822            Float[] r = new Float[k];
823
824            for (i = 0; i < k; i++)
825                r[i] = new Float(((float[]) array)[i]);
826
827            return r;
828        }
829
830        if (array instanceof long[]) {
831            int i;
832            int k = ((long[]) array).length;
833            Long[] r = new Long[k];
834
835            for (i = 0; i < k; i++)
836                r[i] = new Long(((long[]) array)[i]);
837
838            return r;
839        }
840
841        if (array instanceof short[]) {
842            int i;
843            int k = ((short[]) array).length;
844            Short[] r = new Short[k];
845
846            for (i = 0; i < k; i++)
847                r[i] = new Short(((short[]) array)[i]);
848
849            return r;
850        }
851
852        throw new IllegalArgumentException();
853    }
854
855    /** Wrap multi-line strings (and get the individual lines).
856    * @param original  the original string to wrap
857    * @param width     the maximum width of lines
858    * @param breakIterator breaks original to chars, words, sentences, depending on what instance you provide.
859    * @param removeNewLines if <code>true</code>, any newlines in the original string are ignored
860    * @return the lines after wrapping
861    */
862    public static String[] wrapStringToArray(
863        String original, int width, BreakIterator breakIterator, boolean removeNewLines
864    ) {
865        if (original.length() == 0) {
866            return new String[] { original };
867        }
868
869        String[] workingSet;
870
871        // substitute original newlines with spaces,
872        // remove newlines from head and tail
873        if (removeNewLines) {
874            original = trimString(original);
875            original = original.replace('\n', ' ');
876            workingSet = new String[] { original };
877        } else {
878            StringTokenizer tokens = new StringTokenizer(original, "\n"); // NOI18N
879            int len = tokens.countTokens();
880            workingSet = new String[len];
881
882            for (int i = 0; i < len; i++) {
883                workingSet[i] = tokens.nextToken();
884            }
885        }
886
887        if (width < 1) {
888            width = 1;
889        }
890
891        if (original.length() <= width) {
892            return workingSet;
893        }
894
895widthcheck:  {
896            boolean ok = true;
897
898            for (int i = 0; i < workingSet.length; i++) {
899                ok = ok && (workingSet[i].length() < width);
900
901                if (!ok) {
902                    break widthcheck;
903                }
904            }
905
906            return workingSet;
907        }
908
909        java.util.ArrayList<String> lines = new java.util.ArrayList<String>();
910
911        int lineStart = 0; // the position of start of currently processed line in the original string
912
913        for (int i = 0; i < workingSet.length; i++) {
914            if (workingSet[i].length() < width) {
915                lines.add(workingSet[i]);
916            } else {
917                breakIterator.setText(workingSet[i]);
918
919                int nextStart = breakIterator.next();
920                int prevStart = 0;
921
922                do {
923                    while (((nextStart - lineStart) < width) && (nextStart != BreakIterator.DONE)) {
924                        prevStart = nextStart;
925                        nextStart = breakIterator.next();
926                    }
927
928                    if (nextStart == BreakIterator.DONE) {
929                        nextStart = prevStart = workingSet[i].length();
930                    }
931
932                    if (prevStart == 0) {
933                        prevStart = nextStart;
934                    }
935
936                    lines.add(workingSet[i].substring(lineStart, prevStart));
937
938                    lineStart = prevStart;
939                    prevStart = 0;
940                } while (lineStart < workingSet[i].length());
941
942                lineStart = 0;
943            }
944        }
945
946        String[] s = new String[lines.size()];
947
948        return (String[]) lines.toArray(s);
949    }
950    
951    private static String trimString(String s) {
952        int idx = 0;
953        char c;
954        final int slen = s.length();
955
956        if (slen == 0) {
957            return s;
958        }
959
960        do {
961            c = s.charAt(idx++);
962        } while (((c == '\n') || (c == '\r')) && (idx < slen));
963
964        s = s.substring(--idx);
965        idx = s.length() - 1;
966
967        if (idx < 0) {
968            return s;
969        }
970
971        do {
972            c = s.charAt(idx--);
973        } while (((c == '\n') || (c == '\r')) && (idx >= 0));
974
975        return s.substring(0, idx + 2);
976    }    
977}